cypress get all arguments of mocked window open - cypress

I mocked window.open in my cypress test as
cy.visit('url', {
onBeforeLoad: (window) => {
cy.stub(window, 'open');
}
});
in my application window.open is called as window.open(url,'_self')
I need to check cypress whether proper URL is opened or not
I need to fetch URL used and check if its match the regular expression or not
const match = newRegExp(`.*`,g)
cy.window().its('open').should('be.calledWithMatch', match);
I'm getting error as
CypressError: Timed out retrying: expected open to have been called with arguments matching /.*/g
The following calls were made:
open("https://google.com", "_self") at open (https://localhost:3000/__cypress/runner/cypress_runner.js:59432:22)

I figured out how to do it. You need to capture the spy and then apply Chai assertions due to it's a Chai Spy
cy.window()
.its('open')
.then((fetchSpy) => {
const firstCall = fetchSpy.getCall(0);
const [url, extraParams] = firstCall.args;
expect(url).to.match(/anyStringForThisRegex$/i);
expect(extraParams).to.have.nested.property('headers.Authorization');
expect(extraParams?.headers?.Authorization).to.match(/^Bearer .*/);
});
This is the way

Related

Cypress: The command was expected to run against origin

I try to realize multi-tabs test which is supported in Cypress 12 by changing the origin of test with cy.origin(). I use https://www.blender.org/ as my baseUrl set in config file, from the Blender main page I extract href to Instagram and change the origin to it. Cypress gives me the following error:
The command was expected to run against origin https://instagram.com but the application is at origin https://www.instagram.com.
Here what I do in the test:
When('I change the origin of my test configuration', () => {
cy.window().then((win) => {
cy.stub(win, 'open').as('Open');
});
const url = Cypress.config('baseUrl');
cy.visit(url);
cy.window().scrollTo('bottom');
var instaUrlString;
cy.get('.social-icons__instagram')
.invoke('attr', 'href')
.then(($instaUrl) => {
instaUrlString = $instaUrl.toString();
cy.origin(instaUrlString, { args: instaUrlString }, (instaUrlString) => {
cy.visit(instaUrlString);
cy.wait(2000);
cy.contains('Allow essential and optional cookies').click();
});
});
cy.visit(url);
});
When I pass hardcoded string to cy.origin() it works fine. What am I doing wrong?
You are missing the www part of https://www.instagram.com. Cypress is comparing the protocol, path and port number but the paths are different.
The shorthand version you have passed in via the link href is not acceptable in this situation. The DSN will resolve the shorthand, Cypress will not.
You could create a function to correct the short version, but what is the point? Just add the correct and working parameter to your cy.origin() command.

Capture a variable in a stubbed function

I have a test case which opens a secondary window. From what I read online, you should prevent this second window opening and visit the url that should have been opened. However, in all the test example I saw, the second url is static. In my case, I need it to be dynamic. This is why I'm using a cy.stub() to a window opening and trying to get the url from it.
cy.visit('https://myUrl.com');
cy.window().then(win => {
cy.stub(win, 'open', newUrl => {
cy.wrap(newUrl).as('newUrl');
});
});
cy.get('#myButton').click(); // opens new window at new url with generated keys
cy.get('#newUrl').then(newUrl => {
cy.visit(newUrl);
});
However, the cy.wrap() inside the cy.stub() triggers this error:
Error: CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
The command that returned the promise was:
> `cy.click()`
The cy command you invoked inside the promise was:
> `cy.wrap()`
Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.
Basically my question is how to capture the url in a stub for later use. Thanks in advance.
The problem is using cy.wrap() or any cy commands inside an event listener. The code you have is valid, and the error should actually be a warning IMO.
One way to handle it is with a plain JS variable.
You may also need a cy.origin() wrapper for this test.
In a recent similar question How to access new tab by clicking on "href" it was found that the cy.visit(newUrl) was causing a reload of the test window, which looses all the data (variables and aliases) previously obtained.
cy.visit('https://myUrl.com');
let nextUrl;
cy.window().then(win => {
cy.stub(win, 'open', newUrl => {
nextUrl = newUrl
});
});
cy.get('#myButton').click(); // opens new window at new url with generated keys
cy.then(() => {
const newOrigin = nextUrl.split('?')[0] // remove query params
.replace('http://', 'https://') // correct for secure protocol
cy.origin(newOrigin, { args: { nextUrl } }, ({ nextUrl }) => {
cy.visit(nextUrl)
})
})
Alternative way to alias
Looking again at your code, you may be able to apply the alias this way
cy.window().then(win => {
cy.stub(win, 'open').as('newUrl');
})
cy.get('#myButton').click();
cy.get('#newUrl').then(newUrl => {
cy.visit(newUrl);
})

cy.wait(#someXhr) taimeouts

I'm having a problem with stubbing a simple request to an API using the cypress' cy.server() and cy.route().
Here's the failing test:
it.only("should show an error message for server errors", () => {
const name = "It doesnt matter";
const email = "takenemail#yopmail.com";
const pass = "123123";
// run the server and define the stubbed route
cy.server();
cy.route(
"POST",
`${serverBaseUrl}/auth/register`,
"fixture:register-fail.json"
).as("postRegister");
// fill in the registration form and hit submit
cy.visit("/auth/register");
cy.get(selectors.registerForm.name).type(name);
cy.get(selectors.registerForm.email).type(email);
cy.get(selectors.registerForm.password).type(pass);
cy.get(selectors.registerForm.registerButton).click();
// intercept the request and mock it
cy.wait("#postRegister"); // this fails.
cy.get(selectors.registerForm.genericErrors).contains(
"This email has already been taken"
);
});
and the error:
cy.wait() timed out waiting 5000ms for the 1st request to the route: postRegister. No request ever occurred.
Note: even though it says that No request ever occurred. I can still see the request being send and a response received in the console's Network tab (which means the stub has been bypassed and a regular request's been made).
Any ideas what's happening?
Thanks in advance.
Ok, seems like i have found the problem.
It turns out that using the fetch API is not supported by cypress.
The workaround - using whatwg-fetch, which is basically a polyfill for the fetch api, working with XHR behind the scenes.
Install the whatwg-fetch package: npm install whatwg-fetch --save
Import it in your project: import "whatwg-fetch";
Last, but very important - remove the fetch object from the window before every page load in the cypress environment like this:
// you can define this in the commands file for example...
Cypress.on("window:before:load", (win) => delete win.fetch);
or an alternative, per-visit approach:
it("some test", () => {
cy.visit("/url", {
onBeforeLoad: win => delete win.fetch // <---
});
// ...the rest of the test
});
Doing this will kick-in the polyfill and the stubbing should be working properly after this intervention.

Cypress.io - sitemap.xml validation test

:) I chose for automated testing a tool Cypress.io.
I need some tests for my sitemap.xml document and I dont know how to do that :(
I have tried install an npm package libxmljs
npm install libxmljs --save
and load it as plugin in cypress/plugins/index.js
const libxmljs = require('libxmljs');
But there is a problem with this. It shows an error
The plugins file is missing or invalid.
Your pluginsFile is set to /home/my-app/cypress/plugins/index.js, but
either the file is missing,
it contains a syntax error, or threw an error when required.
The pluginsFile must be a .js or .coffee file.
Please fix this, or set pluginsFile to false if a plugins file is not
necessary for your project.
Error: The module '/home/my-app/node_modules/libxmljs/build/Release/xmljs.node'
Please help me, how can I use libxmljs in Cypress.io or how i should write tests for Sitemap.xml in this end-to-end testing tool.
Thanks for your time! :)
Although #NoriSte's answer is correct, I found a simpler alternative without the need for any 3rd party code.
Cypress API exposes all the necessary methods to:
load a file (the sitemap.xml in your case): cy.request.
parse XML file (it exposes the jQuery API): Cypress.$
check if a page successfully loads (with a 200 status code): cy.visit
This is the following test that I use to test if all of the pages declared in the sitemap are loading (and make sure it doesn't point to any 404):
describe('Sitemap', () => {
// initialize the url array
let urls = []
// be sure to get the url list before executing any tests
before(async () => {
// getch the sitemap content
const response = await cy.request('sitemap.xml')
// convert sitemap xml body to an array of urls
urls = Cypress.$(response.body)
// according to the sitemap.xml spec,
// the url value should reside in a <loc /> node
// https://www.google.com/sitemaps/protocol.html
.find('loc')
// map to a js array
.toArray()
// get the text of the <loc /> node
.map(el => el.innerText)
})
it('should succesfully load each url in the sitemap', () => {
urls.forEach(cy.visit)
})
})
If you want to use libxmljs to parse your sitemap you should
read the sitemap itself with cy.request
add a custom task to Cypress (because libxmljs is a node library, cy.task is the only way to consume Node.js scripts from your Cypress tests)
returns the parsed data from your task
assert about it in a Cypress test
Those are the high-level steps you need to do 😉
To add to a great answer by gion_13, here’s his solution refactored to utilize Cypress promise-like-commands instead of async calls.
describe('Sitemap', () => {
let urls = [];
before(() => {
cy.request('sitemap.xml')
.as('sitemap')
.then((response) => {
urls = Cypress.$(response.body)
.find('loc')
.toArray()
.map(el => el.innerText);
});
});
it('should succesfully load each url in the sitemap', () => {
urls.forEach(cy.visit);
});
});
Using async in Cypress may raise error ‘Cypress detected that you returned a promise in a test, but also invoked one or more cy commands inside of that promise’.
describe('Sitemap', () => {
let urls = [];
before(() => {
const parser = new DOMParser();
cy.request('/sitemap.xml').then((response) => {
const document = parser.parseFromString(response.body, 'application/xml');
const parsedUrls = document.getElementsByTagName('loc');
urls = Array.from(parsedUrls).map((item) => item.innerHTML);
});
});
it('Should load each url from the sitemap', () => {
urls.forEach(cy.visit);
});
});

Cypress Uncaught Assertion Error despite cy.on('uncaught:exception')

In relation to the following error:
Uncaught Error: Script error.
Cypress detected that an uncaught error was thrown from a cross origin script.
We cannot provide you the stack trace, line number, or file where this error occurred.
Referencing https://docs.cypress.io/api/events/catalog-of-events.html#To-catch-a-single-uncaught-exception
I am trying to run a test that fills out a form and clicks the button to submit:
it('adds biological sample with maximal input', function(){
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('of undefined')
done()
return false
});
cy.get('a').contains('Add biological sample').click();
. . .
cy.contains('Action results');
});
I get an error despite my spec containing the following:
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('of undefined')
done()
return false
});
Here's an image of the test failing .
The error in the bottom left reads,
Error: Uncaught AssertionError: expected '$f is not defined\n\nThis
error originated from your application code, not from Cypress.
\n\nWhen Cypress detects uncaught errors originating from your
application it will automatically fail the current test.\n\nThis
behavior is configurable, and you can choose to turn this off by
listening to the \'uncaught:exception\'
event.\n\nhttps://on.cypress.io/uncaught-exception-from-application'
to include 'of undefined'
(https://www.flukebook.org/_cypress/runner/cypress_runner.js:49186)
It seems that I am taking Cypress's advice and not getting the desired result. Any suggestions? Has this happened to anyone else?
Can you please remove expect(err.message).to.include('of undefined') and done() from the cypress exception block and add the below piece of code inside the test & run the test again
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
The easiest way to fix this is to add the following to the top of your spec:
Cypress.on('uncaught:exception', (err, runnable) => {
return false;
});
This gets the same indentation level as your "it" blocks, nested directly under "describe". It will cause cypress to ignore all uncaught JS exceptions.
In the question, Atticus29 expects "of undefined" to be present in the error message, but the error doesn't actually contain that string. He could change
expect(err.message).to.include('of undefined')
to
expect(err.message).to.include('is not defined')
then it will pass.
To turn off all uncaught exception handling in a spec (recommended)
https://docs.cypress.io/api/events/catalog-of-events.html#To-turn-off-all-uncaught-exception-handling
To catch a single uncaught exception and assert that it contains a string
https://docs.cypress.io/api/events/catalog-of-events.html#To-catch-a-single-uncaught-exception
Although the fix of suppressing Cypress.on sometimes fix the problem, it doesn't really reveal the root problem. It's still better to figure out why you are having an unhandled error in your code (even in the test).
In my case, my form submission forward the page to another page (or current page), which causes re-render. To fix it, I need to call preventDefault.
const onSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();
};
Every problem is a bit different, the above is only one example. Try to think about what your test actually does in the real site.
Official docs suggest that the cypress.on method is placed in "cypress/suport/e2e.js"
Docs 👉 https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file
CMD + F "Support File"
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false prevents Cypress from failing the test
if (err.message.includes('Navigation cancelled from')) {
console.log('🚀 TO INFINITY AND BEYOND 🚀')
return false
}
})
Add these lines Before your Test Suit.
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
});

Resources