codeceptjs 3.5.0 → 3.5.2
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 +27 -0
- package/README.md +3 -3
- package/docs/ai.md +11 -9
- package/docs/build/OpenAI.js +14 -10
- package/docs/build/Playwright.js +60 -28
- package/docs/build/Puppeteer.js +56 -0
- package/docs/build/TestCafe.js +58 -1
- package/docs/build/WebDriver.js +55 -0
- package/docs/changelog.md +27 -0
- package/docs/helpers/Playwright.md +201 -166
- package/docs/helpers/Puppeteer.md +148 -102
- package/docs/helpers/TestCafe.md +95 -49
- package/docs/helpers/WebDriver.md +150 -104
- package/docs/plugins.md +1 -1
- package/docs/webapi/blur.mustache +17 -0
- package/docs/webapi/focus.mustache +12 -0
- package/lib/cli.js +9 -1
- package/lib/command/init.js +10 -0
- package/lib/event.js +2 -0
- package/lib/helper/OpenAI.js +14 -10
- package/lib/helper/Playwright.js +48 -45
- package/lib/helper/Puppeteer.js +27 -0
- package/lib/helper/TestCafe.js +29 -1
- package/lib/helper/WebDriver.js +26 -0
- package/lib/helper/scripts/blurElement.js +17 -0
- package/lib/helper/scripts/focusElement.js +17 -0
- package/lib/interfaces/gherkin.js +8 -0
- package/lib/plugin/heal.js +5 -7
- package/lib/recorder.js +7 -4
- package/lib/utils.js +13 -0
- package/package.json +7 -10
- package/typings/promiseBasedTypes.d.ts +130 -13
- package/typings/types.d.ts +134 -17
package/docs/plugins.md
CHANGED
|
@@ -612,7 +612,7 @@ Self-healing tests with OpenAI.
|
|
|
612
612
|
|
|
613
613
|
This plugin is experimental and requires OpenAI API key.
|
|
614
614
|
|
|
615
|
-
To use it you need to set OPENAI_API_KEY env variable and enable plugin inside
|
|
615
|
+
To use it you need to set OPENAI_API_KEY env variable and enable plugin inside the config.
|
|
616
616
|
|
|
617
617
|
```js
|
|
618
618
|
plugins: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Remove focus from a text input, button, etc.
|
|
2
|
+
Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
|
|
3
|
+
|
|
4
|
+
Examples:
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
I.blur('.text-area')
|
|
8
|
+
```
|
|
9
|
+
```js
|
|
10
|
+
//element `#product-tile` is focused
|
|
11
|
+
I.see('#add-to-cart-btn');
|
|
12
|
+
I.blur('#product-tile')
|
|
13
|
+
I.dontSee('#add-to-cart-btn');
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
@param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
17
|
+
@param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the matching element.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
I.dontSee('#add-to-cart-btn');
|
|
7
|
+
I.focus('#product-tile')
|
|
8
|
+
I.see('#add-to-cart-bnt');
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
@param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
12
|
+
@param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
|
package/lib/cli.js
CHANGED
|
@@ -81,6 +81,11 @@ class Cli extends Base {
|
|
|
81
81
|
if (!codeceptjsEventDispatchersRegistered) {
|
|
82
82
|
codeceptjsEventDispatchersRegistered = true;
|
|
83
83
|
|
|
84
|
+
event.dispatcher.on(event.bddStep.started, (step) => {
|
|
85
|
+
output.stepShift = 2;
|
|
86
|
+
output.step(step);
|
|
87
|
+
});
|
|
88
|
+
|
|
84
89
|
event.dispatcher.on(event.step.started, (step) => {
|
|
85
90
|
let processingStep = step;
|
|
86
91
|
const metaSteps = [];
|
|
@@ -93,7 +98,10 @@ class Cli extends Base {
|
|
|
93
98
|
for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
|
|
94
99
|
if (currentMetaStep[i] !== metaSteps[i]) {
|
|
95
100
|
output.stepShift = 3 + 2 * i;
|
|
96
|
-
if (metaSteps[i])
|
|
101
|
+
if (!metaSteps[i]) continue;
|
|
102
|
+
// bdd steps are handled by bddStep.started
|
|
103
|
+
if (metaSteps[i].isBDD()) continue;
|
|
104
|
+
output.step(metaSteps[i]);
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
currentMetaStep = metaSteps;
|
package/lib/command/init.js
CHANGED
|
@@ -317,6 +317,16 @@ module.exports = function (initPath) {
|
|
|
317
317
|
|
|
318
318
|
print('Configure helpers...');
|
|
319
319
|
inquirer.prompt(helperConfigs).then((helperResult) => {
|
|
320
|
+
if (helperResult.Playwright_browser === 'electron') {
|
|
321
|
+
delete helperResult.Playwright_url;
|
|
322
|
+
delete helperResult.Playwright_show;
|
|
323
|
+
|
|
324
|
+
helperResult.Playwright_electron = {
|
|
325
|
+
executablePath: '// require("electron") or require("electron-forge")',
|
|
326
|
+
args: ['path/to/your/main.js'],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
320
330
|
Object.keys(helperResult).forEach((key) => {
|
|
321
331
|
const parts = key.split('_');
|
|
322
332
|
const helperName = parts[0];
|
package/lib/event.js
CHANGED
package/lib/helper/OpenAI.js
CHANGED
|
@@ -25,7 +25,9 @@ class OpenAI extends Helper {
|
|
|
25
25
|
chunkSize: 80000,
|
|
26
26
|
};
|
|
27
27
|
this.options = { ...this.options, ...config };
|
|
28
|
+
}
|
|
28
29
|
|
|
30
|
+
_beforeSuite() {
|
|
29
31
|
const helpers = Container.helpers();
|
|
30
32
|
|
|
31
33
|
for (const helperName of standardActingHelpers) {
|
|
@@ -37,16 +39,16 @@ class OpenAI extends Helper {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
* Asks the OpenAI GPT language model a question based on the provided prompt within the context of the current page's HTML.
|
|
43
|
+
*
|
|
44
|
+
* ```js
|
|
45
|
+
* I.askGptOnPage('what does this page do?');
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @async
|
|
49
|
+
* @param {string} prompt - The question or prompt to ask the GPT model.
|
|
50
|
+
* @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
|
|
51
|
+
*/
|
|
50
52
|
async askGptOnPage(prompt) {
|
|
51
53
|
const html = await this.helper.grabSource();
|
|
52
54
|
|
|
@@ -120,3 +122,5 @@ class OpenAI extends Helper {
|
|
|
120
122
|
return response;
|
|
121
123
|
}
|
|
122
124
|
}
|
|
125
|
+
|
|
126
|
+
module.exports = OpenAI;
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -52,13 +52,13 @@ const pathSeparator = path.sep;
|
|
|
52
52
|
/**
|
|
53
53
|
* ## Configuration
|
|
54
54
|
*
|
|
55
|
-
* This helper should be configured in codecept.conf.js
|
|
55
|
+
* This helper should be configured in codecept.conf.(js|ts)
|
|
56
56
|
*
|
|
57
57
|
* @typedef PlaywrightConfig
|
|
58
58
|
* @type {object}
|
|
59
|
-
* @prop {string} url - base url of website to be tested
|
|
59
|
+
* @prop {string} [url] - base url of website to be tested
|
|
60
60
|
* @prop {'chromium' | 'firefox'| 'webkit' | 'electron'} [browser='chromium'] - a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
61
|
-
* @prop {boolean} [show=
|
|
61
|
+
* @prop {boolean} [show=true] - show browser window.
|
|
62
62
|
* @prop {string|boolean} [restart=false] - restart strategy between tests. Possible values:
|
|
63
63
|
* * 'context' or **false** - restarts [browser context](https://playwright.dev/docs/api/class-browsercontext) but keeps running browser. Recommended by Playwright team to keep tests isolated.
|
|
64
64
|
* * 'browser' or **true** - closes browser and opens it again between tests.
|
|
@@ -260,6 +260,22 @@ const config = {};
|
|
|
260
260
|
* }
|
|
261
261
|
* ```
|
|
262
262
|
*
|
|
263
|
+
* * #### Example #9: Launch electron test
|
|
264
|
+
*
|
|
265
|
+
* ```js
|
|
266
|
+
* {
|
|
267
|
+
* helpers: {
|
|
268
|
+
* Playwright: {
|
|
269
|
+
* browser: 'electron',
|
|
270
|
+
* electron: {
|
|
271
|
+
* executablePath: require("electron"),
|
|
272
|
+
* args: [path.join('../', "main.js")],
|
|
273
|
+
* },
|
|
274
|
+
* }
|
|
275
|
+
* },
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
263
279
|
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
264
280
|
*
|
|
265
281
|
* ## Access From Helpers
|
|
@@ -373,15 +389,24 @@ class Playwright extends Helper {
|
|
|
373
389
|
|
|
374
390
|
static _config() {
|
|
375
391
|
return [
|
|
376
|
-
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
377
|
-
{
|
|
378
|
-
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
379
|
-
},
|
|
380
392
|
{
|
|
381
393
|
name: 'browser',
|
|
382
394
|
message: 'Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron',
|
|
383
395
|
default: 'chromium',
|
|
384
396
|
},
|
|
397
|
+
{
|
|
398
|
+
name: 'url',
|
|
399
|
+
message: 'Base url of site to be tested',
|
|
400
|
+
default: 'http://localhost',
|
|
401
|
+
when: (answers) => answers.Playwright_browser !== 'electron',
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'show',
|
|
405
|
+
message: 'Show browser window',
|
|
406
|
+
default: true,
|
|
407
|
+
type: 'confirm',
|
|
408
|
+
when: (answers) => answers.Playwright_browser !== 'electron',
|
|
409
|
+
},
|
|
385
410
|
];
|
|
386
411
|
}
|
|
387
412
|
|
|
@@ -908,17 +933,7 @@ class Playwright extends Helper {
|
|
|
908
933
|
}
|
|
909
934
|
|
|
910
935
|
/**
|
|
911
|
-
*
|
|
912
|
-
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
913
|
-
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
|
|
914
|
-
*
|
|
915
|
-
* Examples:
|
|
916
|
-
*
|
|
917
|
-
* ```js
|
|
918
|
-
* I.dontSee('#add-to-cart-btn');
|
|
919
|
-
* I.focus('#product-tile')
|
|
920
|
-
* I.see('#add-to-cart-bnt');
|
|
921
|
-
* ```
|
|
936
|
+
* {{> focus }}
|
|
922
937
|
*
|
|
923
938
|
*/
|
|
924
939
|
async focus(locator, options = {}) {
|
|
@@ -931,22 +946,7 @@ class Playwright extends Helper {
|
|
|
931
946
|
}
|
|
932
947
|
|
|
933
948
|
/**
|
|
934
|
-
*
|
|
935
|
-
* Calls [blur](https://playwright.dev/docs/api/class-locator#locator-blur) on the element.
|
|
936
|
-
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
937
|
-
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
|
|
938
|
-
*
|
|
939
|
-
* Examples:
|
|
940
|
-
*
|
|
941
|
-
* ```js
|
|
942
|
-
* I.blur('.text-area')
|
|
943
|
-
* ```
|
|
944
|
-
* ```js
|
|
945
|
-
* //element `#product-tile` is focused
|
|
946
|
-
* I.see('#add-to-cart-btn');
|
|
947
|
-
* I.blur('#product-tile')
|
|
948
|
-
* I.dontSee('#add-to-cart-btn');
|
|
949
|
-
* ```
|
|
949
|
+
* {{> blur }}
|
|
950
950
|
*
|
|
951
951
|
*/
|
|
952
952
|
async blur(locator, options = {}) {
|
|
@@ -1542,22 +1542,25 @@ class Playwright extends Helper {
|
|
|
1542
1542
|
}
|
|
1543
1543
|
|
|
1544
1544
|
/**
|
|
1545
|
-
*
|
|
1545
|
+
* Clears the text input element: `<input>`, `<textarea>` or `[contenteditable]` .
|
|
1546
|
+
*
|
|
1547
|
+
*
|
|
1548
|
+
* Examples:
|
|
1549
|
+
*
|
|
1550
|
+
* ```js
|
|
1551
|
+
* I.clearField('.text-area')
|
|
1552
|
+
*
|
|
1553
|
+
* // if this doesn't work use force option
|
|
1554
|
+
* I.clearField('#submit', { force: true })
|
|
1555
|
+
* ```
|
|
1556
|
+
* Use `force` to bypass the [actionability](https://playwright.dev/docs/actionability) checks.
|
|
1557
|
+
*
|
|
1546
1558
|
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
1547
1559
|
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-clear) for available options object as 2nd argument.
|
|
1548
|
-
*
|
|
1549
|
-
* Examples:
|
|
1550
|
-
*
|
|
1551
|
-
* ```js
|
|
1552
|
-
* I.clearField('.text-area')
|
|
1553
|
-
* ```
|
|
1554
|
-
* ```js
|
|
1555
|
-
* I.clearField('#submit', { force: true }) // force to bypass the [actionability](https://playwright.dev/docs/actionability) checks.
|
|
1556
|
-
* ```
|
|
1557
1560
|
*/
|
|
1558
1561
|
async clearField(locator, options = {}) {
|
|
1559
1562
|
let result;
|
|
1560
|
-
const isNewClearMethodPresent = typeof this.page.locator().clear === 'function';
|
|
1563
|
+
const isNewClearMethodPresent = false; // not works, disabled for now. Prev: typeof this.page.locator().clear === 'function';
|
|
1561
1564
|
|
|
1562
1565
|
if (isNewClearMethodPresent) {
|
|
1563
1566
|
const els = await findFields.call(this, locator);
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -35,6 +35,8 @@ const Popup = require('./extras/Popup');
|
|
|
35
35
|
const Console = require('./extras/Console');
|
|
36
36
|
const findReact = require('./extras/React');
|
|
37
37
|
const { highlightElement } = require('./scripts/highlightElement');
|
|
38
|
+
const { blurElement } = require('./scripts/blurElement');
|
|
39
|
+
const { focusElement } = require('./scripts/focusElement');
|
|
38
40
|
|
|
39
41
|
let puppeteer;
|
|
40
42
|
let perfTiming;
|
|
@@ -707,6 +709,31 @@ class Puppeteer extends Helper {
|
|
|
707
709
|
return this._waitForAction();
|
|
708
710
|
}
|
|
709
711
|
|
|
712
|
+
/**
|
|
713
|
+
* {{> focus }}
|
|
714
|
+
*
|
|
715
|
+
*/
|
|
716
|
+
async focus(locator) {
|
|
717
|
+
const els = await this._locate(locator);
|
|
718
|
+
assertElementExists(els, locator, 'Element to focus');
|
|
719
|
+
const el = els[0];
|
|
720
|
+
|
|
721
|
+
await focusElement(el, this.page);
|
|
722
|
+
return this._waitForAction();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* {{> blur }}
|
|
727
|
+
*
|
|
728
|
+
*/
|
|
729
|
+
async blur(locator) {
|
|
730
|
+
const els = await this._locate(locator);
|
|
731
|
+
assertElementExists(els, locator, 'Element to blur');
|
|
732
|
+
|
|
733
|
+
await blurElement(els[0], this.page);
|
|
734
|
+
return this._waitForAction();
|
|
735
|
+
}
|
|
736
|
+
|
|
710
737
|
/**
|
|
711
738
|
* {{> dragAndDrop }}
|
|
712
739
|
*/
|
package/lib/helper/TestCafe.js
CHANGED
|
@@ -346,6 +346,34 @@ class TestCafe extends Helper {
|
|
|
346
346
|
return this.t.resizeWindow(width, height).catch(mapError);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* {{> focus }}
|
|
351
|
+
*
|
|
352
|
+
*/
|
|
353
|
+
async focus(locator) {
|
|
354
|
+
const els = await this._locate(locator);
|
|
355
|
+
await assertElementExists(els, locator, 'Element to focus');
|
|
356
|
+
const element = await els.nth(0);
|
|
357
|
+
|
|
358
|
+
const focusElement = ClientFunction(() => element().focus(), { boundTestRun: this.t, dependencies: { element } });
|
|
359
|
+
|
|
360
|
+
return focusElement();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* {{> blur }}
|
|
365
|
+
*
|
|
366
|
+
*/
|
|
367
|
+
async blur(locator) {
|
|
368
|
+
const els = await this._locate(locator);
|
|
369
|
+
await assertElementExists(els, locator, 'Element to blur');
|
|
370
|
+
const element = await els.nth(0);
|
|
371
|
+
|
|
372
|
+
const blurElement = ClientFunction(() => element().blur(), { boundTestRun: this.t, dependencies: { element } });
|
|
373
|
+
|
|
374
|
+
return blurElement();
|
|
375
|
+
}
|
|
376
|
+
|
|
349
377
|
/**
|
|
350
378
|
* {{> click }}
|
|
351
379
|
*
|
|
@@ -792,7 +820,7 @@ class TestCafe extends Helper {
|
|
|
792
820
|
/**
|
|
793
821
|
* {{> executeScript }}
|
|
794
822
|
*
|
|
795
|
-
* If a function returns a Promise It will wait for
|
|
823
|
+
* If a function returns a Promise It will wait for its resolution.
|
|
796
824
|
*/
|
|
797
825
|
async executeScript(fn, ...args) {
|
|
798
826
|
const browserFn = createClientFunction(fn, args).with({ boundTestRun: this.t });
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -30,6 +30,8 @@ const ConnectionRefused = require('./errors/ConnectionRefused');
|
|
|
30
30
|
const Locator = require('../locator');
|
|
31
31
|
const { highlightElement } = require('./scripts/highlightElement');
|
|
32
32
|
const store = require('../store');
|
|
33
|
+
const { focusElement } = require('./scripts/focusElement');
|
|
34
|
+
const { blurElement } = require('./scripts/blurElement');
|
|
33
35
|
|
|
34
36
|
const SHADOW = 'shadow';
|
|
35
37
|
const webRoot = 'body';
|
|
@@ -1934,6 +1936,30 @@ class WebDriver extends Helper {
|
|
|
1934
1936
|
}
|
|
1935
1937
|
}
|
|
1936
1938
|
|
|
1939
|
+
/**
|
|
1940
|
+
* {{> focus }}
|
|
1941
|
+
*
|
|
1942
|
+
*/
|
|
1943
|
+
async focus(locator) {
|
|
1944
|
+
const els = await this._locate(locator);
|
|
1945
|
+
assertElementExists(els, locator, 'Element to focus');
|
|
1946
|
+
const el = usingFirstElement(els);
|
|
1947
|
+
|
|
1948
|
+
await focusElement(el, this.browser);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* {{> blur }}
|
|
1953
|
+
*
|
|
1954
|
+
*/
|
|
1955
|
+
async blur(locator) {
|
|
1956
|
+
const els = await this._locate(locator);
|
|
1957
|
+
assertElementExists(els, locator, 'Element to blur');
|
|
1958
|
+
const el = usingFirstElement(els);
|
|
1959
|
+
|
|
1960
|
+
await blurElement(el, this.browser);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1937
1963
|
/**
|
|
1938
1964
|
* {{> dragAndDrop }}
|
|
1939
1965
|
* Appium: not tested
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports.blurElement = (element, context) => {
|
|
2
|
+
const clientSideBlurFn = el => {
|
|
3
|
+
el.blur();
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
// Puppeteer
|
|
8
|
+
context.evaluate(clientSideBlurFn, element);
|
|
9
|
+
} catch (e) {
|
|
10
|
+
// WebDriver
|
|
11
|
+
try {
|
|
12
|
+
context.execute(clientSideBlurFn, element);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports.focusElement = (element, context) => {
|
|
2
|
+
const clientSideFn = el => {
|
|
3
|
+
el.focus();
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
// Puppeteer
|
|
8
|
+
context.evaluate(clientSideFn, element);
|
|
9
|
+
} catch (e) {
|
|
10
|
+
// WebDriver
|
|
11
|
+
try {
|
|
12
|
+
context.execute(clientSideFn, element);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const Gherkin = require('@cucumber/gherkin');
|
|
2
2
|
const Messages = require('@cucumber/messages');
|
|
3
3
|
const { Context, Suite, Test } = require('mocha');
|
|
4
|
+
const debug = require('debug')('codeceptjs:bdd');
|
|
4
5
|
|
|
5
6
|
const { matchStep } = require('./bdd');
|
|
6
7
|
const event = require('../event');
|
|
@@ -39,7 +40,9 @@ module.exports = (text, file) => {
|
|
|
39
40
|
for (const step of steps) {
|
|
40
41
|
const metaStep = new Step.MetaStep(null, step.text);
|
|
41
42
|
metaStep.actor = step.keyword.trim();
|
|
43
|
+
let helperStep;
|
|
42
44
|
const setMetaStep = (step) => {
|
|
45
|
+
helperStep = step;
|
|
43
46
|
if (step.metaStep) {
|
|
44
47
|
if (step.metaStep === metaStep) {
|
|
45
48
|
return;
|
|
@@ -67,11 +70,15 @@ module.exports = (text, file) => {
|
|
|
67
70
|
step.startTime = Date.now();
|
|
68
71
|
step.match = fn.line;
|
|
69
72
|
event.emit(event.bddStep.before, step);
|
|
73
|
+
event.emit(event.bddStep.started, metaStep);
|
|
70
74
|
event.dispatcher.prependListener(event.step.before, setMetaStep);
|
|
71
75
|
try {
|
|
76
|
+
debug(`Step '${step.text}' started...`);
|
|
72
77
|
await fn(...fn.params);
|
|
78
|
+
debug('Step passed');
|
|
73
79
|
step.status = 'passed';
|
|
74
80
|
} catch (err) {
|
|
81
|
+
debug(`Step failed: ${err?.message}`);
|
|
75
82
|
step.status = 'failed';
|
|
76
83
|
step.err = err;
|
|
77
84
|
throw err;
|
|
@@ -79,6 +86,7 @@ module.exports = (text, file) => {
|
|
|
79
86
|
step.endTime = Date.now();
|
|
80
87
|
event.dispatcher.removeListener(event.step.before, setMetaStep);
|
|
81
88
|
}
|
|
89
|
+
event.emit(event.bddStep.finished, metaStep);
|
|
82
90
|
event.emit(event.bddStep.after, step);
|
|
83
91
|
}
|
|
84
92
|
};
|
package/lib/plugin/heal.js
CHANGED
|
@@ -26,7 +26,7 @@ const defaultConfig = {
|
|
|
26
26
|
*
|
|
27
27
|
* This plugin is experimental and requires OpenAI API key.
|
|
28
28
|
*
|
|
29
|
-
* To use it you need to set OPENAI_API_KEY env variable and enable plugin inside
|
|
29
|
+
* To use it you need to set OPENAI_API_KEY env variable and enable plugin inside the config.
|
|
30
30
|
*
|
|
31
31
|
* ```js
|
|
32
32
|
* plugins: {
|
|
@@ -82,7 +82,7 @@ module.exports = function (config = {}) {
|
|
|
82
82
|
const test = currentTest;
|
|
83
83
|
|
|
84
84
|
if (healedSteps >= config.healLimit) {
|
|
85
|
-
output.print(colors.bold.red(`Can't heal more than ${config.healLimit}
|
|
85
|
+
output.print(colors.bold.red(`Can't heal more than ${config.healLimit} step(s) in a test`));
|
|
86
86
|
output.print('Entire flow can be broken, please check it manually');
|
|
87
87
|
output.print('or increase healing limit in heal plugin config');
|
|
88
88
|
|
|
@@ -126,7 +126,7 @@ module.exports = function (config = {}) {
|
|
|
126
126
|
print('===================');
|
|
127
127
|
print(colors.bold.green('Self-Healing Report:'));
|
|
128
128
|
|
|
129
|
-
print(`${colors.bold(healSuggestions.length)}
|
|
129
|
+
print(`${colors.bold(healSuggestions.length)} step(s) were healed by AI`);
|
|
130
130
|
|
|
131
131
|
let i = 1;
|
|
132
132
|
print('');
|
|
@@ -145,15 +145,13 @@ module.exports = function (config = {}) {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
async function tryToHeal(failedStep, err) {
|
|
148
|
-
output.debug(`Running
|
|
149
|
-
|
|
150
|
-
const I = Container.support('I');
|
|
148
|
+
output.debug(`Running OpenAI to heal ${failedStep.toCode()} step`);
|
|
151
149
|
|
|
152
150
|
const codeSnippets = await aiAssistant.healFailedStep(
|
|
153
151
|
failedStep, err, currentTest,
|
|
154
152
|
);
|
|
155
153
|
|
|
156
|
-
output.debug(`Received ${codeSnippets.length}
|
|
154
|
+
output.debug(`Received ${codeSnippets.length} suggestions from OpenAI`);
|
|
157
155
|
|
|
158
156
|
for (const codeSnippet of codeSnippets) {
|
|
159
157
|
try {
|
package/lib/recorder.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const debug = require('debug')('codeceptjs:recorder');
|
|
2
2
|
const promiseRetry = require('promise-retry');
|
|
3
|
-
|
|
3
|
+
const { printObjectProperties } = require('./utils');
|
|
4
4
|
const { log } = require('./output');
|
|
5
5
|
|
|
6
6
|
const MAX_TASKS = 100;
|
|
@@ -41,6 +41,7 @@ module.exports = {
|
|
|
41
41
|
* @inner
|
|
42
42
|
*/
|
|
43
43
|
start() {
|
|
44
|
+
debug('Starting recording promises');
|
|
44
45
|
running = true;
|
|
45
46
|
asyncErr = null;
|
|
46
47
|
errFn = null;
|
|
@@ -175,7 +176,7 @@ module.exports = {
|
|
|
175
176
|
return;
|
|
176
177
|
}
|
|
177
178
|
tasks.push(taskName);
|
|
178
|
-
|
|
179
|
+
debug(`${currentQueue()}Queued | ${taskName}`);
|
|
179
180
|
|
|
180
181
|
return promise = Promise.resolve(promise).then((res) => {
|
|
181
182
|
// prefer options for non-conditional retries
|
|
@@ -224,10 +225,11 @@ module.exports = {
|
|
|
224
225
|
* @inner
|
|
225
226
|
*/
|
|
226
227
|
catch(customErrFn) {
|
|
228
|
+
debug(`${currentQueue()}Queued | catch with error handler`);
|
|
227
229
|
return promise = promise.catch((err) => {
|
|
228
230
|
log(`${currentQueue()}Error | ${err}`);
|
|
229
231
|
if (!(err instanceof Error)) { // strange things may happen
|
|
230
|
-
err = new Error(`[Wrapped Error] ${
|
|
232
|
+
err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`); // we should be prepared for them
|
|
231
233
|
}
|
|
232
234
|
if (customErrFn) {
|
|
233
235
|
customErrFn(err);
|
|
@@ -264,8 +266,9 @@ module.exports = {
|
|
|
264
266
|
* @param {*} err
|
|
265
267
|
* @inner
|
|
266
268
|
*/
|
|
269
|
+
|
|
267
270
|
throw(err) {
|
|
268
|
-
return this.add(`throw error ${err}`, () => {
|
|
271
|
+
return this.add(`throw error: ${err.message}`, () => {
|
|
269
272
|
throw err;
|
|
270
273
|
});
|
|
271
274
|
},
|
package/lib/utils.js
CHANGED
|
@@ -459,3 +459,16 @@ module.exports.isNotSet = function (obj) {
|
|
|
459
459
|
module.exports.emptyFolder = async (directoryPath) => {
|
|
460
460
|
require('child_process').execSync(`rm -rf ${directoryPath}/*`);
|
|
461
461
|
};
|
|
462
|
+
|
|
463
|
+
module.exports.printObjectProperties = (obj) => {
|
|
464
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
465
|
+
return obj;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let result = '';
|
|
469
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
470
|
+
result += `${key}: "${value}"; `;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return `{${result}}`;
|
|
474
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acceptance",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@cucumber/gherkin": "^26",
|
|
66
66
|
"@cucumber/messages": "^21.0.1",
|
|
67
67
|
"@xmldom/xmldom": "^0.7.9",
|
|
68
|
-
"acorn": "^
|
|
68
|
+
"acorn": "^8.10.0",
|
|
69
69
|
"arrify": "^2.0.1",
|
|
70
70
|
"axios": "^1.3.3",
|
|
71
71
|
"chai": "^4.3.6",
|
|
@@ -75,13 +75,12 @@
|
|
|
75
75
|
"cross-spawn": "^7.0.3",
|
|
76
76
|
"css-to-xpath": "^0.1.0",
|
|
77
77
|
"envinfo": "^7.8.1",
|
|
78
|
-
"escape-string-regexp": "^
|
|
78
|
+
"escape-string-regexp": "^4.0.0",
|
|
79
79
|
"figures": "^3.2.0",
|
|
80
80
|
"fn-args": "^4.0.0",
|
|
81
81
|
"fs-extra": "^8.1.0",
|
|
82
82
|
"glob": "^6.0.1",
|
|
83
83
|
"html-minifier": "^4.0.0",
|
|
84
|
-
"i": "^0.3.7",
|
|
85
84
|
"inquirer": "^6.5.2",
|
|
86
85
|
"joi": "^17.6.0",
|
|
87
86
|
"js-beautify": "^1.14.0",
|
|
@@ -89,9 +88,7 @@
|
|
|
89
88
|
"lodash.merge": "^4.6.2",
|
|
90
89
|
"mkdirp": "^1.0.4",
|
|
91
90
|
"mocha": "^10.2.0",
|
|
92
|
-
"mocha-junit-reporter": "^1.23.3",
|
|
93
91
|
"ms": "^2.1.3",
|
|
94
|
-
"npm": "^9.6.7",
|
|
95
92
|
"openai": "^3.2.1",
|
|
96
93
|
"ora-classic": "^5.4.2",
|
|
97
94
|
"parse-function": "^5.6.4",
|
|
@@ -118,9 +115,9 @@
|
|
|
118
115
|
"contributor-faces": "^1.0.3",
|
|
119
116
|
"documentation": "^12.3.0",
|
|
120
117
|
"dtslint": "^4.1.6",
|
|
121
|
-
"electron": "^
|
|
118
|
+
"electron": "^25.2.0",
|
|
122
119
|
"eslint": "^6.8.0",
|
|
123
|
-
"eslint-config-airbnb-base": "^
|
|
120
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
124
121
|
"eslint-plugin-import": "^2.25.4",
|
|
125
122
|
"eslint-plugin-mocha": "^6.3.0",
|
|
126
123
|
"expect": "^26.6.2",
|
|
@@ -149,8 +146,8 @@
|
|
|
149
146
|
"typescript": "^4.8.4",
|
|
150
147
|
"wdio-docker-service": "^1.5.0",
|
|
151
148
|
"webdriverio": "^8.3.8",
|
|
152
|
-
"xml2js": "^0.
|
|
153
|
-
"xmldom": "^0.
|
|
149
|
+
"xml2js": "^0.6.0",
|
|
150
|
+
"xmldom": "^0.6.0",
|
|
154
151
|
"xpath": "0.0.27"
|
|
155
152
|
},
|
|
156
153
|
"engines": {
|