codeceptjs 3.4.1 → 3.5.1

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 (75) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +11 -9
  3. package/bin/codecept.js +1 -1
  4. package/docs/ai.md +248 -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 +234 -54
  10. package/docs/build/Protractor.js +3 -1
  11. package/docs/build/Puppeteer.js +101 -12
  12. package/docs/build/REST.js +15 -5
  13. package/docs/build/TestCafe.js +61 -2
  14. package/docs/build/WebDriver.js +85 -5
  15. package/docs/changelog.md +85 -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 +228 -151
  21. package/docs/helpers/Puppeteer.md +153 -101
  22. package/docs/helpers/REST.md +6 -5
  23. package/docs/helpers/TestCafe.md +97 -49
  24. package/docs/helpers/WebDriver.md +159 -107
  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 +2 -2
  30. package/docs/webapi/appendField.mustache +2 -0
  31. package/docs/webapi/blur.mustache +17 -0
  32. package/docs/webapi/focus.mustache +12 -0
  33. package/docs/webapi/type.mustache +3 -0
  34. package/lib/ai.js +171 -0
  35. package/lib/cli.js +10 -2
  36. package/lib/codecept.js +4 -0
  37. package/lib/command/dryRun.js +9 -1
  38. package/lib/command/generate.js +46 -3
  39. package/lib/command/init.js +23 -1
  40. package/lib/command/interactive.js +15 -1
  41. package/lib/command/run-workers.js +2 -1
  42. package/lib/container.js +13 -3
  43. package/lib/event.js +2 -0
  44. package/lib/helper/Appium.js +45 -7
  45. package/lib/helper/JSONResponse.js +4 -4
  46. package/lib/helper/Nightmare.js +1 -1
  47. package/lib/helper/OpenAI.js +122 -0
  48. package/lib/helper/Playwright.js +200 -45
  49. package/lib/helper/Protractor.js +1 -1
  50. package/lib/helper/Puppeteer.js +67 -12
  51. package/lib/helper/REST.js +15 -5
  52. package/lib/helper/TestCafe.js +30 -2
  53. package/lib/helper/WebDriver.js +51 -5
  54. package/lib/helper/scripts/blurElement.js +17 -0
  55. package/lib/helper/scripts/focusElement.js +17 -0
  56. package/lib/helper/scripts/highlightElement.js +20 -0
  57. package/lib/html.js +258 -0
  58. package/lib/interfaces/gherkin.js +8 -0
  59. package/lib/listener/retry.js +2 -1
  60. package/lib/pause.js +73 -17
  61. package/lib/plugin/debugErrors.js +67 -0
  62. package/lib/plugin/fakerTransform.js +4 -6
  63. package/lib/plugin/heal.js +177 -0
  64. package/lib/plugin/screenshotOnFail.js +11 -2
  65. package/lib/recorder.js +11 -8
  66. package/lib/secret.js +5 -4
  67. package/lib/step.js +6 -1
  68. package/lib/ui.js +4 -3
  69. package/lib/utils.js +17 -0
  70. package/lib/workers.js +57 -9
  71. package/package.json +25 -16
  72. package/translations/ja-JP.js +9 -9
  73. package/typings/index.d.ts +43 -9
  74. package/typings/promiseBasedTypes.d.ts +242 -25
  75. package/typings/types.d.ts +260 -35
package/docs/mobile.md CHANGED
@@ -45,22 +45,69 @@ To install Appium use npm:
45
45
  npm i -g appium
