codeceptjs 3.5.4-beta.1 → 3.5.4

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 (62) hide show
  1. package/README.md +0 -2
  2. package/docs/build/Appium.js +8 -6
  3. package/docs/build/GraphQL.js +25 -0
  4. package/docs/build/Nightmare.js +11 -6
  5. package/docs/build/Playwright.js +425 -193
  6. package/docs/build/Protractor.js +13 -8
  7. package/docs/build/Puppeteer.js +20 -14
  8. package/docs/build/TestCafe.js +17 -10
  9. package/docs/build/WebDriver.js +41 -37
  10. package/docs/changelog.md +220 -0
  11. package/docs/community-helpers.md +8 -4
  12. package/docs/examples.md +8 -2
  13. package/docs/helpers/Appium.md +2 -2
  14. package/docs/helpers/GraphQL.md +21 -0
  15. package/docs/helpers/Nightmare.md +1258 -0
  16. package/docs/helpers/Playwright.md +223 -119
  17. package/docs/helpers/Protractor.md +1709 -0
  18. package/docs/helpers/Puppeteer.md +3 -3
  19. package/docs/helpers/TestCafe.md +2 -2
  20. package/docs/helpers/WebDriver.md +3 -3
  21. package/docs/playwright.md +24 -1
  22. package/docs/webapi/dontSeeInField.mustache +1 -1
  23. package/docs/webapi/seeInField.mustache +1 -1
  24. package/docs/wiki/Books-&-Posts.md +0 -0
  25. package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
  26. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
  27. package/docs/wiki/Examples.md +8 -2
  28. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
  29. package/docs/wiki/Home.md +0 -0
  30. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
  31. package/docs/wiki/Release-Process.md +0 -0
  32. package/docs/wiki/Roadmap.md +0 -0
  33. package/docs/wiki/Tests.md +0 -0
  34. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
  35. package/docs/wiki/Videos.md +0 -0
  36. package/lib/command/definitions.js +2 -7
  37. package/lib/command/run-multiple/collection.js +17 -5
  38. package/lib/helper/Appium.js +6 -4
  39. package/lib/helper/GraphQL.js +25 -0
  40. package/lib/helper/Nightmare.js +1415 -0
  41. package/lib/helper/Playwright.js +321 -54
  42. package/lib/helper/Protractor.js +1837 -0
  43. package/lib/helper/Puppeteer.js +18 -12
  44. package/lib/helper/TestCafe.js +15 -8
  45. package/lib/helper/WebDriver.js +39 -35
  46. package/lib/helper/clientscripts/nightmare.js +213 -0
  47. package/lib/helper/errors/ElementNotFound.js +2 -1
  48. package/lib/helper/scripts/highlightElement.js +1 -1
  49. package/lib/interfaces/bdd.js +1 -1
  50. package/lib/mochaFactory.js +2 -1
  51. package/lib/pause.js +5 -4
  52. package/lib/plugin/heal.js +2 -3
  53. package/lib/plugin/selenoid.js +6 -1
  54. package/lib/step.js +27 -10
  55. package/lib/utils.js +4 -0
  56. package/lib/workers.js +3 -1
  57. package/package.json +13 -13
  58. package/typings/promiseBasedTypes.d.ts +145 -126
  59. package/typings/types.d.ts +152 -133
  60. package/CHANGELOG.md +0 -2519
  61. package/docs/build/Polly.js +0 -42
  62. package/docs/build/SeleniumWebdriver.js +0 -76
@@ -23,6 +23,7 @@ const {
23
23
  isModifierKey,
24
24
  clearString,
25
25
  requireWithFallback,
26
+ normalizeSpacesInString,
26
27
  } = require('../utils');
