Cypress Not Allowing Configurable Tests - cypress

I would like Cypress to auto-generate an it block for each item in the below hash. When I currently run cypress, it picks up on the second test just fine, but ignores the one with the while loop. How can I resolve this? I'd prefer not to have to write out an explicit it block for each item in the map.
const testDataMappings = {
1: {e2eTestName: 'test-one'},
2: {e2eTestName: 'test-two'},
3: {e2eTestName: 'test-three'},
}
// Does not work
describe('My Tests', function () {
let i = 1;
while (i < testDataMappings.length + 1) {
let entry = testDataMappings[i];
it("Should Do The Thing Correctly For" + entry.e2eTestName, () => {
const standardCaseUrl = Cypress.config().baseUrl + "?profile_id=" + String(i);
cy.visit(standardCaseUrl);
cy.wait(5000);
cy.get('.some-div-class-name').compareSnapshot(entry.e2eTestName, 0.0);
});
i +=1;
}
// works
describe('Another Describe block', function () {
it('Should do the thing', () => {
const standardCaseUrl = Cypress.config().baseUrl + "?profile_id=1";
cy.visit(standardCaseUrl);
cy.wait(5000);
cy.get('.some-div-class-name').compareSnapshot('some-snapshot-name', 0.0);
});
});
});
Console logs don't seem to show up, so don't have much insight into what is happening.

There is an example in the Cypress Real World App, a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows. The example is in the transaction feeds spec and uses lodash each to iterate over a feedViews object and dynamically generate the tests for each feed.

Related

Pre-scan web page for dynamic tests

