codeceptjs 3.3.2 → 3.3.5-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 (77) hide show
  1. package/CHANGELOG.md +44 -2
  2. package/docs/api.md +4 -0
  3. package/docs/basics.md +2 -0
  4. package/docs/bdd.md +12 -0
  5. package/docs/build/JSONResponse.js +44 -3
  6. package/docs/build/Playwright.js +63 -40
  7. package/docs/build/Puppeteer.js +54 -43
  8. package/docs/build/REST.js +23 -9
  9. package/docs/build/WebDriver.js +39 -30
  10. package/docs/changelog.md +6 -2
  11. package/docs/community-helpers.md +1 -0
  12. package/docs/configuration.md +21 -18
  13. package/docs/helpers/Appium.md +0 -723
  14. package/docs/helpers/JSONResponse.md +24 -0
  15. package/docs/helpers/Playwright.md +276 -264
  16. package/docs/helpers/Puppeteer.md +230 -222
  17. package/docs/helpers/REST.md +21 -6
  18. package/docs/helpers/WebDriver.md +265 -259
  19. package/docs/plugins.md +41 -1
  20. package/docs/reports.md +11 -0
  21. package/docs/secrets.md +30 -0
  22. package/docs/wiki/.git/FETCH_HEAD +1 -0
  23. package/docs/wiki/.git/HEAD +1 -0
  24. package/docs/wiki/.git/ORIG_HEAD +1 -0
  25. package/docs/wiki/.git/config +11 -0
  26. package/docs/wiki/.git/description +1 -0
  27. package/docs/wiki/.git/hooks/applypatch-msg.sample +15 -0
  28. package/docs/wiki/.git/hooks/commit-msg.sample +24 -0
  29. package/docs/wiki/.git/hooks/fsmonitor-watchman.sample +173 -0
  30. package/docs/wiki/.git/hooks/post-update.sample +8 -0
  31. package/docs/wiki/.git/hooks/pre-applypatch.sample +14 -0
  32. package/docs/wiki/.git/hooks/pre-commit.sample +49 -0
  33. package/docs/wiki/.git/hooks/pre-merge-commit.sample +13 -0
  34. package/docs/wiki/.git/hooks/pre-push.sample +53 -0
  35. package/docs/wiki/.git/hooks/pre-rebase.sample +169 -0
  36. package/docs/wiki/.git/hooks/pre-receive.sample +24 -0
  37. package/docs/wiki/.git/hooks/prepare-commit-msg.sample +42 -0
  38. package/docs/wiki/.git/hooks/push-to-checkout.sample +78 -0
  39. package/docs/wiki/.git/hooks/update.sample +128 -0
  40. package/docs/wiki/.git/index +0 -0
  41. package/docs/wiki/.git/info/exclude +6 -0
  42. package/docs/wiki/.git/logs/HEAD +1 -0
  43. package/docs/wiki/.git/logs/refs/heads/master +1 -0
  44. package/docs/wiki/.git/logs/refs/remotes/origin/HEAD +1 -0
  45. package/docs/wiki/.git/objects/pack/pack-5938044f9d30daf1c195fda4dec1d54850933935.idx +0 -0
  46. package/docs/wiki/.git/objects/pack/pack-5938044f9d30daf1c195fda4dec1d54850933935.pack +0 -0
  47. package/docs/wiki/.git/packed-refs +2 -0
  48. package/docs/wiki/.git/refs/heads/master +1 -0
  49. package/docs/wiki/.git/refs/remotes/origin/HEAD +1 -0
  50. package/docs/wiki/Community-Helpers-&-Plugins.md +7 -3
  51. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +29 -0
  52. package/docs/wiki/Examples.md +39 -48
  53. package/docs/wiki/Release-Process.md +8 -8
  54. package/docs/wiki/Tests.md +62 -60
  55. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +2 -2
  56. package/lib/cli.js +1 -1
  57. package/lib/command/generate.js +3 -0
  58. package/lib/command/init.js +83 -24
  59. package/lib/command/interactive.js +1 -1
  60. package/lib/command/run-workers.js +1 -1
  61. package/lib/command/workers/runTests.js +15 -0
  62. package/lib/helper/JSONResponse.js +44 -3
  63. package/lib/helper/Playwright.js +63 -40
  64. package/lib/helper/Puppeteer.js +54 -43
  65. package/lib/helper/REST.js +23 -9
  66. package/lib/helper/WebDriver.js +39 -30
  67. package/lib/interfaces/gherkin.js +1 -1
  68. package/lib/output.js +4 -0
  69. package/lib/plugin/customLocator.js +50 -3
  70. package/lib/plugin/retryFailedStep.js +1 -1
  71. package/lib/plugin/retryTo.js +1 -8
  72. package/lib/secret.js +31 -1
  73. package/lib/step.js +22 -10
  74. package/lib/utils.js +1 -6
  75. package/package.json +4 -4
  76. package/typings/index.d.ts +158 -0
  77. package/typings/types.d.ts +367 -96
