codeceptjs 3.2.3 → 3.3.0-beta.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 (48) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/advanced.md +0 -4
  3. package/docs/api.md +227 -188
  4. package/docs/build/ApiDataFactory.js +13 -6
  5. package/docs/build/Appium.js +36 -36
  6. package/docs/build/GraphQL.js +11 -0
  7. package/docs/build/JSONResponse.js +297 -0
  8. package/docs/build/Nightmare.js +48 -48
  9. package/docs/build/Playwright.js +261 -146
  10. package/docs/build/Puppeteer.js +76 -67
  11. package/docs/build/REST.js +36 -0
  12. package/docs/build/TestCafe.js +44 -44
  13. package/docs/build/WebDriver.js +69 -69
  14. package/docs/helpers/ApiDataFactory.md +7 -0
  15. package/docs/helpers/Appium.md +3 -3
  16. package/docs/helpers/JSONResponse.md +230 -0
  17. package/docs/helpers/Playwright.md +282 -218
  18. package/docs/helpers/Puppeteer.md +9 -1
  19. package/docs/helpers/REST.md +30 -9
  20. package/docs/installation.md +2 -0
  21. package/docs/internal-api.md +265 -0
  22. package/docs/playwright.md +70 -15
  23. package/docs/plugins.md +125 -29
  24. package/docs/puppeteer.md +24 -8
  25. package/docs/quickstart.md +2 -3
  26. package/docs/reports.md +43 -2
  27. package/docs/translation.md +1 -1
  28. package/docs/videos.md +2 -2
  29. package/docs/webdriver.md +90 -2
  30. package/lib/command/init.js +5 -15
  31. package/lib/config.js +17 -13
  32. package/lib/helper/ApiDataFactory.js +13 -6
  33. package/lib/helper/Appium.js +3 -3
  34. package/lib/helper/GraphQL.js +11 -0
  35. package/lib/helper/JSONResponse.js +297 -0
  36. package/lib/helper/Playwright.js +199 -84
  37. package/lib/helper/Puppeteer.js +12 -3
  38. package/lib/helper/REST.js +36 -0
  39. package/lib/helper/extras/Console.js +8 -0
  40. package/lib/helper/extras/PlaywrightRestartOpts.js +35 -0
  41. package/lib/interfaces/bdd.js +3 -1
  42. package/lib/plugin/allure.js +12 -0
  43. package/lib/plugin/eachElement.js +127 -0
  44. package/lib/utils.js +20 -0
  45. package/package.json +6 -4
  46. package/translations/pt-BR.js +8 -0
  47. package/typings/index.d.ts +2 -0
  48. package/typings/types.d.ts +237 -11
