cy.wrap() - difference between passing a string or object - cypress

After working with Cypress for a short time i've noticed a strange behavior with cy.wrap(). While wrapping string inside before hook said string is available across all further tests but object would only be available in first test, while yielding undefined in rest of them.
I've tried wrapping tests with context(), which worked, but unnecessarily clogged the cypress test tree UI.
Without context:
describe('Wrap test', function () {
before(function () {
console.log('Before');
cy.wrap('string').as('string');
cy.wrap({ object: true }).as('object');
});
it('Test 1', function () {
console.log('Test 1');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
it('Test 2', function () {
console.log('Test 2');
console.log(this.string); // 'string'
console.log(this.object); // undefined
});
});
With context:
describe('Wrap test', function () {
before(function () {
console.log('Before');
cy.wrap('string').as('string');
cy.wrap({ object: true }).as('object');
});
context('Context', function () {
it('Test 1', function () {
console.log('Test 1');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
it('Test 2', function () {
console.log('Test 2');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
})
});
I'd like to mock an object in before hook and afterwards use it in few tests. I thought about converting said object into JSON format, as it would be available for all the tests. Looking forward to hearing opinions from more experienced people about presented scenario.

Turns out that clearing context between tests is actually Cypress' intentional behavior, as described in docs here.
When you wrap your individual test cases in another test suite (describe/context), it won't clear the context (as you're seeing) for that suite if the context was created a level above --- this is intentional, too.
The fact that it only clears primitives, and not objects, is a bug, though, and I've created a bug report for this: mocha context cleared between tests only for primitives.

Related

Cypress: Using same fixture file in multiple test

I need to pass the url and other variable in multiple tests[it-function]. For 1st test code run successfully but for 2nd test it showing error. Is there any workaround or solution? My code is as follows
describe('Document Upload', function()
{
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//1st test
it('Login as manager',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
login.getPassword().type(this.testdata.passwordDocumentM)
login.getLoginButton().click()
//Logout
login.getUser().click()
login.getLogout().click()
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
})
The error is
I have tried with above and also using before function again but same error
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
Starting with Cypress version 12 Test Isolation was introduced. This now means the Mocha context (aka this) is completely cleaned between tests.
Mocha context
It used to be (undocumented) that the Mocha context could be used to preserve variables across tests, for example
before(function () {
cy.fixture("example.json").as('testdata') // preserved on "this"
})
it("check testdata 1", function () {
expect(this.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(this.testdata).to.not.be.undefined // fails in Cypress v12
})
but now that does not work.
The use of Mocha context is a bit arbitrary anyway, and requires explicit use of function-style functions which is easy to forget, particularly in places like array method callbacks Array.forEach(() => {}).
You can still use the Cypress context to store data
before(function () {
cy.fixture("example").then(function (testdata) {
Cypress.testdata = testdata;
})
})
it("check testdata 1", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
Note this is also undocumented and may change in the future.
Caching methods
Technically, the way to do this is to set the alias with beforeEach().
The cy.fixture() command caches it's value, so you do not get the read overhead for each test (see Fixture returns outdated/false data #4716)
There is also cy.session() for more complicated scenarios, which would be officially supported.
beforeEach(() => {
cy.session('testdata', () => {
cy.fixture("example").then(testdata => {
sessionStorage.setItem("testdata", testdata)
})
})
})
it("check testdata 1", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
it("check testdata 2", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
Lastly, cypress-data-session which fills a gap
From the docs
Feature
cy.session
cy.dataSession
Command is
official ✅
community 🎁
Can cache
the browser session state
anything
Stability
experimental !!! not in v12
production
Cache across specs
yes
yes
Access to the cached data
no ???
yes
Custom validation
no
yes
Custom restore
no
yes
Dependent caching
no
yes
Static utility methods
limited
all
GUI integration
yes
no
Should you use it?
maybe
yes
Cypress version support
newer versions
all
Cypress.env()
This is another way that is officially supported,
before(() => {
cy.fixture("example").then(testdata => {
Cypress.env("testdata", testdata)
})
})
it("log my fixture 1", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
it("log my fixture 2", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
but there are still certain tests scenarios that reset the browser completely where this may not work.

Mocha .then(done) doesn't work as expected

This question is not about a problem which I can't solve, it is just a curiosity. I'm not very experienced with Mocha, but there's something interesting I've stumbled upon already.
What I want is to use done() to tell Mocha the promise has been resolved.
The following code DOESN'T work:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(done)
})
I know I'm passing the result of the user.save() promise to done, but I think it shouldn't be a problem.
Instead this other code works:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(() => done())
})
It seems to me that Mocha done() has some kind of control flow which leads to: Error: done() invoked with non-Error: {"_id":"5b65b9d2669f7b2ec0a3d503","name":"Dummy","__v":0}
Is it because done() wants strictly an error as its argument?
Why done() does even care about what I pass to it?
Can you make some example showing why done() argument to be an Error is useful?
Thanks in advance ;)
It is because done() in Mocha only accepts Error argument. In your case, your save() method returns json object not an Error ie new Error('failed save').
If we take a look at mocha test file, we can see that it won't accept other type of arguments.
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L358
describe('when done() is invoked with a string', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done('Test error'); // specify done with string/text argument
});
test.run(function (err) {
assert(err.message === 'done() invoked with non-Error: Test error');
done();
});
});
});
But if we see the test when the argument is Error, it works
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L345
describe('when an error is passed', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done(new Error('fail'));
});
test.run(function (err) {
assert(err.message === 'fail');
done();
});
});
});
Btw, I suggest that you avoid using done since mocha supports promise by specifying return statement. So, we change the code into
beforeEach(() => {
user = new User({ name: 'Dummy' })
return user.save().then(user => {
// antyhing todo with user
});
});
Hope it helps.