@@ -1,7 +1,7 @@
1
- 🚀 CodeceptJS 3 is in beta now. Install it:
1
+ 🚀 CodeceptJS 3 is out now. Install it:
2
2
 
3
3
  ```
4
- npm i codeceptjs@3.0.0-beta.3
4
+ npm i codeceptjs
5
5
  ```
6
6
 
7
7
  * [COMPLETE CHANGELOG](https://github.com/Codeception/CodeceptJS/blob/codeceptjs-v3.0/CHANGELOG.md#300-beta)
package/lib/cli.js CHANGED
@@ -19,7 +19,7 @@ class Cli extends Base {
19
19
  if (opts.debug) level = 2;
20
20
  if (opts.verbose) level = 3;
21
21
  output.level(level);
22
- output.print(`CodeceptJS v${require('./codecept').version()}`);
22
+ output.print(`CodeceptJS v${require('./codecept').version()} ${output.standWithUkraine()}`);
23
23
  output.print(`Using test root "${global.codecept_dir}"`);
24
24
 
25
25
  const showSteps = level >= 1;
@@ -33,6 +33,7 @@ module.exports.test = function (genPath) {
33
33
  type: 'input',
34
34
  name: 'feature',
35
35
  message: 'Feature which is being tested (ex: account, login, etc)',
36
+ validate: (val) => !!val,
36
37
  },
37
38
  {
38
39
  type: 'input',
@@ -85,6 +86,7 @@ module.exports.pageObject = function (genPath, opts) {
85
86
  type: 'input',
86
87
  name: 'name',
87
88
  message: `Name of a ${kind} object`,
89
+ validate: (val) => !!val,
88
90
  }, {
89
91
  type: 'input',
90
92
  name: 'filename',
@@ -156,6 +158,7 @@ module.exports.helper = function (genPath) {
156
158
  type: 'input',
157
159
  name: 'name',
158
160
  message: 'Name of a Helper',
161
+ validate: (val) => !!val,
159
162
  }, {
160
163
  type: 'input',
161
164
  name: 'filename',
@@ -18,8 +18,6 @@ const defaultConfig = {
18
18
  output: '',
19
19
  helpers: {},
20
20
  include: {},
21
- bootstrap: null,
22
- mocha: {},
23
21
  };
24
22
 
25
23
  const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'REST', 'GraphQL', 'Appium', 'TestCafe', 'Nightmare'];
@@ -28,10 +26,13 @@ const translations = Object.keys(require('../../translations'));
28
26
  const noTranslation = 'English (no localization)';
29
27
  translations.unshift(noTranslation);
30
28
 
31
- let packages;
29
+ const packages = [];
30
+ let isTypeScript = false;
31
+ let extension = 'js';
32
32
 
33
33
  const configHeader = `const { setHeadlessWhen, setCommonPlugins } = require('@codeceptjs/configure');
34
34
 
35
+
35
36
  // turn on headless mode when running with HEADLESS=true environment variable
36
37
  // export HEADLESS=true && npx codeceptjs run
37
38
  setHeadlessWhen(process.env.HEADLESS);
@@ -53,6 +54,10 @@ module.exports = function() {
53
54
  }
54
55
  `;
55
56
 
57
+ const tsNodeRequired = `require('ts-node/register');
58
+
59
+ `;
60
+
56
61
  module.exports = function (initPath) {
57
62
  const testsPath = getTestRoot(initPath);
58
63
 
@@ -80,11 +85,23 @@ module.exports = function (initPath) {
80
85
  return;
81
86
  }
82
87
 
88
+ const typeScriptconfigFile = path.join(testsPath, 'codecept.conf.ts');
89
+ if (fileExists(typeScriptconfigFile)) {
90
+ error(`Config is already created at ${typeScriptconfigFile}`);
91
+ return;
92
+ }
93
+
83
94
  inquirer.prompt([
95
+ {
96
+ name: 'typescript',
97
+ type: 'confirm',
98
+ default: false,
99
+ message: 'Do you plan to write tests in TypeScript?',
100
+ },
84
101
  {
85
102
  name: 'tests',
86
103
  type: 'input',
87
- default: './*_test.js',
104
+ default: (answers) => `./*_test.${answers.typescript ? 'ts' : 'js'}`,
88
105
  message: 'Where are your tests located?',
89
106
  },
90
107
  {
@@ -105,23 +122,29 @@ module.exports = function (initPath) {
105
122
  choices: translations,
106
123
  },
107
124
  ]).then((result) => {
125
+ if (result.typescript === true) {
126
+ isTypeScript = true;
127
+ extension = isTypeScript === true ? 'ts' : 'js';
128
+ packages.push('typescript');
129
+ packages.push('ts-node');
130
+ packages.push('@types/node');
131
+ }
132
+
108
133
  const config = defaultConfig;
109
134
  config.name = testsPath.split(path.sep).pop();
110
135
  config.output = result.output;
111
136
 
112
137
  config.tests = result.tests;
138
+ if (isTypeScript) {
139
+ config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}`;
140
+ }
141
+
113
142
  // create a directory tests if it is included in tests path
114
143
  const matchResults = config.tests.match(/[^*.]+/);
115
144
  if (matchResults) {
116
145
  mkdirp.sync(path.join(testsPath, matchResults[0]));
117
146
  }
118
147
 
119
- // append file mask to the end of tests
120
- if (!config.tests.match(/\*(.*?)$/)) {
121
- config.tests = `${config.tests.replace(/\/+$/, '')}/*_test.js`;
122
- print(`Adding default test mask: ${config.tests}`);
123
- }
124
-
125
148
  if (result.translation !== noTranslation) config.translation = result.translation;
126
149
 
127
150
  const helperName = result.helper;
@@ -132,7 +155,7 @@ module.exports = function (initPath) {
132
155
  try {
133
156
  const Helper = require(`../helper/${helperName}`);
134
157
  if (Helper._checkRequirements) {
135
- packages = Helper._checkRequirements();
158
+ packages.concat(Helper._checkRequirements());
136
159
  }
137
160
 
138
161
  if (!Helper._config()) return;
@@ -148,20 +171,28 @@ module.exports = function (initPath) {
148
171
 
149
172
  const finish = async () => {
150
173
  // create steps file by default
151
- const stepFile = './steps_file.js';
152
- fs.writeFileSync(path.join(testsPath, stepFile), defaultActor);
153
- config.include.I = stepFile;
154
- print(`Steps file created at ${stepFile}`);
174
+ if (!isTypeScript) { // no extra step file for typescript (as it doesn't match TS conventions)
175
+ const stepFile = `./steps_file.${extension}`;
176
+ fs.writeFileSync(path.join(testsPath, stepFile), defaultActor);
177
+ config.include.I = stepFile;
178
+ print(`Steps file created at ${stepFile}`);
179
+ }
155
180
 
156
- let configSource = beautify(`exports.config = ${inspect(config, false, 4, false)}`);
181
+ let configSource = beautify(`/** @type {CodeceptJS.MainConfig} */\nexports.config = ${inspect(config, false, 4, false)}`);
157
182
 
158
183
  if (require.resolve('@codeceptjs/configure') && isLocal && !initPath) {
159
184
  // prepend @codeceptjs/configure only when this module can be required in config
160
185
  configSource = configHeader + configSource;
161
186
  }
162
187
 
163
- fs.writeFileSync(configFile, configSource, 'utf-8');
164
- print(`Config created at ${configFile}`);
188
+ if (isTypeScript) {
189
+ configSource = `${tsNodeRequired}\n${configSource}`;
190
+ fs.writeFileSync(typeScriptconfigFile, configSource, 'utf-8');
191
+ print(`Config created at ${typeScriptconfigFile}`);
192
+ } else {
193
+ fs.writeFileSync(configFile, configSource, 'utf-8');
194
+ print(`Config created at ${configFile}`);
195
+ }
165
196
 
166
197
  if (config.output) {
167
198
  if (!fileExists(config.output)) {
@@ -177,13 +208,41 @@ module.exports = function (initPath) {
177
208
  allowJs: true,
178
209
  },
179
210
  };
180
- const jsconfigJson = beautify(JSON.stringify(jsconfig));
181
- const jsconfigFile = path.join(testsPath, 'jsconfig.json');
182
- if (fileExists(jsconfigFile)) {
183
- print(`jsconfig.json already exists at ${jsconfigFile}`);
211
+
212
+ const tsconfig = {
213
+ 'ts-node': {
214
+ files: true,
215
+ },
216
+ compilerOptions: {
217
+ target: 'es2018',
218
+ lib: ['es2018', 'DOM'],
219
+ esModuleInterop: true,
220
+ module: 'commonjs',
221
+ strictNullChecks: false,
222
+ types: ['codeceptjs', 'node'],
223
+ declaration: true,
224
+ skipLibCheck: true,
225
+ },
226
+ exclude: ['node_modules'],
227
+ };
228
+
229
+ if (isTypeScript) {
230
+ const tsconfigJson = beautify(JSON.stringify(tsconfig));
231
+ const tsconfigFile = path.join(testsPath, 'tsconfig.json');
232
+ if (fileExists(tsconfigFile)) {
233
+ print(`tsconfig.json already exists at ${tsconfigFile}`);
234
+ } else {
235
+ fs.writeFileSync(tsconfigFile, tsconfigJson);
236
+ }
184
237
  } else {
185
- fs.writeFileSync(jsconfigFile, jsconfigJson);
186
- print(`Intellisense enabled in ${jsconfigFile}`);
238
+ const jsconfigJson = beautify(JSON.stringify(jsconfig));
239
+ const jsconfigFile = path.join(testsPath, 'jsconfig.json');
240
+ if (fileExists(jsconfigFile)) {
241
+ print(`jsconfig.json already exists at ${jsconfigFile}`);
242
+ } else {
243
+ fs.writeFileSync(jsconfigFile, jsconfigJson);
244
+ print(`Intellisense enabled in ${jsconfigFile}`);
245
+ }
187
246
  }
188
247
 
189
248
  const generateDefinitionsManually = colors.bold(`To get auto-completion support, please generate type definitions: ${colors.green('npx codeceptjs def')}`);
@@ -21,7 +21,7 @@ module.exports = async function (path, options) {
21
21
 
22
22
  if (options.verbose) output.level(3);
23
23
 
24
- output.print('String interactive shell for current suite...');
24
+ output.print('Starting interactive shell for current suite...');
25
25
  recorder.start();
26
26
  event.emit(event.suite.before, {
27
27
  fullTitle: () => 'Interactive Shell',
@@ -25,7 +25,7 @@ module.exports = async function (workerCount, options) {
25
25
 
26
26
  const numberOfWorkers = parseInt(workerCount, 10);
27
27
 
28
- output.print(`CodeceptJS v${require('../codecept').version()}`);
28
+ output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`);
29
29
  output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`);
30
30
  output.print();
31
31
 
@@ -114,6 +114,13 @@ function initializeListeners() {
114
114
  parent.title = test.parent.title;
115
115
  }
116
116
 
117
+ if (test.opts) {
118
+ Object.keys(test.opts).forEach(k => {
119
+ if (typeof test.opts[k] === 'object') delete test.opts[k];
120
+ if (typeof test.opts[k] === 'function') delete test.opts[k];
121
+ });
122
+ }
123
+
117
124
  return {
118
125
  opts: test.opts || {},
119
126
  tags: test.tags || [],
@@ -148,6 +155,14 @@ function initializeListeners() {
148
155
  if (step.metaStep) {
149
156
  parent.title = step.metaStep.actor;
150
157
  }
158
+
159
+ if (step.opts) {
160
+ Object.keys(step.opts).forEach(k => {
161
+ if (typeof step.opts[k] === 'object') delete step.opts[k];
162
+ if (typeof step.opts[k] === 'function') delete step.opts[k];
163
+ });
164
+ }
165
+
151
166
  return {
152
167
  opts: step.opts || {},
153
168
  workerIndex,
@@ -1,3 +1,4 @@
1
+ const assert = require('assert');
1
2
  const chai = require('chai');
2
3
  const joi = require('joi');
3
4
  const chaiDeepMatch = require('chai-deep-match');
@@ -173,12 +174,30 @@ class JSONResponse extends Helper {
173
174
  *
174
175
  * I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
175
176
  * ```
177
+ * If an array is received, checks that at least one element contains JSON
178
+ * ```js
179
+ * // response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }]
180
+ *
181
+ * I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
182
+ * ```
176
183
  *
177
184
  * @param {object} json
178
185
  */
179
186
  seeResponseContainsJson(json = {}) {
180
187
  this._checkResponseReady();
181
- expect(this.response.data).to.deep.match(json);
188
+ if (Array.isArray(this.response.data)) {
189
+ let fails = 0;
190
+ for (const el of this.response.data) {
191
+ try {
192
+ expect(el).to.deep.match(json);
193
+ } catch (err) {
194
+ fails++;
195
+ }
196
+ }
197
+ assert.ok(fails < this.response.data.length, `No elements in array matched ${JSON.stringify(json)}`);
198
+ } else {
199
+ expect(this.response.data).to.deep.match(json);
200
+ }
182
201
  }
183
202
 
184
203
  /**
@@ -189,12 +208,22 @@ class JSONResponse extends Helper {
189
208
  *
190
209
  * I.dontSeeResponseContainsJson({ user: 2 });
191
210
  * ```
211
+ * If an array is received, checks that no element of array contains json:
212
+ * ```js
213
+ * // response.data == [{ user: 1 }, { user: 3 }]
214
+ *
215
+ * I.dontSeeResponseContainsJson({ user: 2 });
216
+ * ```
192
217
  *
193
218
  * @param {object} json
194
219
  */
195
220
  dontSeeResponseContainsJson(json = {}) {
196
221
  this._checkResponseReady();
197
- expect(this.response.data).not.to.deep.match(json);
222
+ if (Array.isArray(this.response.data)) {
223
+ this.response.data.forEach(data => expect(data).not.to.deep.match(json));
224
+ } else {
225
+ expect(this.response.data).not.to.deep.match(json);
226
+ }
198
227
  }
199
228
 
200
229
  /**
@@ -206,11 +235,23 @@ class JSONResponse extends Helper {
206
235
  * I.seeResponseContainsKeys(['user']);
207
236
  * ```
208
237
  *
238
+ * If an array is received, check is performed for each element of array:
239
+ *
240
+ * ```js
241
+ * // response.data == [{ user: 'jon' }, { user: 'matt'}]
242
+ *
243
+ * I.seeResponseContainsKeys(['user']);
244
+ * ```
245
+ *
209
246
  * @param {array} keys
210
247
  */
211
248
  seeResponseContainsKeys(keys = []) {
212
249
  this._checkResponseReady();
213
- expect(this.response.data).to.include.keys(keys);
250
+ if (Array.isArray(this.response.data)) {
251
+ this.response.data.forEach(data => expect(data).to.include.keys(keys));
252
+ } else {
253
+ expect(this.response.data).to.include.keys(keys);
254
+ }
214
255
  }
215
256
 
216
257
  /**
@@ -44,6 +44,46 @@ const {
44
44
  } = require('./extras/PlaywrightRestartOpts');
45
45
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
46
46
 
47
+ /**
48
+ * ## Configuration
49
+ *
50
+ * This helper should be configured in codecept.conf.js
51
+ *
52
+ * @typedef PlaywrightConfig
53
+ * @type {object}
54
+ * @prop {string} url - base url of website to be tested
55
+ * @prop {string} browser - a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
56
+ * @prop {boolean} [show=false] - show browser window.
57
+ * @prop {string|boolean} [restart=false] - restart strategy between tests. Possible values:
58
+ * * '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.
59
+ * * 'browser' or **true** - closes browser and opens it again between tests.
60
+ * * 'session' or 'keep' - keeps browser context and session, but cleans up cookies and localStorage between tests. The fastest option when running tests in windowed mode. Works with `keepCookies` and `keepBrowserState` options. This behavior was default before CodeceptJS 3.1
61
+ * @prop {number} [timeout=1000] - - [timeout](https://playwright.dev/docs/api/class-page#page-set-default-timeout) in ms of all Playwright actions .
62
+ * @prop {boolean} [disableScreenshots=false] - don't save screenshot on failure.
63
+ * @prop {any} [emulate] - browser in device emulation mode.
64
+ * @prop {boolean} [video=false] - enables video recording for failed tests; videos are saved into `output/videos` folder
65
+ * @prop {boolean} [trace=false] - record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
66
+ * @prop {boolean} [fullPageScreenshots=false] - make full page screenshots on failure.
67
+ * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
68
+ * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to 'session'.
69
+ * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to 'session'.
70
+ * @prop {number} [waitForAction] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
71
+ * @prop {number} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/main/docs/api.md#pagewaitfornavigationoptions).
72
+ * @prop {number} [pressKeyDelay=10] - Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
73
+ * @prop {number} [getPageTimeout] - config option to set maximum navigation time in milliseconds.
74
+ * @prop {number} [waitForTimeout] - default wait* timeout in ms. Default: 1000.
75
+ * @prop {object} [basicAuth] - the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
76
+ * @prop {string} [windowSize] - default window size. Set a dimension like `640x480`.
77
+ * @prop {string} [colorScheme] - default color scheme. Possible values: `dark` | `light` | `no-preference`.
78
+ * @prop {string} [userAgent] - user-agent string.
79
+ * @prop {string} [locale] - locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
80
+ * @prop {boolean} [manualStart] - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
81
+ * @prop {object} [chromium] - pass additional chromium options
82
+ * @prop {object} [electron] - (pass additional electron options
83
+ * @prop {any} [channel] - (While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge](https://playwright.dev/docs/browsers/#google-chrome--microsoft-edge).
84
+ */
85
+ const config = {};
86
+
47
87
  /**
48
88
  * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
49
89
  *
@@ -65,46 +105,14 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
65
105
  *
66
106
  * Using playwright-core package, will prevent the download of browser binaries and allow connecting to an existing browser installation or for connecting to a remote one.
67
107
  *
68
- * ## Configuration
69
108
  *
70
- * This helper should be configured in codecept.json or codecept.conf.js
71
- *
72
- * * `url`: base url of website to be tested
73
- * * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
74
- * * `show`: (optional, default: false) - show browser window.
75
- * * `restart`: (optional, default: false) - restart strategy between tests. Possible values:
76
- * * '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.
77
- * * 'browser' or **true** - closes browser and opens it again between tests.
78
- * * 'session' or 'keep' - keeps browser context and session, but cleans up cookies and localStorage between tests. The fastest option when running tests in windowed mode. Works with `keepCookies` and `keepBrowserState` options. This behavior was default before CodeceptJS 3.1
79
- * * `timeout`: (optional, default: 1000) - [timeout](https://playwright.dev/docs/api/class-page#page-set-default-timeout) in ms of all Playwright actions .
80
- * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
81
- * * `emulate`: (optional, default: {}) launch browser in device emulation mode.
82
- * * `video`: (optional, default: false) enables video recording for failed tests; videos are saved into `output/videos` folder
83
- * * `trace`: (optional, default: false) record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
84
- * * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
85
- * * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
86
- * * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to 'session'.
87
- * * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to 'session'.
88
- * * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
89
- * * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/main/docs/api.md#pagewaitfornavigationoptions).
90
- * * `pressKeyDelay`: (optional, default: '10'). Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
91
- * * `getPageTimeout` (optional, default: '0') config option to set maximum navigation time in milliseconds.
92
- * * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
93
- * * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
94
- * * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
95
- * * `colorScheme`: (optional) default color scheme. Possible values: `dark` | `light` | `no-preference`.
96
- * * `userAgent`: (optional) user-agent string.
97
- * * `locale`: (optional) locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
98
- * * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
99
- * * `chromium`: (optional) pass additional chromium options
100
- * * `electron`: (optional) pass additional electron options
101
- * * `channel`: (optional) While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge](https://playwright.dev/docs/browsers/#google-chrome--microsoft-edge).
109
+ * <!-- configuration -->
102
110
  *
103
111
  * #### Video Recording Customization
104
112
  *
105
113
  * By default, video is saved to `output/video` dir. You can customize this path by passing `dir` option to `recordVideo` option.
106
114
  *
107
- * * `video`: enables video recording for failed tests; videos are saved into `output/videos` folder
115
+ * `video`: enables video recording for failed tests; videos are saved into `output/videos` folder
108
116
  * * `keepVideoForPassedTests`: - save videos for passed tests
109
117
  * * `recordVideo`: [additional options for videos customization](https://playwright.dev/docs/next/api/class-browser#browser-new-context)
110
118
  *
@@ -167,7 +175,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
167
175
  * Playwright: {
168
176
  * url: "http://localhost",
169
177
  * chromium: {
170
- * browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
178
+ * browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a',
179
+ * cdpConnection: false // default is false
171
180
  * }
172
181
  * }
173
182
  * }
@@ -256,8 +265,6 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
256
265
  * const { browserContext } = this.helpers.Playwright;
257
266
  * await browserContext.cookies(); // get current browser context
258
267
  * ```
259
- *
260
- * ## Methods
261
268
  */
262
269
  class Playwright extends Helper {
263
270
  constructor(config) {
@@ -272,6 +279,7 @@ class Playwright extends Helper {
272
279
  this.sessionPages = {};
273
280
  this.activeSessionName = '';
274
281
  this.isElectron = false;
282
+ this.isCDPConnection = false;
275
283
  this.electronSessions = [];
276
284
  this.storageState = null;
277
285
 
@@ -347,6 +355,7 @@ class Playwright extends Helper {
347
355
  this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
348
356
  this.isElectron = this.options.browser === 'electron';
349
357
  this.userDataDir = this.playwrightOptions.userDataDir;
358
+ this.isCDPConnection = this.playwrightOptions.cdpConnection;
350
359
  popupStore.defaultAction = this.options.defaultPopupAction;
351
360
  }
352
361
 
@@ -692,6 +701,15 @@ class Playwright extends Helper {
692
701
  async _startBrowser() {
693
702
  if (this.isElectron) {
694
703
  this.browser = await playwright._electron.launch(this.playwrightOptions);
704
+ } else if (this.isRemoteBrowser && this.isCDPConnection) {
705
+ try {
706
+ this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions);
707
+ } catch (err) {
708
+ if (err.toString().indexOf('ECONNREFUSED')) {
709
+ throw new RemoteBrowserConnectionRefused(err);
710
+ }
711
+ throw err;
712
+ }
695
713
  } else if (this.isRemoteBrowser) {
696
714
  try {
697
715
  this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
@@ -1054,6 +1072,7 @@ class Playwright extends Helper {
1054
1072
  if (!page) {
1055
1073
  throw new Error(`There is no ability to switch to next tab with offset ${num}`);
1056
1074
  }
1075
+ targetCreatedHandler.call(this, page);
1057
1076
  await this._setPage(page);
1058
1077
  return this._waitForAction();
1059
1078
  }
@@ -1136,7 +1155,9 @@ class Playwright extends Helper {
1136
1155
  if (this.isElectron) {
1137
1156
  throw new Error('Cannot open new tabs inside an Electron container');
1138
1157
  }
1139
- await this._setPage(await this.browserContext.newPage(options));
1158
+ const page = await this.browserContext.newPage(options);
1159
+ targetCreatedHandler.call(this, page);
1160
+ await this._setPage(page);
1140
1161
  return this._waitForAction();
1141
1162
  }
1142
1163
 
@@ -1558,9 +1579,11 @@ class Playwright extends Helper {
1558
1579
  * Get JS log from browser.
1559
1580
  *
1560
1581
  * ```js
1561
- * let logs = await I.grabBrowserLogs();
1562
- * console.log(JSON.stringify(logs))
1582
+ * const logs = await I.grabBrowserLogs();
1583
+ * const errors = logs.map(l => ({ type: l.type(), text: l.text() })).filter(l => l.type === 'error');
1584
+ * console.log(JSON.stringify(errors));
1563
1585
  * ```
1586
+ * [Learn more about console messages](https://playwright.dev/docs/api/class-consolemessage)
1564
1587
  * @return {Promise<any[]>}
1565
1588
  */
1566
1589
  async grabBrowserLogs() {