@@ -15,7 +15,15 @@ Uses [Google Chrome's Puppeteer][1] library to run tests inside headless Chrome.
15
15
  Browser control is executed via DevTools Protocol (instead of Selenium).
16
16
  This helper works with a browser out of the box with no additional tools required to install.
17
17
 
18
- Requires `puppeteer` package to be installed.
18
+ Requires `puppeteer` or `puppeteer-core` package to be installed.
19
+
20
+ npm i puppeteer --save
21
+
22
+ or
23
+
24
+ npm i puppeteer-core --save
25
+
26
+ Using `puppeteer-core` package, will prevent the download of browser binaries and allow connecting to an existing browser installation or for connecting to a remote one.
19
27
 
20
28
  > Experimental Firefox support [can be activated][2].
21
29
 
@@ -71,6 +71,27 @@ Generates url based on format sent (takes endpoint + url if latter lacks 'http')
71
71
 
72
72
  - `url` **any**
73
73
 
74
+ ### amBearerAuthenticated
75
+
76
+ Adds a header for Bearer authentication
77
+
78
+ ```js
79
+ // we use secret function to hide token from logs
80
+ I.amBearerAuthenticated(secret('heregoestoken'))
81
+ ```
82
+
83
+ #### Parameters
84
+
85
+ - `accessToken` **[string][3]** Bearer access token
86
+
87
+ ### haveRequestHeaders
88
+
89
+ Sets request headers for all requests of this test
90
+
91
+ #### Parameters
92
+
93
+ - `headers` **[object][4]** headers list
94
+
74
95
  ### sendDeleteRequest
75
96
 
76
97
  Sends DELETE request to API.
@@ -82,7 +103,7 @@ I.sendDeleteRequest('/api/users/1');
82
103
  #### Parameters
83
104
 
84
105
  - `url` **any**
85
- - `headers` **[object][3]** the headers object to be sent. By default it is sent as an empty object
106
+ - `headers` **[object][4]** the headers object to be sent. By default it is sent as an empty object
86
107
 
87
108
  Returns **[Promise][2]<any>** response
88
109
 
@@ -97,7 +118,7 @@ I.sendGetRequest('/api/users.json');
97
118
  #### Parameters
98
119
 
99
120
  - `url` **any**
100
- - `headers` **[object][3]** the headers object to be sent. By default it is sent as an empty object
121
+ - `headers` **[object][4]** the headers object to be sent. By default it is sent as an empty object
101
122
 
102
123
  Returns **[Promise][2]<any>** response
103
124
 
@@ -114,9 +135,9 @@ I.sendPatchRequest('/api/users.json', secret({ "email": "user@user.com" }));
114
135
 
115
136
  #### Parameters
116
137
 
117
- - `url` **[string][4]**
138
+ - `url` **[string][3]**
118
139
  - `payload` **any** the payload to be sent. By default it is sent as an empty object
119
- - `headers` **[object][3]** the headers object to be sent. By default it is sent as an empty object
140
+ - `headers` **[object][4]** the headers object to be sent. By default it is sent as an empty object
120
141
 
121
142
  Returns **[Promise][2]<any>** response
122
143
 
@@ -135,7 +156,7 @@ I.sendPostRequest('/api/users.json', secret({ "email": "user@user.com" }));
135
156
 
136
157
  - `url` **any**
137
158
  - `payload` **any** the payload to be sent. By default it is sent as an empty object
138
- - `headers` **[object][3]** the headers object to be sent. By default it is sent as an empty object
159
+ - `headers` **[object][4]** the headers object to be sent. By default it is sent as an empty object
139
160
 
140
161
  Returns **[Promise][2]<any>** response
141
162
 
@@ -152,9 +173,9 @@ I.sendPutRequest('/api/users.json', secret({ "email": "user@user.com" }));
152
173
 
153
174
  #### Parameters
154
175
 
155
- - `url` **[string][4]**
176
+ - `url` **[string][3]**
156
177
  - `payload` **any** the payload to be sent. By default it is sent as an empty object
157
- - `headers` **[object][3]** the headers object to be sent. By default it is sent as an empty object
178
+ - `headers` **[object][4]** the headers object to be sent. By default it is sent as an empty object
158
179
 
159
180
  Returns **[Promise][2]<any>** response
160
181
 
@@ -174,8 +195,8 @@ I.setRequestTimeout(10000); // In milliseconds
174
195
 
175
196
  [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
176
197
 
177
- [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
198
+ [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
178
199
 
179
- [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
200
+ [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
180
201
 
181
202
  [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
@@ -33,6 +33,8 @@ Install CodeceptJS + webdriverio into `e2e-tests` directory:
33
33
  npx create-codeceptjs e2e-tests --webdriverio
34
34
  ```
35
35
 
36
+ If you plan to use CodeceptJS for **API testing** only proceed to standard installation
37
+
36
38
  ## Standard Installation
37
39
 
38
40
  Open a directory where you want to install CodeceptJS tests.
@@ -0,0 +1,265 @@
1
+ ---
2
+ permalink: /internal-api
3
+ title: Internal API
4
+ ---
5
+
6
+ ## Concepts
7
+
8
+ In this guide we will overview the internal API of CodeceptJS.
9
+ This knowledge is required for customization, writing plugins, etc.
10
+
11
+ CodeceptJS provides an API which can be loaded via `require('codeceptjs')` when CodeceptJS is installed locally. Otherwise, you can load codeceptjs API via global `codeceptjs` object:
12
+
13
+ ```js
14
+ // via module
15
+ const { recorder, event, output } = require('codeceptjs');
16
+ // or using global object
17
+ const { recorder, event, output } = codeceptjs;
18
+ ```
19
+
20
+ These internal objects are available:
21
+
22
+ * [`codecept`](https://github.com/Codeception/CodeceptJS/blob/master/lib/codecept.js): test runner class
23
+ * [`config`](https://github.com/Codeception/CodeceptJS/blob/master/lib/config.js): current codecept config
24
+ * [`event`](https://github.com/Codeception/CodeceptJS/blob/master/lib/event.js): event listener
25
+ * [`recorder`](https://github.com/Codeception/CodeceptJS/blob/master/lib/recorder.js): global promise chain
26
+ * [`output`](https://github.com/Codeception/CodeceptJS/blob/master/lib/output.js): internal printer
27
+ * [`container`](https://github.com/Codeception/CodeceptJS/blob/master/lib/container.js): dependency injection container for tests, includes current helpers and support objects
28
+ * [`helper`](https://github.com/Codeception/CodeceptJS/blob/master/lib/helper.js): basic helper class
29
+ * [`actor`](https://github.com/Codeception/CodeceptJS/blob/master/lib/actor.js): basic actor (I) class
30
+
31
+ [API reference](https://github.com/Codeception/CodeceptJS/tree/master/docs/api) is available on GitHub.
32
+ Also please check the source code of corresponding modules.
33
+
34
+ ### Container
35
+
36
+ CodeceptJS has a dependency injection container with helpers and support objects.
37
+ They can be retrieved from the container:
38
+
39
+ ```js
40
+ const { container } = require('codeceptjs');
41
+
42
+ // get object with all helpers
43
+ const helpers = container.helpers();
44
+
45
+ // get helper by name
46
+ const { WebDriver } = container.helpers();
47
+
48
+ // get support objects
49
+ const supportObjects = container.support();
50
+
51
+ // get support object by name
52
+ const { UserPage } = container.support();
53
+
54
+ // get all registered plugins
55
+ const plugins = container.plugins();
56
+ ```
57
+
58
+ New objects can also be added to container in runtime:
59
+
60
+ ```js
61
+ const { container } = require('codeceptjs');
62
+
63
+ container.append({
64
+ helpers: { // add helper
65
+ MyHelper: new MyHelper({ config1: 'val1' });
66
+ },
67
+ support: { // add page object
68
+ UserPage: require('./pages/user');
69
+ }
70
+ })
71
+ ```
72
+
73
+ > Use this trick to define custom objects inside `boostrap` script
74
+
75
+ The container also contains the current Mocha instance:
76
+
77
+ ```js
78
+ const mocha = container.mocha();
79
+ ```
80
+
81
+ ### Event Listeners
82
+
83
+ CodeceptJS provides a module with an [event dispatcher and set of predefined events](https://github.com/Codeception/CodeceptJS/blob/master/lib/event.js).
84
+
85
+ It can be required from codeceptjs package if it is installed locally.
86
+
87
+ ```js
88
+ const { event } = require('codeceptjs');
89
+
90
+ module.exports = function() {
91
+
92
+ event.dispatcher.on(event.test.before, function (test) {
93
+
94
+ console.log('--- I am before test --');
95
+
96
+ });
97
+ }
98
+ ```
99
+
100
+ Available events:
101
+
102
+ * `event.test.before(test)` - *async* when `Before` hooks from helpers and from test is executed
103
+ * `event.test.after(test)` - *async* after each test
104
+ * `event.test.started(test)` - *sync* at the very beginning of a test.
105
+ * `event.test.passed(test)` - *sync* when test passed
106
+ * `event.test.failed(test, error)` - *sync* when test failed
107
+ * `event.test.finished(test)` - *sync* when test finished
108
+ * `event.suite.before(suite)` - *async* before a suite
109
+ * `event.suite.after(suite)` - *async* after a suite
110
+ * `event.step.before(step)` - *async* when the step is scheduled for execution
111
+ * `event.step.after(step)`- *async* after a step
112
+ * `event.step.started(step)` - *sync* when step starts.
113
+ * `event.step.passed(step)` - *sync* when step passed.
114
+ * `event.step.failed(step, err)` - *sync* when step failed.
115
+ * `event.step.finished(step)` - *sync* when step finishes.
116
+ * `event.step.comment(step)` - *sync* fired for comments like `I.say`.
117
+ * `event.all.before` - before running tests
118
+ * `event.all.after` - after running tests
119
+ * `event.all.result` - when results are printed
120
+ * `event.workers.before` - before spawning workers in parallel run
121
+ * `event.workers.after` - after workers finished in parallel run
122
+
123
+
124
+ > *sync* - means that event is fired in the moment of the action happening.
125
+ *async* - means that event is fired when an action is scheduled. Use `recorder` to schedule your actions.
126
+
127
+ For further reference look for [currently available listeners](https://github.com/Codeception/CodeceptJS/tree/master/lib/listener) using the event system.
128
+
129
+
130
+ ### Recorder
131
+
132
+ To inject asynchronous functions in a test or before/after a test you can subscribe to corresponding event and register a function inside a recorder object. [Recorder](https://github.com/Codeception/CodeceptJS/blob/master/lib/recorder.js) represents a global promises chain.
133
+
134
+ Provide a function in the first parameter, a function must be async or must return a promise:
135
+
136
+ ```js
137
+ const { event, recorder } = require('codeceptjs');
138
+
139
+ module.exports = function() {
140
+
141
+ event.dispatcher.on(event.test.before, function (test) {
142
+
143
+ const request = require('request');
144
+
145
+ recorder.add('create fixture data via API', function() {
146
+ return new Promise((doneFn, errFn) => {
147
+ request({
148
+ baseUrl: 'http://api.site.com/',
149
+ method: 'POST',
150
+ url: '/users',
151
+ json: { name: 'john', email: 'john@john.com' }
152
+ }), (err, httpResponse, body) => {
153
+ if (err) return errFn(err);
154
+ doneFn();
155
+ }
156
+ });
157
+ }
158
+ });
159
+ }
160
+ ```
161
+
162
+ ### Config
163
+
164
+ CodeceptJS config can be accessed from `require('codeceptjs').config.get()`:
165
+
166
+ ```js
167
+ const { config } = require('codeceptjs');
168
+
169
+ // config object has access to all values of the current config file
170
+
171
+ if (config.get().myKey == 'value') {
172
+ // run something
173
+ }
174
+ ```
175
+
176
+
177
+ ### Output
178
+
179
+ Output module provides four verbosity levels. Depending on the mode you can have different information printed using corresponding functions.
180
+
181
+ * `default`: prints basic information using `output.print`
182
+ * `steps`: toggled by `--steps` option, prints step execution
183
+ * `debug`: toggled by `--debug` option, prints steps, and debug information with `output.debug`
184
+ * `verbose`: toggled by `--verbose` prints debug information and internal logs with `output.log`
185
+
186
+ It is recommended to avoid `console.log` and use output.* methods for printing.
187
+
188
+ ```js
189
+ const output = require('codeceptjs').output;
190
+
191
+ output.print('This is basic information');
192
+ output.debug('This is debug information');
193
+ output.log('This is verbose logging information');
194
+ ```
195
+
196
+ #### Test Object
197
+
198
+ The test events are providing a test object with following properties:
199
+
200
+ * `title` title of the test
201
+ * `body` test function as a string
202
+ * `opts` additional test options like retries, and others
203
+ * `pending` true if test is scheduled for execution and false if a test has finished
204
+ * `tags` array of tags for this test
205
+ * `artifacts` list of files attached to this test. Screenshots, videos and other files can be saved here and shared accross different reporters
206
+ * `file` path to a file with a test
207
+ * `steps` array of executed steps (available only in `test.passed`, `test.failed`, `test.finished` event)
208
+ * `skipInfo` additional test options when test skipped
209
+ * * `message` string with reason for skip
210
+ * * `description` string with test body
211
+ and others
212
+
213
+ #### Step Object
214
+
215
+ Step events provide step objects with following fields:
216
+
217
+ * `name` name of a step, like 'see', 'click', and others
218
+ * `actor` current actor, in most cases it is `I`
219
+ * `helper` current helper instance used to execute this step
220
+ * `helperMethod` corresponding helper method, in most cases is the same as `name`
221
+ * `status` status of a step (passed or failed)
222
+ * `prefix` if a step is executed inside `within` block contain within text, like: 'Within .js-signup-form'.
223
+ * `args` passed arguments
224
+
225
+ Whenever you execute tests with `--verbose` option you will see registered events and promises executed by a recorder.
226
+
227
+ ## Custom Runner
228
+
229
+ You can run CodeceptJS tests from your script.
230
+
231
+ ```js
232
+ const { codecept: Codecept } = require('codeceptjs');
233
+
234
+ // define main config
235
+ const config = {
236
+ helpers: {
237
+ WebDriver: {
238
+ browser: 'chrome',
239
+ url: 'http://localhost'
240
+ }
241
+ }
242
+ };
243
+
244
+ const opts = { steps: true };
245
+
246
+ // run CodeceptJS inside async function
247
+ (async () => {
248
+ const codecept = new Codecept(config, options);
249
+ codecept.init(__dirname);
250
+
251
+ try {
252
+ await codecept.bootstrap();
253
+ codecept.loadTests('**_test.js');
254
+ // run tests
255
+ await codecept.run(test);
256
+ } catch (err) {
257
+ printError(err);
258
+ process.exitCode = 1;
259
+ } finally {
260
+ await codecept.teardown();
261
+ }
262
+ })();
263
+ ```
264
+
265
+ > Also, you can run tests inside workers in a custom scripts. Please refer to the [parallel execution](/parallel) guide for more details.
@@ -175,8 +175,10 @@ If you need to get element's value inside a test you can use `grab*` methods. Th
175
175
  ```js
176
176
  const assert = require('assert');
177
177
  Scenario('get value of current tasks', async ({ I }) => {
178
- I.createTodo('do 1');
179
- I.createTodo('do 2');
178
+ I.fillField('.todo', 'my first item');
179
+ I.pressKey('Enter')
180
+ I.fillField('.todo', 'my second item');
181
+ I.pressKey('Enter')
180
182
  let numTodos = await I.grabTextFrom('.todo-count strong');
181
183
  assert.equal(2, numTodos);
182
184
  });
@@ -188,17 +190,35 @@ In case some actions should be taken inside one element (a container or modal wi
188
190
  Please take a note that you can't use within inside another within in Playwright helper:
189
191
 
190
192
  ```js
191
- within('.todoapp', () => {
192
- I.createTodo('my new item');
193
+ await within('.todoapp', () => {
194
+ I.fillField('.todo', 'my new item');
195
+ I.pressKey('Enter')
193
196
  I.see('1 item left', '.todo-count');
194
197
  I.click('.todo-list input.toggle');
195
198
  });
196
199
  I.see('0 items left', '.todo-count');
197
200
  ```
198
201
 
199
- > [▶ Learn more about basic commands](/basics#writing-tests)
202
+ ### Each Element <Badge text="Since 3.3" type="warning"/>
200
203
 
201
- CodeceptJS allows you to implement custom actions like `I.createTodo` or use **PageObjects**. Learn how to improve your tests in [PageObjects](https://codecept.io/pageobjects/) guide.
204
+ Usually, CodeceptJS performs an action on the first matched element.
205
+ In case you want to do an action on each element found, use the special function `eachElement` which comes from [eachElement](https://codecept.io/plugins/#eachelement) plugin.
206
+
207
+ `eachElement` function matches all elements by locator and performs a callback on each of those element. A callback function receives [ElementHandle instance](https://playwright.dev/docs/api/class-elementhandle) from Playwright API. `eachElement` may perform arbitrary actions on a page, so the first argument should by a description of the actions performed. This description will be used for logging purposes.
208
+
209
+ Usage example
210
+
211
+ ```js
212
+ await eachElement(
213
+ 'tick all checkboxes',
214
+ 'input.custom-checkbox',
215
+ async (el, index) => {
216
+ await el.check();
217
+ });
218
+ );
219
+ ```
220
+
221
+ > ℹ Learn more about [eachElement plugin](/plugins/#eachelement)
202
222
 
203
223
  ## Multi Session Testing
204
224
 
@@ -307,19 +327,40 @@ Scenario('website looks nice on iPhone', () => {
307
327
  });
308
328
  ```
309
329
 
310
- ## Configuring CI
330
+ ## API Requests
311
331
 
312
- ### GitHub Actions
332
+ CodeceptJS has [REST](/helpers/REST) and [GraphQL]((/helpers/GraphQL)) helpers to perform requests to external APIs. This may be helpful to implement [data management](https://codecept.io/data/) strategy.
313
333
 
314
- Playwright can be added to GitHub Actions using [official action](https://github.com/microsoft/playwright-github-action). Use it before starting CodeceptJS tests to install all dependencies. It is important to run tests in headless mode ([otherwise you will need to enable xvfb to emulate desktop](https://github.com/microsoft/playwright-github-action#run-in-headful-mode)).
334
+ However, Playwright since 1.18 has its own [API for making request](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get). It uses cookies from browser session to authenticate requests. So you can use it via [`makeApiRequest`](/helpers/Playwright#makeApiRequest) method:
315
335
 
316
- ```yml
317
- # from workflows/tests.yml
318
- - uses: microsoft/playwright-github-action@v1
319
- - name: run CodeceptJS tests
320
- run: npx codeceptjs run
336
+ ```js
337
+ I.makeApiRequest('GET', '/users')
321
338
  ```
322
339
 
340
+ It is also possible to test JSON responses by adding [`JSONResponse`](/helpers/JSONResponse) and connecting it to Playwright:
341
+
342
+ ```js
343
+ // inside codecept.conf.js
344
+ {
345
+ helpers: {
346
+ Playwright: {
347
+ // current config
348
+ },
349
+ JSONResponse: {
350
+ requestHelper: 'Playwright',
351
+ }
352
+ }
353
+ }
354
+ ```
355
+ This helper provides you methods for [API testing](/api). For instance, you can check for status code, data inclusion and structure:
356
+
357
+ ```js
358
+ I.makeApiRequest('GET', '/users/1');
359
+ I.seeResponseCodeIs(200);
360
+ I.seeResponseContainsKeys(['user']);
361
+ ```
362
+ This way you can do full fledged API testing via Playwright.
363
+
323
364
  ## Accessing Playwright API
324
365
 
325
366
  To get [Playwright API](https://playwright.dev/docs/api/class-playwright) inside a test use `I.usePlaywrightTo` method with a callback.
@@ -350,7 +391,6 @@ I.usePlaywrightTo('emulate offline mode', async (Playwright) => {
350
391
  // call a method of helper, await is required here
351
392
  await Playwright.click('Reload');
352
393
  });
353
-
354
394
  ```
355
395
 
356
396
  ## Mocking Network Requests <Badge text="Since 3.1" type="warning"/>
@@ -496,6 +536,7 @@ Open `index.html` in your browser to view the full interactive coverage report.
496
536
  ![](https://user-images.githubusercontent.com/16587779/131858993-87d1aafc-8ef1-4a82-867d-e64a13e36106.png)
497
537
 
498
538
  ![](https://user-images.githubusercontent.com/16587779/131859006-c6f17d18-c603-44a5-9d59-0670177276cf.png)
539
+
499
540
  ## Extending Helper
500
541
 
501
542
  To create custom `I.*` commands using Playwright API you need to create a custom helper.
@@ -538,3 +579,17 @@ async setPermissions() {
538
579
  > [▶ Learn more about BrowserContext](https://github.com/microsoft/playwright/blob/master/docs/src/api/class-browsercontext.md)
539
580
 
540
581
  > [▶ Learn more about Helpers](https://codecept.io/helpers/)
582
+
583
+
584
+ ## Configuring CI
585
+
586
+ ### GitHub Actions
587
+
588
+ Playwright can be added to GitHub Actions using [official action](https://github.com/microsoft/playwright-github-action). Use it before starting CodeceptJS tests to install all dependencies. It is important to run tests in headless mode ([otherwise you will need to enable xvfb to emulate desktop](https://github.com/microsoft/playwright-github-action#run-in-headful-mode)).
589
+
590
+ ```yml
591
+ # from workflows/tests.yml
592
+ - uses: microsoft/playwright-github-action@v1
593
+ - name: run CodeceptJS tests
594
+ run: npx codeceptjs run
595
+ ```