Referencing Add Command in Webdriverio - mocha.js

I'm using webdriverio (version 6.14.13) and mocha to run my native app tests via Browserstack.
I've created a command script in my command folder, called closeCmp.command.js, as the code was being reused in many tests.
browser.addCommand('cmpDismissal', async function() {
cmpDismissal = $('~ACCEPT AND CLOSE');
if (await this.cmpDismissal.isExisting()) {
this.cmpDismissal.click();
this.cmpDismissal.waitForExist({ reverse: true });
}
},true);
But my question is, how do I reference this in my test script?
I tried what I thought would work;
describe('testing the app cmp.....', () => {
it('close cmp', async () => {
await browser.commands.cmpDismissal();
but when I run the test, the command script is not being run.
It doesn't return an actual failure message, but when I go into Browserstack I can see that the button has not been clicked, indication that the new command has not been executed.
Am I calling it incorrectly in my test script, or is the actual command script the issue here? Thanks

Related

How to programmatically stop a Cypress test

I have a Cypress test that has only one testing case (using v10.9 with the test GUI):
describe("Basic test", () => {
it.only("First test", () => {
cy.visit("http://localhost:9999");
cy.pause();
//if(cy.get("#importantBox").contains(...) {
//}
//else
{
Cypress.runner.stop();
console.log("Stopping...");
}
console.log("Visiting...");
cy.visit("http://localhost:9999/page1");
If a certain element doesn't exist in the page, I don't want the test to continue, so I try to stop it.
Unfortunately, I can see this in the console:
Stopping...
Visiting...
And the test keeps going without the necessary data...
So, can I somehow stop it without using huge if statements?
Stopping the test is relatively easy, the harder part is the condition check.
Cypress runner is built on the Mocha framework, which has a .skip() method. If you issue it in your else clause and inside the Cypress queue, the test will stop.
Two ways to access skip():
Using a function() callback gives access to this which is the Mocha context
it('stops on skip', function() {
...
cy.then(() => this.skip()) // stop here
})
Use cy.state() internal command (may be removed at some point)
it('stops on skip', () => {
...
cy.then(() => cy.state('test').skip()) // stop here
})
You should be aware that all Cypress commands and queries run on an internal queue which is asynchronous to javascript code in the test like console.log("Visiting..."), so you won't get any useful indication from that line.
To use synchronous javascript on the queue, wrap it in a cy.then() command as shown above with the skip() method, so to console log do
cy.then(() => console.log("Visiting..."))

Cypress access alias with this.* doesn't works even with function

Trying to access the alias value on the this () Context, I followed the docs from https://docs.cypress.io/api/commands/as#Fixture
In the .then callback the dataExample parameter is filled OK, but on this.example it is undefined.
describe('cy.as and this', function () {
it('Testing cy.as and this', function () {
cy.fixture('example.json').as('example')
.then(function (dataExample) {
cy.log('ACCESSING this, dataExample', this, dataExample);
});
cy.log(`ACCESSING this.example : ${JSON.stringify(this['example'])}`, this);
});
});
The first log outputs the following: with the dataExample correctly filled, but the contex.example undefined
The second log outside of .then, with this.example also undefined
If I move the cy.fixture line into a beforeEach() it does work. Cold somebody explain this behavior?
describe('alias', () => {
beforeEach(() => {
cy.fixture('example.json').as('example');
});
it('can access all aliases as properties', function () {
expect(this['example']).not.to.eq(undefined); // true
cy.log('THIS', this); // curious => [0]: Context {_runnable: undefined, test: undefined, example: undefined}
cy.log('THIS.example', this['example']); // {name: 'Using fixtures to represent data', email: 'hello#cypress.io',
// body: 'Fixtures are a great way to mock data for responses to routes'}
});
});
Cypress commands are asynchronous. Cypress does not execute a command immediately, it just puts one into a queue to be executed lately. So any command inside a single block of code will not be executed in this block. And the other block of code means any of this:
other it test
before/beforeAll/after/afterAll hooks
then/should callback
So if you want to use a value from a cypress command inside the same it test, you have to use a then callback in order to give Cypress time to execute the command.
If you use a cypress command in a beforeEach hook, the command will be executed by the time when it code starts.
A helpful way to think of the flow is that there are two passes:
first pass, runs the javascript of the test and executes plain JS but enqueues Cypress commands (and their callbacks)
second pass executes those commands
So when
cy.log(`ACCESSING this.example : ${JSON.stringify(this['example'])}`, this)
is enqueued in the first pass, the parameter this['example'] is evaluated immediately (value is undefined).
It's not storing the parameter expression to be evaluated in the second pass, which is what most people expect.
But you can defer the parameter evaluation by putting it inside another callback.
cy.then(() => {
// this callback code is enqueued
// so the evaluation of "this['example']" is deferred until the queue is running
cy.log(`ACCESSING this.example : ${JSON.stringify(this['example'])}`, this)
})
Gleb Bahmutov has a blog
How to replace the confusing cy.then command with the less confusing cy.later command

Can we keep different environment test data files in cypress

The application i am working on has 3 different environments. so is there a way to use a specific test data files for each environment( QA/STG/PRD) when running tests. I know we can use cypress.env file to specify environment related data but i couldn't figure out how to specify different file when running in different environments.
I believe it is not possible to choose which files your cypress will run according to the .env, but you can put your test content inside an IF, so even if cypress runs the test file, everything will be inside one IF that if not satisfied will not perform actions.
describe('Group of tests', () => {
it('Test 1', () => {
if(env == 'dev') {
// Your test here
} else {
/* put something here so that cypress doesn't
throw an error claiming the test suite is empty. */
}
});
});
There's a package cypress-tags that might suit your needs.
Select tests by passing a comma separated list of tags to the Cypress environment variable CYPRESS_INCLUDE_TAGS
describe(['QA'], 'Will tag every test inside the describe with the "QA" tag',
function () { ... });
it(['QA'], 'This is a work-in-progress test', function () { ... });

Check if an error has been written to the console

I'm trying to find a way to check if an error has been written to the console when running a cypress unit test.
I know how to log something to the console
cy.log('log this to the console');
but not how to check if an error has been written to it.
any suggestions how to read errors from the (browser) console log?
note: probably not the "smart" way to test but sometimes my js libraries which I use would "complain" and write the errors to the browser log. this is to simplify testing.
This does exactly what I needed of catching any error in the console and do an assertion of the logs count. Just add the following in cypress/support/index.js
Cypress.on('window:before:load', (win) => {
cy.spy(win.console, 'error');
cy.spy(win.console, 'warn');
});
afterEach(() => {
cy.window().then((win) => {
expect(win.console.error).to.have.callCount(0);
expect(win.console.warn).to.have.callCount(0);
});
});
There have been some updates since the previous answers.
Because the window is re-created with each cy.visit, Cypress recommends stubbing as a part of the cy.visit command.
cy.visit('/', {
onBeforeLoad(win) {
cy.stub(win.console, 'log').as('consoleLog')
cy.stub(win.console, 'error').as('consoleError')
}
})
//...
cy.get('#consoleLog').should('be.calledWith', 'Hello World!')
cy.get('#consoleError').should('be.calledOnce')
For more details see the official FAQ for stubbing out the console: https://docs.cypress.io/faq/questions/using-cypress-faq.html#How-do-I-spy-on-console-log
And the recipe repository: https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__console
Edit: the following does not directly log to terminal when in headless mode, but it nonetheless fails the test on AUT's console.error and displays the error message indirectly, even in the headless terminal, which may be what you want.
I'm not sure exactly what you mean, but let's go through all the places where an output can be logged in cypress, and how to handle several cases.
First, an overview:
To log into the command log, you use:
// from inside your test
cy.log('foo');
To log into devTools console:
// from inside your test
console.log('bar');
To log into terminal, you need to log from within the Cypress' node process:
// from within e.g. your plugin/index.js file
console.log('baz');
How to log AUT's errors to Terminal, Command Log, and fail the test
(note, AUT here stands for Application under test, meaning your application).
I'm also using ansicolor package to make the error red-colored in the terminal, which is optional.
// plugins/index.js
const ansi = require(`ansicolor`);
module.exports = ( on ) => {
on(`task`, {
error ( message ) {
// write the error in red color
console.error( ansi.red(message) );
// play `beep` sound for extra purchase
process.stdout.write(`\u0007`);
return null;
}
});
};
Note: using internal cy.now() command to work around Cypress' tendency to throw Cypress detected that you returned a promise when it (IMO) shouldn't.
(adapted from https://github.com/cypress-io/cypress/issues/300#issuecomment-438176246)
// support/index.js or your test file
Cypress.on(`window:before:load`, win => {
cy.stub( win.console, `error`, msg => {
// log to Terminal
cy.now(`task`, `error`, msg );
// log to Command Log & fail the test
throw new Error( msg );
});
});
Currently there is no straightforward way to do what you are asking but there have been some good discussions on how best to get this information. I copied one solution here but if you follow the github link you can see other solutions proposed.
This snippet was taken from the github issue found here: https://github.com/cypress-io/cypress/issues/300
Just FYI the one easy solution is just to spy on console functions.
cy.window().then((win) => { cy.spy(win.console, "log") })
That will print a command log every time that function is called, and
you could also then assert what has been logged.
Another option depending on why you want to assert that something went wrong is to print the error out under the tests in headless mode. The VP of engineering created an NPM package that does this for you.
Cypress-failed-log
The most easiest way if you simply want to ensure that no error is in the console (which is the most usecase I assume).
# npm
npm install cypress-fail-on-console-error --save-dev
# yarn
yarn add cypress-fail-on-console-error -D
And then add to your support/index.ts file:
import failOnConsoleError from "cypress-fail-on-console-error"
failOnConsoleError()
Now your cypress tests are failing just in time when a console error is printed.
This is the working solution I currently use to check for console errors.
let windowConsoleError;
Cypress.on('window:before:load', (win) => {
windowConsoleError = cy.spy(win.console, 'error');
})
afterEach(() => {
expect(windowConsoleError).to.not.be.called;
})

Is there a way to continue test scenario execution after step failure in a previous scenario?

Whenever there is a step failure while running on a remote server, I would like to capture the failed step and then continue running the remaining scenarios. The captured step would then be included in a file for reporting purposes. Is this a possibility? All replies I've seen elsewhere just say you should fix the test before moving on. I agree, but I only want the tests to stop when running locally, not remotely.
➜ customer git:(pat104) ✗ cucumber.js -f progress (pat104⚡)
...F-----Failed scenario: View and select first contact from contact history
...F-Failed scenario: View and select a contact from multiple contacts in history
..................................................F---Failed scenario: Navigating to profile with url and enrollmentId
...................................................F-Failed scenario: Successful MDN Search with 1 result returned. Tech Selects and continues
.............FFailed scenario: Successful MDN with multiple results
Turns out, one of the step-definitions was using .waitForExist incorrectly. The test was written:
this.browser
.waitForExist('#someElement', 1000, callback)
Callback isn't a parameter for .waitForExist, rewrote to:
.waitForExist('#someElement',1000).then(function (exists) {
assert.equal(exists, true, callback);
})
This is the default behavior, isn't it? Example command
cucumber.js -f json > cucumberOutput.json
Well, that you need to manage in your test itself using callback.fail(e) like below. You can use library like grunt-cucumberjs to add these errors to nice HTML reports.
this.Then(/^the save to wallet button reflects the offer is saved$/, function (callback) {
merchantPage(this.nemo).doStuff().then(function () {
callback();
}, function (e) {
callback.fail(e); //add this to report
});
});
Or you could use Hooks and check whether a scenario is failed and report (take screenshot or add logging etc.)
this.After(function (scenario, callback) {
var driver = this.nemo.driver;
if(scenario.isFailed()){
driver.takeScreenshot().then(function (buffer) {
scenario.attach(new Buffer(buffer, 'base64').toString('binary'), 'image/png');
});
}
driver.quit().then(function () {
callback();
});
});

Resources