Node - Execute command asynchronously and read results on completion - javascript

I have a suite of tests written with Playwright. I am trying to execute these tests from a separate suite of tests. In this separate suite of tests, I need to examine the results of the original results. This leads me to the following directory structure:
/
/checks
checks1.spec.js
/tests
tests1.spec.js
tests2.spec.js
playwright.config.js
My files look like this:
playwright.config.js
// #ts-check
const { devices } = require('#playwright/test');
/**
* #see https://playwright.dev/docs/test-configuration
* #type {import('#playwright/test').PlaywrightTestConfig}
*/
const config = {
testDir: '.',
timeout: 30 * 1000,
expect: {
timeout: 5000
},
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'reports' } ]
],
use: {
actionTimeout: 0,
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
}
]
};
module.exports = config;
tests1.spec.js
const { test, expect } = require('#playwright/test');
test.describe('Field Tests', () => {
test('Should be required', async({ page }) => {
await page.goto('http://localhost:8080');
await expect(true).toBe(true);
});
});
checks1.spec.js
const { exec } = require('child_process');
const { test, expect } = require('#playwright/test');
const command = 'npx playwright test --reporter=json ./tests/tests1.spec.js';
test.describe('Field', () => {
test(`Require state`, async () => {
const { stdout, stderr } = await exec(command);
// Execute the test and get the results
const buffer = child_process.execSync(command);
const json = JSON.parse(buffer.toString());
const results = json.suites[0].suites[0].specs;
const status = results[0].tests[0].results[0].status;
expect(status).toBe('passed');
});
});
When I run npx playwright test --reporter=json ./tests/test1.spec.js, I receive the JSON output as I would expect. However, when I run npx playwright test checks, I receive a Socket instead of the JSON. How do I run the command and wait for the JSON to be returned? Thank you.

It is not clear why your are executing tour command twice (once with await keyword and once with execSync function). However it can be resolved using events:
test.describe('Field', () => {
test(`Require state`, async () => {
const process = await exec(command);
process.stdout.on('data', data => {
// do something with the data here
})
process.on('exit', () => {
// final checks (e.g. - expect) go here
})
});
});
If you want you can build a helper that will return the execution result as promise, thus support the await + async keywords
async cmd(command) {
return new Promise((resolve, reject) => {
const process = await exec(command);
let result = data
process.stdout.on('data', data => {
result = data // any other format manipulations should be added here
})
process.stderr.on('data', reject)
process.on('exit', () => resolve(result))
})
}
Then, somewhere in your code just use:
const result = await cmd('your command goes here')

As per the question, I think you want to execute the playwright for the tests folder first and then wait for the JSON to generate. And, after the JSON is generated, you would like to execute playwright for checks folder.
For this, you can simply split your command into two parts as follow:
npx playwright tests --reporter=json
npx playwright checks
And then execute them as one command using && operator as follow:
npx playwright tests --reporter=json && npx playwright checks.
This way, the tests are execute first, the JSON would be generated at the end of the test. And after JSON is generated, the checks would be executed.
Explaination:
In you command, you were executing npx playwright test checks, since playwrite generates the report at the end of execution (to consolidate all the results), the JSON will be generated after check folder execution is done.
Hence we simply split that command into 2. First we will generate JSON report for tests and then we will execute checks.

Related

Mock implementation of another module not working in jest