Looking for a definitive answer to the question posed by #JeffTanner here about generating dynamic tests. From that question and the Cypress samples, it's clear that we need to know the number of tests required before generating them.
Problem
We have a web page containing a table of Healthcare analytic data that is refreshed many times during the day. Each refresh the team must check the data, and to divvy up the work we run each row as a separate test. But the number of rows varies every time which means I must count the rows and update the system on each run. Looking for a way to programmatically get the row count.
The HTML is a table of <tbody><tr></tr></tbody>, so the following is enough to get the count but I can't run it in a beforeEach(), the error thrown is "No tests found"
let rowCount;
beforeEach(() => {
cy.visit('/analytics')
cy.get('tbody tr').then($els => rowCount = $els.length)
})
Cypress._.times(rowCount => {
it('process row', () => {
...
})
})
The before:run event fires before the tests start, you can scan the web page there.
Set the event listener in setupNodeEvents(). Cypress commands won't run here, but you can use equivalent Node commands.
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:run', async (details) => {
try {
const fetch = require('node-fetch');
const fs = require('fs-extra');
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const response = await fetch(config.env.prescan); // default below
const body = await response.text(); // or pass in command line
const dom = new JSDOM(body);
const rows = dom.window.document.body.querySelectorAll('tr') // query
// save results
fs.writeJson('./cypress/fixtures/analytics-rows.json', {rows:rows.length})
} catch (error) {
console.log('error:', error)
}
})
},
},
env: {
prefetch: 'url-for-analytics-page'
}
})
Test
import {rows} from './cypress/fixtures/analytics-rows.json' // read row count
Cypress._.times(rows, (row) => {
it(`tests row ${row}`, () => {
...
})
}
You can add a script scan-for-rows.js to the project scripts folder, like this
const rp = require('request-promise');
const $ = require('cheerio');
const fs = require('fs-extra');
rp('my-url')
.then(function(html) {
const rowCount = $('big > a', html).length
fs.writeJson('row-count.json', {rowCount})
})
.catch(function(err){
//handle error
});
Then in package.json call a pre-test script every time a new version of the web page appears.
One possibility is to run the above Cypress test in a pretest script which will always run before your main test script.
// package.json
{
...
"scripts": {
"pretest": "npx cypress run --spec cypress/e2e/pre-scan.cy.js",
"test": "npx cypress run --spec cypress/e2e/main-test.cy.js",
}
}
// pre-scan.cy.js
it('scans for table row count', () => {
cy.visit('/analytics');
cy.get('tbody tr').then($els => {
const rowCount = $els.length;
cy.writeFile('cypress/fixtures/rowcount.json', rowCount);
});
});
Here's a way to get the row count in the spec file without using extra packages, plugins, test hooks, or npm scripts.
Basically, you can create a separate module that makes a synchronous HTTP request using the XMLHTTPRequest class to the /analytics endpoint and use the browser's DOMParser class to find the return the number of <tr> tags.
// scripts/get-row-count.js
export function getRowCount() {
let request = new XMLHttpRequest();
// Set async to false because Cypress will not wait for async functions to finish before looking for it() statements
request.open('GET', '/analytics', false);
request.send(null);
const document = new DOMParser().parseFromString(request.response, 'text/html');
const trTags = Array.from(document.getElementsByTagName('tr'));
return trTags.length;
};
Then in the spec file, import the new function and now you can get an updated row count whenever you need it.
import { getRowCount } from '../scripts/get-row-count';
Cypress._.times(getRowCount() => {
it('process row', () => {
...
})
})
The reason for XMLHTTPRequest instead of fetch is because it allows synchronous requests to be made. Synchronous requests are needed because Cypress won't wait for async requests to come back before parsing for it() blocks.
With this, you always have the most up to date row count without it going stale.

How to use a while loop in cypress? The control of is NOT entering the loop when running this spec file? The way I am polling the task is correct?

The way i am polling tasks for async POST call, is it correct??? Because program control doesn't enter 'while' loop in spec file. Please help!
Previous query: How to return a value from Cypress custom command
beforeEach(function () {
cy.server()
cy.route('POST', '/rest/hosts').as("hosts")
})
it('Create Host', function () {
let ts =''
let regex = /Ok|Error|Warning/mg
// Some cypress commands to create a host. POST call is made when I create a host. I want to poll
// task for this Asynchronous POST call.
cy.wait("#hosts").then(function (xhr) {
expect(xhr.status).to.eq(202)
token = xhr.request.headers["X-Auth-Token"]
NEWURL = Cypress.config().baseUrl + xhr.response.body.task
})
while((ts.match(regex)) === null) {
cy.pollTask(NEWURL, token).then(taskStatus => {
ts= taskStatus
})
}
})
-------------------------
//In Commands.js file, I have written a function to return taskStatus, which I am using it in spec
file above
Commands.js -
Cypress.Commands.add("pollTask", (NEWURL, token) => {
cy.request({
method: 'GET',
url: NEWURL ,
failOnStatusCode: false,
headers: {
'x-auth-token': token
}
}).as('fetchTaskDetails')
cy.get('#fetchTaskDetails').then(function (response) {
const taskStatus = response.body.task.status
cy.log('task status: ' + taskStatus)
cy.wrap(taskStatus)
})
})
You can't use while/for loops with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.
With recursion, you can simulate loops.
Add this to your custom commands file (/cypress/support/commands.js):
Cypress.Commands.add('recursionLoop', {times: 'optional'}, function (fn, times) {
if (typeof times === 'undefined') {
times = 0;
}
cy.then(() => {
const result = fn(++times);
if (result !== false) {
cy.recursionLoop(fn, times);
}
});
});
On your tests, just define a function that does what you want for one iteration, and return false if you don't want to iterate again.
cy.recursionLoop(times => {
cy.wait(1000);
console.log(`Iteration: ${times}`);
console.log('Here goes your code.');
return times < 5;
});
while loop is not working for me, so as a workaround I did a for loop, a sort of while loop with a timeout of retries
let found = false
const timeout = 10000
for(let i = 0; i<timeout && !found;i++){
if(..){
// exiting from the loop
found = true
}
}
it is not helpful for everyone, I know.

Spying on puppeteer calls

I'm building a web scraper that uses puppeteer. I'd obviously like to ensure that I don't break things as I work the kinks out and so I'm writing some implementation tests.
How would I go about testing out the code below? The issue is that newPage() is nested and I can't figure out how to create a spy for it.
Any ideas? Should I structure the code differently to make it easier to test (from what I've read this a big no-no). Happy to hear your suggestions.
//myFile
myFn(){
let browser = puppeteer.launch()
let page = browser.newPage();
}
describe('searchAddress', () => {
beforeEach(() => {
browserSpy = spyOn(puppeteer,'launch')
pageSpy = spyOn(puppeteer,'newPage') // <--- ????
})
it('should ensure the calls were made', async () => {
await myFn()
expect(sleepSpy).toHaveBeenCalled();
expect(pageSpy).toHaveBeenCalled();
});
});
In this case the spyOn(puppeteer,'launch') should return an object that contains a spy object for newPage call. I mean the followings:
describe('searchAddress', () => {
let newPageSpy;
let browserSpy;
beforeEach(() => {
// ARRANGE
newPageSpy = jasmine.createSpy();
let browserMock = { newPage: newPageSpy };
browserSpy = spyOn(puppeteer, 'launch').and.returnValue(browserMock);
});
it('should ensure the calls were made', async () => {
// ACT
await myFn();
// ASSERT
expect(newPageSpy).toHaveBeenCalled();
});
});

Testing an Async function using Jest / Enzyme

Trying run a test case for the following:
async getParents() {
const { user, services, FirmId } = this.props;
let types = await Models.getAccounts({ user, services, firmId: FirmId });
let temp = types.map((type) => {
if(this.state.Parent_UID && this.state.Parent_UID.value === type.Account_UID) {
this.setState({Parent_UID: {label: type.AccountName, value: type.Account_UID}})
}
return {
label: type.AccountName,
value: type.Account_UID,
}
})
this.setState({ParentOptions: temp});
}
here is what i have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><AccountForm {...baseProps} /></MemoryRouter>));
it('Test getParents function ',async() => {
wrapper.setProps({
user:{},
services:[],
FirmId:{},
})
wrapper.find('AccountForm').setState({
SourceOptions:[[]],
Parent_UID: [{
label:[],
value:[],
}],
});
wrapper.update();
await
expect(wrapper.find('AccountForm').instance().getParents()).toBeDefined()
});
If i try to make this ToEqual() it expects a promise and not anobject, what else could I add into this test to work properly.
Goal: Make sure the functions gets called correctly. The test is passing at the moment and has a slight increase on test coverage.
Using Jest and Enzyme for React Js
you can put the await before the async method, like:
await wrapper.find('AccountForm').instance().getParents()
and compare if the state was changed.
In another way, if can mock your API request, because this is a test, then you do not need the correct API, but know if the function calls the API correctly and if the return handling is correct.
And, you cand spy the function like:
const spy = jest.spyOn(wrapper.find('AccountForm').instance(), 'getParents');
and campare if the function was called if they are triggered by some action:
expect(spy).toBeCalled()

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.

Resources