codeceptjs 3.5.9 → 3.5.11
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/README.md +14 -16
- package/docs/build/Appium.js +49 -49
- package/docs/build/Expect.js +33 -33
- package/docs/build/Nightmare.js +50 -50
- package/docs/build/Playwright.js +239 -133
- package/docs/build/Protractor.js +59 -59
- package/docs/build/Puppeteer.js +127 -107
- package/docs/build/TestCafe.js +48 -48
- package/docs/build/WebDriver.js +112 -93
- package/docs/helpers/Appium.md +3 -3
- package/docs/helpers/Expect.md +33 -33
- package/docs/helpers/Playwright.md +431 -325
- package/docs/helpers/Puppeteer.md +50 -24
- package/docs/helpers/WebDriver.md +41 -13
- package/docs/internal-api.md +1 -0
- package/docs/parallel.md +114 -2
- package/docs/plugins.md +7 -5
- package/docs/react.md +2 -1
- package/docs/vue.md +22 -0
- package/docs/webapi/grabWebElement.mustache +9 -0
- package/docs/webapi/grabWebElements.mustache +9 -0
- package/docs/webapi/scrollIntoView.mustache +1 -1
- package/lib/ai.js +12 -3
- package/lib/colorUtils.js +10 -0
- package/lib/command/run-multiple.js +1 -1
- package/lib/command/run-workers.js +30 -4
- package/lib/command/workers/runTests.js +39 -0
- package/lib/event.js +2 -0
- package/lib/helper/Appium.js +13 -13
- package/lib/helper/Expect.js +33 -33
- package/lib/helper/Playwright.js +125 -37
- package/lib/helper/Puppeteer.js +49 -38
- package/lib/helper/WebDriver.js +29 -19
- package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
- package/lib/html.js +3 -3
- package/lib/interfaces/gherkin.js +8 -1
- package/lib/interfaces/scenarioConfig.js +1 -0
- package/lib/locator.js +2 -2
- package/lib/pause.js +6 -3
- package/lib/plugin/autoLogin.js +4 -2
- package/lib/plugin/heal.js +40 -7
- package/lib/plugin/retryFailedStep.js +6 -1
- package/lib/plugin/stepByStepReport.js +2 -2
- package/lib/plugin/tryTo.js +5 -4
- package/lib/recorder.js +12 -5
- package/lib/ui.js +1 -0
- package/lib/workers.js +2 -0
- package/package.json +28 -25
- package/typings/index.d.ts +1 -1
- package/typings/promiseBasedTypes.d.ts +195 -76
- package/typings/types.d.ts +191 -145
- package/lib/helper/extras/PlaywrightReact.js +0 -9
package/lib/helper/WebDriver.js
CHANGED
|
@@ -869,6 +869,14 @@ class WebDriver extends Helper {
|
|
|
869
869
|
return findFields.call(this, locator).then(res => res);
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
+
/**
|
|
873
|
+
* {{> grabWebElements }}
|
|
874
|
+
*
|
|
875
|
+
*/
|
|
876
|
+
async grabWebElements(locator) {
|
|
877
|
+
return this._locate(locator);
|
|
878
|
+
}
|
|
879
|
+
|
|
872
880
|
/**
|
|
873
881
|
* Set [WebDriver timeouts](https://webdriver.io/docs/timeouts.html) in realtime.
|
|
874
882
|
*
|
|
@@ -1096,8 +1104,9 @@ class WebDriver extends Helper {
|
|
|
1096
1104
|
}
|
|
1097
1105
|
|
|
1098
1106
|
/**
|
|
1099
|
-
* {{> attachFile }}
|
|
1100
1107
|
* Appium: not tested
|
|
1108
|
+
*
|
|
1109
|
+
* {{> attachFile }}
|
|
1101
1110
|
*/
|
|
1102
1111
|
async attachFile(locator, pathToFile) {
|
|
1103
1112
|
let file = path.join(global.codecept_dir, pathToFile);
|
|
@@ -1124,8 +1133,8 @@ class WebDriver extends Helper {
|
|
|
1124
1133
|
}
|
|
1125
1134
|
|
|
1126
1135
|
/**
|
|
1127
|
-
* {{> checkOption }}
|
|
1128
1136
|
* Appium: not tested
|
|
1137
|
+
* {{> checkOption }}
|
|
1129
1138
|
*/
|
|
1130
1139
|
async checkOption(field, context = null) {
|
|
1131
1140
|
const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
|
|
@@ -1144,8 +1153,8 @@ class WebDriver extends Helper {
|
|
|
1144
1153
|
}
|
|
1145
1154
|
|
|
1146
1155
|
/**
|
|
1147
|
-
* {{> uncheckOption }}
|
|
1148
1156
|
* Appium: not tested
|
|
1157
|
+
* {{> uncheckOption }}
|
|
1149
1158
|
*/
|
|
1150
1159
|
async uncheckOption(field, context = null) {
|
|
1151
1160
|
const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
|
|
@@ -1362,16 +1371,16 @@ class WebDriver extends Helper {
|
|
|
1362
1371
|
}
|
|
1363
1372
|
|
|
1364
1373
|
/**
|
|
1365
|
-
* {{> seeCheckboxIsChecked }}
|
|
1366
1374
|
* Appium: not tested
|
|
1375
|
+
* {{> seeCheckboxIsChecked }}
|
|
1367
1376
|
*/
|
|
1368
1377
|
async seeCheckboxIsChecked(field) {
|
|
1369
1378
|
return proceedSeeCheckbox.call(this, 'assert', field);
|
|
1370
1379
|
}
|
|
1371
1380
|
|
|
1372
1381
|
/**
|
|
1373
|
-
* {{> dontSeeCheckboxIsChecked }}
|
|
1374
1382
|
* Appium: not tested
|
|
1383
|
+
* {{> dontSeeCheckboxIsChecked }}
|
|
1375
1384
|
*/
|
|
1376
1385
|
async dontSeeCheckboxIsChecked(field) {
|
|
1377
1386
|
return proceedSeeCheckbox.call(this, 'negate', field);
|
|
@@ -1508,7 +1517,9 @@ class WebDriver extends Helper {
|
|
|
1508
1517
|
let chunked = chunkArray(props, values.length);
|
|
1509
1518
|
chunked = chunked.filter((val) => {
|
|
1510
1519
|
for (let i = 0; i < val.length; ++i) {
|
|
1511
|
-
|
|
1520
|
+
const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
|
|
1521
|
+
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
|
|
1522
|
+
if (_acutal !== _expected) return false;
|
|
1512
1523
|
}
|
|
1513
1524
|
return true;
|
|
1514
1525
|
});
|
|
@@ -1535,7 +1546,9 @@ class WebDriver extends Helper {
|
|
|
1535
1546
|
let chunked = chunkArray(attrs, values.length);
|
|
1536
1547
|
chunked = chunked.filter((val) => {
|
|
1537
1548
|
for (let i = 0; i < val.length; ++i) {
|
|
1538
|
-
|
|
1549
|
+
const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
|
|
1550
|
+
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
|
|
1551
|
+
if (_acutal !== _expected) return false;
|
|
1539
1552
|
}
|
|
1540
1553
|
return true;
|
|
1541
1554
|
});
|
|
@@ -1594,10 +1607,9 @@ class WebDriver extends Helper {
|
|
|
1594
1607
|
}
|
|
1595
1608
|
|
|
1596
1609
|
/**
|
|
1597
|
-
* {{> executeScript }}
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
1610
|
* Wraps [execute](http://webdriver.io/api/protocol/execute.html) command.
|
|
1611
|
+
*
|
|
1612
|
+
* {{> executeScript }}
|
|
1601
1613
|
*/
|
|
1602
1614
|
executeScript(...args) {
|
|
1603
1615
|
return this.browser.execute.apply(this.browser, args);
|
|
@@ -1719,11 +1731,8 @@ class WebDriver extends Helper {
|
|
|
1719
1731
|
}
|
|
1720
1732
|
|
|
1721
1733
|
/**
|
|
1734
|
+
* Uses Selenium's JSON [cookie format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
|
|
1722
1735
|
* {{> setCookie }}
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1725
|
-
* Uses Selenium's JSON [cookie
|
|
1726
|
-
* format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
|
|
1727
1736
|
*/
|
|
1728
1737
|
async setCookie(cookie) {
|
|
1729
1738
|
return this.browser.setCookies(cookie);
|
|
@@ -1776,7 +1785,7 @@ class WebDriver extends Helper {
|
|
|
1776
1785
|
}
|
|
1777
1786
|
|
|
1778
1787
|
/**
|
|
1779
|
-
* Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt
|
|
1788
|
+
* Dismisses the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`.
|
|
1780
1789
|
*
|
|
1781
1790
|
*/
|
|
1782
1791
|
async cancelPopup() {
|
|
@@ -1850,9 +1859,9 @@ class WebDriver extends Helper {
|
|
|
1850
1859
|
}
|
|
1851
1860
|
|
|
1852
1861
|
/**
|
|
1853
|
-
* {{> pressKeyWithKeyNormalization }}
|
|
1854
|
-
*
|
|
1855
1862
|
* _Note:_ In case a text field or textarea is focused be aware that some browsers do not respect active modifier when combining modifier keys with other keys.
|
|
1863
|
+
*
|
|
1864
|
+
* {{> pressKeyWithKeyNormalization }}
|
|
1856
1865
|
*/
|
|
1857
1866
|
async pressKey(key) {
|
|
1858
1867
|
const modifiers = [];
|
|
@@ -1911,8 +1920,9 @@ class WebDriver extends Helper {
|
|
|
1911
1920
|
}
|
|
1912
1921
|
|
|
1913
1922
|
/**
|
|
1914
|
-
* {{> resizeWindow }}
|
|
1915
1923
|
* Appium: not tested in web, in apps doesn't work
|
|
1924
|
+
*
|
|
1925
|
+
* {{> resizeWindow }}
|
|
1916
1926
|
*/
|
|
1917
1927
|
async resizeWindow(width, height) {
|
|
1918
1928
|
return this._resizeBrowserWindow(this.browser, width, height);
|
|
@@ -1964,8 +1974,8 @@ class WebDriver extends Helper {
|
|
|
1964
1974
|
}
|
|
1965
1975
|
|
|
1966
1976
|
/**
|
|
1967
|
-
* {{> dragAndDrop }}
|
|
1968
1977
|
* Appium: not tested
|
|
1978
|
+
* {{> dragAndDrop }}
|
|
1969
1979
|
*/
|
|
1970
1980
|
async dragAndDrop(srcElement, destElement) {
|
|
1971
1981
|
let sourceEl = await this._locate(srcElement);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
async function findReact(matcher, locator) {
|
|
2
|
+
let _locator = `_react=${locator.react}`;
|
|
3
|
+
let props = '';
|
|
4
|
+
|
|
5
|
+
if (locator.props) {
|
|
6
|
+
props += propBuilder(locator.props);
|
|
7
|
+
_locator += props;
|
|
8
|
+
}
|
|
9
|
+
return matcher.locator(_locator).all();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function findVue(matcher, locator) {
|
|
13
|
+
let _locator = `_vue=${locator.vue}`;
|
|
14
|
+
let props = '';
|
|
15
|
+
|
|
16
|
+
if (locator.props) {
|
|
17
|
+
props += propBuilder(locator.props);
|
|
18
|
+
_locator += props;
|
|
19
|
+
}
|
|
20
|
+
return matcher.locator(_locator).all();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function propBuilder(props) {
|
|
24
|
+
let _props = '';
|
|
25
|
+
|
|
26
|
+
for (const [key, value] of Object.entries(props)) {
|
|
27
|
+
if (typeof value === 'object') {
|
|
28
|
+
for (const [k, v] of Object.entries(value)) {
|
|
29
|
+
_props += `[${key}.${k} = "${v}"]`;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
_props += `[${key} = "${value}"]`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return _props;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { findReact, findVue };
|
package/lib/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { parse, serialize } = require('parse5');
|
|
2
|
-
const { minify } = require('html-minifier');
|
|
2
|
+
const { minify } = require('html-minifier-terser');
|
|
3
3
|
|
|
4
|
-
function minifyHtml(html) {
|
|
4
|
+
async function minifyHtml(html) {
|
|
5
5
|
return minify(html, {
|
|
6
6
|
collapseWhitespace: true,
|
|
7
7
|
removeComments: true,
|
|
@@ -11,7 +11,7 @@ function minifyHtml(html) {
|
|
|
11
11
|
removeStyleLinkTypeAttributes: true,
|
|
12
12
|
collapseBooleanAttributes: true,
|
|
13
13
|
useShortDoctype: true,
|
|
14
|
-
})
|
|
14
|
+
});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const defaultHtmlOpts = {
|
|
@@ -119,7 +119,14 @@ module.exports = (text, file) => {
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name));
|
|
122
|
-
|
|
122
|
+
let title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
|
|
123
|
+
|
|
124
|
+
for (const [key, value] of Object.entries(current)) {
|
|
125
|
+
if (title.includes(`<${key}>`)) {
|
|
126
|
+
title = title.replace(JSON.stringify(current), '').replace(`<${key}>`, value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
|
|
124
131
|
test.tags = suite.tags.concat(tags);
|
|
125
132
|
test.file = file;
|
package/lib/locator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cssToXPath = require('
|
|
1
|
+
const cssToXPath = require('convert-cssxpath');
|
|
2
2
|
const { sprintf } = require('sprintf-js');
|
|
3
3
|
|
|
4
4
|
const { xpathLocator } = require('./utils');
|
|
@@ -162,7 +162,7 @@ class Locator {
|
|
|
162
162
|
*/
|
|
163
163
|
toXPath() {
|
|
164
164
|
if (this.isXPath()) return this.value;
|
|
165
|
-
if (this.isCSS()) return cssToXPath(this.value);
|
|
165
|
+
if (this.isCSS()) return cssToXPath.convert(this.value);
|
|
166
166
|
|
|
167
167
|
throw new Error('Can\'t be converted to XPath');
|
|
168
168
|
}
|
package/lib/pause.js
CHANGED
|
@@ -18,8 +18,7 @@ let nextStep;
|
|
|
18
18
|
let finish;
|
|
19
19
|
let next;
|
|
20
20
|
let registeredVariables = {};
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
let aiAssistant;
|
|
23
22
|
/**
|
|
24
23
|
* Pauses test execution and starts interactive shell
|
|
25
24
|
* @param {Object<string, *>} [passedObject]
|
|
@@ -45,6 +44,8 @@ function pauseSession(passedObject = {}) {
|
|
|
45
44
|
let vars = Object.keys(registeredVariables).join(', ');
|
|
46
45
|
if (vars) vars = `(vars: ${vars})`;
|
|
47
46
|
|
|
47
|
+
aiAssistant = AiAssistant.getInstance();
|
|
48
|
+
|
|
48
49
|
output.print(colors.yellow(' Interactive shell started'));
|
|
49
50
|
output.print(colors.yellow(' Use JavaScript syntax to try steps in action'));
|
|
50
51
|
output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`));
|
|
@@ -102,7 +103,9 @@ async function parseInput(cmd) {
|
|
|
102
103
|
let isAiCommand = false;
|
|
103
104
|
let $res;
|
|
104
105
|
try {
|
|
106
|
+
// eslint-disable-next-line
|
|
105
107
|
const locate = global.locate; // enable locate in this context
|
|
108
|
+
// eslint-disable-next-line
|
|
106
109
|
const I = container.support('I');
|
|
107
110
|
if (cmd.trim().startsWith('=>')) {
|
|
108
111
|
isCustomCommand = true;
|
|
@@ -115,7 +118,7 @@ async function parseInput(cmd) {
|
|
|
115
118
|
executeCommand = executeCommand.then(async () => {
|
|
116
119
|
try {
|
|
117
120
|
const html = await res;
|
|
118
|
-
aiAssistant.setHtmlContext(html);
|
|
121
|
+
await aiAssistant.setHtmlContext(html);
|
|
119
122
|
} catch (err) {
|
|
120
123
|
output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
|
|
121
124
|
return;
|
package/lib/plugin/autoLogin.js
CHANGED
|
@@ -38,12 +38,14 @@ const defaultConfig = {
|
|
|
38
38
|
* ```js
|
|
39
39
|
* // inside a test file
|
|
40
40
|
* // use login to inject auto-login function
|
|
41
|
+
* Feature('Login');
|
|
42
|
+
*
|
|
41
43
|
* Before(({ login }) => {
|
|
42
44
|
* login('user'); // login using user session
|
|
43
45
|
* });
|
|
44
46
|
*
|
|
45
|
-
* // Alternatively log in for one scenario
|
|
46
|
-
* Scenario('log me in', ( {I, login} ) => {
|
|
47
|
+
* // Alternatively log in for one scenario.
|
|
48
|
+
* Scenario('log me in', ( { I, login } ) => {
|
|
47
49
|
* login('admin');
|
|
48
50
|
* I.see('I am logged in');
|
|
49
51
|
* });
|
package/lib/plugin/heal.js
CHANGED
|
@@ -8,6 +8,7 @@ const output = require('../output');
|
|
|
8
8
|
const supportedHelpers = require('./standardActingHelpers');
|
|
9
9
|
|
|
10
10
|
const defaultConfig = {
|
|
11
|
+
healTries: 1,
|
|
11
12
|
healLimit: 2,
|
|
12
13
|
healSteps: [
|
|
13
14
|
'click',
|
|
@@ -54,11 +55,14 @@ const defaultConfig = {
|
|
|
54
55
|
*
|
|
55
56
|
*/
|
|
56
57
|
module.exports = function (config = {}) {
|
|
57
|
-
const aiAssistant =
|
|
58
|
+
const aiAssistant = AiAssistant.getInstance();
|
|
58
59
|
|
|
59
60
|
let currentTest = null;
|
|
60
61
|
let currentStep = null;
|
|
61
62
|
let healedSteps = 0;
|
|
63
|
+
let caughtError;
|
|
64
|
+
let healTries = 0;
|
|
65
|
+
let isHealing = false;
|
|
62
66
|
|
|
63
67
|
const healSuggestions = [];
|
|
64
68
|
|
|
@@ -67,20 +71,35 @@ module.exports = function (config = {}) {
|
|
|
67
71
|
event.dispatcher.on(event.test.before, (test) => {
|
|
68
72
|
currentTest = test;
|
|
69
73
|
healedSteps = 0;
|
|
74
|
+
caughtError = null;
|
|
70
75
|
});
|
|
71
76
|
|
|
72
77
|
event.dispatcher.on(event.step.started, step => currentStep = step);
|
|
73
78
|
|
|
74
|
-
event.dispatcher.on(event.step.
|
|
79
|
+
event.dispatcher.on(event.step.after, (step) => {
|
|
80
|
+
if (isHealing) return;
|
|
75
81
|
const store = require('../store');
|
|
76
82
|
if (store.debugMode) return;
|
|
77
|
-
|
|
78
83
|
recorder.catchWithoutStop(async (err) => {
|
|
79
|
-
|
|
84
|
+
isHealing = true;
|
|
85
|
+
if (caughtError === err) throw err; // avoid double handling
|
|
86
|
+
caughtError = err;
|
|
87
|
+
if (!aiAssistant.isEnabled) {
|
|
88
|
+
output.print(colors.yellow('Heal plugin can\'t operate, AI assistant is disabled. Please set OPENAI_API_KEY env variable to enable it.'));
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
80
91
|
if (!currentStep) throw err;
|
|
81
92
|
if (!config.healSteps.includes(currentStep.name)) throw err;
|
|
82
93
|
const test = currentTest;
|
|
83
94
|
|
|
95
|
+
if (healTries >= config.healTries) {
|
|
96
|
+
output.print(colors.bold.red(`Healing failed for ${config.healTries} time(s)`));
|
|
97
|
+
output.print('AI couldn\'t identify the correct solution');
|
|
98
|
+
output.print('Probably the entire flow has changed and the test should be updated');
|
|
99
|
+
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
if (healedSteps >= config.healLimit) {
|
|
85
104
|
output.print(colors.bold.red(`Can't heal more than ${config.healLimit} step(s) in a test`));
|
|
86
105
|
output.print('Entire flow can be broken, please check it manually');
|
|
@@ -111,9 +130,17 @@ module.exports = function (config = {}) {
|
|
|
111
130
|
|
|
112
131
|
if (!html) throw err;
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
healTries++;
|
|
134
|
+
await aiAssistant.setHtmlContext(html);
|
|
115
135
|
await tryToHeal(step, err);
|
|
116
|
-
|
|
136
|
+
|
|
137
|
+
recorder.add('close healing session', () => {
|
|
138
|
+
recorder.session.restore('heal');
|
|
139
|
+
recorder.ignoreErr(err);
|
|
140
|
+
});
|
|
141
|
+
await recorder.promise();
|
|
142
|
+
|
|
143
|
+
isHealing = false;
|
|
117
144
|
});
|
|
118
145
|
});
|
|
119
146
|
|
|
@@ -155,6 +182,9 @@ module.exports = function (config = {}) {
|
|
|
155
182
|
for (const codeSnippet of codeSnippets) {
|
|
156
183
|
try {
|
|
157
184
|
debug('Executing', codeSnippet);
|
|
185
|
+
recorder.catch((e) => {
|
|
186
|
+
console.log(e);
|
|
187
|
+
});
|
|
158
188
|
await eval(codeSnippet); // eslint-disable-line
|
|
159
189
|
|
|
160
190
|
healSuggestions.push({
|
|
@@ -163,14 +193,17 @@ module.exports = function (config = {}) {
|
|
|
163
193
|
snippet: codeSnippet,
|
|
164
194
|
});
|
|
165
195
|
|
|
166
|
-
output.print(colors.bold.green(' Code healed successfully'));
|
|
196
|
+
recorder.add('healed', () => output.print(colors.bold.green(' Code healed successfully')));
|
|
167
197
|
healedSteps++;
|
|
168
198
|
return;
|
|
169
199
|
} catch (err) {
|
|
170
200
|
debug('Failed to execute code', err);
|
|
201
|
+
recorder.ignoreErr(err); // healing ded not help
|
|
202
|
+
// recorder.catch(() => output.print(colors.bold.red(' Failed healing code')));
|
|
171
203
|
}
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
output.debug(`Couldn't heal the code for ${failedStep.toCode()}`);
|
|
175
207
|
}
|
|
208
|
+
return recorder.promise();
|
|
176
209
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const event = require('../event');
|
|
2
2
|
const recorder = require('../recorder');
|
|
3
|
+
const container = require('../container');
|
|
3
4
|
|
|
4
5
|
const defaultConfig = {
|
|
5
6
|
retries: 3,
|
|
@@ -42,7 +43,7 @@ const defaultConfig = {
|
|
|
42
43
|
* * `factor` - The exponential factor to use. Default is 1.5.
|
|
43
44
|
* * `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000.
|
|
44
45
|
* * `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity.
|
|
45
|
-
* * `randomize` - Randomizes the timeouts by multiplying with a factor
|
|
46
|
+
* * `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.
|
|
46
47
|
* * `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:
|
|
47
48
|
* * `amOnPage`
|
|
48
49
|
* * `wait*`
|
|
@@ -98,6 +99,8 @@ module.exports = (config) => {
|
|
|
98
99
|
config.when = when;
|
|
99
100
|
|
|
100
101
|
event.dispatcher.on(event.step.started, (step) => {
|
|
102
|
+
if (process.env.TRY_TO) return;
|
|
103
|
+
|
|
101
104
|
// if a step is ignored - return
|
|
102
105
|
for (const ignored of config.ignoredSteps) {
|
|
103
106
|
if (step.name === ignored) return;
|
|
@@ -114,6 +117,8 @@ module.exports = (config) => {
|
|
|
114
117
|
|
|
115
118
|
event.dispatcher.on(event.test.before, (test) => {
|
|
116
119
|
if (test && test.disableRetryFailedStep) return; // disable retry when a test is not active
|
|
120
|
+
// this env var is used to set the retries inside _before() block of helpers
|
|
121
|
+
process.env.FAILED_STEP_RETRIES = config.retries;
|
|
117
122
|
recorder.retry(config);
|
|
118
123
|
});
|
|
119
124
|
};
|
|
@@ -89,8 +89,8 @@ module.exports = function (config) {
|
|
|
89
89
|
const reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output;
|
|
90
90
|
|
|
91
91
|
event.dispatcher.on(event.test.before, (test) => {
|
|
92
|
-
const
|
|
93
|
-
dir = path.join(reportDir, `record_${
|
|
92
|
+
const sha256hash = crypto.createHash('sha256').update(test.file + test.title).digest('hex');
|
|
93
|
+
dir = path.join(reportDir, `record_${sha256hash}`);
|
|
94
94
|
mkdirp.sync(dir);
|
|
95
95
|
stepNum = 0;
|
|
96
96
|
error = null;
|
package/lib/plugin/tryTo.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const recorder = require('../recorder');
|
|
2
|
-
const store = require('../store');
|
|
3
2
|
const { debug } = require('../output');
|
|
4
3
|
|
|
5
4
|
const defaultConfig = {
|
|
@@ -9,7 +8,7 @@ const defaultConfig = {
|
|
|
9
8
|
/**
|
|
10
9
|
*
|
|
11
10
|
*
|
|
12
|
-
* Adds global `tryTo` function
|
|
11
|
+
* Adds global `tryTo` function in which all failed steps won't fail a test but will return true/false.
|
|
13
12
|
*
|
|
14
13
|
* Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
|
|
15
14
|
*
|
|
@@ -47,7 +46,7 @@ const defaultConfig = {
|
|
|
47
46
|
* ```js
|
|
48
47
|
* const assert = require('assert');
|
|
49
48
|
* ```
|
|
50
|
-
* Then use the
|
|
49
|
+
* Then use the assertion:
|
|
51
50
|
* const result1 = await tryTo(() => I.see('Hello, user'));
|
|
52
51
|
* const result2 = await tryTo(() => I.seeElement('.welcome'));
|
|
53
52
|
* assert.ok(result1 && result2, 'Assertions were not succesful');
|
|
@@ -70,7 +69,7 @@ const defaultConfig = {
|
|
|
70
69
|
* const tryTo = codeceptjs.container.plugins('tryTo');
|
|
71
70
|
* ```
|
|
72
71
|
*
|
|
73
|
-
*/
|
|
72
|
+
*/
|
|
74
73
|
module.exports = function (config) {
|
|
75
74
|
config = Object.assign(defaultConfig, config);
|
|
76
75
|
|
|
@@ -84,6 +83,7 @@ function tryTo(callback) {
|
|
|
84
83
|
let result = false;
|
|
85
84
|
return recorder.add('tryTo', () => {
|
|
86
85
|
recorder.session.start('tryTo');
|
|
86
|
+
process.env.TRY_TO = 'true';
|
|
87
87
|
callback();
|
|
88
88
|
recorder.add(() => {
|
|
89
89
|
result = true;
|
|
@@ -98,6 +98,7 @@ function tryTo(callback) {
|
|
|
98
98
|
return result;
|
|
99
99
|
});
|
|
100
100
|
return recorder.add('result', () => {
|
|
101
|
+
process.env.TRY_TO = undefined;
|
|
101
102
|
return result;
|
|
102
103
|
}, true, false);
|
|
103
104
|
}, false, false);
|
package/lib/recorder.js
CHANGED
|
@@ -11,6 +11,7 @@ let errFn;
|
|
|
11
11
|
let queueId = 0;
|
|
12
12
|
let sessionId = null;
|
|
13
13
|
let asyncErr = null;
|
|
14
|
+
let ignoredErrs = [];
|
|
14
15
|
|
|
15
16
|
let tasks = [];
|
|
16
17
|
let oldPromises = [];
|
|
@@ -93,6 +94,7 @@ module.exports = {
|
|
|
93
94
|
promise = Promise.resolve();
|
|
94
95
|
oldPromises = [];
|
|
95
96
|
tasks = [];
|
|
97
|
+
ignoredErrs = [];
|
|
96
98
|
this.session.running = false;
|
|
97
99
|
// reset this retries makes the retryFailedStep plugin won't work if there is Before/BeforeSuit block due to retries is undefined on Scenario
|
|
98
100
|
// this.retries = [];
|
|
@@ -226,9 +228,10 @@ module.exports = {
|
|
|
226
228
|
* @inner
|
|
227
229
|
*/
|
|
228
230
|
catch(customErrFn) {
|
|
229
|
-
|
|
231
|
+
const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
|
|
232
|
+
debug(`${currentQueue()}Queued | catch with error handler ${fnDescription || ''}`);
|
|
230
233
|
return promise = promise.catch((err) => {
|
|
231
|
-
log(`${currentQueue()}Error | ${err}
|
|
234
|
+
log(`${currentQueue()}Error | ${err} ${fnDescription}...`);
|
|
232
235
|
if (!(err instanceof Error)) { // strange things may happen
|
|
233
236
|
err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`); // we should be prepared for them
|
|
234
237
|
}
|
|
@@ -247,15 +250,15 @@ module.exports = {
|
|
|
247
250
|
* @inner
|
|
248
251
|
*/
|
|
249
252
|
catchWithoutStop(customErrFn) {
|
|
253
|
+
const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
|
|
250
254
|
return promise = promise.catch((err) => {
|
|
251
|
-
|
|
255
|
+
if (ignoredErrs.includes(err)) return; // already caught
|
|
256
|
+
log(`${currentQueue()}Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`);
|
|
252
257
|
if (!(err instanceof Error)) { // strange things may happen
|
|
253
258
|
err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`); // we should be prepared for them
|
|
254
259
|
}
|
|
255
260
|
if (customErrFn) {
|
|
256
261
|
return customErrFn(err);
|
|
257
|
-
} if (errFn) {
|
|
258
|
-
return errFn(err);
|
|
259
262
|
}
|
|
260
263
|
});
|
|
261
264
|
},
|
|
@@ -274,6 +277,10 @@ module.exports = {
|
|
|
274
277
|
});
|
|
275
278
|
},
|
|
276
279
|
|
|
280
|
+
ignoreErr(err) {
|
|
281
|
+
ignoredErrs.push(err);
|
|
282
|
+
},
|
|
283
|
+
|
|
277
284
|
/**
|
|
278
285
|
* @param {*} err
|
|
279
286
|
* @inner
|
package/lib/ui.js
CHANGED
|
@@ -168,6 +168,7 @@ module.exports = function (suite) {
|
|
|
168
168
|
context.Scenario.only = function (title, opts, fn) {
|
|
169
169
|
const reString = `^${escapeRe(`${suites[0].title}: ${title}`.replace(/( \| {.+})?$/g, ''))}`;
|
|
170
170
|
mocha.grep(new RegExp(reString));
|
|
171
|
+
process.env.SCENARIO_ONLY = true;
|
|
171
172
|
return addScenario(title, opts, fn);
|
|
172
173
|
};
|
|
173
174
|
|
package/lib/workers.js
CHANGED
|
@@ -359,6 +359,7 @@ class Workers extends EventEmitter {
|
|
|
359
359
|
this.stats.start = new Date();
|
|
360
360
|
recorder.startUnlessRunning();
|
|
361
361
|
event.dispatcher.emit(event.workers.before);
|
|
362
|
+
process.env.RUNS_WITH_WORKERS = 'true';
|
|
362
363
|
recorder.add('starting workers', () => {
|
|
363
364
|
for (const worker of this.workers) {
|
|
364
365
|
const workerThread = createWorker(worker);
|
|
@@ -492,6 +493,7 @@ class Workers extends EventEmitter {
|
|
|
492
493
|
}
|
|
493
494
|
|
|
494
495
|
output.result(this.stats.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration));
|
|
496
|
+
process.env.RUNS_WITH_WORKERS = 'false';
|
|
495
497
|
}
|
|
496
498
|
}
|
|
497
499
|
|