Cypress - before() hook in supportFile runs before every spec - automation

According to documentation, this hook should only execute once per suite run (once every cypress run call), but this seems to be executed before every single spec. Is this a bug? Or am I doing something wrong?
Cypress.Cookies.defaults({
preserve: ['cookie']
})
before(() => {
cy.log("Should only run once")
})
});

The before() hook runs once per spec file, not once per cypress run.
It looks like you want the Before Run API
on('before:run', (details) => {
/* ... */
})

Related

Cypress - check test failure in global afterEach

Is there a possibility to check in global afterEach if test (it) failed?
Such global afterEach is located in support/index.js:
afterEach(() => {
// check if test failed and perform some actions
})
You can use afterEach hook and remain in the scope of the Cypress context (where cy commands are available), for example:
afterEach(function() {
if (this.currentTest.state === 'failed') {
// your code
}
});
Reference: https://github.com/cypress-io/cypress/discussions/15047
Or you can use test:after:run event and switch to node context (where any kind of node code can be executed outside of the scope of Cypress, like accessing database or file system), for example:
Cypress.on('test:after:run', (test, runnable) => {
if (test.state === 'failed') {
// your code
}
});
Reference: https://docs.cypress.io/api/events/catalog-of-events#Cypress-Events
What you seek is the after:spec and is exposed via plug-ins. You'll have access to the spec and the results.
https://docs.cypress.io/api/plugins/after-spec-api#Syntax

How to queue something in a Cypress test

I have the following test for a custom Cypress command of mine:
it('can navigate to a url', () => {
const history = createHistory();
cy.window().then(win => ((win as any).__chh__history__ = history));
cy.spy(history, 'push');
history.listen((location, action) => {
expect(action).to.equal('PUSH');
expect(location.pathname).to.equal(PATH);
expect(location.state).to.equal('foobar');
});
cy.navigate(PATH, 'foobar');
cy.wait(0).then(() => {
expect(history.push).to.have.been.called;
});
});
The command works fine, but if I remove the cy.wait(0), only doing the expect, then the test fails.
I assume it's because the cy.navigate command (added via Cypress.Commands.add) is queued, like other Cypress commands, meaning the expect actually runs before any of the Cypress commands have even started?
If so, that's fine and as expected of course, but is there a way in Cypress tests to queue things, without using cy.wait(0)?
For example when I'm checking elements, I can use cy.get(something).should($el => expect(...)), but in this case, there's nothing to "should on"... I could of course cy.get('body') or whatever, but I don't really want to get anything in this case. 🤔
The docs show use of a naked expect() after the spy has been established,
cy.spy(util, 'addListeners')
App.start()
expect(util.addListeners).to.be.called
but I think this is misleading, since expect() runs immediately but intervening Cypress commands may have inherent delays or reties, and can't be guaranteed to complete before the expect is executed.
Two other ways to test this are:
Ensure the expect is only run after your custom command
cy.navigate(PATH, 'foobar').then(() => {
expect(history.push).to.have.been.called;
})
Put the expectation into the Cypress command queue
cy.navigate(PATH, 'foobar');
cy.wrap(history).its('push').should('have.been.called')

Cypress - log response data from an request after a click()

Although I know this may not be considered as a best practice, but what I want to achieve is to silently delete a record from a database after the same was created throughout UI. In htat way I want to keep our test environment clear as much as possible and reduce the noise of test data.
After my tests create a new record by clicking over the UI, I wait for POST request to finish and then I would like to extract the id from the response (so I can reuse it to silently delete that record by calling the cy.request('DELETE', '/id')).
Here's a sample test I have put on as a showcase. I'm wondering why nothing is logged in this example.
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo')
.then((response) => {
cy.log(response.body)
})
})
As far as I can see from here https://docs.cypress.io/api/commands/wait.html#Alias this should be fine.
your code contains two problems.
First:
The click triggers a new page to be loaded but cypress does not wait until the PageLoad event is raised (because you do not use visit). On my PC the Request takes about 5 seconds until it is triggered after the click. So you should use wait(..., { timeout: 10000 }).
Second:
wait() yields the XHR object, not the response. So your code within then is not correct. Also the body is passed as object. So you should use JSON.stringify() to see the result in the command log.
This code works:
describe("asda", () => {
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy
.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo', { timeout: 20000 })
.then((xhr) => {
cy.log(JSON.stringify(xhr.response.body))
})
})
})
Instead of route and server method, try intercept directly

