:) 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);
});
});
Related
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.
Is it possible to save requests made during Cypress test as curls to be reviewed later? E.g. for importing them in Postman.
Network tab, of DevTools of Cypress application, logs request from Cypress app, not from application under test itself.
The network tab will show requests from the AUT, but if you want to find all the requests use intercept
const requests = []
cy.intercept('/api/*', (request) => {
console.log(request.url) // get them from the console
requests.push(request.url) // or save them to a file at end of spec
})
...
after(() => {
cy.writeFile('cypress/fixtures/requests.json', requests)
})
To gain detailed information about the request and response including the response time, you can utilize the HAR file format (http://www.softwareishard.com/blog/har-12-spec/). To generate a HAR file on the fly during the execution of your Cypress tests you can use the cypress-har-generator.
describe('my tests', () => {
before(() => {
// start recording
cy.recordHar();
});
after(() => {
// save the HAR file
cy.saveHar({ waitForIdle: true });
});
it('does something', () => {
cy.visit('https://example.com');
// ...
});
});
This plugin will generate a HAR file that can be imported to the network panel of developer tools (https://developer.chrome.com/blog/new-in-devtools-76/#HAR) or Postman (https://blog.postman.com/postman-now-supports-importing-har-files/), for further analysis.
I want read sitemap.xml file and then want to check status of every url present in sitemap.
URL present in sitemap are around 20K so I dont want to visit url but just wants to check status and as url count is too large wants to run every url as one test case so that one of test case failure would not affect remaining ones.
Wants to implement above in Cypress
There is an answer here Cypress.io - sitemap.xml validation test that may start you off.
Instead of cy.visit(url) use cy.request(url), it should be much faster.
it('check each url in the sitemap.xml', () => {
const results = []
cy.request('sitemap.xml')
.then(response => {
// convert sitemap xml body to an array of urls
urls = Cypress.$(response.body)
.find('loc')
.toArray()
.map(el => el.innerText)
return urls
})
.then(urls => {
urls.forEach(url => {
cy.request({
url,
failOnStatusCode: false // get status good and bad
})
.then(response => {
results.push({url, statusCode: response.statusCode})
})
})
})
// use results inside .then()
cy.then(() => {
console.log(results)
})
})
BTW putting each URL in a separate test is a big headache.
The above pattern will give you status codes on good and bad URLS, and will be faster than separate tests.
I want to cypress.log() out a specific field in the request header whenever my webapp makes requests that way when it fails and adds screenshots/logs I can grab that that requestId that failed.
Is there a way to setup cypress so that for all network requests it checks for this field and log it?
I can add a cy.intercept within each individual file but I want a more generic way to handle this.
Cypress.log is the synchronous version of cy.log().
Add middleware: true to the intercept to pass request to other intercepts.
cy.intercept({ url: '*', middleware: true }, (req) => {
const headerValue = req.headers?['x-custom-headers']
if (headerValue) {
Cypress.log({
name: 'x-custom-header',
message: headerValue
})
}
})
You'll get an Cypress promise error if you try to use cy.log() to log out every request header in an cy.intercept() within a routeHandler callback. This would also make it kind of tough to log to a CI terminal as well.
Instead you can console.log to dev tools. To make it apply to all tests, you can wrap it in a beforeEach() and place it in the support/index.js file.
// support/index.js
beforeEach(() => {
cy.intercept('*', (req) => {
req.continue((res) => {
console.log(JSON.stringify(req.headers))
})
})
})
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+")
})