codeceptjs 3.5.5 → 3.5.7-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.
- package/CHANGELOG.md +63 -0
- package/docs/bdd.md +11 -7
- package/docs/build/ApiDataFactory.js +2 -1
- package/docs/build/Appium.js +25 -23
- package/docs/build/Expect.js +422 -0
- package/docs/build/FileSystem.js +1 -1
- package/docs/build/Nightmare.js +53 -56
- package/docs/build/Playwright.js +209 -135
- package/docs/build/Protractor.js +66 -69
- package/docs/build/Puppeteer.js +83 -83
- package/docs/build/TestCafe.js +56 -55
- package/docs/build/WebDriver.js +85 -86
- package/docs/changelog.md +63 -0
- package/docs/commands.md +12 -0
- package/docs/helpers/Appium.md +50 -32
- package/docs/helpers/Expect.md +275 -0
- package/docs/helpers/FileSystem.md +1 -1
- package/docs/helpers/Nightmare.md +141 -94
- package/docs/helpers/Playwright.md +281 -212
- package/docs/helpers/Protractor.md +229 -169
- package/docs/helpers/Puppeteer.md +257 -186
- package/docs/helpers/TestCafe.md +201 -149
- package/docs/helpers/WebDriver.md +253 -179
- package/docs/mobile.md +17 -21
- package/docs/plugins.md +35 -1
- package/docs/webapi/amOnPage.mustache +1 -1
- package/docs/webapi/appendField.mustache +1 -1
- package/docs/webapi/attachFile.mustache +1 -1
- package/docs/webapi/blur.mustache +1 -0
- package/docs/webapi/checkOption.mustache +1 -1
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/clearField.mustache +1 -1
- package/docs/webapi/click.mustache +1 -1
- package/docs/webapi/clickLink.mustache +1 -1
- package/docs/webapi/closeCurrentTab.mustache +1 -1
- package/docs/webapi/closeOtherTabs.mustache +1 -1
- package/docs/webapi/dontSee.mustache +1 -1
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/dontSeeCookie.mustache +1 -1
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +1 -1
- package/docs/webapi/dontSeeElement.mustache +1 -1
- package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
- package/docs/webapi/dontSeeInCurrentUrl.mustache +1 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/dontSeeInSource.mustache +1 -1
- package/docs/webapi/dontSeeInTitle.mustache +1 -1
- package/docs/webapi/doubleClick.mustache +1 -1
- package/docs/webapi/downloadFile.mustache +1 -1
- package/docs/webapi/dragAndDrop.mustache +1 -1
- package/docs/webapi/dragSlider.mustache +1 -1
- package/docs/webapi/executeAsyncScript.mustache +0 -2
- package/docs/webapi/executeScript.mustache +0 -2
- package/docs/webapi/fillField.mustache +1 -1
- package/docs/webapi/focus.mustache +1 -0
- package/docs/webapi/forceClick.mustache +1 -1
- package/docs/webapi/forceRightClick.mustache +1 -1
- package/docs/webapi/grabCookie.mustache +1 -1
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +1 -1
- package/docs/webapi/moveCursorTo.mustache +1 -1
- package/docs/webapi/openNewTab.mustache +1 -1
- package/docs/webapi/pressKey.mustache +1 -1
- package/docs/webapi/pressKeyDown.mustache +1 -1
- package/docs/webapi/pressKeyUp.mustache +1 -1
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
- package/docs/webapi/refreshPage.mustache +1 -1
- package/docs/webapi/resizeWindow.mustache +1 -1
- package/docs/webapi/rightClick.mustache +1 -1
- package/docs/webapi/saveElementScreenshot.mustache +1 -1
- package/docs/webapi/saveScreenshot.mustache +1 -1
- package/docs/webapi/say.mustache +1 -1
- package/docs/webapi/scrollIntoView.mustache +1 -1
- package/docs/webapi/scrollPageToBottom.mustache +1 -1
- package/docs/webapi/scrollPageToTop.mustache +1 -1
- package/docs/webapi/scrollTo.mustache +1 -1
- package/docs/webapi/see.mustache +1 -1
- package/docs/webapi/seeAttributesOnElements.mustache +1 -1
- package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/seeCookie.mustache +1 -1
- package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
- package/docs/webapi/seeCurrentUrlEquals.mustache +1 -1
- package/docs/webapi/seeElement.mustache +1 -1
- package/docs/webapi/seeElementInDOM.mustache +1 -1
- package/docs/webapi/seeInCurrentUrl.mustache +1 -1
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/webapi/seeInPopup.mustache +1 -1
- package/docs/webapi/seeInSource.mustache +1 -1
- package/docs/webapi/seeInTitle.mustache +1 -1
- package/docs/webapi/seeNumberOfElements.mustache +1 -1
- package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/seeTextEquals.mustache +1 -1
- package/docs/webapi/seeTitleEquals.mustache +1 -1
- package/docs/webapi/selectOption.mustache +1 -1
- package/docs/webapi/setCookie.mustache +1 -1
- package/docs/webapi/setGeoLocation.mustache +1 -1
- package/docs/webapi/switchTo.mustache +1 -1
- package/docs/webapi/switchToNextTab.mustache +1 -1
- package/docs/webapi/switchToPreviousTab.mustache +1 -1
- package/docs/webapi/type.mustache +1 -1
- package/docs/webapi/uncheckOption.mustache +1 -1
- package/docs/webapi/wait.mustache +1 -1
- package/docs/webapi/waitForClickable.mustache +1 -1
- package/docs/webapi/waitForDetached.mustache +1 -1
- package/docs/webapi/waitForElement.mustache +1 -1
- package/docs/webapi/waitForEnabled.mustache +1 -1
- package/docs/webapi/waitForFunction.mustache +1 -1
- package/docs/webapi/waitForInvisible.mustache +1 -1
- package/docs/webapi/waitForText.mustache +1 -1
- package/docs/webapi/waitForValue.mustache +1 -1
- package/docs/webapi/waitForVisible.mustache +1 -1
- package/docs/webapi/waitInUrl.mustache +1 -1
- package/docs/webapi/waitNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/waitToHide.mustache +1 -1
- package/docs/webapi/waitUrlEquals.mustache +1 -1
- package/lib/ai.js +12 -3
- package/lib/cli.js +3 -1
- package/lib/codecept.js +3 -0
- package/lib/command/dryRun.js +2 -1
- package/lib/command/info.js +24 -0
- package/lib/command/run-workers.js +3 -2
- package/lib/command/run.js +3 -2
- package/lib/data/context.js +14 -6
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/Appium.js +7 -5
- package/lib/helper/Expect.js +422 -0
- package/lib/helper/FileSystem.js +1 -1
- package/lib/helper/Playwright.js +134 -64
- package/lib/helper/Puppeteer.js +6 -6
- package/lib/helper/WebDriver.js +4 -4
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/html.js +3 -3
- package/lib/interfaces/gherkin.js +21 -2
- package/lib/output.js +1 -1
- package/lib/pause.js +6 -5
- package/lib/plugin/autoLogin.js +35 -8
- package/lib/plugin/heal.js +40 -7
- package/lib/plugin/retryTo.js +0 -2
- package/lib/plugin/tryTo.js +0 -3
- package/lib/recorder.js +12 -5
- package/lib/session.js +1 -1
- package/package.json +24 -17
- package/translations/de-DE.js +5 -0
- package/translations/fr-FR.js +14 -1
- package/translations/it-IT.js +1 -0
- package/translations/ja-JP.js +5 -0
- package/translations/pl-PL.js +5 -0
- package/translations/pt-BR.js +1 -0
- package/translations/ru-RU.js +1 -0
- package/translations/zh-CN.js +5 -0
- package/translations/zh-TW.js +5 -0
- package/typings/promiseBasedTypes.d.ts +905 -863
- package/typings/types.d.ts +909 -850
package/lib/helper/Playwright.js
CHANGED
|
@@ -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
|
|
|
@@ -827,8 +826,9 @@ class Playwright extends Helper {
|
|
|
827
826
|
|
|
828
827
|
async _stopBrowser() {
|
|
829
828
|
this.withinLocator = null;
|
|
830
|
-
this._setPage(null);
|
|
829
|
+
await this._setPage(null);
|
|
831
830
|
this.context = null;
|
|
831
|
+
this.frame = null;
|
|
832
832
|
popupStore.clear();
|
|
833
833
|
await this.browser.close();
|
|
834
834
|
}
|
|
@@ -867,6 +867,7 @@ class Playwright extends Helper {
|
|
|
867
867
|
this.withinLocator = null;
|
|
868
868
|
this.context = await this.page;
|
|
869
869
|
this.contextLocator = null;
|
|
870
|
+
this.frame = null;
|
|
870
871
|
}
|
|
871
872
|
|
|
872
873
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -941,14 +942,14 @@ class Playwright extends Helper {
|
|
|
941
942
|
* Set headers for all next requests
|
|
942
943
|
*
|
|
943
944
|
* ```js
|
|
944
|
-
* I.
|
|
945
|
+
* I.setPlaywrightRequestHeaders({
|
|
945
946
|
* 'X-Sent-By': 'CodeceptJS',
|
|
946
947
|
* });
|
|
947
948
|
* ```
|
|
948
949
|
*
|
|
949
950
|
* @param {object} customHeaders headers to set
|
|
950
951
|
*/
|
|
951
|
-
async
|
|
952
|
+
async setPlaywrightRequestHeaders(customHeaders) {
|
|
952
953
|
if (!customHeaders) {
|
|
953
954
|
throw new Error('Cannot send empty headers.');
|
|
954
955
|
}
|
|
@@ -1156,6 +1157,9 @@ class Playwright extends Helper {
|
|
|
1156
1157
|
*/
|
|
1157
1158
|
async _locate(locator) {
|
|
1158
1159
|
const context = await this.context || await this._getContext();
|
|
1160
|
+
|
|
1161
|
+
if (this.frame) return findElements(this.frame, locator);
|
|
1162
|
+
|
|
1159
1163
|
return findElements(context, locator);
|
|
1160
1164
|
}
|
|
1161
1165
|
|
|
@@ -1579,7 +1583,7 @@ class Playwright extends Helper {
|
|
|
1579
1583
|
|
|
1580
1584
|
await el.clear();
|
|
1581
1585
|
|
|
1582
|
-
highlightActiveElement.call(this, el
|
|
1586
|
+
await highlightActiveElement.call(this, el);
|
|
1583
1587
|
|
|
1584
1588
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1585
1589
|
|
|
@@ -1609,7 +1613,7 @@ class Playwright extends Helper {
|
|
|
1609
1613
|
|
|
1610
1614
|
const el = els[0];
|
|
1611
1615
|
|
|
1612
|
-
highlightActiveElement.call(this, el
|
|
1616
|
+
await highlightActiveElement.call(this, el);
|
|
1613
1617
|
|
|
1614
1618
|
await el.clear();
|
|
1615
1619
|
|
|
@@ -1624,7 +1628,7 @@ class Playwright extends Helper {
|
|
|
1624
1628
|
async appendField(field, value) {
|
|
1625
1629
|
const els = await findFields.call(this, field);
|
|
1626
1630
|
assertElementExists(els, field, 'Field');
|
|
1627
|
-
highlightActiveElement.call(this, els[0]
|
|
1631
|
+
await highlightActiveElement.call(this, els[0]);
|
|
1628
1632
|
await els[0].press('End');
|
|
1629
1633
|
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1630
1634
|
return this._waitForAction();
|
|
@@ -1670,7 +1674,7 @@ class Playwright extends Helper {
|
|
|
1670
1674
|
assertElementExists(els, select, 'Selectable field');
|
|
1671
1675
|
const el = els[0];
|
|
1672
1676
|
|
|
1673
|
-
highlightActiveElement.call(this, el
|
|
1677
|
+
await highlightActiveElement.call(this, el);
|
|
1674
1678
|
|
|
1675
1679
|
if (!Array.isArray(option)) option = [option];
|
|
1676
1680
|
|
|
@@ -1882,11 +1886,11 @@ class Playwright extends Helper {
|
|
|
1882
1886
|
* @returns {Promise<any>}
|
|
1883
1887
|
*/
|
|
1884
1888
|
async executeScript(fn, arg) {
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1889
|
+
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
1890
|
+
// switching to iframe context
|
|
1891
|
+
return this.context.locator(':root').evaluate(fn, arg);
|
|
1888
1892
|
}
|
|
1889
|
-
return
|
|
1893
|
+
return this.page.evaluate.apply(this.page, [fn, arg]);
|
|
1890
1894
|
}
|
|
1891
1895
|
|
|
1892
1896
|
/**
|
|
@@ -2361,10 +2365,11 @@ class Playwright extends Helper {
|
|
|
2361
2365
|
locator = new Locator(locator, 'css');
|
|
2362
2366
|
|
|
2363
2367
|
const context = await this._getContext();
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
+
try {
|
|
2369
|
+
await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' });
|
|
2370
|
+
} catch (e) {
|
|
2371
|
+
throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`);
|
|
2372
|
+
}
|
|
2368
2373
|
}
|
|
2369
2374
|
|
|
2370
2375
|
/**
|
|
@@ -2376,10 +2381,26 @@ class Playwright extends Helper {
|
|
|
2376
2381
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2377
2382
|
locator = new Locator(locator, 'css');
|
|
2378
2383
|
const context = await this._getContext();
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2384
|
+
let count = 0;
|
|
2385
|
+
|
|
2386
|
+
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2387
|
+
let waiter;
|
|
2388
|
+
if (this.frame) {
|
|
2389
|
+
do {
|
|
2390
|
+
waiter = await this.frame.locator(buildLocatorString(locator)).first().isVisible();
|
|
2391
|
+
await this.wait(1);
|
|
2392
|
+
count += 1000;
|
|
2393
|
+
if (waiter) break;
|
|
2394
|
+
} while (count <= waitTimeout);
|
|
2395
|
+
|
|
2396
|
+
if (!waiter) throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec.`);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
try {
|
|
2400
|
+
await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'visible' });
|
|
2401
|
+
} catch (e) {
|
|
2402
|
+
throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${e.message}`);
|
|
2403
|
+
}
|
|
2383
2404
|
}
|
|
2384
2405
|
|
|
2385
2406
|
/**
|
|
@@ -2389,10 +2410,27 @@ class Playwright extends Helper {
|
|
|
2389
2410
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2390
2411
|
locator = new Locator(locator, 'css');
|
|
2391
2412
|
const context = await this._getContext();
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2413
|
+
let waiter;
|
|
2414
|
+
let count = 0;
|
|
2415
|
+
|
|
2416
|
+
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2417
|
+
if (this.frame) {
|
|
2418
|
+
do {
|
|
2419
|
+
waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden();
|
|
2420
|
+
await this.wait(1);
|
|
2421
|
+
count += 1000;
|
|
2422
|
+
if (waiter) break;
|
|
2423
|
+
} while (count <= waitTimeout);
|
|
2424
|
+
|
|
2425
|
+
if (!waiter) throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec.`);
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
try {
|
|
2430
|
+
await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' });
|
|
2431
|
+
} catch (e) {
|
|
2432
|
+
throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${e.message}`);
|
|
2433
|
+
}
|
|
2396
2434
|
}
|
|
2397
2435
|
|
|
2398
2436
|
/**
|
|
@@ -2402,13 +2440,29 @@ class Playwright extends Helper {
|
|
|
2402
2440
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2403
2441
|
locator = new Locator(locator, 'css');
|
|
2404
2442
|
const context = await this._getContext();
|
|
2405
|
-
|
|
2443
|
+
let waiter;
|
|
2444
|
+
let count = 0;
|
|
2445
|
+
|
|
2446
|
+
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2447
|
+
if (this.frame) {
|
|
2448
|
+
do {
|
|
2449
|
+
waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden();
|
|
2450
|
+
await this.wait(1);
|
|
2451
|
+
count += 1000;
|
|
2452
|
+
if (waiter) break;
|
|
2453
|
+
} while (count <= waitTimeout);
|
|
2454
|
+
|
|
2455
|
+
if (!waiter) throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec.`);
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
return context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' }).catch((err) => {
|
|
2406
2460
|
throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`);
|
|
2407
2461
|
});
|
|
2408
2462
|
}
|
|
2409
2463
|
|
|
2410
2464
|
async _getContext() {
|
|
2411
|
-
if (this.context && this.context.constructor.name === '
|
|
2465
|
+
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2412
2466
|
return this.context;
|
|
2413
2467
|
}
|
|
2414
2468
|
return this.page;
|
|
@@ -2469,7 +2523,12 @@ class Playwright extends Helper {
|
|
|
2469
2523
|
if (context) {
|
|
2470
2524
|
const locator = new Locator(context, 'css');
|
|
2471
2525
|
if (!locator.isXPath()) {
|
|
2472
|
-
|
|
2526
|
+
try {
|
|
2527
|
+
await contextObject.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`).first().waitFor({ timeout: waitTimeout, state: 'visible' });
|
|
2528
|
+
} catch (e) {
|
|
2529
|
+
console.log(e);
|
|
2530
|
+
throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${e.message}`);
|
|
2531
|
+
}
|
|
2473
2532
|
}
|
|
2474
2533
|
|
|
2475
2534
|
if (locator.isXPath()) {
|
|
@@ -2481,11 +2540,18 @@ class Playwright extends Helper {
|
|
|
2481
2540
|
}, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
|
|
2482
2541
|
}
|
|
2483
2542
|
} else {
|
|
2484
|
-
|
|
2543
|
+
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2544
|
+
// eslint-disable-next-line no-lonely-if
|
|
2545
|
+
const _contextObject = this.frame ? this.frame : contextObject;
|
|
2546
|
+
let count = 0;
|
|
2547
|
+
do {
|
|
2548
|
+
waiter = await _contextObject.locator(`:has-text('${text}')`).first().isVisible();
|
|
2549
|
+
await this.wait(1);
|
|
2550
|
+
count += 1000;
|
|
2551
|
+
} while (count <= waitTimeout);
|
|
2552
|
+
|
|
2553
|
+
if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
|
|
2485
2554
|
}
|
|
2486
|
-
return waiter.catch((err) => {
|
|
2487
|
-
throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`);
|
|
2488
|
-
});
|
|
2489
2555
|
}
|
|
2490
2556
|
|
|
2491
2557
|
/**
|
|
@@ -2535,37 +2601,37 @@ class Playwright extends Helper {
|
|
|
2535
2601
|
}
|
|
2536
2602
|
|
|
2537
2603
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2538
|
-
this.context =
|
|
2604
|
+
this.context = await this.page.frameLocator('iframe').nth(locator);
|
|
2539
2605
|
this.contextLocator = locator;
|
|
2540
2606
|
} else {
|
|
2541
2607
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2542
2608
|
}
|
|
2543
2609
|
return;
|
|
2544
2610
|
}
|
|
2545
|
-
let contentFrame;
|
|
2546
2611
|
|
|
2547
2612
|
if (!locator) {
|
|
2548
|
-
this.context =
|
|
2613
|
+
this.context = this.page;
|
|
2549
2614
|
this.contextLocator = null;
|
|
2615
|
+
this.frame = null;
|
|
2550
2616
|
return;
|
|
2551
2617
|
}
|
|
2552
2618
|
|
|
2553
2619
|
// iframe by selector
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2620
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
2621
|
+
const frame = await this._locateElement(locator);
|
|
2622
|
+
|
|
2623
|
+
if (!frame) {
|
|
2624
|
+
throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`);
|
|
2557
2625
|
}
|
|
2558
2626
|
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
// get content of the iframe using its name
|
|
2564
|
-
} else if (locator.value.toLowerCase().includes('name=')) {
|
|
2565
|
-
const frameName = locator.value.split('=')[1].replace(/"/g, '').replaceAll(/]/g, '');
|
|
2566
|
-
contentFrame = await this.page.frame(frameName);
|
|
2627
|
+
if (this.frame) {
|
|
2628
|
+
this.frame = await this.frame.frameLocator(locator);
|
|
2629
|
+
} else {
|
|
2630
|
+
this.frame = await this.page.frameLocator(locator);
|
|
2567
2631
|
}
|
|
2568
2632
|
|
|
2633
|
+
const contentFrame = this.frame;
|
|
2634
|
+
|
|
2569
2635
|
if (contentFrame) {
|
|
2570
2636
|
this.context = contentFrame;
|
|
2571
2637
|
this.contextLocator = null;
|
|
@@ -2644,17 +2710,21 @@ class Playwright extends Helper {
|
|
|
2644
2710
|
let waiter;
|
|
2645
2711
|
const context = await this._getContext();
|
|
2646
2712
|
if (!locator.isXPath()) {
|
|
2647
|
-
|
|
2713
|
+
try {
|
|
2714
|
+
await context.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`).first().waitFor({ timeout: waitTimeout, state: 'detached' });
|
|
2715
|
+
} catch (e) {
|
|
2716
|
+
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${e.message}`);
|
|
2717
|
+
}
|
|
2648
2718
|
} else {
|
|
2649
2719
|
const visibleFn = function ([locator, $XPath]) {
|
|
2650
2720
|
eval($XPath); // eslint-disable-line no-eval
|
|
2651
2721
|
return $XPath(null, locator).length === 0;
|
|
2652
2722
|
};
|
|
2653
2723
|
waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout });
|
|
2724
|
+
return waiter.catch((err) => {
|
|
2725
|
+
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`);
|
|
2726
|
+
});
|
|
2654
2727
|
}
|
|
2655
|
-
return waiter.catch((err) => {
|
|
2656
|
-
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`);
|
|
2657
|
-
});
|
|
2658
2728
|
}
|
|
2659
2729
|
|
|
2660
2730
|
async _waitForAction() {
|
|
@@ -3260,7 +3330,7 @@ async function findElement(matcher, locator) {
|
|
|
3260
3330
|
if (locator.react) return findReact(matcher, locator);
|
|
3261
3331
|
locator = new Locator(locator, 'css');
|
|
3262
3332
|
|
|
3263
|
-
return matcher.locator(buildLocatorString(locator));
|
|
3333
|
+
return matcher.locator(buildLocatorString(locator)).first();
|
|
3264
3334
|
}
|
|
3265
3335
|
|
|
3266
3336
|
async function getVisibleElements(elements) {
|
|
@@ -3290,7 +3360,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3290
3360
|
assertElementExists(els, locator, 'Clickable element');
|
|
3291
3361
|
}
|
|
3292
3362
|
|
|
3293
|
-
highlightActiveElement.call(this, els[0]
|
|
3363
|
+
await highlightActiveElement.call(this, els[0]);
|
|
3294
3364
|
|
|
3295
3365
|
/*
|
|
3296
3366
|
using the force true options itself but instead dispatching a click
|
|
@@ -3340,13 +3410,10 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3340
3410
|
let allText;
|
|
3341
3411
|
|
|
3342
3412
|
if (!context) {
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
el = await this.page.$('body');
|
|
3347
|
-
}
|
|
3413
|
+
const el = await this.context;
|
|
3414
|
+
|
|
3415
|
+
allText = el.constructor.name ? [await el.locator('body').innerText()] : [await el.innerText()];
|
|
3348
3416
|
|
|
3349
|
-
allText = [await el.innerText()];
|
|
3350
3417
|
description = 'web application';
|
|
3351
3418
|
} else {
|
|
3352
3419
|
const locator = new Locator(context, 'css');
|
|
@@ -3519,8 +3586,7 @@ async function elementSelected(element) {
|
|
|
3519
3586
|
function isFrameLocator(locator) {
|
|
3520
3587
|
locator = new Locator(locator);
|
|
3521
3588
|
if (locator.isFrame()) {
|
|
3522
|
-
|
|
3523
|
-
return _locator.value;
|
|
3589
|
+
return locator.value;
|
|
3524
3590
|
}
|
|
3525
3591
|
return false;
|
|
3526
3592
|
}
|
|
@@ -3716,10 +3782,14 @@ async function saveTraceForContext(context, name) {
|
|
|
3716
3782
|
return fileName;
|
|
3717
3783
|
}
|
|
3718
3784
|
|
|
3719
|
-
function highlightActiveElement(element
|
|
3720
|
-
if (
|
|
3721
|
-
|
|
3722
|
-
|
|
3785
|
+
async function highlightActiveElement(element) {
|
|
3786
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
3787
|
+
await element.evaluate(el => {
|
|
3788
|
+
const prevStyle = el.style.boxShadow;
|
|
3789
|
+
el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)';
|
|
3790
|
+
setTimeout(() => el.style.boxShadow = prevStyle, 2000);
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3723
3793
|
}
|
|
3724
3794
|
|
|
3725
3795
|
const createAdvancedTestResults = (url, dataToCheck, requests) => {
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -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
|
|
|
@@ -682,14 +682,14 @@ class Puppeteer extends Helper {
|
|
|
682
682
|
* Set headers for all next requests
|
|
683
683
|
*
|
|
684
684
|
* ```js
|
|
685
|
-
* I.
|
|
685
|
+
* I.setPuppeteerRequestHeaders({
|
|
686
686
|
* 'X-Sent-By': 'CodeceptJS',
|
|
687
687
|
* });
|
|
688
688
|
* ```
|
|
689
689
|
*
|
|
690
690
|
* @param {object} customHeaders headers to set
|
|
691
691
|
*/
|
|
692
|
-
async
|
|
692
|
+
async setPuppeteerRequestHeaders(customHeaders) {
|
|
693
693
|
if (!customHeaders) {
|
|
694
694
|
throw new Error('Cannot send empty headers.');
|
|
695
695
|
}
|
|
@@ -2727,7 +2727,7 @@ function getNormalizedKey(key) {
|
|
|
2727
2727
|
}
|
|
2728
2728
|
|
|
2729
2729
|
function highlightActiveElement(element, context) {
|
|
2730
|
-
if (
|
|
2731
|
-
|
|
2732
|
-
|
|
2730
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
2731
|
+
highlightElement(element, context);
|
|
2732
|
+
}
|
|
2733
2733
|
}
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -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 (
|
|
2922
|
-
|
|
2923
|
-
|
|
2921
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
2922
|
+
highlightElement(element, this.browser);
|
|
2923
|
+
}
|
|
2924
2924
|
}
|
|
2925
2925
|
|
|
2926
2926
|
function prepareLocateFn(context) {
|
package/lib/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { parse, serialize } = require('parse5');
|
|
2
|
-
const { minify } = require('html-minifier');
|
|
2
|
+
const { minify } = require('html-minifier-terser');
|
|
3
3
|
|
|
4
|
-
function minifyHtml(html) {
|
|
4
|
+
async function minifyHtml(html) {
|
|
5
5
|
return minify(html, {
|
|
6
6
|
collapseWhitespace: true,
|
|
7
7
|
removeComments: true,
|
|
@@ -11,7 +11,7 @@ function minifyHtml(html) {
|
|
|
11
11
|
removeStyleLinkTypeAttributes: true,
|
|
12
12
|
collapseBooleanAttributes: true,
|
|
13
13
|
useShortDoctype: true,
|
|
14
|
-
})
|
|
14
|
+
});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const defaultHtmlOpts = {
|
|
@@ -18,6 +18,11 @@ parser.stopAtFirstError = false;
|
|
|
18
18
|
|
|
19
19
|
module.exports = (text, file) => {
|
|
20
20
|
const ast = parser.parse(text);
|
|
21
|
+
let currentLanguage;
|
|
22
|
+
|
|
23
|
+
if (ast.feature) {
|
|
24
|
+
currentLanguage = getTranslation(ast.feature.language);
|
|
25
|
+
}
|
|
21
26
|
|
|
22
27
|
if (!ast.feature) {
|
|
23
28
|
throw new Error(`No 'Features' available in Gherkin '${file}' provided!`);
|
|
@@ -96,7 +101,7 @@ module.exports = (text, file) => {
|
|
|
96
101
|
suite.beforeEach('Before', scenario.injected(async () => runSteps(child.background.steps), suite, 'before'));
|
|
97
102
|
continue;
|
|
98
103
|
}
|
|
99
|
-
if (child.scenario && child.scenario.keyword === 'Scenario Outline') {
|
|
104
|
+
if (child.scenario && (currentLanguage ? child.scenario.keyword === currentLanguage.contexts.ScenarioOutline : child.scenario.keyword === 'Scenario Outline')) {
|
|
100
105
|
for (const examples of child.scenario.examples) {
|
|
101
106
|
const fields = examples.tableHeader.cells.map(c => c.value);
|
|
102
107
|
for (const example of examples.tableBody) {
|
|
@@ -141,7 +146,7 @@ function transformTable(table) {
|
|
|
141
146
|
let str = '';
|
|
142
147
|
for (const id in table.rows) {
|
|
143
148
|
const cells = table.rows[id].cells;
|
|
144
|
-
str += cells.map(c => c.value).map(c => c.
|
|
149
|
+
str += cells.map(c => c.value).map(c => c.padEnd(15)).join(' | ');
|
|
145
150
|
str += '\n';
|
|
146
151
|
}
|
|
147
152
|
return str;
|
|
@@ -162,3 +167,17 @@ function addExampleInTable(exampleSteps, placeholders) {
|
|
|
162
167
|
}
|
|
163
168
|
return steps;
|
|
164
169
|
}
|
|
170
|
+
|
|
171
|
+
function getTranslation(language) {
|
|
172
|
+
const translations = Object.keys(require('../../translations'));
|
|
173
|
+
|
|
174
|
+
for (const availableTranslation of translations) {
|
|
175
|
+
if (!language) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (availableTranslation.includes(language)) {
|
|
180
|
+
return require('../../translations')[availableTranslation];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
package/lib/output.js
CHANGED
|
@@ -106,7 +106,7 @@ module.exports = {
|
|
|
106
106
|
if (!step) return;
|
|
107
107
|
// Avoid to print non-gherkin steps, when gherkin is running for --steps mode
|
|
108
108
|
if (outputLevel === 1) {
|
|
109
|
-
if (step.hasBDDAncestor()) {
|
|
109
|
+
if (typeof step === 'object' && step.hasBDDAncestor()) {
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
}
|
package/lib/pause.js
CHANGED
|
@@ -18,8 +18,7 @@ let nextStep;
|
|
|
18
18
|
let finish;
|
|
19
19
|
let next;
|
|
20
20
|
let registeredVariables = {};
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
let aiAssistant;
|
|
23
22
|
/**
|
|
24
23
|
* Pauses test execution and starts interactive shell
|
|
25
24
|
* @param {Object<string, *>} [passedObject]
|
|
@@ -45,6 +44,8 @@ function pauseSession(passedObject = {}) {
|
|
|
45
44
|
let vars = Object.keys(registeredVariables).join(', ');
|
|
46
45
|
if (vars) vars = `(vars: ${vars})`;
|
|
47
46
|
|
|
47
|
+
aiAssistant = AiAssistant.getInstance();
|
|
48
|
+
|
|
48
49
|
output.print(colors.yellow(' Interactive shell started'));
|
|
49
50
|
output.print(colors.yellow(' Use JavaScript syntax to try steps in action'));
|
|
50
51
|
output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`));
|
|
@@ -78,7 +79,6 @@ async function parseInput(cmd) {
|
|
|
78
79
|
rl.pause();
|
|
79
80
|
next = false;
|
|
80
81
|
recorder.session.start('pause');
|
|
81
|
-
store.debugMode = false;
|
|
82
82
|
if (cmd === '') next = true;
|
|
83
83
|
if (!cmd || cmd === 'resume' || cmd === 'exit') {
|
|
84
84
|
finish();
|
|
@@ -98,13 +98,14 @@ async function parseInput(cmd) {
|
|
|
98
98
|
return cmd;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
store.debugMode = true;
|
|
102
101
|
let isCustomCommand = false;
|
|
103
102
|
let lastError = null;
|
|
104
103
|
let isAiCommand = false;
|
|
105
104
|
let $res;
|
|
106
105
|
try {
|
|
106
|
+
// eslint-disable-next-line
|
|
107
107
|
const locate = global.locate; // enable locate in this context
|
|
108
|
+
// eslint-disable-next-line
|
|
108
109
|
const I = container.support('I');
|
|
109
110
|
if (cmd.trim().startsWith('=>')) {
|
|
110
111
|
isCustomCommand = true;
|
|
@@ -117,7 +118,7 @@ async function parseInput(cmd) {
|
|
|
117
118
|
executeCommand = executeCommand.then(async () => {
|
|
118
119
|
try {
|
|
119
120
|
const html = await res;
|
|
120
|
-
aiAssistant.setHtmlContext(html);
|
|
121
|
+
await aiAssistant.setHtmlContext(html);
|
|
121
122
|
} catch (err) {
|
|
122
123
|
output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
|
|
123
124
|
return;
|