How to detect all tests (in multiple files) finished - mocha.js

I would like to drop database after all tests in all files ran. Is there a hook for it in Mocha?
after() hook can be applied only within 1 file only.

There is a root level hook in mocha. For your case, you can create a new file and specify drop database command in after function. That's it!
// init-test.js
after(function() {
// drop database
});
You don't need to move any test in other test file for this solution.
Reference:
https://mochajs.org/#root-level-hooks

Create a parent file that includes/requires all other tests, then use after within that file:
describe('User', () => {
require('../user/user.spec.js')
})
describe('Post', () => {
require('../post/post.spec.js')
})
describe('Tag', () => {
require('../tag/tag.spec.js')
})
describe('Routes', () => {
require('./routes.spec.js')
})
after(() => {
// drop database
})

I'm using process.on('exit'). It works when running only one file as well as when running tests from package.json.
database.mocha-base.js:
db.connect()
process.on('exit', async () => {
console.log('All tests finished. Droping database')
db.dropDatabase(dbName)
db.disconnect()
})
module.exports = {
applyHooks() {
before(async () => {
// truncate tables
})
}
}
I'm including database.mocha-base.js in all tests that need database access:
const dbMochaBase = require('./path/to/database.mocha-base.js')
describe('Tests', () => {
dbMochaBase.applyHooks()
...
})

Related

Turn off clearing aliases in Cypress test

We have a number of variables from the API which are used throughout the tests. The values are saved to aliases at various points.
How can we turn off clearing aliases between test?
If we use one big test this works fine but Cypress by default clears aliases between tests, so as we break down the tests into smaller more manageable units we are breaking the code.
Simple example:
before(() => {
cy.wrap(123).as('alias')
})
it('test1', () => {
// alias is usable here
cy.get('#alias').should('eq', 123)
cy.wrap(456).as('alias2')
});
it('test2', () => {
// alias is missing here
cy.get('#alias').should('eq', 123)
cy.get('#alias2').should('eq', 456)
});
There's no configuration to turn off alias cleardown.
You would have to create a custom command to do it.
Cypress.Commands.add('keepAliases', function(aliasList) {
if (!aliasList) {
aliasList = Object.keys(this)
.filter(key => !['test', '_runnable', 'currentTest']
.includes(key))
}
aliasList.forEach(key => {
cy.wrap(this[key]).as(key)
})
})
Usage for a single test
before(() => {
cy.wrap(123).as('alias')
})
it('test1', () => {
cy.get('#alias').should('eq', 123)
cy.wrap(456).as('alias2')
});
// ✅ test passes
it('test2', () => {
cy.keepAliases()
cy.get('#alias').should('eq', 123)
cy.get('#alias2').should('eq', 456)
});
Usage for all tests
before(() => {
cy.wrap(123).as('alias')
})
beforeEach(() => {
cy.keepAliases()
})
it('test1', () => {
cy.get('#alias').should('eq', 123)
cy.wrap(456).as('alias2')
});
// ✅ test passes
it('test2', () => {
cy.get('#alias').should('eq', 123)
cy.get('#alias2').should('eq', 456)
});
According to cypress doc
aliases and properties are automatically cleaned up after each test
so with current cypress versions your goal is not achievable.
As a workaround you could use cypress.env as a storage for your tests:
Cypress.env(varName, varValue)
and using a test0 to reset all variables before each texts execution:
it('test0', () => {
// clean up all stored aliases
cy.env("var1",null);
cy.env("var2",null);
//...
});

Cypress: Code in page object file is being executed as a test case before the before hook

