codeceptjs 3.0.6 → 3.1.2

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/CHANGELOG.md +92 -8
  2. package/README.md +9 -1
  3. package/bin/codecept.js +28 -17
  4. package/docs/build/Appium.js +69 -0
  5. package/docs/build/GraphQL.js +9 -10
  6. package/docs/build/Playwright.js +271 -63
  7. package/docs/build/Protractor.js +2 -0
  8. package/docs/build/Puppeteer.js +56 -18
  9. package/docs/build/REST.js +16 -3
  10. package/docs/build/WebDriver.js +82 -16
  11. package/docs/changelog.md +93 -9
  12. package/docs/configuration.md +15 -2
  13. package/docs/email.md +8 -8
  14. package/docs/examples.md +3 -3
  15. package/docs/helpers/Appium.md +66 -68
  16. package/docs/helpers/MockRequest.md +3 -3
  17. package/docs/helpers/Playwright.md +269 -203
  18. package/docs/helpers/Puppeteer.md +17 -1
  19. package/docs/helpers/REST.md +23 -9
  20. package/docs/helpers/WebDriver.md +3 -2
  21. package/docs/locators.md +27 -0
  22. package/docs/mobile.md +2 -1
  23. package/docs/nightmare.md +0 -5
  24. package/docs/parallel.md +14 -7
  25. package/docs/playwright.md +178 -11
  26. package/docs/plugins.md +61 -69
  27. package/docs/react.md +1 -1
  28. package/docs/reports.md +5 -4
  29. package/lib/actor.js +1 -2
  30. package/lib/codecept.js +13 -2
  31. package/lib/command/definitions.js +8 -1
  32. package/lib/command/interactive.js +4 -2
  33. package/lib/command/run-multiple/collection.js +4 -0
  34. package/lib/container.js +3 -3
  35. package/lib/helper/Appium.js +41 -0
  36. package/lib/helper/GraphQL.js +9 -10
  37. package/lib/helper/Playwright.js +218 -70
  38. package/lib/helper/Protractor.js +2 -0
  39. package/lib/helper/Puppeteer.js +56 -18
  40. package/lib/helper/REST.js +12 -0
  41. package/lib/helper/WebDriver.js +82 -16
  42. package/lib/helper/errors/ConnectionRefused.js +1 -1
  43. package/lib/helper/extras/Popup.js +1 -1
  44. package/lib/helper/extras/React.js +44 -32
  45. package/lib/interfaces/gherkin.js +1 -0
  46. package/lib/listener/exit.js +2 -4
  47. package/lib/listener/helpers.js +3 -4
  48. package/lib/locator.js +7 -0
  49. package/lib/mochaFactory.js +11 -6
  50. package/lib/output.js +5 -2
  51. package/lib/plugin/allure.js +7 -18
  52. package/lib/plugin/commentStep.js +1 -1
  53. package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
  54. package/lib/plugin/customLocator.js +2 -2
  55. package/lib/plugin/screenshotOnFail.js +5 -0
  56. package/lib/plugin/subtitles.js +88 -0
  57. package/lib/plugin/tryTo.js +1 -1
  58. package/lib/step.js +4 -2
  59. package/lib/ui.js +6 -2
  60. package/package.json +5 -4
  61. package/typings/index.d.ts +44 -21
  62. package/typings/types.d.ts +137 -16
@@ -19,6 +19,7 @@ const {
19
19
  screenshotOutputFolder,
20
20
  getNormalizedKeyAttributeValue,
21
21
  isModifierKey,
22
+ clearString,
22
23
  } = require('../utils');