46
46
  ```
47
47
 
48
+ To use Appium 2.x:
49
+ ```sh
50
+ npm i -g appium@next
51
+ ```
52
+ Appium 2x (still beta) reenvisions Appium as a platform where “drivers” and “plugins” can be easily created and shared independently.
53
+ Install an Appium driver and its dependencies
54
+ To install the Appium driver and its dependencies, we'll be using the uiautomator2 (Android), XCUITest (iOS) drivers.
55
+
56
+ ```
57
+ appium driver install xcuitest
58
+ appium driver install uiautomator2
59
+ ```
60
+ To make sure that all the drivers are installed successfully, run the following command:
61
+
62
+ ```
63
+ appium driver list
64
+
65
+ tth~$appium driver list
66
+ ✔ Listing available drivers
67
+ - espresso@2.17.0 [installed (NPM)]
68
+ - uiautomator2@2.12.6 [installed (NPM)]
69
+ - xcuitest@4.19.1 [installed (NPM)]
70
+ - mac2 [not installed]
71
+ - safari [not installed]
72
+ - gecko [not installed]
73
+ - chromium [not installed]
74
+ ```
75
+
48
76
  Then you need to prepare application for execution.
49
77
  It should be packed into apk (for Android) or .ipa (for iOS) or zip.
50
78
 
51
- Next, is to launch the emulator or connect physical device.
79
+ Next, is to launch the emulator or connect a physical device.
52
80
  Once they are prepared, launch Appium:
53
81
 
54
82
  ```sh
55
83
  appium
56
84
  ```
57
85
 
86
+ To use Appium 2.x:
87
+ ```sh
88
+ tth~$npx appium --base-path=/wd/hub
89
+ [Appium] Welcome to Appium v2.0.0-beta.57 (REV 3e675c32ae71dc0b00749d5d29213e2ea5b53c5b)
90
+ [Appium] Non-default server args:
91
+ [Appium] {
92
+ [Appium] basePath: '/wd/hub'
93
+ [Appium] }
94
+ [Appium] Attempting to load driver espresso...
95
+ [debug] [Appium] Requiring driver at /Users/trung-thanh/Desktop/thanh-nguyen/task2/node_modules/appium-espresso-driver
96
+ [Appium] Attempting to load driver uiautomator2...
97
+ [debug] [Appium] Requiring driver at /Users/trung-thanh/Desktop/thanh-nguyen/task2/node_modules/appium-uiautomator2-driver
98
+ [Appium] Appium REST http interface listener started on 0.0.0.0:4723
99
+ [Appium] Available drivers:
100
+ [Appium] - espresso@2.17.0 (automationName 'Espresso')
101
+ [Appium] - uiautomator2@2.12.6 (automationName 'UiAutomator2')
102
+ [Appium] No plugins have been installed. Use the "appium plugin" command to install the one(s) you want to use.
103
+ ```
104
+
58
105
  To run mobile test you need either an device emulator (available with Android SDK or iOS), real device connected for mobile testing. Alternatively, you may execute Appium with device emulator inside Docker container.
59
106
 
60
107
  CodeceptJS should be installed with webdriverio support:
61
108
 
62
109
  ```bash
63
- npm install codeceptjs webdriverio --save
110
+ npm install codeceptjs webdriverio@8.6.3 --save
64
111
  ```
65
112
 
66
113
  ## Configuring
package/docs/parallel.md CHANGED
@@ -32,6 +32,62 @@ By default the tests are assigned one by one to the available workers this may l
32
32
  npx codeceptjs run-workers --suites 2
33
33
  ```
34
34
 
35
+ ## Parallel Execution by Workers on Multiple Browsers
36
+
37
+ To run tests in parallel across multiple browsers, modify your `codecept.conf.js` file to configure multiple browsers on which you want to run your tests and your tests will run across multiple browsers.
38
+
39
+ Start with modifying the `codecept.conf.js` file. Add multiple key inside the config which will be used to configure multiple profiles.
40
+
41
+ ```
42
+ exports.config = {
43
+ helpers: {
44
+ WebDriver: {
45
+ url: 'http://localhost:3000',
46
+ desiredCapabilties: {}
47
+ }
48
+ },
49
+ multiple: {
50
+ profile1: {
51
+ browsers: [
52
+ {
53
+ browser: "firefox",
54
+ desiredCapabilties: {
55
+ // override capabilties related to firefox
56
+ }
57
+ },
58
+ {
59
+ browser: "chrome",
60
+ desiredCapabilties: {
61
+ // override capabilties related to chrome
62
+ }
63
+ }
64
+ ]
65
+ },
66
+ profile2: {
67
+ browsers: [
68
+ {
69
+ browser: "safari",
70
+ desiredCapabilties: {
71
+ // override capabilties related to safari
72
+ }
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ };
78
+ ```
79
+ To trigger tests on all the profiles configured, you can use the following command:
80
+ ```
81
+ npx codeceptjs run-workers 3 all -c codecept.conf.js
82
+ ```
83
+ This will run your tests across all browsers configured from profile1 & profile2 on 3 workers.
84
+
85
+ To trigger tests on specific profile, you can use the following command:
86
+ ```
87
+ npx codeceptjs run-workers 2 profile1 -c codecept.conf.js
88
+ ```
89
+ This will run your tests across 2 browsers from profile1 on 2 workers.
90
+
35
91
  ## Custom Parallel Execution