27
28
  const {
28
29
  isColorProperty,
@@ -76,7 +77,7 @@ const pathSeparator = path.sep;
76
77
  * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to 'session'.
77
78
  * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to 'session'.
78
79
  * @prop {number} [waitForAction] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
79
- * @prop {'load' | 'domcontentloaded' | 'networkidle'} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://playwright.dev/docs/api/class-page#page-wait-for-navigation).
80
+ * @prop {'load' | 'domcontentloaded' | 'commit'} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `commit`. Choose one of those options is possible. See [Playwright API](https://playwright.dev/docs/api/class-page#page-wait-for-url).
80
81
  * @prop {number} [pressKeyDelay=10] - Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
81
82
  * @prop {number} [getPageTimeout] - config option to set maximum navigation time in milliseconds.
82
83
  * @prop {number} [waitForTimeout] - default wait* timeout in ms. Default: 1000.
@@ -93,7 +94,7 @@ const pathSeparator = path.sep;
93
94
  * @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).
94
95
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
95
96
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
96
- * @prop {boolean} [highlightElement] - highlight the interacting elements
97
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
97
98
  */
98
99
  const config = {};
99
100
 
@@ -207,6 +208,7 @@ const config = {};
207
208
  * url: "http://localhost",
208
209
  * show: true // headless mode not supported for extensions
209
210
  * chromium: {
211
+ * // Note: due to this would launch persistent context, so to avoid the error when running tests with run-workers a timestamp would be appended to the defined folder name. For instance: playwright-tmp_1692715649511
210
212
  * userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
211
213
  * args: [
212
214
  * `--disable-extensions-except=${pathToExtension}`,
@@ -317,6 +319,12 @@ class Playwright extends Helper {
317
319
  this.recording = false;
318
320
  this.recordedAtLeastOnce = false;
319
321
 
322
+ // for websocket messages
323
+ this.webSocketMessages = [];
324
+ this.recordingWebSocketMessages = false;
325
+ this.recordedWebSocketMessagesAtLeastOnce = false;
326
+ this.cdpSession = null;
327
+
320
328
  // override defaults with config
321
329
  this._setConfig(config);
322
330
  }
@@ -343,7 +351,8 @@ class Playwright extends Helper {
343
351
  show: false,
344
352
  defaultPopupAction: 'accept',
345
353
  use: { actionTimeout: 0 },
346
- ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors
354
+ ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors,
355
+ highlightElement: false,
347
356
  };
348
357
 
349
358
  config = Object.assign(defaults, config);
@@ -388,7 +397,7 @@ class Playwright extends Helper {
388
397
  }
389
398
  this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
390
399
  this.isElectron = this.options.browser === 'electron';
391
- this.userDataDir = this.playwrightOptions.userDataDir;
400
+ this.userDataDir = this.playwrightOptions.userDataDir ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` : undefined;
392
401
  this.isCDPConnection = this.playwrightOptions.cdpConnection;
393
402
  popupStore.defaultAction = this.options.defaultPopupAction;
394
403
  }
@@ -461,7 +470,7 @@ class Playwright extends Helper {
461
470
  this.isAuthenticated = false;
462
471
  if (this.isElectron) {
463
472
  this.browserContext = this.browser.context();
464
- } else if (this.userDataDir) {
473
+ } else if (this.playwrightOptions.userDataDir) {
465
474
  this.browserContext = this.browser;
466
475
  } else {
467
476
  const contextOptions = {
@@ -487,8 +496,17 @@ class Playwright extends Helper {
487
496
  if (this.isElectron) {
488
497
  mainPage = await this.browser.firstWindow();
489
498
  } else {
490
- const existingPages = await this.browserContext.pages();
491
- mainPage = existingPages[0] || await this.browserContext.newPage();
499
+ try {
500
+ const existingPages = await this.browserContext.pages();
501
+ mainPage = existingPages[0] || await this.browserContext.newPage();
502
+ } catch (e) {
503
+ if (this.playwrightOptions.userDataDir) {
504
+ this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
505
+ this.browserContext = this.browser;
506
+ const existingPages = await this.browserContext.pages();
507
+ mainPage = existingPages[0];
508
+ }
509
+ }
492
510
  }
493
511
  await targetCreatedHandler.call(this, mainPage);
494
512
 
@@ -519,13 +537,15 @@ class Playwright extends Helper {
519
537
 
520
538
  // close other sessions
521
539
  try {
522
- const contexts = await this.browser.contexts();
523
- const currentContext = contexts[0];
524
- if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
525
- this.storageState = await currentContext.storageState();
526
- }
540
+ if ((await this.browser)._type === 'Browser') {
541
+ const contexts = await this.browser.contexts();
542
+ const currentContext = contexts[0];
543
+ if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
544
+ this.storageState = await currentContext.storageState();
545
+ }
527
546
 
528
- await Promise.all(contexts.map(c => c.close()));
547
+ await Promise.all(contexts.map(c => c.close()));
548
+ }
529
549
  } catch (e) {
530
550
  console.log(e);
531
551
  }
@@ -555,8 +575,16 @@ class Playwright extends Helper {
555
575
  browserContext = browser.context();
556
576
  page = await browser.firstWindow();
557
577
  } else {
558
- browserContext = await this.browser.newContext(Object.assign(this.options, config));
559
- page = await browserContext.newPage();
578
+ try {
579
+ browserContext = await this.browser.newContext(Object.assign(this.options, config));
580
+ page = await browserContext.newPage();
581
+ } catch (e) {
582
+ if (this.playwrightOptions.userDataDir) {
583
+ browserContext = await playwright[this.options.browser].launchPersistentContext(`${this.userDataDir}_${this.activeSessionName}`, this.playwrightOptions);
584
+ this.browser = browserContext;
585
+ page = await browserContext.pages()[0];
586
+ }
587
+ }
560
588
  }
561
589
 
562
590
  if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true });
@@ -569,10 +597,12 @@ class Playwright extends Helper {
569
597
  // is closed by _after
570
598
  },
571
599
  loadVars: async (context) => {
572
- this.browserContext = context;
573
- const existingPages = await context.pages();
574
- this.sessionPages[this.activeSessionName] = existingPages[0];
575
- return this._setPage(this.sessionPages[this.activeSessionName]);
600
+ if (context) {
601
+ this.browserContext = context;
602
+ const existingPages = await context.pages();
603
+ this.sessionPages[this.activeSessionName] = existingPages[0];
604
+ return this._setPage(this.sessionPages[this.activeSessionName]);
605
+ }
576
606
  },
577
607
  restoreVars: async (session) => {
578
608
  this.withinLocator = null;
@@ -763,7 +793,7 @@ class Playwright extends Helper {
763
793
  }
764
794
  throw err;
765
795
  }
766
- } else if (this.userDataDir) {
796
+ } else if (this.playwrightOptions.userDataDir) {
767
797
  this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
768
798
  } else {
769
799
  this.browser = await playwright[this.options.browser].launch(this.playwrightOptions);
@@ -1038,8 +1068,11 @@ class Playwright extends Helper {
1038
1068
  const body = document.body;
1039
1069
  const html = document.documentElement;
1040
1070
  window.scrollTo(0, Math.max(
1041
- body.scrollHeight, body.offsetHeight,
1042
- html.clientHeight, html.scrollHeight, html.offsetHeight,
1071
+ body.scrollHeight,
1072
+ body.offsetHeight,
1073
+ html.clientHeight,
1074
+ html.scrollHeight,
1075
+ html.offsetHeight,
1043
1076
  ));
1044
1077
  });
1045
1078
  }
@@ -1545,7 +1578,7 @@ class Playwright extends Helper {
1545
1578
 
1546
1579
  await el.clear();
1547
1580
 
1548
- highlightActiveElement.call(this, el, this.page);
1581
+ highlightActiveElement.call(this, el, await this._getContext());
1549
1582
 
1550
1583
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1551
1584
 
@@ -1590,7 +1623,7 @@ class Playwright extends Helper {
1590
1623
  async appendField(field, value) {
1591
1624
  const els = await findFields.call(this, field);
1592
1625
  assertElementExists(els, field, 'Field');
1593
- highlightActiveElement.call(this, els[0], this.page);
1626
+ highlightActiveElement.call(this, els[0], await this._getContext());
1594
1627
  await els[0].press('End');
1595
1628
  await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1596
1629
  return this._waitForAction();
@@ -1600,14 +1633,16 @@ class Playwright extends Helper {
1600
1633
  * {{> seeInField }}
1601
1634
  */
1602
1635
  async seeInField(field, value) {
1603
- return proceedSeeInField.call(this, 'assert', field, value);
1636
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1637
+ return proceedSeeInField.call(this, 'assert', field, _value);
1604
1638
  }
1605
1639
 
1606
1640
  /**
1607
1641
  * {{> dontSeeInField }}
1608
1642
  */
1609
1643
  async dontSeeInField(field, value) {
1610
- return proceedSeeInField.call(this, 'negate', field, value);
1644
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1645
+ return proceedSeeInField.call(this, 'negate', field, _value);
1611
1646
  }
1612
1647
 
1613
1648
  /**
@@ -1634,7 +1669,8 @@ class Playwright extends Helper {
1634
1669
  assertElementExists(els, select, 'Selectable field');
1635
1670
  const el = els[0];
1636
1671
 
1637
- highlightActiveElement.call(this, el, this.page);
1672
+ highlightActiveElement.call(this, el, await this._getContext());
1673
+
1638
1674
  if (!Array.isArray(option)) option = [option];
1639
1675
 
1640
1676
  await el.selectOption(option);
@@ -2553,13 +2589,15 @@ class Playwright extends Helper {
2553
2589
  }
2554
2590
 
2555
2591
  /**
2556
- * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2592
+ * Waits for navigation to finish. By default, it takes configured `waitForNavigation` option.
2557
2593
  *
2558
2594
  * See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
2559
2595
  *
2560
2596
  * @param {*} options
2561
2597
  */
2562
2598
  async waitForNavigation(options = {}) {
2599
+ console.log(`waitForNavigation deprecated:
2600
+ * This method is inherently racy, please use 'waitForURL' instead.`);
2563
2601
  options = {
2564
2602
  timeout: this.options.getPageTimeout,
2565
2603
  waitUntil: this.options.waitForNavigation,
@@ -2568,6 +2606,23 @@ class Playwright extends Helper {
2568
2606
  return this.page.waitForNavigation(options);
2569
2607
  }
2570
2608
 
2609
+ /**
2610
+ * Waits for page navigates to a new URL or reloads. By default, it takes configured `waitForNavigation` option.
2611
+ *
2612
+ * See [Playwright's reference](https://playwright.dev/docs/api/class-page#page-wait-for-url)
2613
+ *
2614
+ * @param {string|RegExp} url - A glob pattern, regex pattern or predicate receiving URL to match while waiting for the navigation. Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to the string.
2615
+ * @param {*} options
2616
+ */
2617
+ async waitForURL(url, options = {}) {
2618
+ options = {
2619
+ timeout: this.options.getPageTimeout,
2620
+ waitUntil: this.options.waitForNavigation,
2621
+ ...options,
2622
+ };
2623
+ return this.page.waitForURL(url, options);
2624
+ }
2625
+
2571
2626
  async waitUntilExists(locator, sec) {
2572
2627
  console.log(`waitUntilExists deprecated:
2573
2628
  * use 'waitForElement' to wait for element to be attached
@@ -2652,16 +2707,16 @@ class Playwright extends Helper {
2652
2707
  }
2653
2708
 
2654
2709
  /**
2655
- * Starts recording of network traffic.
2710
+ * Starts recording the network traffics.
2656
2711
  * This also resets recorded network requests.
2657
2712
  *
2658
2713
  * ```js
2659
2714
  * I.startRecordingTraffic();
2660
2715
  * ```
2661
2716
  *
2662
- * @return {Promise<void>}
2717
+ * @return {void}
2663
2718
  */
2664
- async startRecordingTraffic() {
2719
+ startRecordingTraffic() {
2665
2720
  this.flushNetworkTraffics();
2666
2721
  this.recording = true;
2667
2722
  this.recordedAtLeastOnce = true;
@@ -2672,31 +2727,62 @@ class Playwright extends Helper {
2672
2727
  method: request.method(),
2673
2728
  requestHeaders: request.headers(),
2674
2729
  requestPostData: request.postData(),
2730
+ response: request.response(),
2675
2731
  };
2676
2732
 
2677
2733
  this.debugSection('REQUEST: ', JSON.stringify(information));
2678
2734
 
2679
- information.requestPostData = JSON.parse(information.requestPostData);
2735
+ if (typeof information.requestPostData === 'object') {
2736
+ information.requestPostData = JSON.parse(information.requestPostData);
2737
+ }
2738
+
2680
2739
  this.requests.push(information);
2681
- return this._waitForAction();
2682
2740
  });
2683
2741
  }
2684
2742
 
2685
2743
  /**
2686
2744
  * Grab the recording network traffics
2687
2745
  *
2688
- * @return { Array<any> }
2746
+ * ```js
2747
+ * const traffics = await I.grabRecordedNetworkTraffics();
2748
+ * expect(traffics[0].url).to.equal('https://reqres.in/api/comments/1');
2749
+ * expect(traffics[0].response.status).to.equal(200);
2750
+ * expect(traffics[0].response.body).to.contain({ name: 'this was mocked' });
2751
+ * ```
2752
+ *
2753
+ * @return { Promise<Array<any>> }
2689
2754
  *
2690
2755
  */
2691
- grabRecordedNetworkTraffics() {
2756
+ async grabRecordedNetworkTraffics() {
2692
2757
  if (!this.recording || !this.recordedAtLeastOnce) {
2693
2758
  throw new Error('Failure in test automation. You use "I.grabRecordedNetworkTraffics", but "I.startRecordingTraffic" was never called before.');
2694
2759
  }
2760
+
2761
+ const requests = await this.requests;
2762
+ const promises = requests.map(async (request) => request.response.then(
2763
+ async (response) => {
2764
+ let body;
2765
+ try {
2766
+ // There's no 'body' for some requests (redirect etc...)
2767
+ body = JSON.parse((await response.body()).toString());
2768
+ } catch (e) {
2769
+ // only interested in JSON, not HTML responses.
2770
+ }
2771
+
2772
+ request.response = {
2773
+ status: response.status(),
2774
+ statusText: response.statusText(),
2775
+ body,
2776
+ };
2777
+ },
2778
+ ));
2779
+ await Promise.all(promises);
2780
+
2695
2781
  return this.requests;
2696
2782
  }
2697
2783
 
2698
2784
  /**
2699
- * Blocks traffic for URL.
2785
+ * Blocks traffic of a given URL or a list of URLs.
2700
2786
  *
2701
2787
  * Examples:
2702
2788
  *
@@ -2707,16 +2793,30 @@ class Playwright extends Helper {
2707
2793
  * I.blockTraffic(/\.css$/);
2708
2794
  * ```
2709
2795
  *
2710
- * @param url URL to block . URL can contain * for wildcards. Example: https://www.example.com** to block all traffic for that domain. Regexp are also supported.
2796
+ * ```js
2797
+ * I.blockTraffic(['http://example.com/css/style.css', 'http://example.com/css/*.css']);
2798
+ * ```
2799
+ *
2800
+ * @param {string|Array|RegExp} urls URL or a list of URLs to block . URL can contain * for wildcards. Example: https://www.example.com** to block all traffic for that domain. Regexp are also supported.
2711
2801
  */
2712
- async blockTraffic(url) {
2713
- this.page.route(url, (route) => {
2714
- route
2715
- .abort()
2716
- // Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
2717
- .catch((e) => {});
2718
- });
2719
- return this._waitForAction();
2802
+ blockTraffic(urls) {
2803
+ if (Array.isArray(urls)) {
2804
+ urls.forEach(url => {
2805
+ this.page.route(url, (route) => {
2806
+ route
2807
+ .abort()
2808
+ // Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
2809
+ .catch((e) => {});
2810
+ });
2811
+ });
2812
+ } else {
2813
+ this.page.route(urls, (route) => {
2814
+ route
2815
+ .abort()
2816
+ // Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
2817
+ .catch((e) => {});
2818
+ });
2819
+ }
2720
2820
  }
2721
2821
 
2722
2822
  /**
@@ -2735,7 +2835,7 @@ class Playwright extends Helper {
2735
2835
  * @param responseString string The string to return in fake response's body.
2736
2836
  * @param contentType Content type of fake response. If not specified default value 'application/json' is used.
2737
2837
  */
2738
- async mockTraffic(urls, responseString, contentType = 'application/json') {
2838
+ mockTraffic(urls, responseString, contentType = 'application/json') {
2739
2839
  // Required to mock cross-domain requests
2740
2840
  const headers = { 'access-control-allow-origin': '*' };
2741
2841
 
@@ -2757,7 +2857,6 @@ class Playwright extends Helper {
2757
2857
  });
2758
2858
  });
2759
2859
  });
2760
- return this._waitForAction();
2761
2860
  }
