codeceptjs 3.5.11 → 3.5.12-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/README.md +3 -3
- package/lib/command/run-multiple.js +3 -1
- package/lib/command/run-workers.js +32 -1
- package/lib/command/workers/runTests.js +2 -2
- package/lib/helper/Playwright.js +21 -2
- package/lib/helper/Puppeteer.js +18 -0
- package/lib/helper/WebDriver.js +140 -31
- package/lib/locator.js +17 -4
- package/lib/plugin/retryFailedStep.js +5 -1
- package/lib/plugin/retryTo.js +2 -2
- package/package.json +14 -11
- package/typings/index.d.ts +9 -6
- package/typings/promiseBasedTypes.d.ts +84 -1
- package/typings/types.d.ts +102 -2
- package/docs/advanced.md +0 -351
- package/docs/ai.md +0 -248
- package/docs/api.md +0 -323
- package/docs/basics.md +0 -979
- package/docs/bdd.md +0 -539
- package/docs/best.md +0 -237
- package/docs/books.md +0 -37
- package/docs/bootstrap.md +0 -135
- package/docs/build/ApiDataFactory.js +0 -410
- package/docs/build/Appium.js +0 -2027
- package/docs/build/Expect.js +0 -422
- package/docs/build/FileSystem.js +0 -228
- package/docs/build/GraphQL.js +0 -229
- package/docs/build/GraphQLDataFactory.js +0 -309
- package/docs/build/JSONResponse.js +0 -338
- package/docs/build/Mochawesome.js +0 -71
- package/docs/build/Nightmare.js +0 -2152
- package/docs/build/OpenAI.js +0 -126
- package/docs/build/Playwright.js +0 -5082
- package/docs/build/Protractor.js +0 -2706
- package/docs/build/Puppeteer.js +0 -3878
- package/docs/build/REST.js +0 -344
- package/docs/build/TestCafe.js +0 -2125
- package/docs/build/WebDriver.js +0 -4122
- package/docs/changelog.md +0 -2572
- package/docs/commands.md +0 -266
- package/docs/community-helpers.md +0 -58
- package/docs/configuration.md +0 -157
- package/docs/continuous-integration.md +0 -22
- package/docs/custom-helpers.md +0 -306
- package/docs/data.md +0 -379
- package/docs/detox.md +0 -235
- package/docs/docker.md +0 -136
- package/docs/email.md +0 -183
- package/docs/examples.md +0 -149
- package/docs/helpers/ApiDataFactory.md +0 -266
- package/docs/helpers/Appium.md +0 -1374
- package/docs/helpers/Detox.md +0 -586
- package/docs/helpers/Expect.md +0 -275
- package/docs/helpers/FileSystem.md +0 -152
- package/docs/helpers/GraphQL.md +0 -151
- package/docs/helpers/GraphQLDataFactory.md +0 -226
- package/docs/helpers/JSONResponse.md +0 -254
- package/docs/helpers/Mochawesome.md +0 -8
- package/docs/helpers/MockRequest.md +0 -377
- package/docs/helpers/Nightmare.md +0 -1305
- package/docs/helpers/OpenAI.md +0 -70
- package/docs/helpers/Playwright.md +0 -2744
- package/docs/helpers/Polly.md +0 -44
- package/docs/helpers/Protractor.md +0 -1769
- package/docs/helpers/Puppeteer-firefox.md +0 -86
- package/docs/helpers/Puppeteer.md +0 -2302
- package/docs/helpers/REST.md +0 -218
- package/docs/helpers/TestCafe.md +0 -1321
- package/docs/helpers/WebDriver.md +0 -2473
- package/docs/hooks.md +0 -340
- package/docs/index.md +0 -111
- package/docs/installation.md +0 -75
- package/docs/internal-api.md +0 -266
- package/docs/locators.md +0 -331
- package/docs/mobile-react-native-locators.md +0 -67
- package/docs/mobile.md +0 -338
- package/docs/pageobjects.md +0 -291
- package/docs/parallel.md +0 -400
- package/docs/playwright.md +0 -632
- package/docs/plugins.md +0 -1259
- package/docs/puppeteer.md +0 -316
- package/docs/quickstart.md +0 -162
- package/docs/react.md +0 -70
- package/docs/reports.md +0 -392
- package/docs/secrets.md +0 -36
- package/docs/shadow.md +0 -68
- package/docs/shared/keys.mustache +0 -31
- package/docs/shared/react.mustache +0 -1
- package/docs/testcafe.md +0 -174
- package/docs/translation.md +0 -247
- package/docs/tutorial.md +0 -271
- package/docs/typescript.md +0 -180
- package/docs/ui.md +0 -59
- package/docs/videos.md +0 -28
- package/docs/visual.md +0 -202
- package/docs/vue.md +0 -143
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -11
- package/docs/webapi/attachFile.mustache +0 -12
- package/docs/webapi/blur.mustache +0 -18
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -9
- package/docs/webapi/clearField.mustache +0 -9
- package/docs/webapi/click.mustache +0 -25
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -16
- package/docs/webapi/focus.mustache +0 -13
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/grabWebElement.mustache +0 -9
- package/docs/webapi/grabWebElements.mustache +0 -9
- package/docs/webapi/moveCursorTo.mustache +0 -12
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/selectOption.mustache +0 -21
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -21
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/docs/webdriver.md +0 -655
- package/docs/wiki/Books-&-Posts.md +0 -27
- package/docs/wiki/Community-Helpers-&-Plugins.md +0 -53
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -61
- package/docs/wiki/Examples.md +0 -145
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
- package/docs/wiki/Home.md +0 -16
- package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +0 -83
- package/docs/wiki/Release-Process.md +0 -24
- package/docs/wiki/Roadmap.md +0 -23
- package/docs/wiki/Tests.md +0 -1393
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
- package/docs/wiki/Videos.md +0 -19
package/README.md
CHANGED
|
@@ -304,8 +304,8 @@ Thanks all to those who are and will have contributing to this awesome project!
|
|
|
304
304
|
<a href="https://github.com/APshenkin"><img src="https://avatars.githubusercontent.com/u/14344430?v=4" title="APshenkin" width="80" height="80"></a>
|
|
305
305
|
<a href="https://github.com/fabioel"><img src="https://avatars.githubusercontent.com/u/9824235?v=4" title="fabioel" width="80" height="80"></a>
|
|
306
306
|
<a href="https://github.com/pablopaul"><img src="https://avatars.githubusercontent.com/u/635526?v=4" title="pablopaul" width="80" height="80"></a>
|
|
307
|
-
<a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
|
|
308
307
|
<a href="https://github.com/mirao"><img src="https://avatars.githubusercontent.com/u/12584138?v=4" title="mirao" width="80" height="80"></a>
|
|
308
|
+
<a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
|
|
309
309
|
<a href="https://github.com/KMKoushik"><img src="https://avatars.githubusercontent.com/u/24666922?v=4" title="KMKoushik" width="80" height="80"></a>
|
|
310
310
|
<a href="https://github.com/nikocanvacom"><img src="https://avatars.githubusercontent.com/u/83254493?v=4" title="nikocanvacom" width="80" height="80"></a>
|
|
311
311
|
<a href="https://github.com/elukoyanov"><img src="https://avatars.githubusercontent.com/u/11647141?v=4" title="elukoyanov" width="80" height="80"></a>
|
|
@@ -321,9 +321,9 @@ Thanks all to those who are and will have contributing to this awesome project!
|
|
|
321
321
|
<a href="https://github.com/ngraf"><img src="https://avatars.githubusercontent.com/u/7094389?v=4" title="ngraf" width="80" height="80"></a>
|
|
322
322
|
<a href="https://github.com/maojunxyz"><img src="https://avatars.githubusercontent.com/u/28778042?v=4" title="maojunxyz" width="80" height="80"></a>
|
|
323
323
|
<a href="https://github.com/abhimanyupandian"><img src="https://avatars.githubusercontent.com/u/36107381?v=4" title="abhimanyupandian" width="80" height="80"></a>
|
|
324
|
-
<a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
|
|
325
324
|
<a href="https://github.com/martomo"><img src="https://avatars.githubusercontent.com/u/1850135?v=4" title="martomo" width="80" height="80"></a>
|
|
326
|
-
<a href="https://github.com/
|
|
325
|
+
<a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
|
|
326
|
+
<a href="https://github.com/johnyb"><img src="https://avatars.githubusercontent.com/u/86358?v=4" title="johnyb" width="80" height="80"></a>
|
|
327
327
|
|
|
328
328
|
[//]: contributor-faces
|
|
329
329
|
|
|
@@ -28,7 +28,9 @@ let processesDone;
|
|
|
28
28
|
|
|
29
29
|
module.exports = async function (selectedRuns, options) {
|
|
30
30
|
// registering options globally to use in config
|
|
31
|
-
|
|
31
|
+
if (options.profile) {
|
|
32
|
+
process.env.profile = options.profile;
|
|
33
|
+
}
|
|
32
34
|
const configFile = options.config;
|
|
33
35
|
|
|
34
36
|
const testRoot = getTestRoot(configFile);
|
|
@@ -11,6 +11,7 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
11
11
|
const passedTestArr = [];
|
|
12
12
|
const failedTestArr = [];
|
|
13
13
|
const skippedTestArr = [];
|
|
14
|
+
const stepArr = [];
|
|
14
15
|
|
|
15
16
|
const { config: testConfig, override = '' } = options;
|
|
16
17
|
const overrideConfigs = tryOrDefault(() => JSON.parse(override), {});
|
|
@@ -36,6 +37,14 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
36
37
|
suiteArr.push(suite);
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
workers.on(event.step.passed, (step) => {
|
|
41
|
+
stepArr.push(step);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
workers.on(event.step.failed, (step) => {
|
|
45
|
+
stepArr.push(step);
|
|
46
|
+
});
|
|
47
|
+
|
|
39
48
|
workers.on(event.test.failed, (test) => {
|
|
40
49
|
failedTestArr.push(test);
|
|
41
50
|
output.test.failed(test);
|
|
@@ -48,11 +57,33 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
48
57
|
|
|
49
58
|
workers.on(event.test.skipped, (test) => {
|
|
50
59
|
skippedTestArr.push(test);
|
|
51
|
-
output.test.
|
|
60
|
+
output.test.skipped(test);
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
workers.on(event.all.result, () => {
|
|
55
64
|
// expose test stats after all workers finished their execution
|
|
65
|
+
function addStepsToTest(test, stepArr) {
|
|
66
|
+
stepArr.test.steps.forEach(step => {
|
|
67
|
+
if (test.steps.length === 0) {
|
|
68
|
+
test.steps.push(step);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
stepArr.forEach(step => {
|
|
74
|
+
passedTestArr.forEach(test => {
|
|
75
|
+
if (step.test.title === test.title) {
|
|
76
|
+
addStepsToTest(test, step);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
failedTestArr.forEach(test => {
|
|
81
|
+
if (step.test.title === test.title) {
|
|
82
|
+
addStepsToTest(test, step);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
56
87
|
event.dispatcher.emit(event.workers.result, {
|
|
57
88
|
suites: suiteArr,
|
|
58
89
|
tests: {
|
|
@@ -132,7 +132,7 @@ function initializeListeners() {
|
|
|
132
132
|
duration: test.duration || 0,
|
|
133
133
|
err,
|
|
134
134
|
parent,
|
|
135
|
-
steps: test.steps ? simplifyStepsInTestObject(test.steps, err) : [],
|
|
135
|
+
steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -161,7 +161,7 @@ function initializeListeners() {
|
|
|
161
161
|
actor: step.actor,
|
|
162
162
|
name: step.name,
|
|
163
163
|
status: step.status,
|
|
164
|
-
|
|
164
|
+
args: _args,
|
|
165
165
|
startedAt: step.startedAt,
|
|
166
166
|
startTime: step.startTime,
|
|
167
167
|
endTime: step.endTime,
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -517,8 +517,9 @@ class Playwright extends Helper {
|
|
|
517
517
|
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
518
518
|
if (this.options.locale) contextOptions.locale = this.options.locale;
|
|
519
519
|
if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme;
|
|
520
|
+
this.contextOptions = contextOptions;
|
|
520
521
|
if (!this.browserContext || !restartsSession()) {
|
|
521
|
-
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
522
|
+
this.browserContext = await this.browser.newContext(this.contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
|
|
@@ -606,7 +607,7 @@ class Playwright extends Helper {
|
|
|
606
607
|
page = await browser.firstWindow();
|
|
607
608
|
} else {
|
|
608
609
|
try {
|
|
609
|
-
browserContext = await this.browser.newContext(Object.assign(this.
|
|
610
|
+
browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config));
|
|
610
611
|
page = await browserContext.newPage();
|
|
611
612
|
} catch (e) {
|
|
612
613
|
if (this.playwrightOptions.userDataDir) {
|
|
@@ -2583,6 +2584,24 @@ class Playwright extends Helper {
|
|
|
2583
2584
|
});
|
|
2584
2585
|
}
|
|
2585
2586
|
|
|
2587
|
+
/**
|
|
2588
|
+
* {{> waitForNumberOfTabs }}
|
|
2589
|
+
*/
|
|
2590
|
+
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2591
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2592
|
+
let currentTabs;
|
|
2593
|
+
let count = 0;
|
|
2594
|
+
|
|
2595
|
+
do {
|
|
2596
|
+
currentTabs = await this.grabNumberOfOpenTabs();
|
|
2597
|
+
await this.wait(1);
|
|
2598
|
+
count += 1000;
|
|
2599
|
+
if (currentTabs >= expectedTabs) return;
|
|
2600
|
+
} while (count <= waitTimeout);
|
|
2601
|
+
|
|
2602
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2586
2605
|
async _getContext() {
|
|
2587
2606
|
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2588
2607
|
return this.context;
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -2151,6 +2151,24 @@ class Puppeteer extends Helper {
|
|
|
2151
2151
|
});
|
|
2152
2152
|
}
|
|
2153
2153
|
|
|
2154
|
+
/**
|
|
2155
|
+
* {{> waitForNumberOfTabs }}
|
|
2156
|
+
*/
|
|
2157
|
+
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2158
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2159
|
+
let currentTabs;
|
|
2160
|
+
let count = 0;
|
|
2161
|
+
|
|
2162
|
+
do {
|
|
2163
|
+
currentTabs = await this.grabNumberOfOpenTabs();
|
|
2164
|
+
await this.wait(1);
|
|
2165
|
+
count += 1000;
|
|
2166
|
+
if (currentTabs >= expectedTabs) return;
|
|
2167
|
+
} while (count <= waitTimeout);
|
|
2168
|
+
|
|
2169
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2154
2172
|
async _getContext() {
|
|
2155
2173
|
if (this.context && this.context.constructor.name === 'Frame') {
|
|
2156
2174
|
return this.context;
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -63,6 +63,8 @@ const webRoot = 'body';
|
|
|
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
65
|
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
66
|
+
* @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
|
|
67
|
+
* @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
|
|
66
68
|
*/
|
|
67
69
|
const config = {};
|
|
68
70
|
|
|
@@ -72,6 +74,13 @@ const config = {};
|
|
|
72
74
|
*
|
|
73
75
|
* WebDriver requires Selenium Server and ChromeDriver/GeckoDriver to be installed. Those tools can be easily installed via NPM. Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
|
|
74
76
|
*
|
|
77
|
+
* With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past 🙌. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
|
|
78
|
+
* One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
|
|
79
|
+
* `wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
|
|
80
|
+
*
|
|
81
|
+
* For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
|
|
82
|
+
* If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
|
|
83
|
+
*
|
|
75
84
|
* <!-- configuration -->
|
|
76
85
|
*
|
|
77
86
|
* Example:
|
|
@@ -93,6 +102,28 @@ const config = {};
|
|
|
93
102
|
* }
|
|
94
103
|
* ```
|
|
95
104
|
*
|
|
105
|
+
* Testing Chrome locally is now more convenient than ever. You can define a browser channel, and WebDriver Helper will take care of downloading the specified browser version for you.
|
|
106
|
+
* For example:
|
|
107
|
+
*
|
|
108
|
+
* ```js
|
|
109
|
+
* {
|
|
110
|
+
* helpers: {
|
|
111
|
+
* WebDriver : {
|
|
112
|
+
* smartWait: 5000,
|
|
113
|
+
* browser: "chrome",
|
|
114
|
+
* browserVersion: '116.0.5793.0', // or 'stable', 'beta', 'dev' or 'canary'
|
|
115
|
+
* restart: false,
|
|
116
|
+
* windowSize: "maximize",
|
|
117
|
+
* timeouts: {
|
|
118
|
+
* "script": 60000,
|
|
119
|
+
* "page load": 10000
|
|
120
|
+
* }
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
*
|
|
96
127
|
* Example with basic authentication
|
|
97
128
|
* ```js
|
|
98
129
|
* {
|
|
@@ -133,6 +164,25 @@ const config = {};
|
|
|
133
164
|
* }
|
|
134
165
|
* ```
|
|
135
166
|
*
|
|
167
|
+
* ### Running with devtools protocol
|
|
168
|
+
*
|
|
169
|
+
* ```js
|
|
170
|
+
* {
|
|
171
|
+
* helpers: {
|
|
172
|
+
* WebDriver : {
|
|
173
|
+
* url: "http://localhost",
|
|
174
|
+
* browser: "chrome",
|
|
175
|
+
* devtoolsProtocol: true,
|
|
176
|
+
* desiredCapabilities: {
|
|
177
|
+
* chromeOptions: {
|
|
178
|
+
* args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
|
|
179
|
+
* }
|
|
180
|
+
* }
|
|
181
|
+
* }
|
|
182
|
+
* }
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
136
186
|
* ### Internet Explorer
|
|
137
187
|
*
|
|
138
188
|
* Additional configuration params can be used from [IE options](https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/IE/Options.html)
|
|
@@ -415,7 +465,6 @@ class WebDriver extends Helper {
|
|
|
415
465
|
_validateConfig(config) {
|
|
416
466
|
const defaults = {
|
|
417
467
|
logLevel: 'silent',
|
|
418
|
-
path: '/wd/hub',
|
|
419
468
|
// codeceptjs
|
|
420
469
|
remoteFileUpload: true,
|
|
421
470
|
smartWait: 0,
|
|
@@ -435,12 +484,17 @@ class WebDriver extends Helper {
|
|
|
435
484
|
// override defaults with config
|
|
436
485
|
config = Object.assign(defaults, config);
|
|
437
486
|
|
|
438
|
-
if (
|
|
487
|
+
if (config.host) {
|
|
488
|
+
// webdriverio spec
|
|
489
|
+
config.hostname = config.host;
|
|
490
|
+
config.path = '/wd/hub';
|
|
491
|
+
}
|
|
439
492
|
config.baseUrl = config.url || config.baseUrl;
|
|
440
493
|
if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
|
|
441
494
|
config.capabilities = config.desiredCapabilities;
|
|
442
495
|
}
|
|
443
496
|
config.capabilities.browserName = config.browser || config.capabilities.browserName;
|
|
497
|
+
config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion;
|
|
444
498
|
if (config.capabilities.chromeOptions) {
|
|
445
499
|
config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions;
|
|
446
500
|
delete config.capabilities.chromeOptions;
|
|
@@ -542,6 +596,10 @@ class WebDriver extends Helper {
|
|
|
542
596
|
delete this.options.capabilities.hostname;
|
|
543
597
|
delete this.options.capabilities.port;
|
|
544
598
|
delete this.options.capabilities.path;
|
|
599
|
+
if (this.options.devtoolsProtocol) {
|
|
600
|
+
if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
|
|
601
|
+
this.options.automationProtocol = 'devtools';
|
|
602
|
+
}
|
|
545
603
|
this.browser = await webdriverio.remote(this.options);
|
|
546
604
|
}
|
|
547
605
|
} catch (err) {
|
|
@@ -1043,7 +1101,8 @@ class WebDriver extends Helper {
|
|
|
1043
1101
|
assertElementExists(res, field, 'Field');
|
|
1044
1102
|
const elem = usingFirstElement(res);
|
|
1045
1103
|
highlightActiveElement.call(this, elem);
|
|
1046
|
-
|
|
1104
|
+
await elem.clearValue();
|
|
1105
|
+
await elem.setValue(value.toString());
|
|
1047
1106
|
}
|
|
1048
1107
|
|
|
1049
1108
|
/**
|
|
@@ -1055,6 +1114,10 @@ class WebDriver extends Helper {
|
|
|
1055
1114
|
assertElementExists(res, field, 'Field');
|
|
1056
1115
|
const elem = usingFirstElement(res);
|
|
1057
1116
|
highlightActiveElement.call(this, elem);
|
|
1117
|
+
if (this.options.automationProtocol) {
|
|
1118
|
+
const curentValue = await elem.getValue();
|
|
1119
|
+
return elem.setValue(curentValue + value.toString());
|
|
1120
|
+
}
|
|
1058
1121
|
return elem.addValue(value.toString());
|
|
1059
1122
|
}
|
|
1060
1123
|
|
|
@@ -1067,6 +1130,9 @@ class WebDriver extends Helper {
|
|
|
1067
1130
|
assertElementExists(res, field, 'Field');
|
|
1068
1131
|
const elem = usingFirstElement(res);
|
|
1069
1132
|
highlightActiveElement.call(this, elem);
|
|
1133
|
+
if (this.options.automationProtocol) {
|
|
1134
|
+
return elem.setValue('');
|
|
1135
|
+
}
|
|
1070
1136
|
return elem.clearValue(getElementId(elem));
|
|
1071
1137
|
}
|
|
1072
1138
|
|
|
@@ -1120,7 +1186,7 @@ class WebDriver extends Helper {
|
|
|
1120
1186
|
const el = usingFirstElement(res);
|
|
1121
1187
|
|
|
1122
1188
|
// Remote Upload (when running Selenium Server)
|
|
1123
|
-
if (this.options.remoteFileUpload) {
|
|
1189
|
+
if (this.options.remoteFileUpload && !this.options.automationProtocol) {
|
|
1124
1190
|
try {
|
|
1125
1191
|
this.debugSection('File', 'Uploading file to remote server');
|
|
1126
1192
|
file = await this.browser.uploadFile(file);
|
|
@@ -1498,35 +1564,33 @@ class WebDriver extends Helper {
|
|
|
1498
1564
|
async seeCssPropertiesOnElements(locator, cssProperties) {
|
|
1499
1565
|
const res = await this._locate(locator);
|
|
1500
1566
|
assertElementExists(res, locator);
|
|
1567
|
+
|
|
1568
|
+
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
|
|
1501
1569
|
const elemAmount = res.length;
|
|
1570
|
+
let props = [];
|
|
1502
1571
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const
|
|
1506
|
-
if (isColorProperty(prop)
|
|
1507
|
-
|
|
1572
|
+
for (const element of res) {
|
|
1573
|
+
for (const prop of Object.keys(cssProperties)) {
|
|
1574
|
+
const cssProp = await this.grabCssPropertyFrom(locator, prop);
|
|
1575
|
+
if (isColorProperty(prop)) {
|
|
1576
|
+
props.push(convertColorToRGBA(cssProp));
|
|
1577
|
+
} else {
|
|
1578
|
+
props.push(cssProp);
|
|
1508
1579
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
});
|
|
1512
|
-
|
|
1513
|
-
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1514
1582
|
|
|
1515
1583
|
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
|
|
1516
1584
|
if (!Array.isArray(props)) props = [props];
|
|
1517
1585
|
let chunked = chunkArray(props, values.length);
|
|
1518
1586
|
chunked = chunked.filter((val) => {
|
|
1519
1587
|
for (let i = 0; i < val.length; ++i) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
if (_acutal !== _expected) return false;
|
|
1588
|
+
// eslint-disable-next-line eqeqeq
|
|
1589
|
+
if (val[i] != values[i]) return false;
|
|
1523
1590
|
}
|
|
1524
1591
|
return true;
|
|
1525
1592
|
});
|
|
1526
|
-
return assert.
|
|
1527
|
-
chunked.length === elemAmount,
|
|
1528
|
-
`expected all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`,
|
|
1529
|
-
);
|
|
1593
|
+
return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
|
|
1530
1594
|
}
|
|
1531
1595
|
|
|
1532
1596
|
/**
|
|
@@ -1546,9 +1610,9 @@ class WebDriver extends Helper {
|
|
|
1546
1610
|
let chunked = chunkArray(attrs, values.length);
|
|
1547
1611
|
chunked = chunked.filter((val) => {
|
|
1548
1612
|
for (let i = 0; i < val.length; ++i) {
|
|
1549
|
-
const
|
|
1613
|
+
const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
|
|
1550
1614
|
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
|
|
1551
|
-
if (
|
|
1615
|
+
if (_actual !== _expected) return false;
|
|
1552
1616
|
}
|
|
1553
1617
|
return true;
|
|
1554
1618
|
});
|
|
@@ -1672,7 +1736,11 @@ class WebDriver extends Helper {
|
|
|
1672
1736
|
const res = await this._locate(withStrictLocator(locator), true);
|
|
1673
1737
|
assertElementExists(res, locator);
|
|
1674
1738
|
const elem = usingFirstElement(res);
|
|
1675
|
-
|
|
1739
|
+
try {
|
|
1740
|
+
await elem.moveTo({ xOffset, yOffset });
|
|
1741
|
+
} catch (e) {
|
|
1742
|
+
debug(e.message);
|
|
1743
|
+
}
|
|
1676
1744
|
}
|
|
1677
1745
|
|
|
1678
1746
|
/**
|
|
@@ -1925,7 +1993,7 @@ class WebDriver extends Helper {
|
|
|
1925
1993
|
* {{> resizeWindow }}
|
|
1926
1994
|
*/
|
|
1927
1995
|
async resizeWindow(width, height) {
|
|
1928
|
-
return this.
|
|
1996
|
+
return this.browser.setWindowSize(width, height);
|
|
1929
1997
|
}
|
|
1930
1998
|
|
|
1931
1999
|
async _resizeBrowserWindow(browser, width, height) {
|
|
@@ -2306,12 +2374,33 @@ class WebDriver extends Helper {
|
|
|
2306
2374
|
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
|
|
2307
2375
|
}
|
|
2308
2376
|
|
|
2377
|
+
/**
|
|
2378
|
+
* {{> waitForNumberOfTabs }}
|
|
2379
|
+
*/
|
|
2380
|
+
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2381
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds;
|
|
2382
|
+
let currentTabs;
|
|
2383
|
+
let count = 0;
|
|
2384
|
+
|
|
2385
|
+
do {
|
|
2386
|
+
currentTabs = await this.grabNumberOfOpenTabs();
|
|
2387
|
+
await this.wait(1);
|
|
2388
|
+
count += 1000;
|
|
2389
|
+
if (currentTabs >= expectedTabs) return;
|
|
2390
|
+
} while (count <= waitTimeout);
|
|
2391
|
+
|
|
2392
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2309
2395
|
/**
|
|
2310
2396
|
* {{> switchTo }}
|
|
2311
2397
|
*/
|
|
2312
2398
|
async switchTo(locator) {
|
|
2313
2399
|
this.browser.isInsideFrame = true;
|
|
2314
2400
|
if (Number.isInteger(locator)) {
|
|
2401
|
+
if (this.options.automationProtocol) {
|
|
2402
|
+
return this.browser.switchToFrame(locator + 1);
|
|
2403
|
+
}
|
|
2315
2404
|
return this.browser.switchToFrame(locator);
|
|
2316
2405
|
}
|
|
2317
2406
|
if (!locator) {
|
|
@@ -2448,21 +2537,41 @@ class WebDriver extends Helper {
|
|
|
2448
2537
|
}
|
|
2449
2538
|
|
|
2450
2539
|
/**
|
|
2540
|
+
* This method is **deprecated**.
|
|
2541
|
+
*
|
|
2542
|
+
*
|
|
2451
2543
|
* {{> setGeoLocation }}
|
|
2452
2544
|
*/
|
|
2453
|
-
async setGeoLocation(latitude, longitude
|
|
2454
|
-
if (
|
|
2455
|
-
|
|
2545
|
+
async setGeoLocation(latitude, longitude) {
|
|
2546
|
+
if (!this.options.automationProtocol) {
|
|
2547
|
+
console.log(`setGeoLocation deprecated:
|
|
2548
|
+
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
|
|
2549
|
+
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
|
|
2550
|
+
return;
|
|
2456
2551
|
}
|
|
2457
|
-
|
|
2552
|
+
this.geoLocation = { latitude, longitude };
|
|
2553
|
+
const puppeteerBrowser = await this.browser.getPuppeteer();
|
|
2554
|
+
await this.browser.call(async () => {
|
|
2555
|
+
const pages = await puppeteerBrowser.pages();
|
|
2556
|
+
await pages[0].setGeolocation({ latitude, longitude });
|
|
2557
|
+
});
|
|
2458
2558
|
}
|
|
2459
2559
|
|
|
2460
2560
|
/**
|
|
2561
|
+
* This method is **deprecated**.
|
|
2562
|
+
*
|
|
2461
2563
|
* {{> grabGeoLocation }}
|
|
2462
2564
|
*
|
|
2463
2565
|
*/
|
|
2464
2566
|
async grabGeoLocation() {
|
|
2465
|
-
|
|
2567
|
+
if (!this.options.automationProtocol) {
|
|
2568
|
+
console.log(`grabGeoLocation deprecated:
|
|
2569
|
+
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
|
|
2570
|
+
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
if (!this.geoLocation) return 'No GeoLocation is set!';
|
|
2574
|
+
return this.geoLocation;
|
|
2466
2575
|
}
|
|
2467
2576
|
|
|
2468
2577
|
/**
|
|
@@ -2658,7 +2767,7 @@ async function proceedSeeField(assertType, field, value) {
|
|
|
2658
2767
|
}
|
|
2659
2768
|
};
|
|
2660
2769
|
|
|
2661
|
-
const proceedSingle = el =>
|
|
2770
|
+
const proceedSingle = el => el.getValue().then((res) => {
|
|
2662
2771
|
if (res === null) {
|
|
2663
2772
|
throw new Error(`Element ${el.selector} has no value attribute`);
|
|
2664
2773
|
}
|
package/lib/locator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cssToXPath = require('
|
|
1
|
+
const cssToXPath = require('csstoxpath');
|
|
2
2
|
const { sprintf } = require('sprintf-js');
|
|
3
3
|
|
|
4
4
|
const { xpathLocator } = require('./utils');
|
|
@@ -158,11 +158,12 @@ class Locator {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
+
* @param {string} [pseudoSelector] CSS to XPath extension pseudo: https://www.npmjs.com/package/csstoxpath?activeTab=explore#extension-pseudos
|
|
161
162
|
* @returns {string}
|
|
162
163
|
*/
|
|
163
|
-
toXPath() {
|
|
164
|
+
toXPath(pseudoSelector = '') {
|
|
164
165
|
if (this.isXPath()) return this.value;
|
|
165
|
-
if (this.isCSS()) return cssToXPath
|
|
166
|
+
if (this.isCSS()) return cssToXPath(`${this.value}${pseudoSelector}`);
|
|
166
167
|
|
|
167
168
|
throw new Error('Can\'t be converted to XPath');
|
|
168
169
|
}
|
|
@@ -243,12 +244,24 @@ class Locator {
|
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
/**
|
|
247
|
+
* Find an element containing a text
|
|
246
248
|
* @param {string} text
|
|
247
249
|
* @returns {Locator}
|
|
248
250
|
*/
|
|
249
251
|
withText(text) {
|
|
250
252
|
text = xpathLocator.literal(text);
|
|
251
|
-
const xpath =
|
|
253
|
+
const xpath = this.toXPath(`:text-contains-case(${text})`);
|
|
254
|
+
return new Locator({ xpath });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Find an element with exact text
|
|
259
|
+
* @param {string} text
|
|
260
|
+
* @returns {Locator}
|
|
261
|
+
*/
|
|
262
|
+
withTextEquals(text) {
|
|
263
|
+
text = xpathLocator.literal(text);
|
|
264
|
+
const xpath = this.toXPath(`:text-case(${text})`);
|
|
252
265
|
return new Locator({ xpath });
|
|
253
266
|
}
|
|
254
267
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const event = require('../event');
|
|
2
2
|
const recorder = require('../recorder');
|
|
3
3
|
const container = require('../container');
|
|
4
|
+
const { log } = require('../output');
|
|
4
5
|
|
|
5
6
|
const defaultConfig = {
|
|
6
7
|
retries: 3,
|
|
@@ -99,7 +100,10 @@ module.exports = (config) => {
|
|
|
99
100
|
config.when = when;
|
|
100
101
|
|
|
101
102
|
event.dispatcher.on(event.step.started, (step) => {
|
|
102
|
-
if (process.env.TRY_TO)
|
|
103
|
+
if (process.env.TRY_TO === 'true') {
|
|
104
|
+
log('Info: RetryFailedStep plugin is disabled inside tryTo block');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
103
107
|
|
|
104
108
|
// if a step is ignored - return
|
|
105
109
|
for (const ignored of config.ignoredSteps) {
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -89,9 +89,9 @@ module.exports = function (config) {
|
|
|
89
89
|
let err = null;
|
|
90
90
|
|
|
91
91
|
return new Promise((done) => {
|
|
92
|
-
const tryBlock = () => {
|
|
92
|
+
const tryBlock = async () => {
|
|
93
93
|
recorder.session.start(`retryTo ${tries}`);
|
|
94
|
-
callback(tries);
|
|
94
|
+
await callback(tries);
|
|
95
95
|
recorder.add(() => {
|
|
96
96
|
recorder.session.restore(`retryTo ${tries}`);
|
|
97
97
|
done(null);
|