I am running e2e tests on a web app using Cypress.
This section fails intermittently.
Here, search text is entered in the appropriate field. When {enter} is clicked, the search happens & the dialog only shows the search results.
Here is the code:
function selectDesign(designName: string, designCode: string) {
// enter search text & click enter
cy.get(singlePickerSearch).type(designName + '{enter}', { force: true });
// select your design from search results
cy.get(designPickerDesign(designCode))
.scrollIntoView()
.should('have.length', 1)
.click({ force: true });
}
When it fails, it is like '{enter}' was not executed. I can click enter manually, and it works.
Because it is an intermittent failure, it is difficult to obtain logs.
Every single test uses the same design. In a single run there are 19 tests. The test runner can find the file 18 times & then fail on 1 test.
Also tried using backticks in the .type() command:
cy.get(singlePickerSearch)
.clear()
.type(`${designName}{enter}`);
There was no appreciable improvement.
I finally got this working by using the following:
function selectDesign(e2eDesign: any) {
// open dodi picker
cy.get(createCardQueryOpenDodiPicker)
.should('be.visible')
.click()
.then(() => {
// wait for loader
cy.get(loader).should('not.be.visible');
// enter search text
cy.get(dodiPickerSearchText)
.clear()
.type(`${e2eDesign.design.name}{enter}`)
.then(() => {
// wait for loader to go
cy.get(loader).should('not.be.visible');
// short wait to avoid intermittent failure here
cy.wait(400);
// select required design
cy.get(dodiPickerItems)
.should('have.length', 1) // should only be one match
.click();
});
});
}
I think the last .should('have.length' 1) causes it to wait until that is true before trying to execute the next statement.
Related
I have a search input and result display area that are being handled by Ajax call. When user enter a keyword, Ajax call the backend and return HTML string. The JS handler then appends the response HTML into result display area.
Here my steps:
Search input: Vancouver (auto input by browser location)
Result: Welcome to Vancouver
Type into search input: Calgary
Expected: Welcome to Calgary
// Test case
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.xpath(selectors.head_info).then((heading) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
cy.get(heading)
.children('h1')
.should('be.visible');
cy.get(heading)
.children('h1')
.invoke('text')
.should('equals', 'Welcome to Calgary');
});
});
});
Test error:
AssertionError
Timed out retrying after 4000ms: expected 'Welcome to Vancouver' to equal 'Welcome to Calgary'
However, the visual screenshot showed that the 'Welcome to Calgary' text was displayed while the test could not see.
I did follow the guide from Cypress app
Timed out retrying after 4000ms: cy.should() failed because this element is detached from the DOM.
...
You typically need to re-query for the element or add 'guards' which delay Cypress from running new commands. Learn more
I added 'a guard', cy.wait()...but nothing works.
Would you please teach me how to handle this issue?
Thank you.
UPDATE SOLUTION:
Thanks for #jhelguero helped me to find out.
Solution 1:
I added this and it worked well.
cy.contains('h1', 'Welcome to Calgary')
.should('be.visible');
Because I have put all the tests in block of
cy.xpath(selectors.head_info).then((heading) => {
...
});
So, when I do cy.get(heading) it caught the element before Ajax done. It was 'Welcome to Vancouver' instead.
Solution 2:
Then, I separated test steps like this.
cy.searchForArea('Calgary');
cy.xpath(selectors.head_info)
.children('h1')
.should('have.text', 'Welcome to Calgary');
and it was successful.
Summary:
Do this:
cy.xpath({selector}).should(have.text,'ABC');
cy.xpath({selector}).find('h1').should('have.text, '123');
DON'T do this:
cy.xpath({selector}).then((ele) => {
cy.get(ele).should(have.text, 'ABC');
cy.get(ele).find('h1').should('have.text, '123');
});
It looks like your DOM rerenders after the ajax call and thus the element('Welcome to') you were previously reference has been detached.
To fix this you will need to re query using the same selector after the ajax call is completed. You will not need cy.xpath(selectors.head_info).then().
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
// use .contains() to search a selector with regex text
cy.contains(selectors.head_info, /Welcome to Calgary/)
.should('be.visible');
});
});
If you visually confirmed the text "Welcome to Calgary", the problem might be that .should('equals', 'Welcome to Calgary') is not retrying enough of preceding steps.
Try shortening the query.
You can also increase the timeout if you notice a lag before the text appears.
cy.get(`${heading} > h1`, {timeout:10_000})
.should('have.text', 'Welcome to Calgary')
I am writing a test in which if I land on a page and if any records are available, I need to click on three dots buttons near the record. But I should skip the test if no records are available on the page.
cy.get('body')
.then(($body) => {
if ($body.find('.ant-empty-description').length) {
cy.log('Element not found. Skip the Test')
}
else {
cy.xpath("//tbody[#class='ant-table-tbody']//tr[" + rowNumber + "]//td[4]//button//em").click()
}
})
I am using an approach in which if 'No Record found' message is present, I need to skip the test else click on the button present near the record.
Sometimes it's necessary to test conditionally, but using <body> as a base element is a mistake IMO.
<body> is always on the page, but the table data may not be if fetched from an API.
The test will always run faster than the API, always see the empty row placeholder.
Pattern to use
Add an intercept() and wait for the API to respond.
Use the table rows as base element for conditional check - there will always be at least one row (but it may be the "No records" row).
cy.intercept(...).as('tableData')
...
cy.wait('#tableData')
cy.get('tbody tr').then($rows => { // may be only one "No record" row
const noData = $rows.text().includes('No Record found')
if (!noData) {
$rows.eq(rowNumber).find('td').eq(4]).find('button').click()
}
})
You can use mocha's .skip() functionality. Please note that you'll have to use function() instead of arrow functions.
it('test', function() {
cy.get('body')
.then(function($body) {
if ($body.find('.ant-empty-description').length) {
cy.log('Element not found. Skip the Test')
this.skip()
} else {
cy.xpath("//tbody[#class='ant-table-tbody']//tr[" + rowNumber + "]//td[4]//button//em").click()
}
})
})
That being said, I agree with #jjhelguero -- using skips in this way is an anti-pattern for testing. Ideally, you should control whether or not an element will appear on the webpage, and use your test setup to manipulate the page into having/not having the element.
I'm using the cy.visit() command but the website i'm visiting (which i don't own) doesn't always fire the load event, although the content itself that i need for testing does appear on the website.
Despite the content appearing, since the load event is not fired sometimes (for some reason which i can't fix since i don't have ownership over this website), the cy.visit() command fails.
Is there a way to "force" it somehow, similar to how we can pass { force: true} for the cy.click() command?
Add the below to your cypress commands file
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
And in your tests, you can call
cy.forceVisit("www.google.com")
It's hard to simulate the problem, but I think I managed by setting pageLoadTimeout really low (30ms).
You can catch the onLoad fail in an event handler and checking for the page load error message.
I recommend doing it in a beforeEach().
beforeEach(() => {
Cypress.config("pageLoadTimeout", 30) // set this to whatever time length
// you feel is appropriate to start testing
// You'll need to experiment to get this right
// and in CI it will be a lot longer
cy.once('fail', (err) => { // "once" to just catch a single error
const message = err.parsedStack[0].message
if (message.match(/Timed out after waiting `\d+ms` for your remote page to load/)) {
return false
}
throw err // any other error, fail it
})
cy.visit('www.example.com');
})
it('checks the heading of the page', () => {
cy.get('h1').should('have.text', 'Example Domain') // ✅
})
As you can already assume, that is highly discouraged. It also really depends on how it fails and with which errors, but, without any code to reproduce, you may want to try this if you haven't already:
cy.visit('/', {failOnStatusCode: false});
Reference: https://docs.cypress.io/api/commands/visit#Arguments
I need to close the tab/window after each test so I can start the next from scratch
describe('theImplementationIamTesting', () => {
after(() => {
// CLOSE THE TAB AFTER THE TEST...
});
});
I am looking a way to close the current tab after the test. I am not talking about closing a child tab/window. I am talking about the initial tab.
In selenium, it will be something like webdriver.close().
I cannot find a single place online, including the cypress website, where it said how to close the tab browser.
Thanks for helping
If you separate the cases in different test files it will close the whole browser and reopen it every time. This is the only way I had found so far and works for me very well to start every case from scratch since sometimes it continues to run unfinished API requests from the first case after the start of the second case.
The downside is you need to make the initial preparation of the system every time and it increases the runtime.
The way I resolved this was to actually add an extra line at the end of each test which would click to navigate to a page from where the other tests could continue, say the 'home page'.
describe('Test Inline Text Entry Interactions', () => {
beforeEach('Log in as CypressEditor', () => {
cy.MockLoginUser('cypressEditor');
cy.visit('http://localhost:4200/homepage');
})
it('should test 1st thing', () => {
//Test something, then...
cy.get('#logo-label').click(); //To navigate back to http://localhost:4200/homepage
});
it('should test the 2nd thing', () => {
//Test something else...
cy.get('#logo-label').click(); //To navigate back to http://localhost:4200/homepage
});
it('should test the 3rd thing', () => {
//Test some more stuff, then...
cy.get('#logo-label').click(); //this might not be necessary since it's the last one.
});
For me this ensured that each test could finish and continue with the next.
In a test spec, I need to click a button on a web page, and wait for the new page completely loaded.
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
// ...Here need to wait for page complete... How?
ptor.waitForAngular();
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Depending on what you want to do, you can try:
browser.waitForAngular();
or
btnLoginEl.click().then(function() {
// do some stuff
});
to solve the promise. It would be better if you can do that in the beforeEach.
NB: I noticed that the expect() waits for the promise inside (i.e. getCurrentUrl) to be solved before comparing.
I just had a look at the source - Protractor is waiting for Angular only in a few cases (like when element.all is invoked, or setting / getting location).
So Protractor won't wait for Angular to stabilise after every command.
Also, it looks like sometimes in my tests I had a race between Angular digest cycle and click event, so sometimes I have to do:
elm.click();
browser.driver.sleep(1000);
browser.waitForAngular();
using sleep to wait for execution to enter AngularJS context (triggered by click event).
You don't need to wait. Protractor automatically waits for angular to be ready and then it executes the next step in the control flow.
With Protractor, you can use the following approach
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain newPageName
browser.wait(EC.urlContains('newPageName'), 10000);
So your code will look something like,
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain efg
ptor.wait(EC.urlContains('efg'), 10000);
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Note: This may not mean that new page has finished loading and DOM is ready. The subsequent 'expect()' statement will ensure Protractor waits for DOM to be available for test.
Reference: Protractor ExpectedConditions
In this case, you can used:
Page Object:
waitForURLContain(urlExpected: string, timeout: number) {
try {
const condition = browser.ExpectedConditions;
browser.wait(condition.urlContains(urlExpected), timeout);
} catch (e) {
console.error('URL not contain text.', e);
};
}
Page Test:
page.waitForURLContain('abc#/efg', 30000);
I typically just add something to the control flow, i.e.:
it('should navigate to the logfile page when attempting ' +
'to access the user login page, after logging in', function() {
userLoginPage.login(true);
userLoginPage.get();
logfilePage.expectLogfilePage();
});
logfilePage:
function login() {
element(by.buttonText('Login')).click();
// Adding this to the control flow will ensure the resulting page is loaded before moving on
browser.getLocationAbsUrl();
}
Use this I think it's better
*isAngularSite(false);*
browser.get(crmUrl);
login.username.sendKeys(username);
login.password.sendKeys(password);
login.submit.click();
*isAngularSite(true);*
For you to use this setting of isAngularSite should put this in your protractor.conf.js here:
global.isAngularSite = function(flag) {
browser.ignoreSynchronization = !flag;
};
to wait until the click itself is complete (ie to resolve the Promise), use await keyword
it('test case 1', async () => {
await login.submit.click();
})
This will stop the command queue until the click (sendKeys, sleep or any other command) is finished
If you're lucky and you're on angular page that is built well and doesn't have micro and macro tasks pending then Protractor should wait by itself until the page is ready. But sometimes you need to handle waiting yourself, for example when logging in through a page that is not Angular (read how to find out if page has pending tasks and how to work with non angular pages)
In the case you're handling the waiting manually, browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page
let $animation = $$('.loading');
await browser.wait(
async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
5000, // timeout
`message on timeout`
);
Make sure to use await
you can do something like this
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click().then(function(){
browser.wait(5000);
});
browser.waitForAngular();
btnLoginEl.click().then(function() { Do Something });
to solve the promise.