2762
2861
 
2763
2862
  /**
@@ -2829,7 +2928,7 @@ class Playwright extends Helper {
2829
2928
  }
2830
2929
 
2831
2930
  if (!this.recording || !this.recordedAtLeastOnce) {
2832
- throw new Error('Failure in test automation. You use "I.seeInTraffic", but "I.startRecordingTraffic" was never called before.');
2931
+ throw new Error('Failure in test automation. You use "I.seeTraffic", but "I.startRecordingTraffic" was never called before.');
2833
2932
  }
2834
2933
 
2835
2934
  for (let i = 0; i <= timeout * 2; i++) {
@@ -2837,7 +2936,9 @@ class Playwright extends Helper {
2837
2936
  if (found) {
2838
2937
  return true;
2839
2938
  }
2840
- await new Promise((done) => setTimeout(done, 1000));
2939
+ await new Promise((done) => {
2940
+ setTimeout(done, 1000);
2941
+ });
2841
2942
  }
2842
2943
 
2843
2944
  // check request post data
@@ -2974,6 +3075,163 @@ class Playwright extends Helper {
2974
3075
  });
2975
3076
  return dumpedTraffic;
2976
3077
  }
3078
+
3079
+ /**
3080
+ * Starts recording of websocket messages.
3081
+ * This also resets recorded websocket messages.
3082
+ *
3083
+ * ```js
3084
+ * await I.startRecordingWebSocketMessages();
3085
+ * ```
3086
+ *
3087
+ */
3088
+ async startRecordingWebSocketMessages() {
3089
+ this.flushWebSocketMessages();
3090
+ this.recordingWebSocketMessages = true;
3091
+ this.recordedWebSocketMessagesAtLeastOnce = true;
3092
+
3093
+ this.cdpSession = await this.getNewCDPSession();
3094
+ await this.cdpSession.send('Network.enable');
3095
+ await this.cdpSession.send('Page.enable');
3096
+
3097
+ this.cdpSession.on(
3098
+ 'Network.webSocketFrameReceived',
3099
+ (payload) => {
3100
+ this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload));
3101
+ },
3102
+ );
3103
+
3104
+ this.cdpSession.on(
3105
+ 'Network.webSocketFrameSent',
3106
+ (payload) => {
3107
+ this._logWebsocketMessages(this._getWebSocketLog('SENT', payload));
3108
+ },
3109
+ );
3110
+
3111
+ this.cdpSession.on(
3112
+ 'Network.webSocketFrameError',
3113
+ (payload) => {
3114
+ this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload));
3115
+ },
3116
+ );
3117
+ }
3118
+
3119
+ /**
3120
+ * Stops recording WS messages. Recorded WS messages is not flashed.
3121
+ *
3122
+ * ```js
3123
+ * await I.stopRecordingWebSocketMessages();
3124
+ * ```
3125
+ */
3126
+ async stopRecordingWebSocketMessages() {
3127
+ await this.cdpSession.send('Network.disable');
3128
+ await this.cdpSession.send('Page.disable');
3129
+ this.page.removeAllListeners('Network');
3130
+ this.recordingWebSocketMessages = false;
3131
+ }
3132
+
3133
+ /**
3134
+ * Grab the recording WS messages
3135
+ *
3136
+ * @return { Array<any> }
3137
+ *
3138
+ */
3139
+ grabWebSocketMessages() {
3140
+ if (!this.recordingWebSocketMessages) {
3141
+ if (!this.recordedWebSocketMessagesAtLeastOnce) {
3142
+ throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.');
3143
+ }
3144
+ }
3145
+ return this.webSocketMessages;
3146
+ }
3147
+
3148
+ /**
3149
+ * Resets all recorded WS messages.
3150
+ */
3151
+ flushWebSocketMessages() {
3152
+ this.webSocketMessages = [];
3153
+ }
3154
+
3155
+ /**
3156
+ * Return a performance metric from the chrome cdp session.
3157
+ * Note: Chrome-only
3158
+ *
3159
+ * Examples:
3160
+ *
3161
+ * ```js
3162
+ * const metrics = await I.grabMetrics();
3163
+ *
3164
+ * // returned metrics
3165
+ *
3166
+ * [
3167
+ * { name: 'Timestamp', value: 1584904.203473 },
3168
+ * { name: 'AudioHandlers', value: 0 },
3169
+ * { name: 'AudioWorkletProcessors', value: 0 },
3170
+ * { name: 'Documents', value: 22 },
3171
+ * { name: 'Frames', value: 10 },
3172
+ * { name: 'JSEventListeners', value: 366 },
3173
+ * { name: 'LayoutObjects', value: 1240 },
3174
+ * { name: 'MediaKeySessions', value: 0 },
3175
+ * { name: 'MediaKeys', value: 0 },
3176
+ * { name: 'Nodes', value: 4505 },
3177
+ * { name: 'Resources', value: 141 },
3178
+ * { name: 'ContextLifecycleStateObservers', value: 34 },
3179
+ * { name: 'V8PerContextDatas', value: 4 },
3180
+ * { name: 'WorkerGlobalScopes', value: 0 },
3181
+ * { name: 'UACSSResources', value: 0 },
3182
+ * { name: 'RTCPeerConnections', value: 0 },
3183
+ * { name: 'ResourceFetchers', value: 22 },
3184
+ * { name: 'AdSubframes', value: 0 },
3185
+ * { name: 'DetachedScriptStates', value: 2 },
3186
+ * { name: 'ArrayBufferContents', value: 1 },
3187
+ * { name: 'LayoutCount', value: 0 },
3188
+ * { name: 'RecalcStyleCount', value: 0 },
3189
+ * { name: 'LayoutDuration', value: 0 },
3190
+ * { name: 'RecalcStyleDuration', value: 0 },
3191
+ * { name: 'DevToolsCommandDuration', value: 0.000013 },
3192
+ * { name: 'ScriptDuration', value: 0 },
3193
+ * { name: 'V8CompileDuration', value: 0 },
3194
+ * { name: 'TaskDuration', value: 0.000014 },
3195
+ * { name: 'TaskOtherDuration', value: 0.000001 },
3196
+ * { name: 'ThreadTime', value: 0.000046 },
3197
+ * { name: 'ProcessTime', value: 0.616852 },
3198
+ * { name: 'JSHeapUsedSize', value: 19004908 },
3199
+ * { name: 'JSHeapTotalSize', value: 26820608 },
3200
+ * { name: 'FirstMeaningfulPaint', value: 0 },
3201
+ * { name: 'DomContentLoaded', value: 1584903.690491 },
3202
+ * { name: 'NavigationStart', value: 1584902.841845 }
3203
+ * ]
3204
+ *
3205
+ * ```
3206
+ *
3207
+ * @return {Promise<Array<Object>>}
3208
+ */
3209
+ async grabMetrics() {
3210
+ const client = await this.page.context().newCDPSession(this.page);
3211
+ await client.send('Performance.enable');
3212
+ const perfMetricObject = await client.send('Performance.getMetrics');
3213
+ return perfMetricObject?.metrics;
3214
+ }
3215
+
3216
+ _getWebSocketMessage(payload) {
3217
+ if (payload.errorMessage) {
3218
+ return payload.errorMessage;
3219
+ }
3220
+
3221
+ return payload.response.payloadData;
3222
+ }
3223
+
3224
+ _getWebSocketLog(prefix, payload) {
3225
+ return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`;
3226
+ }
3227
+
3228
+ async getNewCDPSession() {
3229
+ return this.page.context().newCDPSession(this.page);
3230
+ }
3231
+
3232
+ _logWebsocketMessages(message) {
3233
+ this.webSocketMessages += message;
3234
+ }
2977
3235
  }
2978
3236
 
2979
3237
  module.exports = Playwright;
@@ -3028,7 +3286,7 @@ async function proceedClick(locator, context = null, options = {}) {
3028
3286
  assertElementExists(els, locator, 'Clickable element');
3029
3287
  }
3030
3288
 
3031
- highlightActiveElement.call(this, els[0], this.page);
3289
+ highlightActiveElement.call(this, els[0], await this._getContext());
3032
3290
 
3033
3291
  /*
3034
3292
  using the force true options itself but instead dispatching a click
@@ -3041,7 +3299,7 @@ async function proceedClick(locator, context = null, options = {}) {
3041
3299
  }
3042
3300
  const promises = [];
3043
3301
  if (options.waitForNavigation) {
3044
- promises.push(this.waitForNavigation());
3302
+ promises.push(this.waitForURL(/.*/, { waitUntil: options.waitForNavigation }));
3045
3303
  }
3046
3304
  promises.push(this._waitForAction());
3047
3305
 
@@ -3097,7 +3355,7 @@ async function proceedSee(assertType, text, context, strict = false) {
3097
3355
  if (strict) {
3098
3356
  return allText.map(elText => equals(description)[assertType](text, elText));
3099
3357
  }
3100
- return stringIncludes(description)[assertType](text, allText.join(' | '));
3358
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
3101
3359
  }
3102
3360
 
3103
3361
  async function findCheckable(locator, context) {
@@ -3206,7 +3464,16 @@ async function proceedSeeInField(assertType, field, value) {
3206
3464
  return proceedMultiple(els[0]);
3207
3465
  }
3208
3466
 
3209
- const fieldVal = await el.inputValue();
3467
+ let fieldVal;
3468
+
3469
+ try {
3470
+ fieldVal = await el.inputValue();
3471
+ } catch (e) {
3472
+ if (e.message.includes('Error: Node is not an <input>, <textarea> or <select> element')) {
3473
+ fieldVal = await el.innerText();
3474
+ }
3475
+ }
3476
+
3210
3477
  return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal);
3211
3478
  }
3212
3479
 
@@ -3443,7 +3710,7 @@ async function saveTraceForContext(context, name) {
3443
3710
  }
3444
3711
 
3445
3712
  function highlightActiveElement(element, context) {
3446
- if (!this.options.enableHighlight && !store.debugMode) return;
3713
+ if (!this.options.highlightElement && !store.debugMode) return;
3447
3714
 
3448
3715
  highlightElement(element, context);
3449
3716
  }