How to send the success of a test to testingBot from a Protractor project?

Following the testingBot example for protractor-based projects I got this code
var TestingBot = require('testingbot-api');
describe('Protractor Demo App', function () {
var tb;
beforeEach(function () {
tb = new TestingBot({
api_key: "master_key",
api_secret: "secret_007"
});
});
afterEach(function () {
browser.getSession().then(function (session) {
tb.updateTest({
'test[success]': true/*where do I get this 'test[success]' attribute? */
}, session.getId(), function () {
console.log("Hi! :D");
});
})
});
it('should have a title', function () {
browser.get('http://juliemr.github.io/protractor-demo/');
expect(browser.getTitle()).toEqual('Super Calculator');
});
});
I need to send the success of the test back through the tb.updateTest() but I don't know where I get the value of a passed or failed test. For now the value is a static true. I'd appreciate a jasmine approach too.
You can use a custom reporter with Jasmine.
There you can hook into specDone or suiteDone which has a result parameter, containing the test's success state.
You can then use this state to write a custom report or send it to somewhere else.

Async call in beforeAll

Here are 2 samples of the same test. The only difference is that first one uses a promise in beforeAll block to assign a value to the variable while the second one assigns the value directly.
I raised a similar question Running spec after promise has been resolved with one of the comments pointing to this issue https://github.com/jasmine/jasmine/issues/412 which says that this is not supported in Jasmine. Has somebody figured out any workaround?
This fails with TypeError: Cannot read property 'forEach' of undefined
describe('Async car test', function () {
var cars;
beforeAll(function (done) {
// getCars() is a promise which resolves to ['audi', 'bmw']
getCars().then(function (data) {
cars = data;
console.log(cars) // ['audi', 'bmw']
done();
});
});
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
This works fine
describe('Car test', function () {
var cars = ['audi', 'bmw'];
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
Posting it as an answer, because I can't see things properly in comments.
I'm actually generating tests in my spec as well, and I'm using https://www.npmjs.com/package/jasmine-data-provider , I think you probably cannot generate it directly from resolved promise. And wrapping in another it doesn't work for you. This should work:
var using = require('jasmine-data-provider');
using(cars.forEach, function (car) {
it(car + ' should be' + car, function () {
expect(car).toBe(car);
});
});
This is not an issue with jasmine, it is an issue with your code.
beforeAll does not block subsequent code below the statement. it blocks code that is defined in it('should ...', (done)=>{...});
it('should have cars', (done) => {
cars.forEach(function (car) {
expect(car).toBe(car);
});
});
Since Jasmine does not support adding tests at runtime, the trick is to request the asynchronous data before starting Jasmine, and then using the retrieved data during runtime instead. This can be achieved with a singleton and programmatically starting Jasmine.
See here for a working example.
// car-collection.js
class CarCollection {
static load() {
return this.request()
then((data) => this.cars = data);
}
static request() {
// in practice this function would do something cooler
return Promise.resolve(['audi', 'bmw']);
}
}
modules.export = CarCollection;
Since CarCollection has methods that are static they will be shared across imports and this.cars will persist.
// launcher.js
const Jasmine = require('jasmine');
const CarCollection = require('./car-collection');
CarCollection.load()
.then(() => {
console.log(`car count is ${CarCollection.cars.length}`); // prints: car count is 2
const jasmine = new Jasmine();
jasmine.loadConfigFile(...); // path to jasmine.json
jasmine.execute();
});
An important step here is configure jasmine to know where to look for the test files. Either by loading a config or passing specifics into the execute function.
// car.spec.js
const CarCollection = require('./car-collection');
describe('test', function () {
CarCollection.cars.forEach((car) => {
it('test' + car, () => {
expect(car).toBe(car);
});
});
});
Now run node ./launcher.js and the tests should run.

Using jasmine to test amplifyjs request call backs

I'm using amplifyjs for AJAX requests. That's working fine. I'm using jasmine to test the code. I'm wondering what the best method is to test the success and error call backs. The current unit test I've written doesn't work because the call back is executed after the jasmine expect. Here's my code under test:
function logout() {
ns.busy.show();
amplify.request({
resourceId: 'logout',
success: _logoutSuccess
});
};
function _logoutSuccess(response) {
ns.busy.hide();
};
Here's the unit test, where I want to validate that a function is called when the request is returned:
it('should hide busy when successful', function () {
// arrange
ns.busy = { show: function () { }, hide: function () { } };
spyOn(ns.busy, 'hide');
amplify.request.define('logout', function (settings) {
settings.success({});
});
// act
ns.accountLogoutViewModel.logout();
// assert
expect(ns.busy.hide).toHaveBeenCalled();
});
Note: ns is just a variable holding the application namespace. If I place break points on the expect and on the ns.busy.hide() in the _logoutSuccess function, jasmine hits the expect and then hits the _logoutSuccess, hence the spyOn fails.
As I say, the code is working, I just want to know how to write a test for it. I've looked into the jasmine done() function, but I'm not sure how to use it in this circumstance (or even if it is a solution).
Thanks
Isn't it always the way, when I finally get round to posting a question, I then immediately find the answer. Here's the test that passes, using the jasmine done() function:
it('should hide busy when successful', function (done) {
// arrange
ns.busy = { show: function () { }, hide: function () { } };
spyOn(ns.busy, 'hide');
amplify.request.define('logout', function (settings) {
settings.success({});
done();
});
// act
ns.accountLogoutViewModel.logout();
// assert
expect(ns.busy.hide).toHaveBeenCalled();
});

Resources