codeceptjs 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +9 -7
  3. package/bin/codecept.js +1 -1
  4. package/docs/ai.md +246 -0
  5. package/docs/build/Appium.js +47 -7
  6. package/docs/build/JSONResponse.js +4 -4
  7. package/docs/build/Nightmare.js +3 -1
  8. package/docs/build/OpenAI.js +122 -0
  9. package/docs/build/Playwright.js +193 -45
  10. package/docs/build/Protractor.js +3 -1
  11. package/docs/build/Puppeteer.js +45 -12
  12. package/docs/build/REST.js +15 -5
  13. package/docs/build/TestCafe.js +3 -1
  14. package/docs/build/WebDriver.js +30 -5
  15. package/docs/changelog.md +70 -0
  16. package/docs/helpers/Appium.md +152 -147
  17. package/docs/helpers/JSONResponse.md +4 -4
  18. package/docs/helpers/Nightmare.md +2 -0
  19. package/docs/helpers/OpenAI.md +70 -0
  20. package/docs/helpers/Playwright.md +194 -152
  21. package/docs/helpers/Puppeteer.md +6 -0
  22. package/docs/helpers/REST.md +6 -5
  23. package/docs/helpers/TestCafe.md +2 -0
  24. package/docs/helpers/WebDriver.md +10 -4
  25. package/docs/mobile.md +49 -2
  26. package/docs/parallel.md +56 -0
  27. package/docs/plugins.md +87 -33
  28. package/docs/secrets.md +6 -0
  29. package/docs/tutorial.md +5 -5
  30. package/docs/webapi/appendField.mustache +2 -0
  31. package/docs/webapi/type.mustache +3 -0
  32. package/lib/ai.js +171 -0
  33. package/lib/cli.js +1 -1
  34. package/lib/codecept.js +4 -0
  35. package/lib/command/dryRun.js +9 -1
  36. package/lib/command/generate.js +46 -3
  37. package/lib/command/init.js +13 -1
  38. package/lib/command/interactive.js +15 -1
  39. package/lib/command/run-workers.js +2 -1
  40. package/lib/container.js +13 -3
  41. package/lib/helper/Appium.js +45 -7
  42. package/lib/helper/JSONResponse.js +4 -4
  43. package/lib/helper/Nightmare.js +1 -1
  44. package/lib/helper/OpenAI.js +122 -0
  45. package/lib/helper/Playwright.js +190 -38
  46. package/lib/helper/Protractor.js +1 -1
  47. package/lib/helper/Puppeteer.js +40 -12
  48. package/lib/helper/REST.js +15 -5
  49. package/lib/helper/TestCafe.js +1 -1
  50. package/lib/helper/WebDriver.js +25 -5
  51. package/lib/helper/scripts/highlightElement.js +20 -0
  52. package/lib/html.js +258 -0
  53. package/lib/listener/retry.js +2 -1
  54. package/lib/pause.js +73 -17
  55. package/lib/plugin/debugErrors.js +67 -0
  56. package/lib/plugin/fakerTransform.js +4 -6
  57. package/lib/plugin/heal.js +179 -0
  58. package/lib/plugin/screenshotOnFail.js +11 -2
  59. package/lib/plugin/wdio.js +4 -12
  60. package/lib/recorder.js +4 -4
  61. package/lib/scenario.js +6 -4
  62. package/lib/secret.js +5 -4
  63. package/lib/step.js +6 -1
  64. package/lib/ui.js +4 -3
  65. package/lib/utils.js +4 -0
  66. package/lib/workers.js +57 -9
  67. package/package.json +26 -14
  68. package/translations/ja-JP.js +9 -9
  69. package/typings/index.d.ts +43 -9
  70. package/typings/promiseBasedTypes.d.ts +124 -24
  71. package/typings/types.d.ts +138 -30