23
24
  const {
24
25
  isColorProperty,
@@ -28,6 +29,7 @@ const ElementNotFound = require('./errors/ElementNotFound');
28
29
  const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
29
30
  const Popup = require('./extras/Popup');
30
31
  const Console = require('./extras/Console');
32
+ const findReact = require('./extras/React');
31
33
 
32
34
  let playwright;
33
35
  let perfTiming;
@@ -38,6 +40,7 @@ const consoleLogStore = new Console();
38
40
  const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
39
41
 
40
42
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
43
+
41
44
  /**
42
45
  * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
43
46
  *
@@ -63,6 +66,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
63
66
  * * `restart`: (optional, default: true) - restart browser between tests.
64
67
  * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
65
68
  * * `emulate`: (optional, default: {}) launch browser in device emulation mode.
69
+ * * `video`: (optional, default: false) enables video recording for failed tests; videos are saved into `output/videos` folder
70
+ * * `trace`: (optional, default: false) record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
66
71
  * * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
67
72
  * * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
68
73
  * * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
@@ -79,6 +84,22 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
79
84
  * * `chromium`: (optional) pass additional chromium options
80
85
  * * `electron`: (optional) pass additional electron options
81
86
  *
87
+ * #### Video Recording Customization
88
+ *
89
+ * By default, video is saved to `output/video` dir. You can customize this path by passing `dir` option to `recordVideo` option.
90
+ *
91
+ * * `video`: enables video recording for failed tests; videos are saved into `output/videos` folder
92
+ * * `keepVideoForPassedTests`: - save videos for passed tests
93
+ * * `recordVideo`: [additional options for videos customization](https://playwright.dev/docs/next/api/class-browser#browser-new-context)
94
+ *
95
+ * #### Trace Recording Customization
96
+ *
97
+ * Trace recording provides a complete information on test execution and includes DOM snapshots, screenshots, and network requests logged during run.
98
+ * Traces will be saved to `output/trace`
99
+ *
100
+ * * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
101
+ * * `keepTraceForPassedTests`: - save trace for passed tests
102
+ *
82
103
  * #### Example #1: Wait for 0 network connections.
83
104
  *
84
105
  * ```js
@@ -130,7 +151,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
130
151
  * Playwright: {
131
152
  * url: "http://localhost",
132
153
  * chromium: {
133
- * browserWSEndpoint: { wsEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a' }
154
+ * browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
134
155
  * }
135
156
  * }
136
157
  * }
@@ -210,6 +231,7 @@ class Playwright extends Helper {
210
231
  this.activeSessionName = '';
211
232
  this.isElectron = false;
212
233
  this.electronSessions = [];
234
+ this.storageState = null;
213
235
 
214
236
  // override defaults with config
215
237
  this._setConfig(config);
@@ -219,7 +241,6 @@ class Playwright extends Helper {
219
241
  const defaults = {
220
242
  // options to emulate context
221
243
  emulate: {},
222
-
223
244
  browser: 'chromium',
224
245
  waitForAction: 100,
225
246
  waitForTimeout: 1000,
@@ -248,10 +269,16 @@ class Playwright extends Helper {
248
269
  }
249
270
 
250
271
  _getOptionsForBrowser(config) {
251
- return config[config.browser] ? {
252
- ...config[config.browser],
253
- wsEndpoint: config[config.browser].browserWSEndpoint,
254
- } : {};
272
+ if (config[config.browser]) {
273
+ if (config[config.browser].browserWSEndpoint && config[config.browser].browserWSEndpoint.wsEndpoint) {
274
+ config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint;
275
+ }
276
+ return {
277
+ ...config[config.browser],
278
+ wsEndpoint: config[config.browser].browserWSEndpoint,
279
+ };
280
+ }
281
+ return {};
255
282
  }
256
283
 
257
284
  _setConfig(config) {
@@ -260,6 +287,12 @@ class Playwright extends Helper {
260
287
  headless: !this.options.show,
261
288
  ...this._getOptionsForBrowser(config),
262
289
  };
290
+ if (this.options.video) {
291
+ this.options.recordVideo = { size: parseWindowSize(this.options.windowSize) };
292
+ }
293
+ if (this.options.recordVideo && !this.options.recordVideo.dir) {
294
+ this.options.recordVideo.dir = `${global.output_dir}/videos/`;
295
+ }
263
296
  this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
264
297
  this.isElectron = this.options.browser === 'electron';
265
298
  this.userDataDir = this.playwrightOptions.userDataDir;
@@ -318,8 +351,37 @@ class Playwright extends Helper {
318
351
  return err.message.includes('context');
319
352
  },
320
353
  });
321
- if (this.options.restart && !this.options.manualStart) return this._startBrowser();
322
- if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
354
+ if (this.options.restart && !this.options.manualStart) await this._startBrowser();
355
+ if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
356
+
357
+ if (this.isElectron) {
358
+ this.browserContext = this.browser.context();
359
+ } else if (this.userDataDir) {
360
+ this.browserContext = this.browser;
361
+ } else {
362
+ const contextOptions = {
363
+ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors,
364
+ acceptDownloads: true,
365
+ ...this.options.emulate,
366
+ };
367
+ if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
368
+ if (this.storageState) contextOptions.storageState = this.storageState;
369
+ this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
370
+ }
371
+
372
+ let mainPage;
373
+ if (this.isElectron) {
374
+ mainPage = await this.browser.firstWindow();
375
+ } else {
376
+ const existingPages = await this.browserContext.pages();
377
+ mainPage = existingPages[0] || await this.browserContext.newPage();
378
+ }
379
+ targetCreatedHandler.call(this, mainPage);
380
+
381
+ await this._setPage(mainPage);
382
+
383
+ if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true });
384
+
323
385
  return this.browser;
324
386
  }
325
387
 
@@ -332,43 +394,24 @@ class Playwright extends Helper {
332
394
  return;
333
395
  }
334
396
 
397
+ if (this.options.restart) {
398
+ this.isRunning = false;
399
+ return this._stopBrowser();
400
+ }
401
+
335
402
  // close other sessions
336
403
  try {
337
404
  const contexts = await this.browser.contexts();
338
- contexts.shift();
405
+ const currentContext = contexts[0];
406
+ if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
407
+ this.storageState = await currentContext.storageState();
408
+ }
339
409
 
340
410
  await Promise.all(contexts.map(c => c.close()));
341
411
  } catch (e) {
342
412
  console.log(e);
343
413
  }
344
414
 
345
- if (this.options.restart) {
346
- this.isRunning = false;
347
- return this._stopBrowser();
348
- }
349
-
350
- // ensure current page is in default context
351
- if (this.page) {
352
- const existingPages = await this.browserContext.pages();
353
- await this._setPage(existingPages[0]);
354
- }
355
-
356
- if (this.options.keepBrowserState) return;
357
-
358
- if (!this.options.keepCookies) {
359
- this.debugSection('Session', 'cleaning cookies and localStorage');
360
- await this.clearCookie();
361
- }
362
- const currentUrl = await this.grabCurrentUrl();
363
-
364
- if (currentUrl.startsWith('http')) {
365
- await this.executeScript('localStorage.clear();').catch((err) => {
366
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
367
- });
368
- await this.executeScript('sessionStorage.clear();').catch((err) => {
369
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
370
- });
371
- }
372
415
  // await this.closeOtherTabs();
373
416
  return this.browser;
374
417
  }
@@ -515,6 +558,7 @@ class Playwright extends Helper {
515
558
  if (!page) return;
516
559
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
517
560
  this.context = await this.page;
561
+ this.contextLocator = null;
518
562
  if (this.config.browser === 'chrome') {
519
563
  await page.bringToFront();
520
564
  }
@@ -531,6 +575,7 @@ class Playwright extends Helper {
531
575
  if (!page) {
532
576
  return;
533
577
  }
578
+ page.removeAllListeners('dialog');
534
579
  page.on('dialog', async (dialog) => {
535
580
  popupStore.popup = dialog;
536
581
  const action = popupStore.actionType || this.options.defaultPopupAction;
@@ -577,7 +622,7 @@ class Playwright extends Helper {
577
622
  this.browser = await playwright._electron.launch(this.playwrightOptions);
578
623
  } else if (this.isRemoteBrowser) {
579
624
  try {
580
- this.browser = await playwright[this.options.browser].connect(this.playwrightOptions.browserWSEndpoint);
625
+ this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
581
626
  } catch (err) {
582
627
  if (err.toString().indexOf('ECONNREFUSED')) {
583
628
  throw new RemoteBrowserConnectionRefused(err);
@@ -595,26 +640,6 @@ class Playwright extends Helper {
595
640
  this.debugSection('Url', target.url());
596
641
  });
597
642
 
598
- if (this.isElectron) {
599
- this.browserContext = this.browser.context();
600
- } else if (this.userDataDir) {
601
- this.browserContext = this.browser;
602
- } else {
603
- this.browserContext = await this.browser.newContext({ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate });// Adding the HTTPSError ignore in the context so that we can ignore those errors
604
- }
605
-
606
- let mainPage;
607
- if (this.isElectron) {
608
- mainPage = await this.browser.firstWindow();
609
- } else {
610
- const existingPages = await this.browserContext.pages();
611
- mainPage = existingPages[0] || await this.browserContext.newPage();
612
- }
613
- targetCreatedHandler.call(this, mainPage);
614
-
615
- await this._setPage(mainPage);
616
- await this.closeOtherTabs();
617
-
618
643
  this.isRunning = true;
619
644
  }
620
645
 
@@ -656,6 +681,7 @@ class Playwright extends Helper {
656
681
  const els = await this._locate(locator);
657
682
  assertElementExists(els, locator);
658
683
  this.context = els[0];
684
+ this.contextLocator = locator;
659
685
 
660
686
  this.withinLocator = new Locator(locator);
661
687
  }
@@ -663,6 +689,7 @@ class Playwright extends Helper {
663
689
  async _withinEnd() {
664
690
  this.withinLocator = null;
665
691
  this.context = await this.page;
692
+ this.contextLocator = null;
666
693
  }
667
694
 
668
695
  _extractDataFromPerformanceTiming(timing, ...dataNames) {
@@ -1046,7 +1073,7 @@ class Playwright extends Helper {
1046
1073
  */
1047
1074
  async seeElement(locator) {
1048
1075
  let els = await this._locate(locator);
1049
- els = await Promise.all(els.map(el => el.boundingBox()));
1076
+ els = await Promise.all(els.map(el => el.isVisible()));
1050
1077
  return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1051
1078
  }
1052
1079
 
@@ -1056,7 +1083,7 @@ class Playwright extends Helper {
1056
1083
  */
1057
1084
  async dontSeeElement(locator) {
1058
1085
  let els = await this._locate(locator);
1059
- els = await Promise.all(els.map(el => el.boundingBox()));
1086
+ els = await Promise.all(els.map(el => el.isVisible()));
1060
1087
  return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1061
1088
  }
1062
1089
 
@@ -1360,7 +1387,7 @@ class Playwright extends Helper {
1360
1387
  */
1361
1388
  async grabNumberOfVisibleElements(locator) {
1362
1389
  let els = await this._locate(locator);
1363
- els = await Promise.all(els.map(el => el.boundingBox()));
1390
+ els = await Promise.all(els.map(el => el.isVisible()));
1364
1391
  return els.filter(v => v).length;
1365
1392
  }
1366
1393
 
@@ -1527,6 +1554,7 @@ class Playwright extends Helper {
1527
1554
  async clearCookie() {
1528
1555
  // Playwright currently doesn't support to delete a certain cookie
1529
1556
  // https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browsercontext
1557
+ if (!this.browserContext) return;
1530
1558
  return this.browserContext.clearCookies();
1531
1559
  }
1532
1560
 
@@ -1562,15 +1590,32 @@ class Playwright extends Helper {
1562
1590
  return context.evaluate.apply(context, [fn, arg]);
1563
1591
  }
1564
1592
 
1593
+ /**
1594
+ * Grab Locator if called within Context
1595
+ *
1596
+ * @param {*} locator
1597
+ */
1598
+ _contextLocator(locator) {
1599
+ locator = buildLocatorString(new Locator(locator, 'css'));
1600
+
1601
+ if (this.contextLocator) {
1602
+ const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
1603
+ locator = `${contextLocator} >> ${locator}`;
1604
+ }
1605
+
1606
+ return locator;
1607
+ }
1608
+
1565
1609
  /**
1566
1610
  * {{> grabTextFrom }}
1567
1611
  *
1568
1612
  */
1569
1613
  async grabTextFrom(locator) {
1570
- const texts = await this.grabTextFromAll(locator);
1571
- assertElementExists(texts, locator);
1572
- this.debugSection('Text', texts[0]);
1573
- return texts[0];
1614
+ locator = this._contextLocator(locator);
1615
+ const text = await this.page.textContent(locator);
1616
+ assertElementExists(text, locator);
1617
+ this.debugSection('Text', text);
1618
+ return text;
1574
1619
  }
1575
1620
 
1576
1621
  /**
@@ -1804,8 +1849,37 @@ class Playwright extends Helper {
1804
1849
  return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
1805
1850
  }
1806
1851
 
1807
- async _failed() {
1852
+ async _failed(test) {
1808
1853
  await this._withinEnd();
1854
+ if (this.options.recordVideo && this.page.video()) {
1855
+ test.artifacts.video = await this.page.video().path();
1856
+ }
1857
+
1858
+ if (this.options.trace) {
1859
+ const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
1860
+ await this.browserContext.tracing.stop({ path });
1861
+ test.artifacts.trace = path;
1862
+ }
1863
+ }
1864
+
1865
+ async _passed(test) {
1866
+ if (this.options.recordVideo && this.page.video()) {
1867
+ if (this.options.keepVideoForPassedTests) {
1868
+ test.artifacts.video = await this.page.video().path();
1869
+ } else {
1870
+ this.page.video().delete().catch(e => {});
1871
+ }
1872
+ }
1873
+
1874
+ if (this.options.trace) {
1875
+ if (this.options.keepTraceForPassedTests) {
1876
+ const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
1877
+ await this.browserContext.tracing.stop({ path });
1878
+ test.artifacts.trace = path;
1879
+ } else {
1880
+ await this.browserContext.tracing.stop();
1881
+ }
1882
+ }
1809
1883
  }
1810
1884
 
1811
1885
  /**
@@ -2093,6 +2167,7 @@ class Playwright extends Helper {
2093
2167
 
2094
2168
  if (locator >= 0 && locator < childFrames.length) {
2095
2169
  this.context = childFrames[locator];
2170
+ this.contextLocator = locator;
2096
2171
  } else {
2097
2172
  throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2098
2173
  }
@@ -2100,6 +2175,7 @@ class Playwright extends Helper {
2100
2175
  }
2101
2176
  if (!locator) {
2102
2177
  this.context = this.page;
2178
+ this.contextLocator = null;
2103
2179
  return;
2104
2180
  }
2105
2181
 
@@ -2110,8 +2186,10 @@ class Playwright extends Helper {
2110
2186
 
2111
2187
  if (contentFrame) {
2112
2188
  this.context = contentFrame;
2189
+ this.contextLocator = null;
2113
2190
  } else {
2114
2191
  this.context = els[0];
2192
+ this.contextLocator = locator;
2115
2193
  }
2116
2194
  }
2117
2195
 
@@ -2209,6 +2287,39 @@ class Playwright extends Helper {
2209
2287
  if (prop) return rect[prop];
2210
2288
  return rect;
2211
2289
  }
2290
+
2291
+ /**
2292
+ * Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
2293
+ *
2294
+ * ```js
2295
+ * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
2296
+ * ```
2297
+ * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
2298
+ *
2299
+ * @param {string} [url] URL, regex or pattern for to match URL
2300
+ * @param {function} [handler] a function to process request
2301
+ *
2302
+ */
2303
+ async mockRoute(url, handler) {
2304
+ return this.browserContext.route(...arguments);
2305
+ }
2306
+
2307
+ /**
2308
+ * Stops network mocking created by `mockRoute`.
2309
+ *
2310
+ * ```js
2311
+ * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
2312
+ * I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
2313
+ * ```
2314
+ * If no handler is passed, all mock requests for the rote are disabled.
2315
+ *
2316
+ * @param {string} [url] URL, regex or pattern for to match URL
2317
+ * @param {function} [handler] a function to process request
2318
+ *
2319
+ */
2320
+ async stopMockingRoute(url, handler) {
2321
+ return this.browserContext.unroute(...arguments);
2322
+ }
2212
2323
  }
2213
2324
 
2214
2325
  module.exports = Playwright;
@@ -2224,10 +2335,24 @@ function buildLocatorString(locator) {
2224
2335
  }
2225
2336
 
2226
2337
  async function findElements(matcher, locator) {
2338
+ if (locator.react) return findReact(matcher, locator);
2227
2339
  locator = new Locator(locator, 'css');
2228
2340
  return matcher.$$(buildLocatorString(locator));
2229
2341
  }
2230
2342
 
2343
+ async function getVisibleElements(elements) {
2344
+ const visibleElements = [];
2345
+ for (const element of elements) {
2346
+ if (await element.isVisible()) {
2347
+ visibleElements.push(element);
2348
+ }
2349
+ }
2350
+ if (visibleElements.length === 0) {
2351
+ return elements;
2352
+ }
2353
+ return visibleElements;
2354
+ }
2355
+
2231
2356
  async function proceedClick(locator, context = null, options = {}) {
2232
2357
  let matcher = await this._getContext();
2233
2358
  if (context) {
@@ -2247,7 +2372,8 @@ async function proceedClick(locator, context = null, options = {}) {
2247
2372
  if (options.force) {
2248
2373
  await els[0].dispatchEvent('click');
2249
2374
  } else {
2250
- await els[0].click(options);
2375
+ const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
2376
+ await element.click(options);
2251
2377
  }
2252
2378
  const promises = [];
2253
2379
  if (options.waitForNavigation) {
@@ -2258,6 +2384,8 @@ async function proceedClick(locator, context = null, options = {}) {
2258
2384
  }
2259
2385
 
2260
2386
  async function findClickable(matcher, locator) {
2387
+ if (locator.react) return findReact(matcher, locator);
2388
+
2261
2389
  locator = new Locator(locator);
2262
2390
  if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
2263
2391
 
@@ -2364,6 +2492,15 @@ async function findFields(locator) {
2364
2492
  }
2365
2493
 
2366
2494
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
2495
+ // modern drag and drop in Playwright
2496
+ if (this.page.dragAndDrop) {
2497
+ const source = new Locator(sourceLocator);
2498
+ const dest = new Locator(destinationLocator);
2499
+ if (source.isBasic() && dest.isBasic()) {
2500
+ return this.page.dragAndDrop(source.simplify(), dest.simplify());
2501
+ }
2502
+ }
2503
+
2367
2504
  const src = await this._locate(sourceLocator);
2368
2505
  assertElementExists(src, sourceLocator, 'Source Element');
2369
2506
 
@@ -2505,11 +2642,13 @@ async function targetCreatedHandler(page) {
2505
2642
  // we are inside iframe?
2506
2643
  const frameEl = await this.context.frameElement();
2507
2644
  this.context = await frameEl.contentFrame();
2645
+ this.contextLocator = null;
2508
2646
  return;
2509
2647
  }
2510
2648
  // if context element was in iframe - keep it
2511
2649
  // if (await this.context.ownerFrame()) return;
2512
2650
  this.context = page;
2651
+ this.contextLocator = null;
2513
2652
  });
2514
2653
  });
2515
2654
  page.on('console', (msg) => {
@@ -2521,11 +2660,20 @@ async function targetCreatedHandler(page) {
2521
2660
  await page.setUserAgent(this.options.userAgent);
2522
2661
  }
2523
2662
  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
2524
- const dimensions = this.options.windowSize.split('x');
2525
- const width = parseInt(dimensions[0], 10);
2526
- const height = parseInt(dimensions[1], 10);
2527
- await page.setViewportSize({ width, height });
2663
+ await page.setViewportSize(parseWindowSize(this.options.windowSize));
2664
+ }
2665
+ }
2666
+
2667
+ function parseWindowSize(windowSize) {
2668
+ if (!windowSize) return { width: 800, height: 600 };
2669
+ const dimensions = windowSize.split('x');
2670
+ if (dimensions.length < 2 || windowSize === 'maximize') {
2671
+ console.log('Invalid window size, setting window to default values');
2672
+ return { width: 800, height: 600 }; // invalid size
2528
2673
  }
2674
+ const width = parseInt(dimensions[0], 10);
2675
+ const height = parseInt(dimensions[1], 10);
2676
+ return { width, height };
2529
2677
  }
2530
2678
 
2531
2679
  // List of key values to key definitions
@@ -124,6 +124,8 @@ class Protractor extends Helper {
124
124
 
125
125
  this.isRunning = false;
126
126
  this._setConfig(config);
127
+
128
+ console.log('Protractor helper is deprecated as well as Protractor itself.\nThis helper will be removed in next major release');
127
129
  }
128
130
 
129
131
  _validateConfig(config) {