36
92
 
37
93
  To get a full control of parallelization create a custom execution script to match your needs.
package/docs/plugins.md CHANGED
@@ -479,6 +479,30 @@ I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
479
479
 
480
480
  - `config`
481
481
 
482
+ ## debugErrors
483
+
484
+ Prints errors found in HTML code after each failed test.
485
+
486
+ It scans HTML and searches for elements with error classes.
487
+ If an element found prints a text from it to console and adds as artifact to the test.
488
+
489
+ Enable this plugin in config:
490
+
491
+ ```js
492
+ plugins: {
493
+ debugErrors: {
494
+ enabled: true,
495
+ }
496
+ ```
497
+
498
+ Additional config options:
499
+
500
+ - `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)
501
+
502
+ ### Parameters
503
+
504
+ - `config` (optional, default `{}`)
505
+
482
506
  ## eachElement
483
507
 
484
508
  Provides `eachElement` global function to iterate over found elements to perform actions on them.
@@ -546,17 +570,15 @@ Returns **([Promise][7]<any> | [undefined][8])**
546
570
 
547
571
  ## fakerTransform
548
572
 
549
- Use the [faker.js][9] package to generate fake data inside examples on your gherkin tests
550
-
551
- ![Faker.js][10]
573
+ Use the `@faker-js/faker` package to generate fake data inside examples on your gherkin tests
552
574
 
553
575
  #### Usage
554
576
 
555
- To start please install `faker.js` package
577
+ To start please install `@faker-js/faker` package
556
578
 
557
- npm install -D faker
579
+ npm install -D @faker-js/faker
558
580
 
559
- yarn add -D faker
581
+ yarn add -D @faker-js/faker
560
582
 
561
583
  Add this plugin to config file:
562
584
 
@@ -584,9 +606,45 @@ Scenario Outline: ...
584
606
 
585
607
  - `config`
586
608
 
609
+ ## heal
610
+
611
+ Self-healing tests with OpenAI.
612
+
613
+ This plugin is experimental and requires OpenAI API key.
614
+
615
+ To use it you need to set OPENAI_API_KEY env variable and enable plugin inside the config.
616
+
617
+ ```js
618
+ plugins: {
619
+ heal: {
620
+ enabled: true,
621
+ }
622
+ }
623
+ ```
624
+
625
+ More config options are available:
626
+
627
+ - `healLimit` - how many steps can be healed in a single test (default: 2)
628
+ - `healSteps` - which steps can be healed (default: all steps that interact with UI, see list below)
629
+
630
+ Steps to heal:
631
+
632
+ - `click`
633
+ - `fillField`
634
+ - `appendField`
635
+ - `selectOption`
636
+ - `attachFile`
637
+ - `checkOption`
638
+ - `uncheckOption`
639
+ - `doubleClick`
640
+
641
+ ### Parameters
642
+
643
+ - `config` (optional, default `{}`)
644
+
587
645
  ## pauseOnFail
588
646
 
589
- Automatically launches [interactive pause][11] when a test fails.
647
+ Automatically launches [interactive pause][9] when a test fails.
590
648
 
591
649
  Useful for debugging flaky tests on local environment.
592
650
  Add this plugin to config file:
@@ -769,14 +827,14 @@ Possible config options:
769
827
 
770
828
  ## selenoid
771
829
 
772
- [Selenoid][12] plugin automatically starts browsers and video recording.
830
+ [Selenoid][10] plugin automatically starts browsers and video recording.
773
831
  Works with WebDriver helper.
774
832
 
775
833
  ### Prerequisite
776
834
 
777
835
  This plugin **requires Docker** to be installed.
778
836
 
779
- > If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][13] tool from Selenoid
837
+ > If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][11] tool from Selenoid
780
838
 
781
839
  ### Usage
782
840
 
@@ -805,7 +863,7 @@ plugins: {
805
863
  }
806
864
  ```