I am unit testing (using jest) a function named "getTripDetails" (inside file trip.js) that calls another file "getTrace.js" from different module (which exports a function as shown below).
I want to mock the call of function "getTrace" while testing "getTripDetails" function.
file: trips.js
const gpsTrace = require("./gpsTrace");
getTripDetails = async(req, res)=>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return {status:200};
}
file: getTrace.js
module.exports = async(payload, token) =>{
try {
//code
} catch (e) {
error(e)
throw new Error(e)
}
}
This is what i tried after reading the docs.
file: test.js
let ctrl = require("./trips");
describe("API -- testing", function () {
it("Trip details", async function () {
jest.mock('./gpsTrace');
const gpsTrace = require('./gpsTrace');
gpsTrace.mockImplementation(() => {});
gpsTrace();
await ctrl.getTripDetails({},{});
expect(response.status).to.eql(200);
});
});
It did not get mocked, instead it was calling the original implementation.
Any suggesstions?
You were pretty close! Here are the updated files with comments describing the changes:
gpsTrace.js
Added a console.log message. We won't see this in the test if the mock works successfully.
module.exports = async (payload, token) => {
try {
//code
console.log("You won't see me in the Jest test because of the mock implementation")
} catch (e) {
error(e)
throw new Error(e)
}
}
trips.js
You needed to export your code to be used in other modules. Seeing as you're calling ctrl.getTripDetails() in the test, it makes sense to export your getTripDetails() on an object at the bottom of the file.
const gpsTrace = require("./gpsTrace");
const getTripDetails = async (req, res) =>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return { status:200 };
}
module.exports = {
getTripDetails,
}
gpsTrace.test.js
Make sure to import your modules at the top of the file. Remember that ctrl.getTripDetails({}, {}) calls gpsTrace internally, so no need to call it twice in your test. You also needed to save the response returned from getTripDetails into a variable to be able to compare it: const response = await ctrl.getTripDetails({}, {});.
// make sure your require statements go at the top of the module
const gpsTrace = require('./gpsTrace');
let ctrl = require("./trips");
jest.mock('./gpsTrace');
gpsTrace.mockImplementation(() => {});
describe("API -- testing", function () {
it("Trip details", async function () {
// ctrl.getTripDeals() calls your gpsTrace function internally, so no need to call it twice
// gpsTrace(); <-- can be removed
// you needed to save the returned response into a variable to be able to test it.
const response = await ctrl.getTripDetails({}, {});
expect(response.status).toEqual(200);
});
});
Result
After running the test it now successfully passes. Notice that we DO NOT see the console.log message in the gpsTrace function, which indicates our mockedImplementation of the function is working in the test script. 👍

Tests randomly failing with jest and node

I needed to execute shell commands and keep the process alive to keep the changes made in the env variables, so I made a function that looks like this:
import { spawn } from 'child_process';
function exec(command) {
const bash = spawn('/bin/bash', {
detached: true
});
bash.stdout.setEncoding('utf-8');
bash.stderr.setEncoding('utf-8');
return new Promise(resolve => {
const result = {
stdout: '',
stderr: '',
code: 0
};
bash.stdout.on('data', data => {
// Removes the last line break (i.e \n) from the data.
result.stdout += data.substring(0, data.length - 1);
// Gets the code by getting the number before the `:` on the last line
result.code = Number(result.stdout.split('\n').pop().split(':')[0]);
resolve(result);
});
bash.stderr.on('data', err => {
result.stderr += err;
resolve(result);
});
// Writes the passed command and another to check the code of the command
bash.stdin.write(command + `; echo "$?:${ command }"\n`);
});
}
The test is simple:
it('should run a command that outputs an error', async () => {
// Notice the typo in `echoo`
const res = await exec('echoo test');
expect(res.code).toBe(127);
});
When running this test, it sometimes fails with res.code being 0 instead of 127.
When testing without jest, it works flawlessly 100% of the time.

Promise.allSettled is not a function in jest test, how to mock?

