codeceptjs 3.4.1 → 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.
- package/CHANGELOG.md +65 -0
- package/README.md +9 -7
- package/bin/codecept.js +1 -1
- package/docs/ai.md +246 -0
- package/docs/build/Appium.js +47 -7
- package/docs/build/JSONResponse.js +4 -4
- package/docs/build/Nightmare.js +3 -1
- package/docs/build/OpenAI.js +122 -0
- package/docs/build/Playwright.js +193 -45
- package/docs/build/Protractor.js +3 -1
- package/docs/build/Puppeteer.js +45 -12
- package/docs/build/REST.js +15 -5
- package/docs/build/TestCafe.js +3 -1
- package/docs/build/WebDriver.js +30 -5
- package/docs/changelog.md +65 -0
- package/docs/helpers/Appium.md +152 -147
- package/docs/helpers/JSONResponse.md +4 -4
- package/docs/helpers/Nightmare.md +2 -0
- package/docs/helpers/OpenAI.md +70 -0
- package/docs/helpers/Playwright.md +194 -152
- package/docs/helpers/Puppeteer.md +6 -0
- package/docs/helpers/REST.md +6 -5
- package/docs/helpers/TestCafe.md +2 -0
- package/docs/helpers/WebDriver.md +10 -4
- package/docs/mobile.md +49 -2
- package/docs/parallel.md +56 -0
- package/docs/plugins.md +87 -33
- package/docs/secrets.md +6 -0
- package/docs/tutorial.md +2 -2
- package/docs/webapi/appendField.mustache +2 -0
- package/docs/webapi/type.mustache +3 -0
- package/lib/ai.js +171 -0
- package/lib/cli.js +1 -1
- package/lib/codecept.js +4 -0
- package/lib/command/dryRun.js +9 -1
- package/lib/command/generate.js +46 -3
- package/lib/command/init.js +13 -1
- package/lib/command/interactive.js +15 -1
- package/lib/command/run-workers.js +2 -1
- package/lib/container.js +13 -3
- package/lib/helper/Appium.js +45 -7
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/Nightmare.js +1 -1
- package/lib/helper/OpenAI.js +122 -0
- package/lib/helper/Playwright.js +190 -38
- package/lib/helper/Protractor.js +1 -1
- package/lib/helper/Puppeteer.js +40 -12
- package/lib/helper/REST.js +15 -5
- package/lib/helper/TestCafe.js +1 -1
- package/lib/helper/WebDriver.js +25 -5
- package/lib/helper/scripts/highlightElement.js +20 -0
- package/lib/html.js +258 -0
- package/lib/listener/retry.js +2 -1
- package/lib/pause.js +73 -17
- package/lib/plugin/debugErrors.js +67 -0
- package/lib/plugin/fakerTransform.js +4 -6
- package/lib/plugin/heal.js +179 -0
- package/lib/plugin/screenshotOnFail.js +11 -2
- package/lib/recorder.js +4 -4
- package/lib/secret.js +5 -4
- package/lib/step.js +6 -1
- package/lib/ui.js +4 -3
- package/lib/utils.js +4 -0
- package/lib/workers.js +57 -9
- package/package.json +25 -13
- package/translations/ja-JP.js +9 -9
- package/typings/index.d.ts +43 -9
- package/typings/promiseBasedTypes.d.ts +124 -24
- 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
|
|
6
|
-
*
|
|
7
|
-
* 
|
|
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
|
|
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
|
-
|
|
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)),
|
|
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');
|
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<*>
|
|
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
|
-
}
|
|
255
|
-
errFn(err);
|
|
253
|
+
return customErrFn(err);
|
|
254
|
+
} if (errFn) {
|
|
255
|
+
return errFn(err);
|
|
256
256
|
}
|
|
257
257
|
});
|
|
258
258
|
},
|
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
|
-
|
|
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
|
-
*
|
|
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
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
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": "
|
|
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": "^
|
|
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,6 +80,8 @@
|
|
|
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",
|
|
@@ -82,7 +91,11 @@
|
|
|
82
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": "^
|
|
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": "^
|
|
100
|
-
"@wdio/selenium-standalone-service": "^
|
|
101
|
-
"@wdio/utils": "^
|
|
102
|
-
"apollo-server-express": "
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
153
|
+
"xmldom": "^0.5.0",
|
|
142
154
|
"xpath": "0.0.27"
|
|
143
155
|
},
|
|
144
156
|
"engines": {
|
|
145
|
-
"node": ">=
|
|
157
|
+
"node": ">=16.0",
|
|
146
158
|
"npm": ">=5.6.0"
|
|
147
159
|
},
|
|
148
160
|
"es6": true
|
package/translations/ja-JP.js
CHANGED
|
@@ -14,34 +14,34 @@ module.exports = {
|
|
|
14
14
|
amOnPage: 'ページを移動する',
|
|
15
15
|
click: 'クリックする',
|
|
16
16
|
doubleClick: 'ダブルクリックする',
|
|
17
|
-
see: '
|
|
17
|
+
see: 'テキストがあることを確認する',
|
|
18
18
|
dontSee: 'テキストがないことを確認する',
|
|
19
19
|
selectOption: 'オプションを選ぶ',
|
|
20
20
|
fillField: 'フィールドに入力する',
|
|
21
21
|
pressKey: 'キー入力する',
|
|
22
22
|
triggerMouseEvent: 'マウスイベントを発火させる',
|
|
23
23
|
attachFile: 'ファイルを添付する',
|
|
24
|
-
seeInField: '
|
|
25
|
-
dontSeeInField: '
|
|
24
|
+
seeInField: 'フィールドに文字が入っていることを確認する',
|
|
25
|
+
dontSeeInField: 'フィールドに文字が入っていないことを確認する',
|
|
26
26
|
appendField: 'フィールドに文字を追加する',
|
|
27
27
|
checkOption: 'オプションをチェックする',
|
|
28
|
-
seeCheckboxIsChecked: '
|
|
28
|
+
seeCheckboxIsChecked: 'チェックされていることを確認する',
|
|
29
29
|
dontSeeCheckboxIsChecked: 'チェックされていないことを確認する',
|
|
30
30
|
grabTextFrom: 'テキストを取得する',
|
|
31
31
|
grabValueFrom: '入力値を取得する',
|
|
32
32
|
grabAttributeFrom: '要素を取得する',
|
|
33
|
-
seeInTitle: '
|
|
33
|
+
seeInTitle: 'タイトルに文字が含まれていることを確認する',
|
|
34
34
|
dontSeeInTitle: 'タイトルに文字が含まれないことを確認する',
|
|
35
35
|
grabTitle: 'タイトルを取得する',
|
|
36
|
-
seeElement: '
|
|
36
|
+
seeElement: '要素があることを確認する',
|
|
37
37
|
dontSeeElement: '要素がないことを確認する',
|
|
38
|
-
seeInSource: '
|
|
38
|
+
seeInSource: 'ソースにあることを確認する',
|
|
39
39
|
dontSeeInSource: 'ソースにないことを確認する',
|
|
40
40
|
executeScript: 'スクリプトを実行する',
|
|
41
41
|
executeAsyncScript: '非同期スクリプトを実行する',
|
|
42
|
-
seeInCurrentUrl: 'URL
|
|
42
|
+
seeInCurrentUrl: 'URLに含まれることを確認する',
|
|
43
43
|
dontSeeInCurrentUrl: 'URLに含まれないことを確認する',
|
|
44
|
-
seeCurrentUrlEquals: 'URL
|
|
44
|
+
seeCurrentUrlEquals: 'URLが等しいことを確認する',
|
|
45
45
|
dontSeeCurrentUrlEquals: 'URLが等しくないことを確認する',
|
|
46
46
|
saveScreenshot: 'スクリーンショットを保存する',
|
|
47
47
|
setCookie: 'Cookieをセットする',
|