mocha programmatically set vue error handler

I find myself writing this at the start of pretty much all of my unit tests in mocha:
it('should do something', (done) => {
Vue.config.errorHandler = done;
// do something aynchronous
});
By default, Vue catches all errors itself and logs them to the console, so mocha can't see them. This code makes sure that thrown errors fail the tests.
Is there a way with mocha to do this without having to start every single async test with this line of code? If I have to write / use a plugin, that's fine.
Try:
Vue.config.errorHandler = function (err, vm, info) {
throw err
}
in your test entry.

How to set jasmine for karma e2e for testing angular app?

I try to create e2e tests with karma and jasmine with yeoman. In my karma-e2e.conf.js I add jasmine:
files = [
JASMINE,
JASMINE_ADAPTER,
ANGULAR_SCENARIO,
ANGULAR_SCENARIO_ADAPTER,
'test/e2e/**/*.js'
];
A need async testing so I need to use runs, waits, waitsFor (https://github.com/pivotal/jasmine/wiki/Asynchronous-specs)
But if I try to use it:
it('test', function () {
runs(function () {
...
});
});
Scenatio test runner returns this:
TypeError: Cannot call method 'runs' of null
at runs (http://localhost:8080/adapter/lib/jasmine.js:562:32)
at Object.<anonymous> (http://localhost:8080/base/test/e2e/eduUser.js:42:3)
at Object.angular.scenario.SpecRunner.run (http://localhost:8080/adapter/lib/angular-scenario.js:27057:15)
at Object.run (http://localhost:8080/adapter/lib/angular-scenario.js:10169:18)
I don't know where the problem is. Can you help me please?
Angular e2e tests with Karma don't and can't use the JASMINE adapter. Instead you have the ANGULAR_SCENARIO_ADAPTER which has a similar feel to writing Jasmine tests.
All commands in the adapter's API are asynchronous anyway. For example element('#nav-items').count() doesn't return a number, it returns a Future object. Future objects are placed in a queue and executed asynchronously as the runner progresses. To quote the API docs:
expect(future).{matcher}:
[...] All API statements return a future object, which get a value assigned after they are executed.
If you need to run your own asynchronous test code, you can extend the adapter's DSL, this is easier than it might sound. The idea is that you return your own Future which can be evaluated by a matcher such as toBe(). There are some examples on how to do this in the e2e-tests.js Gist from Vojta. Just remember to call done(null, myRetrunValue); when your test code is successful (myRetrunValue is the value evaluated by your matcher). Or done('Your own error message'); if you want the test to fail.
UPDATE: In response to question below. To simulate a login, first add a function called login to the dsl:
angular.scenario.dsl('login', function() {
return function(selector) {
// #param {DOMWindow} appWindow The window object of the iframe (the application)
// #param {jQuery} $document jQuery wrapped document of the application
// #param {function(error, value)} done Callback that should be called when done
// (will basically call the next item in the queuue)
return this.addFutureAction('Logging in', function(appWindow, $document, done) {
// You can do normal jQuery/jqLite stuff here on $document, just call done() when your asynchronous tasks have completed
// Create some kind of listener to handle when your login is complete
$document.one('loginComplete', function(e){
done(null, true);
}).one('loginError', function(e){
done('Login error', false);
});
// Simulate the button click
var loginButton = $document.find(selector || 'button.login');
loginButton.click();
})
};
});
And then call:
beforeEach( function()
{
expect( login('button.login') ).toBeTruthy();
});