diff --git a/README.md b/README.md
index 3417207aa..b483e3c80 100644
--- a/README.md
+++ b/README.md
@@ -315,8 +315,8 @@ Thanks all to those who are and will have contributing to this awesome project!
-
+
diff --git a/docs/helpers/Expect.md b/docs/helpers/ExpectHelper.md
similarity index 98%
rename from docs/helpers/Expect.md
rename to docs/helpers/ExpectHelper.md
index 4628ea41c..7ffe415ab 100644
--- a/docs/helpers/Expect.md
+++ b/docs/helpers/ExpectHelper.md
@@ -1,8 +1,8 @@
---
-permalink: /helpers/Expect
+permalink: /helpers/ExpectHelper
editLink: false
sidebar: auto
-title: Expect
+title: ExpectHelper
---
@@ -20,7 +20,7 @@ Zero-configuration when paired with other helpers like REST, Playwright:
{
helpers: {
Playwright: {...},
- Expect: {},
+ ExpectHelper: {},
}
}
```
diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md
index 3bf4cf9a0..55fba6b8c 100644
--- a/docs/helpers/Puppeteer.md
+++ b/docs/helpers/Puppeteer.md
@@ -2476,7 +2476,7 @@ Returns **void** automatically synchronized promise through #recorder
[18]: https://codecept.io/helpers/FileSystem
-[19]: https://pptr.dev/next/guides/request-interception
+[19]: https://pptr.dev/guides/network-interception
[20]: https://github.com/GoogleChrome/puppeteer/issues/1313
diff --git a/lib/command/gherkin/init.js b/lib/command/gherkin/init.js
index b0aabe4a8..7ac70231c 100644
--- a/lib/command/gherkin/init.js
+++ b/lib/command/gherkin/init.js
@@ -4,7 +4,7 @@ const mkdirp = require('mkdirp');
const output = require('../../output');
const { fileExists } = require('../../utils');
const {
- getConfig, getTestRoot, updateConfig, safeFileWrite,
+ getConfig, getTestRoot, updateConfig, safeFileWrite, findConfigFile,
} = require('../utils');
const featureFile = `Feature: Business rules
@@ -26,7 +26,17 @@ Given('I have a defined step', () => {
module.exports = function (genPath) {
const testsPath = getTestRoot(genPath);
+ const configFile = findConfigFile(testsPath);
+
+ if (!configFile) {
+ output.error(
+ "Can't initialize Gherkin. This command must be run in an already initialized project."
+ );
+ process.exit(1);
+ }
+
const config = getConfig(testsPath);
+ const extension = path.extname(configFile).substring(1);
output.print('Initializing Gherkin (Cucumber BDD) for CodeceptJS');
output.print('--------------------------');
@@ -53,18 +63,18 @@ module.exports = function (genPath) {
output.success(`Created ${dir}, place step definitions into it`);
}
- if (safeFileWrite(path.join(dir, 'steps.js'), stepsFile)) {
- output.success('Created sample steps file: step_definitions/steps.js');
+ if (safeFileWrite(path.join(dir, `steps.${extension}`), stepsFile)) {
+ output.success(
+ `Created sample steps file: step_definitions/steps.${extension}`
+ );
}
config.gherkin = {
- features: './features/*.feature',
- steps: [
- './step_definitions/steps.js',
- ],
+ features: "./features/*.feature",
+ steps: [`./step_definitions/steps.${extension}`],
};
- updateConfig(testsPath, config);
+ updateConfig(testsPath, config, extension);
output.success('Gherkin setup is done.');
output.success('Start writing feature files and implement corresponding steps.');
diff --git a/lib/command/utils.js b/lib/command/utils.js
index ac3cf7fd6..b8c9ca2fa 100644
--- a/lib/command/utils.js
+++ b/lib/command/utils.js
@@ -41,15 +41,15 @@ function fail(msg) {
module.exports.fail = fail;
-function updateConfig(testsPath, config, key, extension = 'js') {
+function updateConfig(testsPath, config, extension) {
const configFile = path.join(testsPath, `codecept.conf.${extension}`);
if (!fileExists(configFile)) {
- console.log();
const msg = `codecept.conf.${extension} config can\'t be updated automatically`;
+ console.log();
console.log(`${output.colors.bold.red(msg)}`);
- console.log('Please update it manually:');
+ console.log(`${output.colors.bold.red("Please update it manually:")}`);
console.log();
- console.log(`${key}: ${config[key]}`);
+ console.log(config);
console.log();
return;
}
@@ -104,3 +104,14 @@ module.exports.createOutputDir = (config, testRoot) => {
mkdirp.sync(outputDir);
}
};
+
+module.exports.findConfigFile = (testsPath) => {
+ const extensions = ['js', 'ts'];
+ for (const ext of extensions) {
+ const configFile = path.join(testsPath, `codecept.conf.${ext}`);
+ if (fileExists(configFile)) {
+ return configFile;
+ }
+ }
+ return null;
+}
diff --git a/lib/helper/Expect.js b/lib/helper/ExpectHelper.js
similarity index 99%
rename from lib/helper/Expect.js
rename to lib/helper/ExpectHelper.js
index c9de3693c..c6a9dd27d 100644
--- a/lib/helper/Expect.js
+++ b/lib/helper/ExpectHelper.js
@@ -23,7 +23,7 @@ import('chai').then(chai => {
*{
* helpers: {
* Playwright: {...},
- * Expect: {},
+ * ExpectHelper: {},
* }
*}
* ```
diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js
index 697e27561..d03e3af2b 100644
--- a/lib/helper/Playwright.js
+++ b/lib/helper/Playwright.js
@@ -1981,7 +1981,7 @@ class Playwright extends Helper {
*/
async dontSeeCookie(name) {
const cookies = await this.browserContext.cookies();
- empty(`cookie ${name} to be set`).assert(cookies.filter(c => c.name === name));
+ empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name));
}
/**
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index 2bb8edac8..2ae747dea 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -1627,7 +1627,7 @@ class Puppeteer extends Helper {
*/
async dontSeeCookie(name) {
const cookies = await this.page.cookies();
- empty(`cookie ${name} to be set`).assert(cookies.filter(c => c.name === name));
+ empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name));
}
/**
@@ -2472,12 +2472,12 @@ class Puppeteer extends Helper {
}
/**
- * Mocks network request using [`Request Interception`](https://pptr.dev/next/guides/request-interception)
+ * Mocks network request using [`Request Interception`](https://pptr.dev/guides/network-interception)
*
* ```js
* I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
* ```
- * This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/next/guides/request-interception)
+ * This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/guides/network-interception)
*
* @param {string|RegExp} [url] URL, regex or pattern for to match URL
* @param {function} [handler] a function to process request
diff --git a/lib/plugin/retryTo.js b/lib/plugin/retryTo.js
index 9929944f1..211b39ac7 100644
--- a/lib/plugin/retryTo.js
+++ b/lib/plugin/retryTo.js
@@ -1,5 +1,4 @@
const recorder = require('../recorder');
-const store = require('../store');
const { debug } = require('../output');
const defaultConfig = {
@@ -73,49 +72,55 @@ const defaultConfig = {
* const retryTo = codeceptjs.container.plugins('retryTo');
* ```
*
-*/
+ */
module.exports = function (config) {
config = Object.assign(defaultConfig, config);
+ function retryTo(callback, maxTries, pollInterval = config.pollInterval) {
+ return new Promise((done, reject) => {
+ let tries = 1;
- if (config.registerGlobal) {
- global.retryTo = retryTo;
- }
- return retryTo;
+ function handleRetryException(err) {
+ recorder.throw(err);
+ reject(err);
+ }
- function retryTo(callback, maxTries, pollInterval = undefined) {
- let tries = 1;
- if (!pollInterval) pollInterval = config.pollInterval;
-
- let err = null;
-
- return new Promise((done) => {
const tryBlock = async () => {
+ tries++;
recorder.session.start(`retryTo ${tries}`);
- await callback(tries);
+ try {
+ await callback(tries);
+ } catch (err) {
+ handleRetryException(err);
+ }
+
+ // Call done if no errors
recorder.add(() => {
recorder.session.restore(`retryTo ${tries}`);
done(null);
});
- recorder.session.catch((e) => {
- err = e;
+
+ // Catch errors and retry
+ recorder.session.catch((err) => {
recorder.session.restore(`retryTo ${tries}`);
- tries++;
if (tries <= maxTries) {
debug(`Error ${err}... Retrying`);
- err = null;
-
- recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval));
+ recorder.add(`retryTo ${tries}`, () =>
+ setTimeout(tryBlock, pollInterval)
+ );
} else {
- done(null);
+ // if maxTries reached
+ handleRetryException(err);
}
});
};
- recorder.add('retryTo', async () => {
- tryBlock();
- });
- }).then(() => {
- if (err) recorder.throw(err);
+ recorder.add('retryTo', tryBlock);
});
}
+
+ if (config.registerGlobal) {
+ global.retryTo = retryTo;
+ }
+
+ return retryTo;
};
diff --git a/package.json b/package.json
index 93eba907c..2c56b255b 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
"@xmldom/xmldom": "0.8.10",
"acorn": "8.11.3",
"arrify": "2.0.1",
- "axios": "1.6.7",
+ "axios": "1.7.2",
"chai": "5.1.1",
"chai-deep-match": "1.2.1",
"chai-exclude": "2.1.0",
@@ -89,7 +89,7 @@
"cross-spawn": "7.0.3",
"css-to-xpath": "0.1.0",
"csstoxpath": "1.6.0",
- "devtools": "8.36.1",
+ "devtools": "8.38.0",
"envinfo": "7.11.1",
"escape-string-regexp": "4.0.0",
"figures": "3.2.0",
@@ -148,7 +148,7 @@
"jsdoc": "4.0.3",
"jsdoc-typeof-plugin": "1.0.0",
"json-server": "0.10.1",
- "playwright": "1.44.0",
+ "playwright": "1.44.1",
"puppeteer": "22.10.0",
"qrcode-terminal": "0.12.0",
"rosie": "2.1.1",
@@ -161,7 +161,7 @@
"tsd": "^0.31.0",
"tsd-jsdoc": "2.5.0",
"typedoc": "0.25.13",
- "typedoc-plugin-markdown": "3.17.1",
+ "typedoc-plugin-markdown": "4.0.3",
"typescript": "5.3.3",
"wdio-docker-service": "1.5.0",
"webdriverio": "8.36.1",
diff --git a/test/acceptance/codecept.Playwright.coverage.js b/test/acceptance/codecept.Playwright.coverage.js
index f1c57c214..11017f986 100644
--- a/test/acceptance/codecept.Playwright.coverage.js
+++ b/test/acceptance/codecept.Playwright.coverage.js
@@ -23,7 +23,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: 'test/acceptance/output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: false,
diff --git a/test/acceptance/codecept.Playwright.js b/test/acceptance/codecept.Playwright.js
index d45a49f50..e4dca9ded 100644
--- a/test/acceptance/codecept.Playwright.js
+++ b/test/acceptance/codecept.Playwright.js
@@ -23,7 +23,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: 'test/acceptance/output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: false,
diff --git a/test/acceptance/codecept.Playwright.retryTo.js b/test/acceptance/codecept.Playwright.retryTo.js
index d45a49f50..e4dca9ded 100644
--- a/test/acceptance/codecept.Playwright.retryTo.js
+++ b/test/acceptance/codecept.Playwright.retryTo.js
@@ -23,7 +23,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: 'test/acceptance/output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: false,
diff --git a/test/acceptance/codecept.Puppeteer.js b/test/acceptance/codecept.Puppeteer.js
index d18087620..8c2a8e987 100644
--- a/test/acceptance/codecept.Puppeteer.js
+++ b/test/acceptance/codecept.Puppeteer.js
@@ -19,7 +19,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: './output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: false,
diff --git a/test/acceptance/codecept.Testcafe.js b/test/acceptance/codecept.Testcafe.js
index 1191bb18a..13b529103 100644
--- a/test/acceptance/codecept.Testcafe.js
+++ b/test/acceptance/codecept.Testcafe.js
@@ -9,7 +9,7 @@ module.exports.config = {
url: TestHelper.siteUrl(),
show: true,
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: false,
diff --git a/test/acceptance/codecept.WebDriver.devtools.coverage.js b/test/acceptance/codecept.WebDriver.devtools.coverage.js
index d4f1089cf..6fb8f5aaa 100644
--- a/test/acceptance/codecept.WebDriver.devtools.coverage.js
+++ b/test/acceptance/codecept.WebDriver.devtools.coverage.js
@@ -21,7 +21,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: './output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
mocha: {},
diff --git a/test/acceptance/codecept.WebDriver.devtools.js b/test/acceptance/codecept.WebDriver.devtools.js
index fc749d22e..f86312789 100644
--- a/test/acceptance/codecept.WebDriver.devtools.js
+++ b/test/acceptance/codecept.WebDriver.devtools.js
@@ -21,7 +21,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: './output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: async () => new Promise(done => {
diff --git a/test/acceptance/codecept.WebDriver.js b/test/acceptance/codecept.WebDriver.js
index fd7af8b22..54dd4e3e4 100644
--- a/test/acceptance/codecept.WebDriver.js
+++ b/test/acceptance/codecept.WebDriver.js
@@ -21,7 +21,7 @@ module.exports.config = {
require: '../support/ScreenshotSessionHelper.js',
outputPath: './output',
},
- Expect: {},
+ ExpectHelper: {},
},
include: {},
bootstrap: async () => new Promise(done => {
diff --git a/test/acceptance/retryTo_test.js b/test/acceptance/retryTo_test.js
index b568e8607..9d0e1f631 100644
--- a/test/acceptance/retryTo_test.js
+++ b/test/acceptance/retryTo_test.js
@@ -14,3 +14,15 @@ Scenario('retryTo works with non await steps @plugin', async () => {
if (tryNum < 3) I.waitForVisible('.nothing', 1);
}, 4);
});
+
+Scenario('Should fail after reached max retries', async () => {
+ await retryTo(() => {
+ throw new Error('Custom pluginRetryTo Error');
+ }, 3);
+});
+
+Scenario('Should succeed at the third attempt @plugin', async () => {
+ await retryTo(async (tryNum) => {
+ if (tryNum < 2) throw new Error('Custom pluginRetryTo Error');
+ }, 3);
+});
\ No newline at end of file
diff --git a/test/data/sandbox/configs/gherkin/config_js/codecept.conf.init.js b/test/data/sandbox/configs/gherkin/config_js/codecept.conf.init.js
new file mode 100644
index 000000000..61e56f1dd
--- /dev/null
+++ b/test/data/sandbox/configs/gherkin/config_js/codecept.conf.init.js
@@ -0,0 +1,16 @@
+/** @type {CodeceptJS.MainConfig} */
+exports.config = {
+ tests: "./*_test.js",
+ output: "./output",
+ helpers: {
+ Playwright: {
+ browser: "chromium",
+ url: "http://localhost",
+ show: true,
+ },
+ },
+ include: {
+ I: "./steps_file.js",
+ },
+ name: "CodeceptJS",
+};
diff --git a/test/data/sandbox/configs/gherkin/config_ts/codecept.conf.init.ts b/test/data/sandbox/configs/gherkin/config_ts/codecept.conf.init.ts
new file mode 100644
index 000000000..86fe45f4f
--- /dev/null
+++ b/test/data/sandbox/configs/gherkin/config_ts/codecept.conf.init.ts
@@ -0,0 +1,15 @@
+export const config: CodeceptJS.MainConfig = {
+ tests: "./*_test.ts",
+ output: "./output",
+ helpers: {
+ Playwright: {
+ browser: "chromium",
+ url: "http://localhost",
+ show: true
+ }
+ },
+ include: {
+ I: "./steps_file"
+ },
+ name: "CodeceptJS"
+}
diff --git a/test/helper/Expect_test.js b/test/helper/Expect_test.js
index 9567943c0..7db470e01 100644
--- a/test/helper/Expect_test.js
+++ b/test/helper/Expect_test.js
@@ -5,7 +5,7 @@ import('chai').then(chai => {
expect = chai.expect;
});
-const ExpectHelper = require('../../lib/helper/Expect');
+const ExpectHelper = require('../../lib/helper/ExpectHelper');
global.codeceptjs = require('../../lib');
diff --git a/test/plugin/plugin_test.js b/test/plugin/plugin_test.js
index 2f615d1d5..1185967b4 100644
--- a/test/plugin/plugin_test.js
+++ b/test/plugin/plugin_test.js
@@ -61,4 +61,17 @@ describe('CodeceptJS plugin', function () {
done();
});
});
+
+ it('should retry to failure', (done) => {
+ exec(
+ `${config_run_config('codecept.Playwright.retryTo.js', 'Should fail after reached max retries')} --verbose`, (err, stdout) => {
+ const lines = stdout.split('\n');
+ expect(lines).toEqual(
+ expect.arrayContaining([expect.stringContaining('Custom pluginRetryTo Error')])
+ );
+ expect(err).toBeTruthy();
+ done();
+ }
+ );
+ });
});
diff --git a/test/runner/gherkin_test.js b/test/runner/gherkin_test.js
new file mode 100644
index 000000000..7a1dbd94a
--- /dev/null
+++ b/test/runner/gherkin_test.js
@@ -0,0 +1,93 @@
+const assert = require("assert");
+const path = require("path");
+const fs = require("fs");
+const exec = require("child_process").exec;
+
+const runner = path.join(__dirname, "/../../bin/codecept.js");
+const codecept_dir = path.join(__dirname, "/../data/sandbox/configs/gherkin/");
+
+describe("gherkin bdd commands", () => {
+ describe("bdd:init", () => {
+ let codecept_dir_js = path.join(codecept_dir, "config_js");
+ let codecept_dir_ts = path.join(codecept_dir, "config_ts");
+
+ beforeEach(() => {
+ fs.copyFileSync(
+ path.join(codecept_dir_js, "codecept.conf.init.js"),
+ path.join(codecept_dir_js, "codecept.conf.js")
+ );
+ fs.copyFileSync(
+ path.join(codecept_dir_ts, "codecept.conf.init.ts"),
+ path.join(codecept_dir_ts, "codecept.conf.ts")
+ );
+ });
+
+ afterEach(() => {
+ try {
+ fs.rmSync(path.join(codecept_dir_js, "codecept.conf.js"));
+ fs.rmSync(path.join(codecept_dir_js, "features"), {
+ recursive: true,
+ });
+ fs.rmSync(path.join(codecept_dir_js, "step_definitions"), {
+ recursive: true,
+ });
+ } catch (e) {}
+ try {
+ fs.rmSync(path.join(codecept_dir_ts, "codecept.conf.ts"));
+ fs.rmSync(path.join(codecept_dir_ts, "features"), {
+ recursive: true,
+ });
+ fs.rmSync(path.join(codecept_dir_ts, "step_definitions"), {
+ recursive: true,
+ });
+ } catch (e) {}
+ });
+
+ [
+ {
+ codecept_dir_test: codecept_dir_js,
+ extension: "js",
+ },
+ {
+ codecept_dir_test: codecept_dir_ts,
+ extension: "ts",
+ },
+ ].forEach(({ codecept_dir_test, extension }) => {
+ it(`prepare CodeceptJS to run feature files (codecept.conf.${extension})`, (done) => {
+ exec(`${runner} gherkin:init ${codecept_dir_test}`, (err, stdout) => {
+ let dir = path.join(codecept_dir_test, "features");
+
+ stdout.should.include(
+ "Initializing Gherkin (Cucumber BDD) for CodeceptJS"
+ );
+ stdout.should.include(
+ `Created ${dir}, place your *.feature files in it`
+ );
+ stdout.should.include(
+ "Created sample feature file: features/basic.feature"
+ );
+
+ dir = path.join(codecept_dir_test, "step_definitions");
+ stdout.should.include(
+ `Created ${dir}, place step definitions into it`
+ );
+ stdout.should.include(
+ `Created sample steps file: step_definitions/steps.${extension}`
+ );
+ assert(!err);
+
+ const configResult = fs
+ .readFileSync(
+ path.join(codecept_dir_test, `codecept.conf.${extension}`)
+ )
+ .toString();
+ configResult.should.contain(`features: './features/*.feature'`);
+ configResult.should.contain(
+ `steps: ['./step_definitions/steps.${extension}']`
+ );
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/test/unit/plugin/retryto_test.js b/test/unit/plugin/retryto_test.js
index 28187e02c..293adf1d7 100644
--- a/test/unit/plugin/retryto_test.js
+++ b/test/unit/plugin/retryto_test.js
@@ -20,11 +20,11 @@ describe('retryTo plugin', () => {
it('should execute few times command on fail', async () => {
let counter = 0;
let errorCaught = false;
- await retryTo(() => {
- recorder.add(() => counter++);
- recorder.add(() => { throw new Error('Ups'); });
- }, 5, 10);
try {
+ await retryTo(() => {
+ recorder.add(() => counter++);
+ recorder.add(() => { throw new Error('Ups'); });
+ }, 5, 10);
await recorder.promise();
} catch (err) {
errorCaught = true;