@@ -0,0 +1,67 @@
1
+ const Container = require('../container');
2
+ const recorder = require('../recorder');
3
+ const event = require('../event');
4
+ const supportedHelpers = require('./standardActingHelpers');
5
+ const { scanForErrorMessages } = require('../html');
6
+ const { output } = require('..');
7
+
8
+ const defaultConfig = {
9
+ errorClasses: ['error', 'warning', 'alert', 'danger'],
10
+ };
11
+
12
+ /**
13
+ * Prints errors found in HTML code after each failed test.
14
+ *
15
+ * It scans HTML and searches for elements with error classes.
16
+ * If an element found prints a text from it to console and adds as artifact to the test.
17
+ *
18
+ * Enable this plugin in config:
19
+ *
20
+ * ```js
21
+ * plugins: {
22
+ * debugErrors: {
23
+ * enabled: true,
24
+ * }
25
+ * ```
26
+ *
27
+ * Additional config options:
28
+ *
29
+ * * `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)
30
+ *
31
+ */
32
+ module.exports = function (config = {}) {
33
+ const helpers = Container.helpers();
34
+ let helper;
35
+
36
+ config = Object.assign(defaultConfig, config);
37
+
38
+ for (const helperName of supportedHelpers) {
39
+ if (Object.keys(helpers).indexOf(helperName) > -1) {
40
+ helper = helpers[helperName];
41
+ }
42
+ }
43
+
44
+ if (!helper) return; // no helpers for screenshot
45
+
46
+ event.dispatcher.on(event.test.failed, (test) => {
47
+ recorder.add('HTML snapshot failed test', async () => {
48
+ try {
49
+ const currentOutputLevel = output.level();
50
+ output.level(0);
51
+ const html = await helper.grabHTMLFrom('body');
52
+ output.level(currentOutputLevel);
53
+
54
+ if (!html) return;
55
+
56
+ const errors = scanForErrorMessages(html, config.errorClasses);
57
+ if (errors.length) {
58
+ output.debug('Detected errors in HTML code');
59
+ errors.forEach((error) => output.debug(error));
60
+ test.artifacts.errors = errors;
61
+ }
62
+ } catch (err) {
63
+ // not really needed
64
+ }
65
+ });
66
+ });
67
+ };
@@ -2,20 +2,18 @@ const { faker } = require('@faker-js/faker');
2
2
  const transform = require('../transform');
3
3
 
