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
Related
I have an application that has a document section showing some notes and I am trying to identify and verify the text ("Release Notes") with Cypress. But all my locating strategies are failing and need some help. Please see if you can recommend some other way to locate this.
DOM:
Below is my code:
cy.get('.release-notes')
.should('be.visible')
.then((text => {
cy.log(text.text()) // blank
cy.log(text) // <span.release-notes>
}))
cy.get('[data="release-notes.html"]').should('be.visible').then((text => {
cy.log(text.text()) // blank
cy.log(text) // <object.release-notes-object>
}))
//Below lines - Fails
//cy.get('h1:contains("Release Notes")') -- Timed out retrying after 4000ms: Expected to find element: h1:contains("Release Notes"), but never found it.
//cy.get('body').should('contains', "Release Notes"); --Timed out retrying after 4000ms: object tested must be an array, a map, an object, a set, a string, or a weakset, but object given
Note: I have "includeShadowDom": true inside my cypress.json
You may be getting blocked by the #document. It does not look like an iframe, but may behave like one.
Try
cy.get('object.release-notes-object')
.its('0.contentDocument').should('exist')
.its('body').should('not.be.undefined').within(() => {
cy.get('h1').should('contain', 'Release Notes')
})
Also turn off chromeWebSecurity in case there's a cross-domain issue
cypress.json
{
"chromeWebSecurity": false
}
Debugging the inner document
Since you can get into the inner document body, in theory you should now be able to query it.
Take a look at the DOM inside body from Cypress' perspective, experiment with timing - the release notes likely to be fetched from a server
cy.get('object.release-notes-object')
.its('0.contentDocument').should('exist')
.its('body').should('not.be.undefined')
.then($body => {
cy.wait(10000) // just for debugging, explicit wait here
.then(() => console.log($body[0].innerHTML)) // inspect the elements inside after wait
})
Another way, add a .should() to retry
cy.wait(10000) // for debugging only, waiting for fetch
cy.get('object.release-notes-object')
.its('0.contentDocument').should('exist')
.its('body', {timeout: 10000}) //vary timeout here
.should('not.be.undefined')
.should('have.descendants', 'h1') // retries the body fetch until true
The difference between the last and adding a timeout to cy.get('h1') is that .should('have.descendants', 'h1') will re-query the body during each retry.
It's possible that there is an empty body element before the release notes are fetched, and it is replaced when the release notes are added.
Before the "includeShadowDom": true global flag we have to do something like this:
cy.get('selector').shadow().find('selector')
Now may be you can omit the .shadow() part and try
cy.get('[app-name="Voyage Planning"]').find('h1:contains("Release Notes")')
You have attributes data-gr-ext-installed and data-new-gr-c-s-check-loaded on the body. These look like they mark the end of something loading.
Add a check for that in #Mihi's solution (which must be nearly correct).
cy.get('object.release-notes-object')
.its('0.contentDocument').should('exist')
.its('body')
.should('not.be.undefined')
.should('have.attr', 'data-gr-ext-installed') // or data-new-gr-c-s-check-loaded
.within(() => {
cy.get('h1').should('contain', 'Release Notes')
})
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.
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.
I have a simple casperjs test to submit a search form on my homepage. Then I assert that the title on the landing page is correct.
Works fine on my computer (OSX 10.9.2) but on my colleague's laptops (a Win 7 and Win 8), the test fails randomly because casper "thinks" it is still on the search page.
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.then(function() {
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
}
Introducing a casper.waitFor(3000) before checking the page title does not change the outcome. I've also tried to replace the then step by a waitForUrl, but it fails after 5 secs, saying it is still on the current page.
Plenty of other tests work fine on all computers but it's the only one with form submition.
Any hints on how to solve or properly work around this? I'd rather not simulate a click on the submit button (more coupling to the form internals) if possible (but it would be okay I guess).
Thanks
$ casperjs --version
1.1.0-beta3
$ phantomjs --version
1.9.7
EDIT: submitting the form and waitForUrldid not help. I found out that the test actually runs fine on its own, even on the Windows 7 machine. But when I run two tests:
01 search.js (the one described above)
02 menu.js (a simple one, merely containing assertExists)
'search.js' fails most of the time... and sometimes 'menu.js' fails instead! I suspect some mishandled concurrent access, although it consistently works on OSX. I must be doing something wrong. Both tests have the same structure:
casper.test.begin('Some test', function(test) {
casper.start(someUrl, function() {
// some test
});
casper.run(function() {
test.done();
});
});
Any clue?
Try :
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, {
query: goodQuery
},false);
this.click("your selector for submit button");
});
casper.then(function() {//you should use waitForUrl/Selector/Text() instead
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
casper.run(function() {
this.test.comment('------ Tests over ----\n');
test.done();
});
});
It's better to submit the form by clicking. Sometimes (often) it doesn't pass putting the fill arg at true. Just put the correct selector for the submit button.
You should wait for an item to appear on the following page. I would change your code to the following:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.waitForSelector('#someSelectorOnNextPage', function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
I also experience same issue. Suprisingly adding empty then() handler fixes that in v1.1.0-beta3. I don't think this is expected behavior though:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
// Do nothing here, just call it as a placeholder
// Here http://localhost:8080/site sends us to the next endpoint
casper.then(function() {});
// Now this is the final page we actually want to assert
casper.then(function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
EDIT:
Although question author says casper.waitForUrl() didn't work for them, it did work for me as an alternative solution.
What does look strange is that in verbose mode whatever returns a 301 status code along with Location Header is recognized as HTTP 200 response by Casper.
EDIT 2:
Well obviously it doesn't happen every time, but what I noticed is that Casper sometimes doubles the previous response (that's why I thought it recognizes some specific HTTP codes as 200 mistakenly and that's why author's code functioned as if it stayed on same page after form submission) and sometimes not.
waitForUrl() fixes that obviously but there is still some underneath issue in Casper which scares me a bit and I hope I will find some time to report it with all the dumps to Casper issue tracker.
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.