codeceptjs 3.5.4 → 3.5.6

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 (42) hide show
  1. package/docs/build/Appium.js +40 -1
  2. package/docs/build/FileSystem.js +1 -1
  3. package/docs/build/Nightmare.js +4 -0
  4. package/docs/build/Playwright.js +59 -41
  5. package/docs/build/Protractor.js +4 -0
  6. package/docs/build/Puppeteer.js +21 -10
  7. package/docs/build/TestCafe.js +2 -0
  8. package/docs/build/WebDriver.js +8 -4
  9. package/docs/changelog.md +1 -170
  10. package/docs/community-helpers.md +4 -8
  11. package/docs/examples.md +2 -8
  12. package/docs/helpers/Appium.md +37 -0
  13. package/docs/helpers/FileSystem.md +1 -1
  14. package/docs/helpers/Nightmare.md +26 -24
  15. package/docs/helpers/Playwright.md +1 -1
  16. package/docs/helpers/Protractor.md +4 -2
  17. package/docs/helpers/Puppeteer.md +29 -27
  18. package/docs/helpers/TestCafe.md +16 -15
  19. package/docs/helpers/WebDriver.md +32 -30
  20. package/docs/webapi/executeAsyncScript.mustache +2 -0
  21. package/docs/webapi/executeScript.mustache +2 -0
  22. package/lib/codecept.js +4 -0
  23. package/lib/command/info.js +24 -0
  24. package/lib/command/init.js +40 -4
  25. package/lib/command/run-workers.js +5 -0
  26. package/lib/command/run.js +7 -0
  27. package/lib/data/context.js +14 -6
  28. package/lib/helper/Appium.js +40 -1
  29. package/lib/helper/FileSystem.js +1 -1
  30. package/lib/helper/Playwright.js +58 -40
  31. package/lib/helper/Puppeteer.js +17 -10
  32. package/lib/helper/WebDriver.js +4 -4
  33. package/lib/helper/scripts/highlightElement.js +1 -1
  34. package/lib/pause.js +1 -2
  35. package/lib/plugin/autoLogin.js +0 -5
  36. package/lib/plugin/retryTo.js +0 -2
  37. package/lib/plugin/tryTo.js +0 -3
  38. package/lib/session.js +1 -1
  39. package/package.json +88 -88
  40. package/translations/fr-FR.js +13 -1
  41. package/typings/promiseBasedTypes.d.ts +19 -1
  42. package/typings/types.d.ts +36 -16
@@ -37,3 +37,27 @@ module.exports = async function (path) {
37
37
  output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues');
38
38
  output.print('***************************************');
39
39
  };
40
+
41
+ module.exports.getMachineInfo = async () => {
42
+ const info = {
43
+ nodeInfo: await envinfo.helpers.getNodeInfo(),
44
+ osInfo: await envinfo.helpers.getOSInfo(),
45
+ cpuInfo: await envinfo.helpers.getCPUInfo(),
46
+ chromeInfo: await envinfo.helpers.getChromeInfo(),
47
+ edgeInfo: await envinfo.helpers.getEdgeInfo(),
48
+ firefoxInfo: await envinfo.helpers.getFirefoxInfo(),
49
+ safariInfo: await envinfo.helpers.getSafariInfo(),
50
+ };
51
+
52
+ output.print('***************************************');
53
+ for (const [key, value] of Object.entries(info)) {
54
+ if (Array.isArray(value)) {
55
+ output.print(`${key}: ${value[1]}`);
56
+ } else {
57
+ output.print(`${key}: ${JSON.stringify(value, null, ' ')}`);
58
+ }
59
+ }
60
+ output.print('If you need more detailed info, just run this: npx codeceptjs info');
61
+ output.print('***************************************');
62
+ return info;
63
+ };
@@ -201,7 +201,13 @@ module.exports = function (initPath) {
201
201
  // no extra step file for typescript (as it doesn't match TS conventions)
202
202
  const stepFile = `./steps_file.${extension}`;
203
203
  fs.writeFileSync(path.join(testsPath, stepFile), extension === 'ts' ? defaultActorTs : defaultActor);
204
- config.include.I = isTypeScript === true ? './steps_file' : stepFile;
204
+
205
+ if (isTypeScript) {
206
+ config.include = _actorTranslation('./steps_file', config.translation);
207
+ } else {
208
+ config.include = _actorTranslation(stepFile, config.translation);
209
+ }
210
+
205
211
  print(`Steps file created at ${stepFile}`);
206
212
 
207
213
  let configSource;
@@ -316,7 +322,7 @@ module.exports = function (initPath) {
316
322
  };
317
323
 
318
324
  print('Configure helpers...');
319
- inquirer.prompt(helperConfigs).then((helperResult) => {
325
+ inquirer.prompt(helperConfigs).then(async (helperResult) => {
320
326
  if (helperResult.Playwright_browser === 'electron') {
321
327
  delete helperResult.Playwright_url;
322
328
  delete helperResult.Playwright_show;
@@ -336,12 +342,12 @@ module.exports = function (initPath) {
336
342
  });
337
343
 
338
344
  print('');
339
- finish();
345
+ await finish();
340
346
  });
341
347
  });
342
348
  };
