Parallel requests in cypress - cypress

I want to make parallel requests in cypress. I define a command for that:
const resetDb = () => {
const apiUrl = `${Cypress.config().baseUrl}/api`;
Cypress.Promise.all([
cy.request(`${apiUrl}/group/seed/resetDb`),
cy.request(`${apiUrl}/auth/seed/resetDb`),
cy.request(`${apiUrl}/email/seed/resetDb`),
]);
};
Cypress.Commands.add('resetDb', resetDb);
However, it is still making those requests in sequence. What am I doing wrong?

I was able to solve this problem using task in Cypress, which allows you to use nodejs API.
In the plugins index file, I define a task as follows:
const fetch = require('isomorphic-unfetch');
module.exports = on => {
on('task', {
resetDb() {
const apiUrl = `http://my.com/api`;
return Promise.all([
fetch(`${apiUrl}/group/seed/resetDb`),
fetch(`${apiUrl}/auth/seed/resetDb`),
fetch(`${apiUrl}/email/seed/resetDb`),
]);
},
});
};
The it can be used as follows:
before(() => {
return cy.task('resetDb');
});

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.

The task 'rp_Log' was not handled in the plugins file. The following tasks are registered: gmail:get-messages

I am writing the cypress test for my website. I have included reportportal js client in my test and my test was running without any issues.
Now I have added gmail-tester for email verification. When I run it I am getting the error
cy.task('rp_Log') failed with the following error:
The task 'rp_Log' was not handled in the plugins file. The following tasks are registered: gmail:get-messages
my plugin/index.js file looks like this
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* #type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
const registerReportPortalPlugin = require('#reportportal/agent-js-cypress/lib/plugin');
const debug = require('debug');
const path = require('path');
const gmail_tester = require('gmail-tester');
module.exports = (on) => registerReportPortalPlugin(on);
module.exports = (on, config) => {
on("before:browser:launch", (browser = {}, launchOptions) => {
if (browser.name === "chrome"&& browser.isHeadless) {
launchOptions.args.push('--disable-gpu');
return launchOptions;
}
});
on("task", {
"gmail:get-messages": async args => {
const messages = await gmail_tester.get_messages(
path.resolve(__dirname, "credentials.json"),
path.resolve(__dirname, "token.json"),
args.options
);
return messages;
}
});
};
My test file looks like this
describe('Launch website',() => {
it('Home visit',() => {
cy.visit('http://localhost:3000')
cy.log("Visited the page")
cy.screenshot("Launch_name.png")
cy.rp_screenshot("Launch.png")
})
})
When I run the test I can see the my page is getting launched and it's printing the log also. But after that it's telling cy.task('rp_log') is not defined instead it can see the gmail get messages.
can anyone help me to get rid of this error?
I resolved an issue. We cant use two modules.export in index.js file. The answer should look like this
module.exports = (on, config) => {
registerReportPortalPlugin(on);
on("before:browser:launch", (browser = {}, launchOptions) => {
if (browser.name === "chrome"&& browser.isHeadless) {
launchOptions.args.push('--disable-gpu');
return launchOptions;
}
});
on("task", {
"gmail:get-messages": async args => {
const messages = await gmail_tester.get_messages(
path.resolve(__dirname, "credentials.json"),
path.resolve(__dirname, "token.json"),
args.options
);
return messages;
}
});
};

How to test dispatched react function using Jest

I am trying to unit test a function which makes an async call using an Axios helper instance. I have attempted multiple ways of trying to unit test this but I can not seem to find any material online which has helped. I've been stuck on this problem for a few days which is frustrating so any help would be appreciated! Below are the Axios Helper file (api.js)
api.js
import axios from 'axios'
const API = (token = null) => {
let headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-key': process.env.NEXT_PUBLIC_API_HEADER_SUBSCRIPTION_KEY
}
if (token) {
const tokenHeader = { Authorization: 'Bearer ' + token }
headers = { ...headers, ...tokenHeader }
}
const url = process.env.NEXT_PUBLIC_API_BASE_URL
const API = axios.create({
baseURL: url,
headers
})
return API
}
export default API
mocked API
export default {
post: jest.fn(() =>
Promise.resolve({
data: {}
})
),
get: jest.fn(() =>
Promise.resolve({
data: {}
})
)
}
action file
export const initiate2FA = (destinationValue) => async () => {
const twoFactorAuth = destinationValue
const res = await API().post('/foo', {
Destination: twoFactorAuth
})
return res
}
Action.test.js
import API from 'api/api'
import { initiate2FA } from 'actions/userActions'
jest.mock('api/api')
const mockedAxios = API
const dispatch = jest.fn()
describe('Initiate2FA function', () => {
it('bar', async () => {
mockedAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200 }))
const t = await dispatch(initiate2FA('test#test.com'))
console.log(t)
})
})
My issue with the above test file is that it returns an anonymous function and I do not know how to handle this to pass the unit test. The goal of the test is to make sure the function is called. I am not sure if I am approaching this the correct way or should change my approach.
Again, any suggestions would be great!
Mocking an API call is something you can mock on your own React component, instead of a function, and the best option would be to not mock anything on your component. Here you can read all about why you should not mock your API functions. At the end of the article, you're going to find a library called Mock Service Worker which you can use for your purpose.
The way you declare you have an actual HTTP called that needs to be mocked would be something like this:
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
If you just need to unit test a function, you can still use Mock Service Worker to resolve the HTTP request, and then test what happens after that. This would still be your first choice. And the test would look like:
// this could be in another file or on top of your tests.
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
// and this would be your test
describe('Initiate2FA function', () => {
it('bar', async () => {
const res = await initiate2FA('test#test.com');
expect(res).toBe({bar: '');
})
})

How do I include run time arguments while executing a google cloud workflow in Nodejs?

I'm trying to include run time variables while executing a google cloud workflow. I can't find the documentation to do so unless you're using a REST API.
Here's my code that's mostly from their documentation I just get null for the arguments. I think it could be something with the second parameter it expects on createExecution named execution, but I can't figure it out.
const { ExecutionsClient } = require('#google-cloud/workflows');
const client = new ExecutionsClient();
const execute = () => {
return client.createExecution(
{
parent: client.workflowPath('project_id', 'location', 'name'),
},
{
argument: {
users: ['info here'],
},
},
);
};
module.exports = execute;
Thanks for the help!
In case anyone else has this problem you pass the parameter execution to createExecution() along with parent. It's just an object and you can specify argument there which takes a string. Stringify your object and you're good to go!
const { ExecutionsClient } = require('#google-cloud/workflows');
const client = new ExecutionsClient();
const execute = () => {
return client.createExecution({
parent: client.workflowPath('', '', ''),
execution: {
argument: JSON.stringify({
users: [],
}),
},
});
};
module.exports = execute;

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