Cypress: How does custom event handler `cy.task()` work? - cypress

I understand what cy.task() provides. It allows Cypress developers to create an event handler with the intent outside of the Cypress workspace.
Correct me if my description is not correct.
What I would like to understand is how cy.task() works within the Cypress Test Framework.
For example, from Cypress documentation, cy.task() usage. It does what is expected, but how?
// in plugins file
module.exports = (on, config) => {
on('task', {
log(message) {
console.log(message)
return null
},
})
}
The diagram below is a work in progress, and I want to add to it where cy.task() are handled:
Thank you

Related

Cypress error: cy.find() failed because the page updated as a result of this command, but you tried to continue the command chain

I wrote the following to select values of a dropdown and assert selected value to make sure it is selected, one by one.
When("User should see example Cover option with values {string}", (exampleCovers) => {
const exampleCover = exampleCovers.split("|");
exampleCover.forEach(chooseCoverLimit);
function chooseCoverLimit(item) {
acoSection.popups(`aco_popup`).as('popupACO').then(function () {
cy.get('#popupACO').contains('text');
acoSection.dropDowns(`example_cover`).as('coverLimit').select(item, { force: true }).then(function () {
cy.get('#coverLimit').find(':selected').should('have.text', item)
})
})
}
});
This works locally on Cypress Test Runner, as well as through headless from the CLI.
Cypress results on local machine CLI
But when I run on Jenkins, some tests get failed.
Cypress results on Cypress Cloud
I get the following CypressError on Cypress Cloud.
Timed out retrying after 10000ms: cy.find() failed because the page updated as a result of this command, but you tried to continue the command chain. The subject is no longer attached to the DOM, and Cypress cannot requery the page after commands such as cy.find().
Common situations why this happens:
Your JS framework re-rendered asynchronously
Your app code reacted to an event firing and removed the element
You can typically solve this by breaking up a chain. For example, rewrite:
cy.get('button').click().should('have.class', 'active')
to
cy.get('button').as('btn').click()
cy.get('#btn').should('have.class', 'active')
https://on.cypress.io/element-has-detached-from-dom
As per the suggestion, I tried breaking cy.get('#coverLimit').find(':selected').should('have.text', item) into two but it still didn't solve the problem. The same code works for one environment but not another.
I've made following changes to make it work.
When("User should see example Cover option with values {string}", (exampleCovers) => {
const exampleCover = exampleCovers.split("|");
exampleCover.forEach(chooseCoverLimit);
function chooseCoverLimit(item) {
acoSection.popups(`aco_popup`).as('popupACO').then(function () {
cy.get('#popupACO').contains('text')
acoSection.dropDowns(`example_cover`).select(item, { force: true }).then(function () {
acoSection.dropDowns(`example_cover`).find(':selected').should('have.text', item).wait(1000)
})
})
}
});

Nightwatch.js Global afterEach not working

I am trying to use the global hook afterEach to close the browser after each test, but once the first test completes it does not perform the global afterEach. Here is an example of my global.js, any help would be amazing!
module.exports = {
afterEach: function (browser, done) {
browser.end(function () {
done();
})
},
}
documentation says it'll run after each test suite, not after each test, so that might be what you're experiencing :)

Cypress test failing due to error in Application Code