I have an action that uses Promise.allSettled to return multiple stat objects from an API.
When I run the tests with mocking I get the error
Promise.allSettled is not a function
I have a get endpoint to returns different types of stats.
myapi.com/get_stats/:type
I have an action for this API as follows
const types = ['seo', 'referrers', 'clicks', 'posts', 'videos'];
const promises = [];
types.forEach(type =>
promises.push(
api().stats.getStats(type),
),
);
Promise.allSettled(promises).then(res => {
const { statData, statErrors } = mapData(res); // Data manipulation
dispatch({ type: FETCH_STATS_RESOLVED, payload: { statData, statErrors } });
});
My Test set up
jest.mock('api-file.js', () => ({
api: () => ({
stats: {
getStats: type => {
switch (type) {
case: 'seo':
return mockSeoStats;
}
}
}
})
}));
in beforeEach()
mockSeoStats.mockImplementation(() => Promise.resolve({ value: {data: myData} }));
I can see that the Action is receiving these mocked values, but Promise.allSettled is complaining
I assume it's having a hard time with the jest mock structure
So, how can i mock Promise.allSettled to just return what I expect it to, instead of looking at my mocked functions?
A similar question was asked at Execute batch of promise with Promise.allSettled()
Promise.allSettled is available from Node version 12.0 +
You can update node using Node's version manager
nvm ls
# Look for a 12.17.0 version (latest)
nvm install 12.17.0
# Automatically switches to new version
npm run test
# Should properly run your Promise.all
Hope that helped.
Try wrapping Promise.allSettled in try-catch block if you don't want to update node js.
Example:
try {
Promise.allSettled([
// your code
]).finally(() => {
// your code`enter code here`
});
} catch (e) {
console.log('promise.allSettled', e);
}

Testing async/await method. Exception not being caught by Jest in unit test

I'm attempting to test some code that uses await and async using Jest. The problem I'm having is an exception is thrown, but Jest doesn't seem to catch it.
For example, here is run method that checks to see if session.url is present. If not, an exception is thrown:
const args = require('args');
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
// Loads JSON configuration file
module.exports.loadConfigFile = async (filename) => {
const contents = await readFile(filename, 'utf8');
return JSON.parse(contents);
};
// Prepare session ensuring command line flags override config
module.exports.prepareSession = async (flags) => {
if(!flags.config) return flags;
const config = await this.loadConfigFile(flags.config);
return {...config, ...flags};
};
// Runs the race application
module.exports.run = async (flags = {}) => {
const session = await this.prepareSession(flags);
if(!session.url) throw new Error('A valid URL is required');
};
Here I test to see if an exception is thrown:
describe('Race Module', () => {
test('Throws exception if no URL is provided', async () => {
const runner = await race.run();
expect(runner).toThrowError();
});
...
The test runs and it appears an exception is thrown but jest hasn't caught it and it doesn't pass:
Race Module
✕ Throws exception if no URL is provided (3ms)
● Race Module › Throws exception if no URL is provided
A valid URL is required
at Object.<anonymous>.module.exports.run (src/race.js:23:27)
at <anonymous>
Any ideas where I'm going wrong?
My initial thought was to chain catch(() => {}) to race.run() in the test but I am not entirely sure how I would test that. That might not even be the problem.
The fix was to use rejects.toThrow. But, note that this functionality is currently broken in master. I had to use a beta branch. See here: https://github.com/facebook/jest/pull/4884
test('Throws exception if no URL is provided', async () => {
await expect(race.run())
.rejects
.toThrow('A valid URL is required');
});

Loading existing HTML file with JSDOM for frontend unit testing

I'm new to unit testing, and I'm aware my tests may not be valuable or following a specific best practice, but I'm focused on getting this working, which will allow me to test my frontend code using JSDOM.
const { JSDOM } = require('jsdom');
const { describe, it, beforeEach } = require('mocha');
const { expect } = require('chai');
let checkboxes;
const options = {
contentType: 'text/html',
};
describe('component.js', () => {
beforeEach(() => {
JSDOM.fromFile('/Users/johnsoct/Dropbox/Development/andybeverlyschool/dist/individual.html', options).then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
});
});
describe('checkboxes', () => {
it('Checkboxes should be an array', () => {
expect(checkboxes).to.be.a('array');
});
});
});
I'm getting the error "AssertionError: expected undefined to be an array". I'm simply using the array test as a test to ensure I have JSDOM functioning correctly. There are no other errors occurring. Any help would be much appreciated!
fromFile is an async function, meaning that by the time your beforeEach() has finished and the tests start running, it is (probably) still loading the file.
Mocha handles async code in two ways: either return a promise or pass in a callback. So either return the promise from fromFile or do this:
beforeEach(function(done) {
JSDOM.fromFile(myFile)
.then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
})
.then(done, done);
});
The promise version looks like this:
beforeEach(function() {
return JSDOM.fromFile(myFile)
.then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
});
});

Categories