I was trying to use a condition as a function within a page object.
class Folders {
DropdownCheckFunction(){
cy.get('.more-less-container').then((dropdown) => {
if(dropdown.find('.name').contains('More')){
cy.get('more-less-container').click()
}
else{
console.log('folders are in expanded state')
}
})
}
Drafts(){
this.DropdownCheckFunction()
cy.get('.category-content').find('[title="Drafts"]').click()
.get(".folder-details").should('contain.text', 'Drafts')
}
Issue here is that the page object is getting executed as a test case and is happening before the code in BEFORE hook is being run. Below is the test file code
describe('Testing all cases related to Drafts', () => {
before(() => {
cy.login()
})
})
it('Needs to open the Drafts folder', () => {
openFolder.Drafts()
});
Attaching the error seen on the test runner for reference
The problem is bad formatting.
If you line up your code, you can see it - your test is outside the scope of the before().
describe('Testing all cases related to Drafts', () => {
before(() => {
cy.login()
})
}) // move this bracket to below the `it()`
it ('Needs to open the Drafts folder', () => {
openFolder.Drafts()
});

Looking for a way tu use Cypress fixtures for all my custom commands outside an it block

I'm building some custom commands and trying to use my fixtures data for all my commands. Right now I'm forced to define it inside an it block.
Looks similar to this:
it("Commands", () => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
This it block is being count as a test case, Is there a better way to pass this for more than one command at the same time?
The workaround I found was to put everything inside a before block, for example:
before(() => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
Is there a reason why you won't use the following:
import {data} from '../fixtures/FixtureFile'
Considering you have the following JSON file:
{
"data": {
"userEmail": "blah",
"userPass": "blah",
"releaseVersion": "1"
}
}
You can include this on your tests, commands (Cypress.custom.commands), etc.
before(() => {
const data = cy.fixture("fixtureFile");
cy.login(data);
cy.createTagMedia(data);
})
You could literally do something like the above. With your Cypress.Commands in your command.ts or js whichever you're using.
And make the commands take in a parameter. Then the above before hook would just be in your tests.

Why am I receiving this warning in a cypress task?

I'm running a task to seed my database in a before hook. Cypress is complaining that
Cypress Warning: Cypress detected that you returned a promise in a test, but also invoked one or more cy commands inside of that promise.
Here is the task
import { seed } from '../../../src/server/db/seed'
const pluginHandler = on => {
on('task', {
'seed:db': () => {
return seed()
}
})
}
export default pluginHandler
This is the seed function
import { exec } from 'child_process'
import util from 'util'
const execP = util.promisify(exec)
export const seed = () => {
// Drop notes.
return execP('mongo starter_test --eval "db.notes.drop()"')
.then(async () => {
// Insert notes fixtures.
await execP(
'mongoimport --db starter_test --collection notes --file ./src/server/db/notes.json'
)
})
.then(() => {
return 0
})
}
And finally the test, which doesn't do anything yet
describe('My First Test', () => {
before(async () => {
await cy.task('seed:db')
})
it('Does not do much!', () => {
cy.visit(Cypress.env('HOST'))
})
})
I'm not, as far as I can see, using commands inside the promise as the warning suggests.
As far as I know, async should not be used inside Cypress before, or any other command (unless you install a 3rd-party lib).
Please try with before(() => cy.task('seed:db'))

Conditionally run tests at runtime using Nightwatchjs

I'm using nightwatch to run my end to end tests but I would like to conditionally run certain tests based on some global settings at runtime.
// globals.js
module.exports = {
FLAG: true
};
// test.js
describe('Something', () => {
it('should do something', client => {
if (client.globals.FLAG) {
expect(1).to.equal(1);
}
});
});
The above works fine, but I want to silent the whole test and conditionally include the it e.g:
// test.js
describe('Something', () => {
// client does not exist out here so it does not work.
if (client.globals.FLAG) {
it('should do something', client => {
expect(1).to.equal(1);
});
}
});
I am aware I can skip tests by defining them in the nightwatch.js and excluding files etc etc but thats not the approach I can use in this implementation. Another solution might be to use tags but I'm not sure this is possible using Mocha.
You could access the flag in the second example by importing your module globals.js:
// test.js
const globals = require('../globals.js');
describe('Something', () => {
if (globals.FLAG) {
it('should do something', client => {
expect(1).to.equal(1);
});
}
});
you could also create a function to ignore the test when the condition is met:
// test.js
const FLAG = require('../globals.js').FLAG;
const not = function(v){ return {it: v ? function(){}: it} };
describe('Something', () => {
not(FLAG).it('should do something', client => {
expect(1).to.equal(1);
});
});

Resources