Cypress tests are failing due to application code error and not my code error. I have tried to bypass the error by using below code, but still it does not work. I can see similar bug is open in Github by Cypress team but someone can please provide a workaround, if any?
on('uncaught:exception', (err, runnable) => {
return false
})
From https://github.com/cypress-io/cypress/issues/987:
[Cypress doesn't] have a handler on top.onerror, only on the spec iframe and app iframe, and some errors will bubble straight to top.
As a workaround, you can add this to the top of your spec file or support/index.js:
// ignore uncaught exceptions
Cypress.on('uncaught:exception', (err) => {
return false
})
// patch Cypress top.onerror
Object.defineProperty(top, 'onerror', {
value: window.onerror,
})

How to listen global events with Cypress?

We have an application that polls the server periodically until a task is completed. We fire a global event so that Cypress can catch and find out if the task is finished but we had trouble using document.addEventListener on Cypress. Here's what we're doing:
document.addEventListener('queryEnd', () => {
cy.get('.chart').should('be.visible')
cy.get('.table').should('be.visible')
})
However; when we use it in a spec, it doesn't work expected and we're not able to catch it. Also, Cypress doesn't wait for the test and runs afterEach without waiting for the callback to run.
The reason why your code isn't working like you expect is because in Cypress, your tests run in a separate frame than your application under test (AUT). The event you're waiting for will never fire inside of Cypress's document.
To get the document of the AUT, use cy.document() like this:
cy.document()
.then($document => {
// now $document is a reference to the AUT Document
$document.addEventListener(...)
})
To make Cypress wait for your event before continuing, you can just wrap it in a Cypress.Promise. The Cypress docs have an example about waiting for a Promise to complete. For your queryEnd event, it would look something like this:
cy.document() // get a handle for the document
.then($document => {
return new Cypress.Promise(resolve => { // Cypress will wait for this Promise to resolve
const onQueryEnd = () => {
$document.removeEventListener('queryEnd', onQueryEnd) // cleanup
resolve() // resolve and allow Cypress to continue
}
$document.addEventListener('queryEnd', onQueryEnd)
})
})
.then(() => {
cy.get('.chart').should('be.visible')
cy.get('.table').should('be.visible')
})

Can't intercept Cypress API call

I have stuck with Cypress fixtures. Can't intercept an XHR request with SSR and navigation routing.
cypress/integration/page.js:
const fetch = require("unfetch")
describe("/about", () => {
beforeEach(() => {
cy.visit("/", { // Visit home page to trigger SSR
onBeforeLoad (win) {
win.fetch = fetch // replace fetch with xhr implementation
},
})
})
it("Has a correct title", () => {
cy.server()
cy.fixture("about").then(about => {
// about object is correct here, like {title: "About+"}
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Not sure where .route should be
cy.get(".main > :nth-child(1) > a").click() // Navigate to the /about page
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Tried both ways
// This hits my server API without stubbing, getting {title: "About"}
cy.title().should("eq", "About+") // About != About+
})
})
})
cypress/fixtures/about.json:
{"title": "About+"}
I see an XHR request (type=xhr) in Dev Tools and it doesn't use the above about stub object but hits real API instead. Why? Double checked URL and method – 100% the same. Can it be that route is coupled to visit and ignores click-based routing?!
Rechecking this once again, I've found a solution. Let me share the details for everyone interested:
1) I use Next.js which is an excellent tool for SSR but it doesn't allow you to disable server-side rendering (yet) according to this and this issues.
2) You can use Cypress with SSR pages but, in this way, you're limited to testing real HTML. Which means you have to either couple tests to real data (not good in most cases) or stub the database itself (slow). In general, you want to stub HTTP requests.
3) Cypress can't stub fetch requests and mocking fetch with XHR-based implementation was trickier than I thought.
First you need to:
// cypress/integration/your-test.js
Cypress.on('window:before:load', (win) => {
delete win.fetch
})
Then:
// pages/your-page.js
Entry.getInitialProps = async function() {
window.fetch = require("unfetch").default
...
}
Other combinations of delete & update code lines I tried didn't yield positive results. For example, when I had window.fetch = line in the test file it didn't work and fetch.toString() gave "native code". Not sure why, no time to explore further.
Axios solves the above but I don't like to bloat my bundle with extra stuff. You can inject XHR-based fetch for tests only.
4) The most important missing piece. You need to wait for route.
it("Has a correct title", () => {
cy.visit("/")
cy.server()
cy.route("GET", "http://localhost:8080/api/documents/url/about", {title: "About+"}).as("about")
cy.get("[href='/about']").click()
cy.wait("#about") // !!!
cy.get("h1").contains("About+")
})

Resources