I am writing a test case which requires me to reload the page N number of times, and compare its title for a value, if that value does not exists then break the while loop without rising error.
Below is a demo program, similar to the one that I am looking to implement.
/// <reference types='cypress' />
it("Visiting Google",function(){
var webUrl = 'https://html5test.com/'
cy.visit(webUrl)
var loop_iter = 0
while(loop_iter < 5)
{
cy.get('body:nth-child(2) div:nth-child(2) div.header h1:nth-child(1) > em:nth-child(2)').then(($text_data) =>{
if($text_data.text().contains('HTML123'))
{
cy.log(" --> ITERATION = ",loop_iter)
cy.reload()
}
else
{
cy.log("Unknown website")
loop_iter = 10
}
})
loop_iter += 1
}
})
I need a way to break from the while loop when the else part is executed, without rising any error.
The if condition when false returns AssertionError, in such case it should execute else part.
cy.title() is asynchronous (proof is, you need to use .then()), so, the entire while loop ends even before the first .then() triggers. That's how asynchronism works.
You need another approach :
it("Visiting Google", async function () {
var webUrl = 'https://html5test.com/'
cy.visit(webUrl)
for (let i = 0; i < 5; i++) { // You can't await in a 'while' loop
const $text_data = await cy.title();
if ($text_data.includes('HTML')) {
cy.log(" --> ITERATION = ", i)
cy.reload()
}
else {
cy.log("Unknown website")
break;
}
}
})
Please take a look at the sample recipe Page reloads. It uses recursion as suggested in comments.
This is your code adapted to the pattern,
it('reload until "HTML" disappears', () => {
// our utility function
const checkAndReload = (recurse_level = 0) => {
cy.title().then(title => {
if (title.includes('HTML') && recurse_level < 5) {
cy.log(" --> ITERATION = ", recurse_level)
cy.wait(500, { log: false }) // just breathe here
cy.reload() // reload
checkAndReload(recurse_level + 1) // check again
} else {
cy.log("Unknown website")
}
})
}
cy.visit('https://html5test.com/') // start the test by visiting the page
checkAndReload() // and kicking off the first check
})
Related
hi how can I break out of the for loop ? I want to be able to break out of it in the callback in the if statement
I want this program to create a folder in the given directory and every time it throws an error I want it to change the folder name and add a number to it so when it says that the folder already exists, It'll create a unique folder name until it doesn't throw an error.
I will check for the error code later help me solve this first
const path = require('path');
function folder(folderName) {
for (let i = 1; i <= 10; i++) {
let pathNumber = i;
let fullPath = folderName + pathNumber;
fs.mkdir(path.join("D:", fullPath), (err) => {
if (!err) {
return; // I want to break out of the loop here
}
})
}
}
folder("folder");
You can't write the code that way because the for loop will already be done before any of the fs.mkdir() callbacks are called. They are asynchronous and happen LATER.
If you want to execute one iteration of the loop, including the fs.mkdir() before moving on to any other, then you can use async/await with fs.promises.mkdir().
Here's what a solution could look like with fs.promises.mkdir(). I've also added error handling for the case where all 10 sub-dir names you're trying already exist.
async function folder(folderName) {
let lastError;
for (let pathNumber = 1; pathNumber <= 10; pathNumber++) {
let fullPath = path.join("D:", folderName + pathNumber);
try {
await fs.promises.mkdir(fullPath);
return fullPath;
} catch(e) {
lastError = e;
// ignore error so we keep trying other numbers
}
}
throw lastError;
}
folder("folder").then(fullPath => {
console.log(`dir created: ${fullPath}`);
}).catch(err => {
console.log(err);
});
Much simpler without await
const numFolders = 10,
folders = Array.from(Array(numFolders), (_,i) => `folder${i+1}`), len = folder.length;
let cnt = 0;
const makeFolder = () => {
if (cnt >= len) return; // stop because done
fs.mkdir(path.join("D:", fullPath), (err) => {
if (err) {
makeFolder(); // only call again if error
}
cnt++
}
makeFolder()
I want to check whether the text element changes on x seconds and apply the for loop and conditional structure to verify if the change is applied. If the text is still not changed it will refresh the page and check again
Cypress.Commands.add('checkApprovedStatus', (targetData) =>{
cy.get('div[class^=ui-table-scrollable-body]').contains('tr', targetData).first().parent().within(function(){
cy.get('td').eq(10).children('span[class^=customer-badge]').then(($status) =>
{
//let currStatus = $status.text()
//cy.log(currStatus)
for(let count = 0; count <= 5; count++)
{
let currStatus = $status.text()
cy.log(currStatus)
if (currStatus === 'approved')
{
//if (currStatus.contains('Approved', {matchCase:false}))
//{
cy.log("End to end testing completed. All checks have passed!!")
break
//}
}
else
{
cy.reload()
cy.wait(5000)
$status.trigger('change')
}
}
})
})
})
For loops generally crash and burn in Cypress, but you can use a recursive function to simulate the loop.
When the loop (reload/trigger change) fails to find the status, throw an error to fail the test, or just return.
const checkApprovedStatus = (targetData, attempt = 0) => {
if (attempt === 5) {
// used up all attempts, can either fail the test
throw 'Was never approved'
// or just return without failing
return
}
cy.get('div[class^=ui-table-scrollable-body]')
.contains('tr', targetData).first().parent()
.within(() => {
cy.get('td').eq(10).children('span[class^=customer-badge]')
.then($status => {
if ($status.text() === 'approved') {
cy.log("All checks have passed!!")
} else {
cy.reload()
cy.wait(5000)
$status.trigger('change')
checkApprovedStatus(targetData, ++attempt)
}
})
})
})
I am trying to get a custom action running to simply incrementing a value on passing a specific node on the flow.
My custom actions looks like this:
function action(bp: typeof sdk, event: sdk.IO.IncomingEvent, args: any, { user, temp, session } = event.state) {
/** Your code starts below */
let i = undefined
const p = new Promise((resolve, reject) => {
if (i === undefined) {
resolve((i = 0))
} else if (i >= 0) {
resolve(i + 1)
} else {
reject('i cannot be < 0')
}
})
const runCount = async () => {
try {
const counter = await p
i = counter
return (session.count = counter)
} catch (err) {
console.log(err)
}
}
return runCount()
/** Your code ends here */
}
When I runCount() variable i will be set to 0. But then, after in rerun runCount() it does not increment further.
What do I need to do to save the variable so it increments on every runCount() call.
Greetings
Lorenz
I just managed to solve the problem.
I had to declare i = session.count at the beginning.
Now it gets the value out of the session state and increments the state on every call.
Maybe someone gets some help out of this.
Lorenz
Relatively new to writing end to end tests with Protractor. Also relatively inexperienced at working with promises.
I am writing a test where in some cases I need to loop through my code b/c the record that I select does not meet certain criteria. In those cases I would like to proceed back to a previous step and try another record (and continue doing so until I find a suitable record). I am not able to get my test to enter into my loop though.
I can write regular e2e tests with Protractor, but solving this looping issue is proving difficult. I know it must be because I'm dealing with Promises, and am not handling them correctly. Although I've seen examples of looping through protractor code, they often involve a single method that needs to be done to every item in a list. Here I have multiple steps that need to be done in order to arrive at the point where I can find and set my value to break out of the loop.
Here are some of the threads I've looked at trying to resolve this:
protractor and for loops
https://www.angularjsrecipes.com/recipes/27910331/using-protractor-with-loops
Using protractor with loops
Looping through fields in an Angular form and testing input validations using Protractor?
Protractors, promises, parameters, and closures
Asynchronously working of for loop in protractor
My code as it currently stands:
it('should select a customer who has a valid serial number', () => {
const products = new HomePage();
let serialIsValid: boolean = false;
let selectedElement, serialNumber, product, recordCount, recordList;
recordList = element.all(by.css(`mat-list.desco-list`));
recordList.then((records) => {
recordCount = records.length;
console.log('records', records.length, 'recordCount', recordCount);
}
);
for (let i = 0; i < recordCount; i++) {
if (serialIsValid === false) {
const j = i + 1;
products.btnFormsSelector.click();
products.formSelectorRepossession.click();
browser.wait(EC.visibilityOf(products.itemSearch));
products.itemSearch.element(by.tagName('input')).sendKeys(browser.params.search_string);
products.itemSearch.element(by.id('btnSearch')).click();
browser.wait(EC.visibilityOf(products.itemSearch.element(by.id('list-container'))));
selectedElement = element(by.tagName(`#itemSearch mat-list:nth-child(${{j}})`));
selectedElement.click();
browser.wait(EC.visibilityOf(products.doStuffForm));
browser.sleep(1000);
element(by.css('#successful mat-radio-button:nth-child(1) label')).click();
browser.sleep(1000);
expect(element(by.css('.itemDetailsContainer'))).toBeTruthy();
product = products.productIDNumber.getText();
product.then((item) => {
serialNumber = item;
if (item !== 'Unknown') {
expect(serialNumber).not.toContain('Unknown');
serialIsValid = true;
} else {
i++
}
})
} else {
console.log('serial is valid: ' + serialIsValid);
expect(serialNumber).not.toContain('Unknown');
break;
}
}
console.log('serial number validity: ', serialIsValid);
})
I have rewritten and reorganized my code several times, including trying to break out my code into functions grouping related steps together (as recommended in one of the threads above, and then trying to chain them together them together, like this:
findValidCustomer() {
const gotoProductSearch = (function () {...})
const searchForRecord = (function () {...})
const populateForm = (function (j) {...})
for (let i = 0; i < recordCount; i++) {
const j = i + 1;
if (serialIsValid === false) {
gotoProductSearch
.then(searchForRecord)
.then(populateForm(j))
.then(findValidSerial(i))
} else {
console.log('serial number validity' + serialIsValid);
expect(serialIsValid).not.toContain('Unknown');
break;
}
}
console.log('serial number validity' + serialIsValid);
}
When I've tried to chain them like that, I received this error
- TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'
Have edited my code from my actual test and apologies if I've made mistakes in doing so. Would greatly appreciate comments or explanation on how to do this in general though, b/c I know I'm not doing it correctly. Thanks in advance.
I would suggest looking into async / await and migrating this test. Why migrate? Protractor 6 and moving forward will require async / await. In order to do that, you will need to have SELENIUM_PROMISE_MANAGER: false in your config and await your promises. In my answer below, I'll use async / await.
Below is my attempt to rewrite this as async / await. Also try to define your ElementFinders, numbers, and other stuff when you need them so you can define them as consts.
it('should select a customer who has a valid serial number', async () => {
const products = new HomePage();
let serialIsValid = false; // Setting the value to false is enough
// and :boolean is not needed
const recordList = element.all(by.css(`mat-list.desco-list`));
const recordCount = await recordList.count();
console.log(`recordCount ${recordCount}`);
// This could be rewritten with .each
// See https://github.com/angular/protractor/blob/master/lib/element.ts#L575
// await recordList.each(async (el: WebElement, index: number) => {
for (let i = 0; i < recordCount; i++) {
if (serialIsValid === false) {
const j = index + 1; // Not sure what j is being used for...
await products.btnFormsSelector.click();
await products.formSelectorRepossession.click();
await browser.wait(EC.visibilityOf(products.itemSearch));
await products.itemSearch.element(by.tagName('input'))
.sendKeys(browser.params.search_string);
await products.itemSearch.element(by.id('btnSearch')).click();
await browser.wait(
EC.visibilityOf(await products.itemSearch.element(
by.id('list-container')))); // Maybe use a boolean check?
const selectedElement = element(by.tagName(
`#itemSearch mat-list:nth-child(${{j}})`));
await selectedElement.click();
// not sure what doStuffForm is but I'm guessing it returns a promise.
await browser.wait(EC.visibilityOf(await products.doStuffForm));
await browser.sleep(1000); // I would avoid sleeps since this might
// cause errors (if ran on a slower machine)
// or just cause your test to run slow
await element(by.css(
'#successful mat-radio-button:nth-child(1) label')).click();
await browser.sleep(1000);
expect(await element(by.css('.itemDetailsContainer'))).toBeTruthy();
const serialNumber = await products.productIDNumber.getText();
if (item !== 'Unknown') {
expect(serialNumber).not.toContain('Unknown');
serialIsValid = true;
}
// The else statement if you were using i in a for loop, it is not
// a good idea to increment it twice.
} else {
// So according to this, if the last item is invalid, you will not break
// and not log this. This will not fail the test. It might be a good idea
// to not have this in an else statement.
console.log(`serial is valid: ${serialIsValid}`);
expect(serialNumber).not.toContain('Unknown');
break;
}
}
console.log('serial number validity: ', serialIsValid);
});
Can you check the count again after updating your code by following snippet
element.all(by.css(`mat-list.desco-list`)).then(function(records) => {
recordCount = records.length;
console.log(recordCount);
});
OR
There is count() function in ElementArrayFinder class which returns promise with count of locator
element.all(by.css(`mat-list.desco-list`)).then(function(records) => {
records.count().then(number => {
console.log(number); })
});
I need help with loop beaking.
For my check I did the simple test:
while(i < 10) {
element(by.xpath("//a[contains(#id, 'ma-apply')]")).isPresent().then(function(result) {
if(!result) {
helper.one_page_ahead();
} else {
console.log('there is on the page');
break;
}
});
i++;
};
This code leads to the error.
I tried to follow advice via StackOverflow and changed break to return.
But this leads to full loop execution (up to 10).
Here is the output:
[14:17:46] I/launcher - Running 1 instances of WebDriver Started user
skills: AJAX there is on the page there is on the page there is on the
page there is on the page there is on the page there is on the page
there is on the page there is on the page .
1 spec, 0 failures Finished in 37.93 seconds
I tried the same with for loop like
for(i = 0; i < 10; i++) {
//code
break;
}
Would be glad to find the answer.
This is some commentary about why the while statement does not work: When you call isPresent you are returned a webdriver.promise.Promise<boolean>. Since you are in the webdriver control flow, you'll need to throw an error,
browser.get('http://angularjs.org');
var i = 0;
var running = true;
while(i < 3 && running) {
console.log('while: ' + running + ' ' + i);
element(by.model('username')).isPresent().then((result) => {
console.log('element: ' + running + ' ' + i);
if (result) {
// huzzah we found it, so lets break the element after the first test
browser.get('https://docs.angularjs.org/tutorial');
} else {
running = false;
throw new Error('no username')
}
}).catch((err) => {
console.log(err);
});
i++;
}
This basically prints out:
[19:07:18] I/hosted - Using the selenium server at http://localhost:4444/wd/hub
[19:07:18] I/launcher - Running 1 instances of WebDriver
Started
while: true 0
while: true 1
while: true 2
element: true 3
[Error: no username]
element: false 3
[Error: no username]
element: false 3
[Error: no username]
So basically your while loop queues up items in the control flow to execute. These then will get executed asynchronously in order.
I like the suggestion by Sudharsan Selvaraj to do this recursively.
You need to implement a recursive method to achieve what you want, try the below piece of code,
function runTillElementFound(totalCount,currentCount){
var self = this;
var _element = element(by.xpath("//a[contains(#id, 'ma-apply')]"));
if(currentCount < totalCount){
return _element.isPresent().then(function(isElementPresent){
if(isElementPresent){
return;
}else{
helper.one_page_ahead();
self.runTillElementFound(totalCount,currentCount++);
}
})
}else{
return false; //if element not present after Max count reached.
}
}
this.runTillElementFound(10,0); //this will execute the method untill the required element found on the page.
If you want to avoid recursion you could modify the index variable inside the returned promised
while(i < 10) {
element(by.xpath("//a[contains(#id, 'ma-apply')]")).isPresent().then(function(result) {
if(!result) {
helper.one_page_ahead();
} else {
console.log('there is on the page');
i = 10;
}
});
i++;
};
And I would add a browser.sleep(x) in between each repetion to avoid the code to be run before the result from the promise is evaluated.
i = 10; is not effecting, Still loop iterating