343
349
 
344
- function install(dependencies, verbose) {
350
+ function install(dependencies) {
345
351
  let command;
346
352
  let args;
347
353
 
@@ -374,9 +380,39 @@ function install(dependencies, verbose) {
374
380
  ].concat(dependencies);
375
381
  }
376
382
 
383
+ if (process.env._INIT_DRY_RUN_INSTALL) {
384
+ args.push('--dry-run');
385
+ }
386
+
377
387
  const { status } = spawn.sync(command, args, { stdio: 'inherit' });
378
388
  if (status !== 0) {
379
389
  throw new Error(`${command} ${args.join(' ')} failed`);
380
390
  }
381
391
  return true;
382
392
  }
393
+
394
+ function _actorTranslation(stepFile, translationSelected) {
395
+ let actor;
396
+
397
+ for (const translationAvailable of translations) {
398
+ if (actor) {
399
+ break;
400
+ }
401
+
402
+ if (translationSelected === translationAvailable) {
403
+ const nameOfActor = require('../../translations')[translationAvailable].I;
404
+
405
+ actor = {
406
+ [nameOfActor]: stepFile,
407
+ };
408
+ }
409
+ }
410
+
411
+ if (!actor) {
412
+ actor = {
413
+ I: stepFile,
414
+ };
415
+ }
416
+
417
+ return actor;
418
+ }
@@ -39,6 +39,11 @@ module.exports = async function (workerCount, selectedRuns, options) {
39
39
  });
40
40
 
41
41
  try {
42
+ if (options.verbose) {
43
+ global.debugMode = true;
44
+ const { getMachineInfo } = require('./info');
45
+ await getMachineInfo();
46
+ }
42
47
  await workers.bootstrapAll();
43
48
  await workers.run();
44
49
  } catch (err) {
@@ -28,6 +28,13 @@ module.exports = async function (test, options) {
28
28
  codecept.init(testRoot);
29
29
  await codecept.bootstrap();
30
30
  codecept.loadTests(test);
31
+
32
+ if (options.verbose) {
33
+ global.debugMode = true;
34
+ const { getMachineInfo } = require('./info');
35
+ await getMachineInfo();
36
+ }
37
+
31
38
  await codecept.run();
32
39
  } catch (err) {
33
40
  printError(err);
@@ -23,6 +23,7 @@ module.exports = function (context) {
23
23
  .inject({ current: dataRow.data }));
24
24
  }
25
25
  });
26
+ maskSecretInTitle(scenarios);
26
27
  return new DataScenarioConfig(scenarios);
27
28
  },
28
29
  only: {
@@ -42,6 +43,7 @@ module.exports = function (context) {
42
43
  .inject({ current: dataRow.data }));
43
44
  }
44
45
  });
46
+ maskSecretInTitle(scenarios);
45
47
  return new DataScenarioConfig(scenarios);
46
48
  },
47
49
  },
