Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/codecept.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ program.command('run [test]')
.option('--features', 'run only *.feature files and skip tests')
.option('--tests', 'run only JS test files and skip features')
.option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
.option('--rerun-tests','to run the selected / failed tests from last execution')

// mocha options
.option('--colors', 'force enabling of colors')
Expand Down Expand Up @@ -133,6 +134,7 @@ program.command('run-workers <workers>')
.option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
.option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
.option('-R, --reporter <name>', 'specify the reporter to use')
.option('--rerun-tests','to run the selected / failed tests from last execution')
.action(require('../lib/command/run-workers'));

program.command('run-multiple [suites...]')
Expand Down
43 changes: 27 additions & 16 deletions lib/codecept.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { existsSync, readFileSync } = require('fs');
const fs = require('fs');
const glob = require('glob');
const fsPath = require('path');
const { resolve } = require('path');
Expand All @@ -7,7 +8,8 @@ const container = require('./container');
const Config = require('./config');
const event = require('./event');
const runHook = require('./hooks');
const output = require('./output');
const {output,print} = require('./output');
const FailedTestsRerun = require('./rerunFailed');

/**
* CodeceptJS runner
Expand Down Expand Up @@ -122,25 +124,34 @@ class Codecept {
*
* @param {string} [pattern]
*/
loadTests(pattern) {
loadTests(pattern){
let flag = 0;
let invalidTest = [];
const options = {
cwd: global.codecept_dir,
};

let patterns = [pattern];
if (!pattern) {
patterns = [];
if (this.config.tests && !this.opts.features) patterns.push(this.config.tests);
if (this.config.gherkin.features && !this.opts.tests) patterns.push(this.config.gherkin.features);
if(this.opts.rerunTests){
print('Re-running The Selected/Failed Scripts ..');
const failedTestRerun = new FailedTestsRerun();
failedTestRerun.checkForFailedTestsExist(this.opts);
this.testFiles= JSON.parse(fs.readFileSync('failedCases.json', 'utf8'));
failedTestRerun.checkForFileExistence(this.testFiles);
}

for (pattern of patterns) {
glob.sync(pattern, options).forEach((file) => {
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file);
}
this.testFiles.push(fsPath.resolve(file));
});
else {
let patterns = [pattern];
if (!pattern) {
patterns = [];
if (this.config.tests && !this.opts.features) patterns.push(this.config.tests);
if (this.config.gherkin.features && !this.opts.tests) patterns.push(this.config.gherkin.features);
}
for (pattern of patterns) {
glob.sync(pattern, options).forEach((file) => {
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file);
}
this.testFiles.push(fsPath.resolve(file));
});
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/command/run-workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const { tryOrDefault } = require('../utils');
const output = require('../output');
const event = require('../event');
const Workers = require('../workers');
const FailedTestRerun = require('./../rerunFailed');
const failedTestRerun = new FailedTestRerun();

module.exports = async function (workerCount, options) {
satisfyNodeVersion(
Expand Down
4 changes: 3 additions & 1 deletion lib/command/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ const {
} = require('./utils');
const Config = require('../config');
const Codecept = require('../codecept');
const FailedTestRerun = require('../rerunFailed');

const failedTestRerun = new FailedTestRerun();

module.exports = async function (test, options) {
// registering options globally to use in config
Expand All @@ -19,7 +22,6 @@ module.exports = async function (test, options) {
createOutputDir(config, testRoot);

const codecept = new Codecept(config, options);

try {
codecept.init(testRoot);
await codecept.bootstrap();
Expand Down
14 changes: 14 additions & 0 deletions lib/listener/exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ const event = require('../event');

module.exports = function () {
let failedTests = [];
let failedTestName = [];
let passedTest = [];
const FailedTestRerun = require('./../rerunFailed');
const failedTestRerun = new FailedTestRerun();

event.dispatcher.on(event.test.failed, (testOrSuite) => {
// NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
// is a suite and not a test
const id = testOrSuite.id || (testOrSuite.ctx && testOrSuite.ctx.test.id) || 'empty';
const name = testOrSuite.file;
failedTests.push(id);
failedTestName.push(name);
});

// if test was successful after retries
event.dispatcher.on(event.test.passed, (testOrSuite) => {
// NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
// is a suite and not a test
const id = testOrSuite.id || (testOrSuite.ctx && testOrSuite.ctx.test.id) || 'empty';
const name = testOrSuite.file;
failedTests = failedTests.filter(failed => id !== failed);
failedTestRerun.removePassedTests(name);
});

event.dispatcher.on(event.all.result, () => {
Expand All @@ -24,6 +32,12 @@ module.exports = function () {
}
});

event.dispatcher.on(event.all.after, () => {
// Writes the Failed Test Names In The JSON File For Rerun
failedTestRerun.writeFailedTests(failedTestName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a correct place to put this logic
this listener only sets the exit code for failed tests

failedTestRerun.checkAndRemoveFailedCasesFile(failedTestName)
});

process.on('beforeExit', (code) => {
if (code) {
process.exit(code);
Expand Down
7 changes: 7 additions & 0 deletions lib/listener/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const store = require('../store');

let currentTest;
let currentHook;
let failedTests = new Array(0);

/**
* Register steps inside tests
Expand Down Expand Up @@ -33,6 +34,12 @@ module.exports = function () {
});

event.dispatcher.on(event.test.failed, () => {
//Getting Failed Test Scrips To Add in JSON File for Rerun
// @ts-ignore
let failedTestName = currentTest.file;
failedTests.push(failedTestName);


const cutSteps = function (current) {
const failureIndex = current.steps.findIndex(el => el.status === 'failed');
// To be sure that failed test will be failed in report
Expand Down
112 changes: 112 additions & 0 deletions lib/rerunFailed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
const fs = require('fs');
const { print } = require('codeceptjs/lib/output');

class FailedTestsRerun {
/**
* Initial Check To Find the Existence Of Failed Cases JSON File
* and Content
* @param options
*/
checkForFailedTestsExist(options) {
if (options.rerunTests) {
if (!fs.existsSync('failedCases.json')) {
print('There Are No Failed Tests In Previous Execution');
process.exit();
} else {
const failedTests = this.getFailedTestContent();
if (failedTests.length === 0 || failedTests.toString() === '') {
print('There Are No Files Present In the Failed Cases Json File');
process.exit();
}
}
}
}

/**
* Checks If The Valid Files are Passed in Failed Cases Json File
*@param testFiles
*/
checkForFileExistence(testFiles) {
const invalidTest = [];
let flag = 0;
for (let i = 0; i < testFiles.length; i++) {
const path = testFiles[i];
if (!fs.existsSync(path)) {
invalidTest.push(path);
flag++;
}
}
if (flag > 0) {
// eslint-disable-next-line no-useless-concat
print('\nInvalid File(s) Found :' + '\n');
for (let j = 0; j < invalidTest.length; j++) {
print(invalidTest[j]);
}
process.exit();
}
}

/**
* Returns The Content In JSON File For File/Content existence Check
* @return {string[]}
*/
getFailedTestContent() {
return fs.readFileSync('failedCases.json', { encoding: 'utf8' }).split(',');
}

/**
* Returns The Failed/Selected Tests
* @return {any}
*/
getFailedTests() {
return JSON.parse(fs.readFileSync('failedCases.json', 'utf8'));
}

/**
* Writes The Failed Tests in The Failed Cases JSON File Post Execution
* @param failedTests
*/
writeFailedTests(failedTests) {
if (failedTests.length > 0) {
fs.writeFileSync('failedCases.json', JSON.stringify(failedTests), (err) => {
if (err) { return print(err); }
});
}
}

/**
* Removes The Passed Test From The Failed Cases JSON During the Execution
* Deletes the File When All Tests Are Passed
* @param passedTest
*/
removePassedTests(passedTest) {
if (fs.existsSync('failedCases.json')) {
const currentFile = JSON.parse(fs.readFileSync('failedCases.json', 'utf8'));
currentFile.forEach((t) => {
if (currentFile.includes(passedTest)) {
const index = currentFile.indexOf(t);
if (index > -1) {
currentFile.splice(index, 1);
}
}
});
fs.writeFile('failedCases.json', JSON.stringify(currentFile), (err) => {
if (err) throw err;
});
}
}

/**
* To Remove The Failed Cases JSON file if No Tests Are There
* @param failedTests
*/
checkAndRemoveFailedCasesFile(failedTests) {
if (failedTests.length < 1) {
if (fs.existsSync('failedCases.json')) {
fs.unlinkSync('failedCases.json');
}
}
}
}

module.exports = FailedTestsRerun;
18 changes: 17 additions & 1 deletion lib/workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const recorder = require('./recorder');
const runHook = require('./hooks');
const WorkerStorage = require('./workerStorage');

const FailedTestRerun = require('./rerunFailed');

const failedTestRerun = new FailedTestRerun();

const testsArrObj = [];
const failedTests = [];
const passedTests = [];

const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js');

const initializeCodecept = (configPath, options = {}) => {
Expand Down Expand Up @@ -56,7 +64,6 @@ const createWorker = (workerObject) => {
},
});
worker.on('error', err => output.error(`Worker Error: ${err.stack}`));

WorkerStorage.addWorker(worker);
return worker;
};
Expand Down Expand Up @@ -241,6 +248,7 @@ class Workers extends EventEmitter {
mocha.suite.eachTest((test) => {
const i = groupCounter % groups.length;
if (test) {
testsArrObj.push(test);
const { id } = test;
groups[i].push(id);
groupCounter++;
Expand All @@ -263,6 +271,7 @@ class Workers extends EventEmitter {
const i = indexOfSmallestElement(groups);
suite.tests.forEach((test) => {
if (test) {
testsArrObj.push(test);
const { id } = test;
groups[i].push(id);
}
Expand Down Expand Up @@ -325,10 +334,16 @@ class Workers extends EventEmitter {
break;
case event.test.failed:
this._updateFinishedTests(repackTest(message.data));
// eslint-disable-next-line no-case-declarations
const failTest = testsArrObj.filter(t => t.id === message.data.id);
failedTests.push(failTest[0].file);
this.emit(event.test.failed, repackTest(message.data));
break;
case event.test.passed:
this._updateFinishedTests(repackTest(message.data));
// eslint-disable-next-line no-case-declarations
const passTest = testsArrObj.filter(t => t.id === message.data.id);
passedTests.push(passTest[0].file);
this.emit(event.test.passed, repackTest(message.data));
break;
case event.test.skipped:
Expand All @@ -340,6 +355,7 @@ class Workers extends EventEmitter {
this.emit(event.test.after, repackTest(message.data));
break;
case event.all.after:
failedTestRerun.writeFailedTests(failedTests);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, this looks like breaking the abstraction hierarchy.
this class represents only workers and should know nothing about failedTestRerun

this._appendStats(message.data); break;
}
});
Expand Down