4
4
  /**
5
- * Use the [faker.js](https://www.npmjs.com/package/faker) package to generate fake data inside examples on your gherkin tests
6
- *
7
- * ![Faker.js](https://raw.githubusercontent.com/Marak/faker.js/master/logo.png)
5
+ * Use the `@faker-js/faker` package to generate fake data inside examples on your gherkin tests
8
6
  *
9
7
  * #### Usage
10
8
  *
11
- * To start please install `faker.js` package
9
+ * To start please install `@faker-js/faker` package
12
10
  *
13
11
  * ```
14
- * npm install -D faker
12
+ * npm install -D @faker-js/faker
15
13
  * ```
16
14
  *
17
15
  * ```
18
- * yarn add -D faker
16
+ * yarn add -D @faker-js/faker
19
17
  * ```
20
18
  *
21
19
  * Add this plugin to config file:
@@ -0,0 +1,179 @@
1
+ const debug = require('debug')('codeceptjs:heal');
2
+ const colors = require('chalk');
3
+ const Container = require('../container');
4
+ const AiAssistant = require('../ai');
5
+ const recorder = require('../recorder');
6
+ const event = require('../event');
7
+ const output = require('../output');
8
+ const supportedHelpers = require('./standardActingHelpers');
9
+
10
+ const defaultConfig = {
11
+ healLimit: 2,
12
+ healSteps: [
13
+ 'click',
14
+ 'fillField',
15
+ 'appendField',
16
+ 'selectOption',
17
+ 'attachFile',
18
+ 'checkOption',
19
+ 'uncheckOption',
20
+ 'doubleClick',
21
+ ],
22
+ };
23
+
24
+ /**
25
+ * Self-healing tests with OpenAI.
26
+ *
27
+ * This plugin is experimental and requires OpenAI API key.
28
+ *
29
+ * To use it you need to set OPENAI_API_KEY env variable and enable plugin inside condig.
30
+ *
31
+ * ```js
32
+ * plugins: {
33
+ * heal: {
34
+ * enabled: true,
35
+ * }
36
+ * }
37
+ * ```
38
+ *
39
+ * More config options are available:
40
+ *
41
+ * * `healLimit` - how many steps can be healed in a single test (default: 2)
42
+ * * `healSteps` - which steps can be healed (default: all steps that interact with UI, see list below)
43
+ *
44
+ * Steps to heal:
45
+ *
46
+ * * `click`
47
+ * * `fillField`
48
+ * * `appendField`
49
+ * * `selectOption`
50
+ * * `attachFile`
51
+ * * `checkOption`
52
+ * * `uncheckOption`
53
+ * * `doubleClick`
54
+ *
55
+ */
56
+ module.exports = function (config = {}) {
57
+ const aiAssistant = new AiAssistant();
58
+
59
+ let currentTest = null;
60
+ let currentStep = null;
61
+ let healedSteps = 0;
62
+
63
+ const healSuggestions = [];
64
+
65
+ config = Object.assign(defaultConfig, config);
66
+
67
+ event.dispatcher.on(event.test.before, (test) => {
68
+ currentTest = test;
69
+ healedSteps = 0;
70
+ });
71
+
72
+ event.dispatcher.on(event.step.started, step => currentStep = step);
73
+
74
+ event.dispatcher.on(event.step.before, () => {
75
+ const store = require('../store');
76
+ if (store.debugMode) return;
77
+
78
+ recorder.catchWithoutStop(async (err) => {
79
+ if (!aiAssistant.isEnabled) throw err;
80
+ if (!currentStep) throw err;
81
+ if (!config.healSteps.includes(currentStep.name)) throw err;
82
+ const test = currentTest;
83
+
84
+ if (healedSteps >= config.healLimit) {
85
+ output.print(colors.bold.red(`Can't heal more than ${config.healLimit} steps in a test`));
86
+ output.print('Entire flow can be broken, please check it manually');
87
+ output.print('or increase healing limit in heal plugin config');
88
+
89
+ throw err;
90
+ }
91
+
92
+ recorder.session.start('heal');
93
+ const helpers = Container.helpers();
94
+ let helper;
95
+
96
+ for (const helperName of supportedHelpers) {
97
+ if (Object.keys(helpers).indexOf(helperName) > -1) {
98
+ helper = helpers[helperName];
99
+ }
100
+ }
101
+
102
+ if (!helper) throw err; // no helpers for html
103
+
104
+ const step = test.steps[test.steps.length - 1];
105
+ debug('Self-healing started', step.toCode());
106
+
107
+ const currentOutputLevel = output.level();
108
+ output.level(0);
109
+ const html = await helper.grabHTMLFrom('body');
110
+ output.level(currentOutputLevel);
111
+
112
+ if (!html) throw err;
113
+
114
+ aiAssistant.setHtmlContext(html);
115
+ await tryToHeal(step, err);
116
+ recorder.session.restore();
117
+ });
118
+ });
119
+
120
+ event.dispatcher.on(event.all.result, () => {
121
+ if (!healSuggestions.length) return;
122
+
123
+ const { print } = output;
124
+
125
+ print('');
126
+ print('===================');
127
+ print(colors.bold.green('Self-Healing Report:'));
128
+
129
+ print(`${colors.bold(healSuggestions.length)} steps were healed by AI`);
130
+
131
+ let i = 1;
132
+ print('');
133
+ print('Suggested changes:');
134
+ print('');
135
+
136
+ for (const suggestion of healSuggestions) {
137
+ print(`${i}. To fix ${colors.bold.blue(suggestion.test.title)}`);
138
+ print('Replace the failed code with:');
139
+ print(colors.red(`- ${suggestion.step.toCode()}`));
140
+ print(colors.green(`+ ${suggestion.snippet}`));
141
+ print(suggestion.step.line());
142
+ print('');
143
+ i++;
144
+ }
145
+ });
146
+
147
+ async function tryToHeal(failedStep, err) {
148
+ output.debug(`Running OpenAPI to heal ${failedStep.toCode()} step`);
149
+
150
+ const I = Container.support('I');
151
+
152
+ const codeSnippets = await aiAssistant.healFailedStep(
153
+ failedStep, err, currentTest,
154
+ );
155
+
156
+ output.debug(`Received ${codeSnippets.length} proposals from OpenAI`);
157
+
158
+ for (const codeSnippet of codeSnippets) {
159
+ try {
160
+ debug('Executing', codeSnippet);
161
+ await eval(codeSnippet); // eslint-disable-line
162
+
163
+ healSuggestions.push({
164
+ test: currentTest,
165
+ step: failedStep,
166
+ snippet: codeSnippet,
167
+ });
168
+
169
+ output.print(colors.bold.green(' Code healed successfully'));
170
+ healedSteps++;
171
+ return;
172
+ } catch (err) {
173
+ debug('Failed to execute code', err);
174
+ }
175
+ }
176
+
177
+ output.debug(`Couldn't heal the code for ${failedStep.toCode()}`);
178
+ }
179
+ };
@@ -75,7 +75,8 @@ module.exports = function (config) {
75
75
  event.dispatcher.on(event.test.failed, (test) => {
76
76
  recorder.add('screenshot of failed test', async () => {
77
77
  let fileName = clearString(test.title);
78
- // This prevent data driven to be included in the failed screenshot file name
78
+ const dataType = 'image/png';
79
+ // This prevents data driven to be included in the failed screenshot file name
79
80
  if (fileName.indexOf('{') !== -1) {
80
81
  fileName = fileName.substr(0, (fileName.indexOf('{') - 3)).trim();
81
82
  }
@@ -106,7 +107,15 @@ module.exports = function (config) {
106
107
 
107
108
  const allureReporter = Container.plugins('allure');
108
109
  if (allureReporter) {
109
- allureReporter.addAttachment('Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), 'image/png');
110
+ allureReporter.addAttachment('Main session - Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), dataType);
111
+
112
+ if (helper.activeSessionName) {
113
+ for (const sessionName in helper.sessionPages) {
114
+ const screenshotFileName = `${sessionName}_${fileName}`;
115
+ test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(global.output_dir, screenshotFileName);
116
+ allureReporter.addAttachment(`${sessionName} - Last Seen Screenshot`, fs.readFileSync(path.join(global.output_dir, screenshotFileName)), dataType);
117
+ }
118
+ }
110
119
  }
111
120
 
112
121
  const cucumberReporter = Container.plugins('cucumberJsonReporter');
@@ -1,6 +1,4 @@
1
1
  const debug = require('debug')('codeceptjs:plugin:wdio');
2
- const path = require('path');
3
- const fs = require('fs');
4
2
 
5
3
  const container = require('../container');
6
4
  const mainConfig = require('../config');
@@ -100,21 +98,15 @@ module.exports = (config) => {
100
98
  const launchers = [];
101
99
 
102
100
  for (const name of config.services) {
103
- // webdriverio v5 style
104
101
  const Service = safeRequire(`@wdio/${name.toLowerCase()}-service`);
105
102
  if (Service) {
106
103
  if (Service.launcher && typeof Service.launcher === 'function') {
107
104
  const Launcher = Service.launcher;
108
105
 
109
- const version = JSON.parse(fs.readFileSync(path.join(require.resolve('webdriverio'), '/../../', 'package.json')).toString()).version;
110
- if (version.indexOf('5') === 0) {
111
- launchers.push(new Launcher(config));
112
- } else {
113
- const options = {
114
- logPath: global.output_dir, installArgs: seleniumInstallArgs, args: seleniumArgs, ...wdioOptions,
115
- };
116
- launchers.push(new Launcher(options, [config.capabilities], config));
117
- }
106
+ const options = {
107
+ logPath: global.output_dir, installArgs: seleniumInstallArgs, args: seleniumArgs, ...wdioOptions,
108
+ };
109
+ launchers.push(new Launcher(options, [config.capabilities], config));
118
110
  }
119
111
  if (typeof Service === 'function') {
120
112
  services.push(new Service(config, config.capabilities));
package/lib/recorder.js CHANGED
@@ -161,7 +161,7 @@ module.exports = {
161
161
  * true: it will retries if `retryOpts` set.
162
162
  * false: ignore `retryOpts` and won't retry.
163
163
  * @param {number} [timeout]
164
- * @return {Promise<*> | undefined}
164
+ * @return {Promise<*>}
165
165
  * @inner
166
166
  */
167
167
  add(taskName, fn = undefined, force = false, retry = undefined, timeout = undefined) {
@@ -250,9 +250,9 @@ module.exports = {
250
250
  err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`); // we should be prepared for them
251
251
  }
252
252
  if (customErrFn) {
253
- customErrFn(err);
254
- } else if (errFn) {
255
- errFn(err);
253
+ return customErrFn(err);
254
+ } if (errFn) {
255
+ return errFn(err);
256
256
  }
257
257
  });
258
258
  },
package/lib/scenario.js CHANGED
@@ -137,16 +137,18 @@ module.exports.injected = function (fn, suite, hookName) {
137
137
  const opts = suite.opts || {};
138
138
  const retries = opts[`retry${ucfirst(hookName)}`] || 0;
139
139
 
140
- promiseRetry(async (retry) => {
140
+ promiseRetry(async (retry, number) => {
141
141
  try {
142
142
  recorder.startUnlessRunning();
143
143
  await fn.call(this, getInjectedArguments(fn));
144
- await recorder.promise();
144
+ await recorder.promise().catch(err => retry(err));
145
145
  } catch (err) {
146
146
  retry(err);
147
147
  } finally {
148
- recorder.stop();
149
- recorder.start();
148
+ if (number < retries) {
149
+ recorder.stop();
150
+ recorder.start();
151
+ }
150
152
  }
151
153
  }, { retries })
152
154
  .then(() => {
package/lib/secret.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /* eslint-disable max-classes-per-file */
2
2
  const { deepClone } = require('./utils');
3
3
 
4
+ const maskedString = '*****';
5
+
4
6
  /** @param {string} secret */
5
7
  class Secret {
6
8
  constructor(secret) {
@@ -13,7 +15,7 @@ class Secret {
13
15
  }
14
16
 
15
17
  getMasked() {
16
- return '*****';
18
+ return maskedString;
17
19
  }
18
20
 
19
21
  /**
@@ -36,12 +38,11 @@ function secretObject(obj, fieldsToHide = []) {
36
38
  if (prop === 'toString') {
37
39
  return function () {
38
40
  const maskedObject = deepClone(obj);
39
- fieldsToHide.forEach(f => maskedObject[f] = '****');
41
+ fieldsToHide.forEach(f => maskedObject[f] = maskedString);
40
42
  return JSON.stringify(maskedObject);
41
43
  };
42
44
  }
43
-
44
- return obj[prop];
45
+ return fieldsToHide.includes(prop) ? new Secret(obj[prop]) : obj[prop];
45
46
  },
46
47
  };
47
48
 
package/lib/step.js CHANGED
@@ -172,7 +172,12 @@ class Step {
172
172
  } else if (arg.toString && arg.toString() !== '[object Object]') {
173
173
  return arg.toString();
174
174
  } else if (typeof arg === 'object') {
175
- return JSON.stringify(arg);
175
+ const returnedArg = {};
176
+ for (const [key, value] of Object.entries(arg)) {
177
+ returnedArg[key] = value;
178
+ if (value instanceof Secret) returnedArg[key] = value.getMasked();
179
+ }
180
+ return JSON.stringify(returnedArg);
176
181
  }
177
182
  return arg;
178
183
  }).join(', ');
package/lib/ui.js CHANGED
@@ -23,10 +23,11 @@ const setContextTranslation = (context) => {
23
23
  /**
24
24
  * Codecept-style interface:
25
25
  *
26
- * Feature('login')
27
- * Scenario('login as regular user', (I) {
26
+ * Feature('login');
27
+ *
28
+ * Scenario('login as regular user', ({I}) {
28
29
  * I.fillField();
29
- * I.click()
30
+ * I.click();
30
31
  * I.see('Hello, '+data.login);
31
32
  * });
32
33
  *
package/lib/utils.js CHANGED
@@ -455,3 +455,7 @@ module.exports.isNotSet = function (obj) {
455
455
  if (obj === undefined) return true;
456
456
  return false;
457
457
  };
458
+
459
+ module.exports.emptyFolder = async (directoryPath) => {
460
+ require('child_process').execSync(`rm -rf ${directoryPath}/*`);
461
+ };
package/lib/workers.js CHANGED
@@ -10,12 +10,14 @@ const MochaFactory = require('./mochaFactory');
10
10
  const Container = require('./container');
11
11
  const { getTestRoot } = require('./command/utils');
12
12
  const { isFunction, fileExists } = require('./utils');
13
+ const { replaceValueDeep, deepClone } = require('./utils');
13
14
  const mainConfig = require('./config');
14
15
  const output = require('./output');
15
16
  const event = require('./event');
16
17
  const recorder = require('./recorder');
17
18
  const runHook = require('./hooks');
18
19
  const WorkerStorage = require('./workerStorage');
20
+ const collection = require('./command/run-multiple/collection');
19
21
 
20
22
  const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js');
21
23
 
@@ -79,15 +81,39 @@ const repackTest = (test) => {
79
81
  return test;
80
82
  };
81
83
 
82
- const createWorkerObjects = (testGroups, config, testRoot, options) => {
83
- return testGroups.map((tests, index) => {
84
- const workerObj = new WorkerObject(index);
85
- workerObj.addConfig(config);
86
- workerObj.addTests(tests);
87
- workerObj.setTestRoot(testRoot);
88
- workerObj.addOptions(options);
89
- return workerObj;
84
+ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns) => {
85
+ selectedRuns = options && options.all && config.multiple ? Object.keys(config.multiple) : selectedRuns;
86
+ if (selectedRuns === undefined || !selectedRuns.length || config.multiple === undefined) {
87
+ return testGroups.map((tests, index) => {
88
+ const workerObj = new WorkerObject(index);
89
+ workerObj.addConfig(config);
90
+ workerObj.addTests(tests);
91
+ workerObj.setTestRoot(testRoot);
92
+ workerObj.addOptions(options);
93
+ return workerObj;
94
+ });
95
+ }
96
+ const workersToExecute = [];
97
+ collection.createRuns(selectedRuns, config).forEach((worker) => {
98
+ const workerName = worker.getOriginalName() || worker.getName();
99
+ const workerConfig = worker.getConfig();
100
+ workersToExecute.push(getOverridenConfig(workerName, workerConfig, config));
101
+ });
102
+ const workers = [];
103
+ let index = 0;
104
+ testGroups.forEach((tests) => {
105
+ const testWorkerArray = [];
106
+ workersToExecute.forEach((finalConfig) => {
107
+ const workerObj = new WorkerObject(index++);
108
+ workerObj.addConfig(finalConfig);
109
+ workerObj.addTests(tests);
110
+ workerObj.setTestRoot(testRoot);
111
+ workerObj.addOptions(options);
112
+ testWorkerArray.push(workerObj);
113
+ });
114
+ workers.push(...testWorkerArray);
90
115
  });
116
+ return workers;
91
117
  };
92
118
 
93
119
  const indexOfSmallestElement = (groups) => {
@@ -115,6 +141,28 @@ const convertToMochaTests = (testGroup) => {
115
141
  return group;
116
142
  };
117
143
 
144
+ const getOverridenConfig = (workerName, workerConfig, config) => {
145
+ // clone config
146
+ const overriddenConfig = deepClone(config);
147
+
148
+ // get configuration
149
+ const browserConfig = workerConfig.browser;
150
+
151
+ for (const key in browserConfig) {
152
+ overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]);
153
+ }
154
+
155
+ // override tests configuration
156
+ if (overriddenConfig.tests) {
157
+ overriddenConfig.tests = workerConfig.tests;
158
+ }
159
+
160
+ if (overriddenConfig.gherkin && workerConfig.gherkin && workerConfig.gherkin.features) {
161
+ overriddenConfig.gherkin.features = workerConfig.gherkin.features;
162
+ }
163
+ return overriddenConfig;
164
+ };
165
+
118
166
  class WorkerObject {
119
167
  /**
120
168
  * @param {Number} workerIndex - Unique ID for worker
@@ -183,7 +231,7 @@ class Workers extends EventEmitter {
183
231
 
184
232
  _initWorkers(numberOfWorkers, config) {
185
233
  this.splitTestsByGroups(numberOfWorkers, config);
186
- this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options);
234
+ this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns);
187
235
  this.numberOfWorkers = this.workers.length;
188
236
  }
189
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -45,8 +45,14 @@
45
45
  "test": "npm run test:unit && npm run test:runner",
46
46
  "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'",
47
47
  "test:appium-other": "mocha test/helper/Appium_test.js --grep 'second'",
48
+ "test-app:start": "php -S 127.0.0.1:8000 -t test/data/app",
49
+ "test-app:stop": "kill -9 $(lsof -t -i:8000)",
50
+ "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js",
51
+ "test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js",
52
+ "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js",
53
+ "test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js",
48
54
  "def": "./runok.js def",
49
- "dev:graphql": "nodemon test/data/graphql/index.js",
55
+ "dev:graphql": "node test/data/graphql/index.js",
50
56
  "publish:site": "./runok.js publish:site",
51
57
  "update-contributor-faces": "contributor-faces .",
52
58
  "dtslint": "dtslint typings --localTs './node_modules/typescript/lib'",
@@ -58,13 +64,14 @@
58
64
  "@cucumber/cucumber-expressions": "^16",
59
65
  "@cucumber/gherkin": "^26",
60
66
  "@cucumber/messages": "^21.0.1",
67
+ "@xmldom/xmldom": "^0.7.9",
61
68
  "acorn": "^7.4.1",
62
69
  "arrify": "^2.0.1",
63
70
  "axios": "^1.3.3",
64
71
  "chai": "^4.3.6",
65
72
  "chai-deep-match": "^1.2.1",
66
73
  "chalk": "^4.1.2",
67
- "commander": "^2.20.3",
74
+ "commander": "^11.0.0",
68
75
  "cross-spawn": "^7.0.3",
69
76
  "css-to-xpath": "^0.1.0",
70
77
  "envinfo": "^7.8.1",
@@ -73,16 +80,22 @@
73
80
  "fn-args": "^4.0.0",
74
81
  "fs-extra": "^8.1.0",
75
82
  "glob": "^6.0.1",
83
+ "html-minifier": "^4.0.0",
84
+ "i": "^0.3.7",
76
85
  "inquirer": "^6.5.2",
77
86
  "joi": "^17.6.0",
78
87
  "js-beautify": "^1.14.0",
79
88
  "lodash.clonedeep": "^4.5.0",
80
89
  "lodash.merge": "^4.6.2",
81
90
  "mkdirp": "^1.0.4",
82
- "mocha": "^8.2.0",
91
+ "mocha": "^10.2.0",
83
92
  "mocha-junit-reporter": "^1.23.3",
84
93
  "ms": "^2.1.3",
94
+ "npm": "^9.6.7",
95
+ "openai": "^3.2.1",
96
+ "ora-classic": "^5.4.2",
85
97
  "parse-function": "^5.6.4",
98
+ "parse5": "^7.1.2",
86
99
  "promise-retry": "^1.1.1",
87
100
  "resq": "^1.10.2",
88
101
  "sprintf-js": "^1.1.1",
@@ -92,14 +105,14 @@
92
105
  "@codeceptjs/detox-helper": "^1.0.2",
93
106
  "@codeceptjs/mock-request": "^0.3.1",
94
107
  "@faker-js/faker": "^7.6.0",
95
- "@pollyjs/adapter-puppeteer": "^5.1.0",
108
+ "@pollyjs/adapter-puppeteer": "^6.0.5",
96
109
  "@pollyjs/core": "^5.1.0",
97
110
  "@types/inquirer": "^0.0.35",
98
111
  "@types/node": "^8.10.66",
99
- "@wdio/sauce-service": "^5.22.5",
100
- "@wdio/selenium-standalone-service": "^5.16.10",
101
- "@wdio/utils": "^5.23.0",
102
- "apollo-server-express": "^2.25.3",
112
+ "@wdio/sauce-service": "^8.3.8",
113
+ "@wdio/selenium-standalone-service": "^8.3.2",
114
+ "@wdio/utils": "^8.3.0",
115
+ "apollo-server-express": "2.25.3",
103
116
  "chai-as-promised": "^7.1.1",
104
117
  "chai-subset": "^1.6.0",
105
118
  "contributor-faces": "^1.0.3",
@@ -120,11 +133,10 @@
120
133
  "jsdoc-typeof-plugin": "^1.0.0",
121
134
  "json-server": "^0.10.1",
122
135
  "nightmare": "^3.0.2",
123
- "nodemon": "^1.19.4",
124
- "playwright": "^1.23.2",
136
+ "playwright": "^1.35.1",
125
137
  "puppeteer": "^10.4.0",
126
138
  "qrcode-terminal": "^0.12.0",
127
- "rosie": "^1.6.0",
139
+ "rosie": "^2.1.0",
128
140
  "runok": "^0.9.2",
129
141
  "sinon": "^9.2.4",
130
142
  "sinon-chai": "^3.7.0",
@@ -138,11 +150,11 @@
138
150
  "wdio-docker-service": "^1.5.0",
139
151
  "webdriverio": "^8.3.8",
140
152
  "xml2js": "^0.4.23",
141
- "xmldom": "^0.1.31",
153
+ "xmldom": "^0.5.0",
142
154
  "xpath": "0.0.27"
143
155
  },
144
156
  "engines": {
145
- "node": ">=8.9.1",
157
+ "node": ">=16.0",
146
158
  "npm": ">=5.6.0"
147
159
  },
148
160
  "es6": true