@@ -71,12 +73,6 @@ function replaceTitle(title, dataRow) {
71
73
  // it should be printed
72
74
  if (Object.prototype.toString.call(dataRow.data) === (Object()).toString()
73
75
  && dataRow.data.toString() !== (Object()).toString()) {
74
- Object.entries(dataRow.data).forEach(entry => {
75
- const [key, value] = entry;
76
- if (value instanceof Secret) {
77
- dataRow.data[key] = value.getMasked();
78
- }
79
- });
80
76
  return `${title} | ${dataRow.data}`;
81
77
  }
82
78
 
@@ -119,3 +115,15 @@ function detectDataType(dataTable) {
119
115
 
120
116
  throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function');
121
117
  }
118
+
119
+ function maskSecretInTitle(scenarios) {
120
+ scenarios.forEach(scenario => {
121
+ const res = [];
122
+
123
+ scenario.test.title.split(',').forEach(item => {
124
+ res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'));
125
+ });
126
+
127
+ scenario.test.title = res.join(',');
128
+ });
129
+ }
@@ -117,6 +117,43 @@ const vendorPrefix = {
117
117
  * }
118
118
  * ```
119
119
  *
120
+ * Example Android App using Appiumv2 on BrowserStack:
121
+ *
122
+ * ```js
123
+ * {
124
+ * helpers: {
125
+ * Appium: {
126
+ * appiumV2: true,
127
+ * host: "hub-cloud.browserstack.com",
128
+ * port: 4444,
129
+ * user: process.env.BROWSERSTACK_USER,
130
+ * key: process.env.BROWSERSTACK_KEY,
131
+ * app: `bs://c700ce60cf1gjhgjh3ae8ed9770ghjg5a55b8e022f13c5827cg`,
132
+ * browser: '',
133
+ * desiredCapabilities: {
134
+ * 'appPackage': data.packageName,
135
+ * 'deviceName': process.env.DEVICE || 'Google Pixel 3',
136
+ * 'platformName': process.env.PLATFORM || 'android',
137
+ * 'platformVersion': process.env.OS_VERSION || '10.0',
138
+ * 'automationName': process.env.ENGINE || 'UIAutomator2',
139
+ * 'newCommandTimeout': 300000,
140
+ * 'androidDeviceReadyTimeout': 300000,
141
+ * 'androidInstallTimeout': 90000,
142
+ * 'appWaitDuration': 300000,
143
+ * 'autoGrantPermissions': true,
144
+ * 'gpsEnabled': true,
145
+ * 'isHeadless': false,
146
+ * 'noReset': false,
147
+ * 'noSign': true,
148
+ * 'bstack:options' : {
149
+ * "appiumVersion" : "2.0.1",
150
+ * },
151
+ * }
152
+ * }
153
+ * }
154
+ * }
155
+ * ```
156
+ *
120
157
  * Additional configuration params can be used from <https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md>
121
158
  *
122
159
  * ## Access From Helpers
@@ -234,7 +271,9 @@ class Appium extends Webdriver {
234
271
  const _convertedCaps = {};
235
272
  for (const [key, value] of Object.entries(capabilities)) {
236
273
  if (!key.startsWith(vendorPrefix.appium)) {
237
- _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
274
+ if (key !== 'platformName') {
275
+ _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
276
+ }
238
277
  } else {
239
278
  _convertedCaps[`${key}`] = value;
240
279
  }
@@ -52,7 +52,7 @@ class FileSystem extends Helper {
52
52
  }
53
53
 
54
54
  /**
55
- * Writes test to file
55
+ * Writes text to file
56
56
  * @param {string} name
57
57
  * @param {string} text
58
58
  */
@@ -47,7 +47,6 @@ const {
47
47
  setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
48
48
  } = require('./extras/PlaywrightRestartOpts');
49
49
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
50
- const { highlightElement } = require('./scripts/highlightElement');
51
50
 
52
51
  const pathSeparator = path.sep;
53
52
 
@@ -94,7 +93,7 @@ const pathSeparator = path.sep;
94
93
  * @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type).
95
94
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
96
95
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
97
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
96
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
98
97
  */
99
98
  const config = {};
100
99
 
@@ -482,6 +481,7 @@ class Playwright extends Helper {
482
481
  contextOptions.httpCredentials = this.options.basicAuth;
483
482
  this.isAuthenticated = true;
484
483
  }
484
+ if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP;
485
485
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
486
486
  if (this.storageState) contextOptions.storageState = this.storageState;
487
487
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
@@ -826,8 +826,9 @@ class Playwright extends Helper {
826
826
 
827
827
  async _stopBrowser() {
828
828
  this.withinLocator = null;
829
- this._setPage(null);
829
+ await this._setPage(null);
830
830
  this.context = null;
831
+ this.frame = null;
831
832
  popupStore.clear();
832
833
  await this.browser.close();
833
834
  }
@@ -849,8 +850,8 @@ class Playwright extends Helper {
849
850
  await this.switchTo(null);
850
851
  return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve());
851
852
  }
852
- await this.switchTo(locator);
853
- this.withinLocator = new Locator(locator);
853
+ await this.switchTo(frame);
854
+ this.withinLocator = new Locator(frame);
854
855
  return;
855
856
  }
856
857
 
@@ -866,6 +867,7 @@ class Playwright extends Helper {
866
867
  this.withinLocator = null;
867
868
  this.context = await this.page;
868
869
  this.contextLocator = null;
870
+ this.frame = null;
869
871
  }
870
872
 
871
873
  _extractDataFromPerformanceTiming(timing, ...dataNames) {
@@ -1155,6 +1157,9 @@ class Playwright extends Helper {
1155
1157
  */
1156
1158
  async _locate(locator) {
1157
1159
  const context = await this.context || await this._getContext();
1160
+
1161
+ if (this.frame) return findElements(this.frame, locator);
1162
+
1158
1163
  return findElements(context, locator);
1159
1164
  }
1160
1165
 
@@ -1578,7 +1583,7 @@ class Playwright extends Helper {
1578
1583
 
1579
1584
  await el.clear();
1580
1585
 
1581
- highlightActiveElement.call(this, el, await this._getContext());
1586
+ await highlightActiveElement.call(this, el);
1582
1587
 
1583
1588
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1584
1589
 
@@ -1608,7 +1613,7 @@ class Playwright extends Helper {
1608
1613
 
1609
1614
  const el = els[0];
1610
1615
 
1611
- highlightActiveElement.call(this, el, this.page);
1616
+ await highlightActiveElement.call(this, el);
1612
1617
 
1613
1618
  await el.clear();
1614
1619
 
@@ -1623,7 +1628,7 @@ class Playwright extends Helper {
1623
1628
  async appendField(field, value) {
1624
1629
  const els = await findFields.call(this, field);
1625
1630
  assertElementExists(els, field, 'Field');
1626
- highlightActiveElement.call(this, els[0], await this._getContext());
1631
+ await highlightActiveElement.call(this, els[0]);
1627
1632
  await els[0].press('End');
1628
1633
  await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1629
1634
  return this._waitForAction();
@@ -1669,7 +1674,7 @@ class Playwright extends Helper {
1669
1674
  assertElementExists(els, select, 'Selectable field');
1670
1675
  const el = els[0];
1671
1676
 
1672
- highlightActiveElement.call(this, el, await this._getContext());
1677
+ await highlightActiveElement.call(this, el);
1673
1678
 
1674
1679
  if (!Array.isArray(option)) option = [option];
1675
1680
 
@@ -1881,11 +1886,11 @@ class Playwright extends Helper {
1881
1886
  * @returns {Promise<any>}
1882
1887
  */
1883
1888
  async executeScript(fn, arg) {
1884
- let context = this.page;
1885
- if (this.context && this.context.constructor.name === 'Frame') {
1886
- context = this.context; // switching to iframe context
1889
+ if (this.context && this.context.constructor.name === 'FrameLocator') {
1890
+ // switching to iframe context
1891
+ return this.context.locator(':root').evaluate(fn, arg);
1887
1892
  }
1888
- return context.evaluate.apply(context, [fn, arg]);
1893
+ return this.page.evaluate.apply(this.page, [fn, arg]);
1889
1894
  }
1890
1895
 
1891
1896
  /**
@@ -2407,7 +2412,7 @@ class Playwright extends Helper {
2407
2412
  }
2408
2413
 
2409
2414
  async _getContext() {
2410
- if (this.context && this.context.constructor.name === 'Frame') {
2415
+ if (this.context && this.context.constructor.name === 'FrameLocator') {
2411
2416
  return this.context;
2412
2417
  }
2413
2418
  return this.page;
@@ -2480,6 +2485,14 @@ class Playwright extends Helper {
2480
2485
  }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
2481
2486
  }
2482
2487
  } else {
2488
+ // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
2489
+ if (this.frame) {
2490
+ const { setTimeout } = require('timers/promises');
2491
+ await setTimeout(waitTimeout);
2492
+ waiter = await this.frame.locator(`:has-text('${text}')`).first().isVisible();
2493
+ if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
2494
+ return;
2495
+ }
2483
2496
  waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, text, { timeout: waitTimeout });
2484
2497
  }
2485
2498
  return waiter.catch((err) => {
@@ -2534,39 +2547,42 @@ class Playwright extends Helper {
2534
2547
  }
2535
2548
 
2536
2549
  if (locator >= 0 && locator < childFrames.length) {
2537
- this.context = childFrames[locator];
2550
+ this.context = await this.page.frameLocator('iframe').nth(locator);
2538
2551
  this.contextLocator = locator;
2539
2552
  } else {
2540
2553
  throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2541
2554
  }
2542
2555
  return;
2543
2556
  }
2544
- let contentFrame;
2545
2557
 
2546
2558
  if (!locator) {
2547
- this.context = await this.page.frames()[0];
2559
+ this.context = this.page;
2548
2560
  this.contextLocator = null;
2561
+ this.frame = null;
2549
2562
  return;
2550
2563
  }
2551
2564
 
2552
2565
  // iframe by selector
2553
- const els = await this._locate(locator);
2554
- // assertElementExists(els, locator);
2566
+ locator = buildLocatorString(new Locator(locator, 'css'));
2567
+ const frame = await this._locateElement(locator);
2568
+
2569
+ if (!frame) {
2570
+ throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`);
2571
+ }
2555
2572
 