807
865
 
808
- When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][14] and start Selenoid automatically.
866
+ When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][12] and start Selenoid automatically.
809
867
  It will also create `browsers.json` file required by Selenoid.
810
868
 
811
869
  In automatic mode the latest version of browser will be used for tests. It is recommended to specify exact version of each browser inside `browsers.json` file.
@@ -817,10 +875,10 @@ In automatic mode the latest version of browser will be used for tests. It is re
817
875
  While this plugin can create containers for you for better control it is recommended to create and launch containers manually.
818
876
  This is especially useful for Continous Integration server as you can configure scaling for Selenoid containers.
819
877
 
820
- > Use [Selenoid Configuration Manager][13] to create and start containers semi-automatically.
878
+ > Use [Selenoid Configuration Manager][11] to create and start containers semi-automatically.
821
879
 
822
880
  1. Create `browsers.json` file in the same directory `codecept.conf.js` is located
823
- [Refer to Selenoid documentation][15] to know more about browsers.json.
881
+ [Refer to Selenoid documentation][13] to know more about browsers.json.
824
882
 
825
883
  _Sample browsers.json_
826
884
 
@@ -845,7 +903,7 @@ _Sample browsers.json_
845
903
 
846
904
  2. Create Selenoid container
847
905
 
848
- Run the following command to create a container. To know more [refer here][16]
906
+ Run the following command to create a container. To know more [refer here][14]
849
907
 
850
908
  ```bash
851
909
  docker create \
@@ -878,7 +936,7 @@ When `allure` plugin is enabled a video is attached to report automatically.
878
936
  | enableVideo | Enable video recording and use `video` folder of output (default: false) |
879
937
  | enableLog | Enable log recording and use `logs` folder of output (default: false) |
880
938
  | deletePassed | Delete video and logs of passed tests (default : true) |
881
- | additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][17] to know more |
939
+ | additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][15] to know more |
882
940
 
883
941
  ### Parameters
884
942
 
@@ -886,7 +944,7 @@ When `allure` plugin is enabled a video is attached to report automatically.
886
944
 
887
945
  ## stepByStepReport
888
946
 
889
- ![step-by-step-report][18]
947
+ ![step-by-step-report][16]
890
948
 
891
949
  Generates step by step report for a test.
892
950
  After each step in a test a screenshot is created. After test executed screenshots are combined into slideshow.
@@ -1067,7 +1125,7 @@ This plugin allows to run webdriverio services like:
1067
1125
  - browserstack
1068
1126
  - appium
1069
1127
 
1070
- A complete list of all available services can be found on [webdriverio website][19].
1128
+ A complete list of all available services can be found on [webdriverio website][17].
1071
1129
 
1072
1130
  #### Setup
1073
1131
 
@@ -1079,7 +1137,7 @@ See examples below:
1079
1137
 
1080
1138
  #### Selenium Standalone Service
1081
1139
 
1082
- Install `@wdio/selenium-standalone-service` package, as [described here][20].
1140
+ Install `@wdio/selenium-standalone-service` package, as [described here][18].
1083
1141
  It is important to make sure it is compatible with current webdriverio version.
1084
1142
 
1085
1143
  Enable `wdio` plugin in plugins list and add `selenium-standalone` service:
@@ -1098,7 +1156,7 @@ Please note, this service can be used with Protractor helper as well!
1098
1156
 
1099
1157
  #### Sauce Service
1100
1158
 
1101
- Install `@wdio/sauce-service` package, as [described here][21].
1159
+ Install `@wdio/sauce-service` package, as [described here][19].
1102
1160
  It is important to make sure it is compatible with current webdriverio version.
1103
1161
 
1104
1162
  Enable `wdio` plugin in plugins list and add `sauce` service:
@@ -1144,28 +1202,24 @@ In the same manner additional services from webdriverio can be installed, enable
1144
1202
 
1145
1203
  [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
1146
1204
 
1147
- [9]: https://www.npmjs.com/package/faker
1148
-
1149
- [10]: https://raw.githubusercontent.com/Marak/faker.js/master/logo.png
1150
-
1151
- [11]: /basics/#pause
1205
+ [9]: /basics/#pause
1152
1206
 
1153
- [12]: https://aerokube.com/selenoid/
1207
+ [10]: https://aerokube.com/selenoid/
1154
1208
 
1155
- [13]: https://aerokube.com/cm/latest/
1209
+ [11]: https://aerokube.com/cm/latest/
1156
1210
 
1157
- [14]: https://hub.docker.com/u/selenoid
1211
+ [12]: https://hub.docker.com/u/selenoid
1158
1212
 
1159
- [15]: https://aerokube.com/selenoid/latest/#_prepare_configuration
1213
+ [13]: https://aerokube.com/selenoid/latest/#_prepare_configuration
1160
1214
 
1161
- [16]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container
1215
+ [14]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container
1162
1216
 
1163
- [17]: https://docs.docker.com/engine/reference/commandline/create/
1217
+ [15]: https://docs.docker.com/engine/reference/commandline/create/
1164
1218
 
1165
- [18]: https://codecept.io/img/codeceptjs-slideshow.gif
1219
+ [16]: https://codecept.io/img/codeceptjs-slideshow.gif
1166
1220
 
1167
- [19]: https://webdriver.io
1221
+ [17]: https://webdriver.io
1168
1222
 
1169
- [20]: https://webdriver.io/docs/selenium-standalone-service.html
1223
+ [18]: https://webdriver.io/docs/selenium-standalone-service.html
1170
1224
 
1171
- [21]: https://webdriver.io/docs/sauce-service.html
1225
+ [19]: https://webdriver.io/docs/sauce-service.html
package/docs/secrets.md CHANGED
@@ -15,6 +15,12 @@ When executed it will be printed like this:
15
15
  ```
16
16
  I fill field "password" "*****"
17
17
  ```
18
+ **Other Examples**
19
+ ```js
20
+ I.fillField('password', secret('123456'));
21
+ I.append('password', secret('123456'));
22
+ I.type('password', secret('123456'));
23
+ ```
18
24
 
19
25
  For an object, which can be a payload to POST request, specify which fields should be masked:
20
26
 
package/docs/tutorial.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  permalink: /tutorial
3
- title: CoeceptJS Complete Tutorial
3
+ title: CodeceptJS Complete Tutorial
4
4
  ---
5
5
 
6
6
  # Tutorial: Writing Tests for Checkout Page
@@ -216,7 +216,7 @@ module.exports = {
216
216
  Feels really empty. What should we do about it? Should we write more code? No, we already have it. Let's copy code blocks from a test we have it and place them under a corredponnding function names:
217
217
 
218
218
  ```js
219
- connst { I } = inject();
219
+ const { I } = inject();
220
220
 