2556
- // get content of the first iframe
2557
- if ((locator.frame && locator.frame === 'iframe') || locator.toLowerCase() === 'iframe') {
2558
- contentFrame = await this.page.frames()[1];
2559
- // get content of the iframe using its name
2560
- } else if (locator.toLowerCase().includes('name=')) {
2561
- const frameName = locator.split('=')[1].replace(/"/g, '').replaceAll(/]/g, '');
2562
- contentFrame = await this.page.frame(frameName);
2573
+ if (this.frame) {
2574
+ this.frame = await this.frame.frameLocator(locator);
2575
+ } else {
2576
+ this.frame = await this.page.frameLocator(locator);
2563
2577
  }
2564
2578
 
2579
+ const contentFrame = this.frame;
2580
+
2565
2581
  if (contentFrame) {
2566
2582
  this.context = contentFrame;
2567
2583
  this.contextLocator = null;
2568
2584
  } else {
2569
- this.context = els[0];
2585
+ this.context = this.page.frame(this.page.frames()[1].name());
2570
2586
  this.contextLocator = locator;
2571
2587
  }
2572
2588
  }
@@ -3256,7 +3272,7 @@ async function findElement(matcher, locator) {
3256
3272
  if (locator.react) return findReact(matcher, locator);
3257
3273
  locator = new Locator(locator, 'css');
3258
3274
 
3259
- return matcher.locator(buildLocatorString(locator));
3275
+ return matcher.locator(buildLocatorString(locator)).first();
3260
3276
  }
3261
3277
 
3262
3278
  async function getVisibleElements(elements) {
@@ -3286,7 +3302,7 @@ async function proceedClick(locator, context = null, options = {}) {
3286
3302
  assertElementExists(els, locator, 'Clickable element');
3287
3303
  }
3288
3304
 
3289
- highlightActiveElement.call(this, els[0], await this._getContext());
3305
+ await highlightActiveElement.call(this, els[0]);
3290
3306
 
3291
3307
  /*
3292
3308
  using the force true options itself but instead dispatching a click
@@ -3336,13 +3352,9 @@ async function proceedSee(assertType, text, context, strict = false) {
3336
3352
  let allText;
3337
3353
 
3338
3354
  if (!context) {
3339
- let el = await this.context;
3340
- if (el && !el.getProperty) {
3341
- // Fallback to body
3342
- el = await this.page.$('body');
3343
- }
3355
+ const el = await this.context;
3344
3356
 
3345
- allText = [await el.innerText()];
3357
+ allText = [await el.locator('body').innerText()];
3346
3358
  description = 'web application';
3347
3359
  } else {
3348
3360
  const locator = new Locator(context, 'css');
@@ -3514,7 +3526,9 @@ async function elementSelected(element) {
3514
3526
 
3515
3527
  function isFrameLocator(locator) {
3516
3528
  locator = new Locator(locator);
3517
- if (locator.isFrame()) return locator.value;
3529
+ if (locator.isFrame()) {
3530
+ return locator.value;
3531
+ }
3518
3532
  return false;
3519
3533
  }
3520
3534
 
@@ -3709,10 +3723,14 @@ async function saveTraceForContext(context, name) {
3709
3723
  return fileName;
3710
3724
  }
3711
3725
 
3712
- function highlightActiveElement(element, context) {
3713
- if (!this.options.highlightElement && !store.debugMode) return;
3714
-
3715
- highlightElement(element, context);
3726
+ async function highlightActiveElement(element) {
3727
+ if (this.options.highlightElement && global.debugMode) {
3728
+ await element.evaluate(el => {
3729
+ const prevStyle = el.style.boxShadow;
3730
+ el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)';
3731
+ setTimeout(() => el.style.boxShadow = prevStyle, 2000);
3732
+ });
3733
+ }
3716
3734
  }
3717
3735
 
3718
3736
  const createAdvancedTestResults = (url, dataToCheck, requests) => {
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
69
69
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
70
70
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
71
71
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
72
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
72
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
73
73
  */
74
74
  const config = {};
75
75
 
@@ -604,8 +604,8 @@ class Puppeteer extends Helper {
604
604
  return this.switchTo(null)
605
605
  .then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
606
606
  }
607
- await this.switchTo(locator);
608
- this.withinLocator = new Locator(locator);
607
+ await this.switchTo(frame);
608
+ this.withinLocator = new Locator(frame);
609
609
  return;
610
610
  }
611
611
 
@@ -2011,7 +2011,7 @@ class Puppeteer extends Helper {
2011
2011
  assertElementExists(els, locator);
2012
2012
 
2013
2013
  return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
2014
- if (/failed: timeout/i.test(e.message)) {
2014
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2015
2015
  throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
2016
2016
  } else {
2017
2017
  throw e;
@@ -2115,7 +2115,7 @@ class Puppeteer extends Helper {
2115
2115
  return currUrl.indexOf(urlPart) > -1;
2116
2116
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2117
2117
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2118
- if (/failed: timeout/i.test(e.message)) {
2118
+ if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2119
2119
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
2120
2120
  } else {
2121
2121
  throw e;
@@ -2139,7 +2139,7 @@ class Puppeteer extends Helper {
2139
2139
  return currUrl.indexOf(urlPart) > -1;
2140
2140
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2141
2141
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2142
- if (/failed: timeout/i.test(e.message)) {
2142
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2143
2143
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
2144
2144
  } else {
2145
2145
  throw e;
@@ -2348,6 +2348,10 @@ async function findElements(matcher, locator) {
2348
2348
  if (locator.react) return findReact(matcher.executionContext(), locator);
2349
2349
  locator = new Locator(locator, 'css');
2350
2350
  if (!locator.isXPath()) return matcher.$$(locator.simplify());
2351
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2352
+ if (puppeteer.default?.defaultBrowserRevision) {
2353
+ return matcher.$$(`xpath/${locator.value}`);
2354
+ }
2351
2355
  return matcher.$x(locator.value);
2352
2356
  }
2353
2357
 
@@ -2602,7 +2606,10 @@ async function elementSelected(element) {
2602
2606
 
2603
2607
  function isFrameLocator(locator) {
2604
2608
  locator = new Locator(locator);
2605
- if (locator.isFrame()) return locator.value;
2609
+ if (locator.isFrame()) {
2610
+ const _locator = new Locator(locator);
2611
+ return _locator.value;
2612
+ }
2606
2613
  return false;
2607
2614
  }
2608
2615
 
@@ -2720,7 +2727,7 @@ function getNormalizedKey(key) {
2720
2727
  }
2721
2728
 
2722
2729
  function highlightActiveElement(element, context) {
2723
- if (!this.options.highlightElement && !store.debugMode) return;
2724
-
2725
- highlightElement(element, context);
2730
+ if (this.options.highlightElement && global.debugMode) {
2731
+ highlightElement(element, context);
2732
+ }
2726
2733
  }
@@ -62,7 +62,7 @@ const webRoot = 'body';
62
62
  * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
63
63
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
64
64
  * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
65
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
65
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
66
66
  */
67
67
  const config = {};
68
68
 
@@ -2918,9 +2918,9 @@ function isModifierKey(key) {
2918
2918
  }
2919
2919
 
2920
2920
  function highlightActiveElement(element) {
2921
- if (!this.options.highlightElement && !store.debugMode) return;
2922
-
2923
- highlightElement(element, this.browser);
2921
+ if (this.options.highlightElement && global.debugMode) {
2922
+ highlightElement(element, this.browser);
2923
+ }
2924
2924
  }
2925
2925
 
2926
2926
  function prepareLocateFn(context) {
@@ -7,7 +7,7 @@ module.exports.highlightElement = (element, context) => {
7
7
  };
8
8
 
9
9
  try {
10
- // Playwright, Puppeteer
10
+ // Puppeteer
11
11
  context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err));
12
12
  } catch (e) {
13
13
  // WebDriver
package/lib/pause.js CHANGED
@@ -22,6 +22,7 @@ const aiAssistant = new AiAssistant();
22
22
 
23
23
  /**
24
24
  * Pauses test execution and starts interactive shell
25
+ * @param {Object<string, *>} [passedObject]
25
26
  */
26
27
  const pause = function (passedObject = {}) {
27
28
  if (store.dryRun) return;
@@ -77,7 +78,6 @@ async function parseInput(cmd) {
77
78
  rl.pause();
78
79
  next = false;
79
80
  recorder.session.start('pause');
80
- store.debugMode = false;
81
81
  if (cmd === '') next = true;
82
82
  if (!cmd || cmd === 'resume' || cmd === 'exit') {
83
83
  finish();
@@ -97,7 +97,6 @@ async function parseInput(cmd) {
97
97
  return cmd;
98
98
  };
99
99
 
100
- store.debugMode = true;
101
100
  let isCustomCommand = false;
102
101
  let lastError = null;
103
102
  let isAiCommand = false;
@@ -251,20 +251,16 @@ module.exports = function (config) {
251
251
  } else {
252
252
  userSession.login(I);
253
253
  }
254
- store.debugMode = true;
255
254
  const cookies = await userSession.fetch(I);
256
255
  if (config.saveToFile) {
257
256
  debug(`Saved user session into file for ${name}`);
258
257
  fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies));
259
258
  }
260
259
  store[`${name}_session`] = cookies;
261
- store.debugMode = false;
262
260
  };
263
261
 
264
262
  if (!cookies) return loginAndSave();
265
263
 
266
- store.debugMode = true;
267
-
268
264
  recorder.session.start('check login');
269
265
  if (shouldAwait) {
270
266
  await userSession.restore(I, cookies);
@@ -287,7 +283,6 @@ module.exports = function (config) {
287
283
  });
288
284
  });
289
285
  recorder.add(() => {
290
- store.debugMode = false;
291
286
  recorder.session.restore('check login');
292
287
  });
293
288
 
@@ -83,7 +83,6 @@ module.exports = function (config) {
83
83
  return retryTo;
84
84
 
85
85
  function retryTo(callback, maxTries, pollInterval = undefined) {
86
- const mode = store.debugMode;
87
86
  let tries = 1;
88
87
  if (!pollInterval) pollInterval = config.pollInterval;
89
88
 
@@ -113,7 +112,6 @@ module.exports = function (config) {
113
112
  };
114
113
 
115
114
  recorder.add('retryTo', async () => {
116
- store.debugMode = true;
117
115
  tryBlock();
118
116
  });
119
117
  }).then(() => {