221
221
  module.exports = {
222
222
 
@@ -3,6 +3,8 @@ Field is located by name, label, CSS or XPath
3
3
 
4
4
  ```js
5
5
  I.appendField('#myTextField', 'appended');
6
+ // typing secret
7
+ I.appendField('password', secret('123456'));
6
8
  ```
7
9
  @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
8
10
  @param {string} value text value to append.
@@ -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.
@@ -11,6 +11,9 @@ I.type('4141555311111111', 100);
11
11
 
12
12
  // passing in an array
13
13
  I.type(['T', 'E', 'X', 'T']);
14
+
15
+ // passing a secret
16
+ I.type(secret('123456'));
14
17
  ```
15
18
 
16
19
  @param {string|string[]} key or array of keys to type.
package/lib/ai.js ADDED
@@ -0,0 +1,171 @@
1
+ const { Configuration, OpenAIApi } = require('openai');
2
+ const debug = require('debug')('codeceptjs:ai');
3
+ const config = require('./config');
4
+ const output = require('./output');
5
+ const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html');
6
+
7
+ const defaultConfig = {
8
+ model: 'gpt-3.5-turbo-16k',
9
+ temperature: 0.1,
10
+ };
11
+
12
+ const htmlConfig = {
13
+ maxLength: 50000,
14
+ simplify: true,
15
+ minify: true,
16
+ html: {},
17
+ };
18
+
19
+ class AiAssistant {
20
+ constructor() {
21
+ this.config = config.get('ai', defaultConfig);
22
+ this.htmlConfig = Object.assign(htmlConfig, this.config.html);
23
+ delete this.config.html;
24
+ this.html = null;
25
+ this.response = null;
26
+
27
+ this.isEnabled = !!process.env.OPENAI_API_KEY;
28
+
29
+ if (!this.isEnabled) return;
30
+
31
+ const configuration = new Configuration({
32
+ apiKey: process.env.OPENAI_API_KEY,
33
+ });
34
+
35
+ this.openai = new OpenAIApi(configuration);
36
+ }
37
+
38
+ setHtmlContext(html) {
39
+ let processedHTML = html;
40
+
41
+ if (this.htmlConfig.simplify) {
42
+ processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig);
43
+ }
44
+ if (this.htmlConfig.minify) processedHTML = minifyHtml(processedHTML);
45
+ if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0];
46
+
47
+ debug(processedHTML);
48
+
49
+ this.html = processedHTML;
50
+ }
51
+
52
+ getResponse() {
53
+ return this.response || '';
54
+ }
55
+
56
+ mockResponse(response) {
57
+ this.mockedResponse = response;
58
+ }
59
+
60
+ async createCompletion(messages) {
61
+ if (!this.openai) return;
62
+
63
+ debug(messages);
64
+
65
+ if (this.mockedResponse) return this.mockedResponse;
66
+
67
+ this.response = null;
68
+
69
+ try {
70
+ const completion = await this.openai.createChatCompletion({
71
+ ...this.config,
72
+ messages,
73
+ });
74
+
75
+ this.response = completion?.data?.choices[0]?.message?.content;
76
+
77
+ debug(this.response);
78
+
79
+ return this.response;
80
+ } catch (err) {
81
+ debug(err.response);
82
+ output.print('');
83
+ output.error(`OpenAI error: ${err.message}`);
84
+ output.error(err?.response?.data?.error?.code);
85
+ output.error(err?.response?.data?.error?.message);
86
+ return '';
87
+ }
88
+ }
89
+
90
+ async healFailedStep(step, err, test) {
91
+ if (!this.isEnabled) return [];
92
+ if (!this.html) throw new Error('No HTML context provided');
93
+
94
+ const messages = [
95
+ { role: 'user', content: 'As a test automation engineer I am testing web application using CodeceptJS.' },
96
+ { role: 'user', content: `I want to heal a test that fails. Here is the list of executed steps: ${test.steps.join(', ')}` },
97
+ { role: 'user', content: `Propose how to adjust ${step.toCode()} step to fix the test.` },
98
+ { role: 'user', content: 'Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with ```.' },
99
+ { role: 'user', content: `Here is the error message: ${err.message}` },
100
+ { role: 'user', content: `Here is HTML code of a page where the failure has happened: \n\n${this.html}` },
101
+ ];
102
+
103
+ const response = await this.createCompletion(messages);
104
+ if (!response) return [];
105
+
106
+ return parseCodeBlocks(response);
107
+ }
108
+
109
+ async writeSteps(input) {
110
+ if (!this.isEnabled) return;
111
+ if (!this.html) throw new Error('No HTML context provided');
112
+
113
+ const snippets = [];
114
+
115
+ const messages = [
116
+ {
117
+ role: 'user',
118
+ content: `I am test engineer writing test in CodeceptJS
119
+ I have opened web page and I want to use CodeceptJS to ${input} on this page
120
+ Provide me valid CodeceptJS code to accomplish it
121
+ Use only locators from this HTML: \n\n${this.html}`,
122
+ },
123
+ { role: 'user', content: 'Propose only CodeceptJS steps code. Do not include Scenario or Feature into response' },
124
+
125
+ // old prompt
126
+ // { role: 'user', content: 'I want to click button Submit using CodeceptJS on this HTML page: <html><body><button>Submit</button></body></html>' },
127
+ // { role: 'assistant', content: '```js\nI.click("Submit");\n```' },
128
+ // { role: 'user', content: 'I want to click button Submit using CodeceptJS on this HTML page: <html><body><button>Login</button></body></html>' },
129
+ // { role: 'assistant', content: 'No suggestions' },
130
+ // { role: 'user', content: `Now I want to ${input} on this HTML page using CodeceptJS code` },
131
+ // { role: 'user', content: `Provide me with CodeceptJS code to achieve this on THIS page.` },
132
+ ];
133
+ const response = await this.createCompletion(messages);
134
+ if (!response) return;
135
+ snippets.push(...parseCodeBlocks(response));
136
+
137
+ debug(snippets[0]);
138
+
139
+ return snippets[0];
140
+ }
141
+ }
142
+
143
+ function parseCodeBlocks(response) {
144
+ // Regular expression pattern to match code snippets
145
+ const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g;
146
+
147
+ // Array to store extracted code snippets
148
+ const codeSnippets = [];
149
+
150
+ response = response.split('\n').map(line => line.trim()).join('\n');
151
+
152
+ // Iterate over matches and extract code snippets
153
+ let match;
154
+ while ((match = codeSnippetPattern.exec(response)) !== null) {
155
+ codeSnippets.push(match[1]);
156
+ }
157
+
158
+ // Remove "Scenario", "Feature", and "require()" lines
159
+ const modifiedSnippets = codeSnippets.map(snippet => {
160
+ const lines = snippet.split('\n');
161
+
162
+ const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('));
163
+
164
+ return filteredLines.join('\n');
165
+ // remove snippets that move from current url
166
+ }); // .filter(snippet => !line.includes('I.amOnPage'));
167
+
168
+ return modifiedSnippets.filter(snippet => !!snippet);
169
+ }
170
+
171
+ module.exports = AiAssistant;
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]) output.step(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;
@@ -168,7 +176,7 @@ class Cli extends Base {
168
176
  }
169
177
 
170
178
  // display artifacts in debug mode
171
- if (test.artifacts && Object.keys(test.artifacts).length) {
179
+ if (test?.artifacts && Object.keys(test.artifacts).length) {
172
180
  log += `\n${output.styles.bold('Artifacts:')}`;
173
181
  for (const artifact of Object.keys(test.artifacts)) {
174
182
  log += `\n- ${artifact}: ${test.artifacts[artifact]}`;
package/lib/codecept.js CHANGED
@@ -8,6 +8,7 @@ const Config = require('./config');
8
8
  const event = require('./event');
9
9
  const runHook = require('./hooks');
10
10
  const output = require('./output');
11
+ const { emptyFolder } = require('./utils');
11
12
 
12
13
  /**
13
14
  * CodeceptJS runner
@@ -66,6 +67,8 @@ class Codecept {
66
67
  global.codecept_dir = dir;
67
68
  global.output_dir = fsPath.resolve(dir, this.config.output);
68
69
 
70
+ if (this.config.emptyOutputFolder) emptyFolder(global.output_dir);
71
+
69
72
  if (!this.config.noGlobals) {
70
73
  global.Helper = global.codecept_helper = require('@codeceptjs/helper');
71
74
  global.actor = global.codecept_actor = require('./actor');
@@ -158,6 +161,7 @@ class Codecept {
158
161
 
159
162
  for (pattern of patterns) {
160
163
  glob.sync(pattern, options).forEach((file) => {
164
+ if (file.includes('node_modules')) return;
161
165
  if (!fsPath.isAbsolute(file)) {
162
166
  file = fsPath.join(global.codecept_dir, file);
163
167
  }