codeceptjs 2.6.1 → 2.6.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.
- package/CHANGELOG.md +13 -0
- package/docs/basics.md +5 -1
- package/docs/build/Playwright.js +36 -4
- package/docs/build/Puppeteer.js +58 -1
- package/docs/build/WebDriver.js +80 -4
- package/docs/changelog.md +13 -0
- package/docs/helpers/Appium.md +35 -1
- package/docs/helpers/MockRequest.md +281 -38
- package/docs/helpers/Playwright.md +45 -23
- package/docs/helpers/Puppeteer.md +36 -0
- package/docs/helpers/WebDriver.md +39 -1
- package/docs/reports.md +12 -0
- package/docs/visual.md +0 -73
- package/docs/webapi/forceClick.mustache +27 -0
- package/docs/webdriver.md +3 -1
- package/lib/command/run-workers.js +7 -4
- package/lib/command/workers/runTests.js +1 -0
- package/lib/event.js +2 -0
- package/lib/helper/Playwright.js +36 -4
- package/lib/helper/Puppeteer.js +31 -1
- package/lib/helper/WebDriver.js +53 -4
- package/lib/plugin/allure.js +1 -0
- package/lib/plugin/wdio.js +10 -1
- package/lib/reporter/cli.js +30 -1
- package/package.json +4 -3
- package/typings/types.d.ts +87 -1
package/docs/reports.md
CHANGED
|
@@ -199,6 +199,18 @@ Allure reports can also be generated for `dry-run` command. So you can get the f
|
|
|
199
199
|
npx codeceptjs dry-run --debug -p allure
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
## ReportPortal
|
|
203
|
+
|
|
204
|
+
Allure is a great reportin tool, however, if you are running tests on different machines it is hard to merge its XML result files to build a proper report. So, for enterprise grade reporting we recommend using [ReportPortal](https://reportportal.io).
|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+
[ReportPortal](https://reportportal.io) is open-source self-hosted service for aggregating test execution reports.
|
|
209
|
+
Think of it as Kibana but for test reports.
|
|
210
|
+
|
|
211
|
+
Use official [CodeceptJS Agent for ReportPortal](https://github.com/reportportal/agent-js-codecept/) to start publishing your test results.
|
|
212
|
+
|
|
213
|
+
|
|
202
214
|
## XML
|
|
203
215
|
|
|
204
216
|
Use default xunit reporter of Mocha to print xml reports. Provide `--reporter xunit` to get the report to screen.
|
package/docs/visual.md
CHANGED
|
@@ -112,79 +112,6 @@ MisMatch Percentage Calculated is 2.85
|
|
|
112
112
|
1) `seeVisualDiff` which can be used to compare two images and calculate the misMatch percentage.
|
|
113
113
|
2) `seeVisualDiffForElement` which can be used to compare elements on the two images and calculate misMatch percentage.
|
|
114
114
|
|
|
115
|
-
## Using Visual Knight
|
|
116
|
-
|
|
117
|
-
Visual Knight is a SaaS product which strongly supports CodeceptJS with multiple use cases. It provides an user interface to handle mismatches, statistics and more. It was designed to support Designer, Product Owner and other roles which are not familiar with coding and all this tools. All captured images are saved in a secure cloud system to not mess up your git repository.
|
|
118
|
-
|
|
119
|
-
### Setup
|
|
120
|
-
|
|
121
|
-
Create an account at [Visual Knight](https://www.visual-knight.io) and install the npm package
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
npm install @visual-knight/codeceptjs -D
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Configuring
|
|
128
|
-
|
|
129
|
-
```json
|
|
130
|
-
{
|
|
131
|
-
"helpers": {
|
|
132
|
-
"VisualKnight": {
|
|
133
|
-
"require": "@visual-knight/codeceptjs",
|
|
134
|
-
"key": "YOUR_API_KEY",
|
|
135
|
-
"project": "YOUR_PROJECT_ID OR PROJECT_NAME"
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Usage
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
144
|
-
/**
|
|
145
|
-
* @param testName {string} Is provided to visual knight (must be unique)
|
|
146
|
-
* @param options {ScreenshotOptions} Contains additional settings
|
|
147
|
-
*/
|
|
148
|
-
I.compareFullpageScreenshot(testName, options)
|
|
149
|
-
/**
|
|
150
|
-
* @param testName {string} Is provided to visual knight (must be unique)
|
|
151
|
-
* @param options {ScreenshotOptions} Contains additional settings
|
|
152
|
-
*/
|
|
153
|
-
I.compareViewportScreenshot(testName, options)
|
|
154
|
-
/**
|
|
155
|
-
* @param cssSelector {string} Is provided to visual knight
|
|
156
|
-
* @param testName {string} Is provided to visual knight (must be unique)
|
|
157
|
-
* @param options {ScreenshotOptions} Contains additional settings
|
|
158
|
-
*/
|
|
159
|
-
I.compareElementScreenshot(cssSelector, testName, options)
|
|
160
|
-
|
|
161
|
-
/*
|
|
162
|
-
ScreenshotOptions {
|
|
163
|
-
hide?: string[] // Array of css selectors which gets hidden by "opacity: 0",
|
|
164
|
-
remove?: string[] // Array of css selectors which gets hidden by "display: none",
|
|
165
|
-
additional?: object // Data is saved as relation to the variation. (Future: can be used for filtering)
|
|
166
|
-
}
|
|
167
|
-
*/
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
> You can find the latest documentation here [CodeceptJS helper page](https://doc.visual-knight.io/adapters/codeceptjs)
|
|
171
|
-
|
|
172
|
-
### Example
|
|
173
|
-
|
|
174
|
-
Lets consider visual testing for [CodeceptJS Home](http://codecept.io)
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
Feature('To test screen comparison with Visual Knight Example test');
|
|
178
|
-
|
|
179
|
-
Scenario('Compare CodeceptIO Home Page @visual-test', async (I, adminPage) => {
|
|
180
|
-
I.amOnPage("/");
|
|
181
|
-
I.compareFullpageScreenshot("CodeceptIO Home Page")
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Depending of your configuration this test will fail if no baseline exists and log the link to the image to accept or automatically accept the first run as baseline.
|
|
186
|
-
> You can accept the first image as baseline automatically via ```autoBaseline: true``` _default is false_
|
|
187
|
-
|
|
188
115
|
## Using Applitools
|
|
189
116
|
|
|
190
117
|
Applitools helps Test Automation engineers, DevOps, and FrontEnd Developers continuously test and validate visually perfect mobile, web, and native apps. Now it can be used with CodeceptJS.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Perform an emulated click on a link or a button, given by a locator.
|
|
2
|
+
Unlike normal click instead of sending native event, emulates a click with JavaScript.
|
|
3
|
+
This works on hidden, animated or inactive elements as well.
|
|
4
|
+
|
|
5
|
+
If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
6
|
+
For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
|
|
7
|
+
For images, the "alt" attribute and inner text of any parent links are searched.
|
|
8
|
+
|
|
9
|
+
The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// simple link
|
|
13
|
+
I.forceClick('Logout');
|
|
14
|
+
// button of form
|
|
15
|
+
I.forceClick('Submit');
|
|
16
|
+
// CSS button
|
|
17
|
+
I.forceClick('#form input[type=submit]');
|
|
18
|
+
// XPath
|
|
19
|
+
I.forceClick('//form/*[@type=submit]');
|
|
20
|
+
// link in context
|
|
21
|
+
I.forceClick('Logout', '#nav');
|
|
22
|
+
// using strict locator
|
|
23
|
+
I.forceClick({css: 'nav a.login'});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
@param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
|
|
27
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
|
package/docs/webdriver.md
CHANGED
|
@@ -53,6 +53,8 @@ exports.config = {
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
> ⚠ It is not recommended to use wdio plugin & selenium-standalone when running tests in parallel. Consider **switching to Selenoid** if you need parallel run or using cloud services.
|
|
57
|
+
|
|
56
58
|
|
|
57
59
|
## Configuring WebDriver
|
|
58
60
|
|
|
@@ -104,7 +106,7 @@ path: '/',
|
|
|
104
106
|
|
|
105
107
|
> If you face issues connecting to WebDriver, please check that corresponding server is running on a specified port. If host is other than `localhost` or port is other than `4444`, update the configuration.
|
|
106
108
|
|
|
107
|
-
### Selenium in Docker
|
|
109
|
+
### Selenium in Docker (Selenoid)
|
|
108
110
|
|
|
109
111
|
Browsers can be executed in Docker containers. This is useful when testing on Continous Integration server.
|
|
110
112
|
We recommend using [Selenoid](https://aerokube.com/selenoid/) to run browsers in container.
|
|
@@ -101,10 +101,11 @@ module.exports = function (workers, options) {
|
|
|
101
101
|
|
|
102
102
|
switch (message.event) {
|
|
103
103
|
case event.test.failed:
|
|
104
|
-
updateFinishedTests(repackTest(message.data), maxReruns);
|
|
105
104
|
output.test.failed(repackTest(message.data));
|
|
105
|
+
updateFinishedTests(repackTest(message.data), maxReruns);
|
|
106
106
|
break;
|
|
107
|
-
case event.test.passed:
|
|
107
|
+
case event.test.passed:
|
|
108
|
+
output.test.passed(repackTest(message.data));
|
|
108
109
|
updateFinishedTests(repackTest(message.data), maxReruns);
|
|
109
110
|
break;
|
|
110
111
|
case event.suite.before: output.suite.started(message.data); break;
|
|
@@ -232,11 +233,13 @@ function simplifyObject(object) {
|
|
|
232
233
|
const updateFinishedTests = (test, maxReruns) => {
|
|
233
234
|
const { id } = test;
|
|
234
235
|
if (finishedTests[id]) {
|
|
235
|
-
const stats = { passes: 0, failures: -1, tests: 0 };
|
|
236
236
|
if (finishedTests[id].runs <= maxReruns) {
|
|
237
237
|
finishedTests[id].runs++;
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
if (test.retries !== -1) {
|
|
240
|
+
const stats = { passes: 0, failures: -1, tests: 0 };
|
|
241
|
+
appendStats(stats);
|
|
242
|
+
}
|
|
240
243
|
} else {
|
|
241
244
|
finishedTests[id] = test;
|
|
242
245
|
finishedTests[id].runs = 1;
|
package/lib/event.js
CHANGED
|
@@ -24,6 +24,7 @@ module.exports = {
|
|
|
24
24
|
* @property {'test.passed'} passed
|
|
25
25
|
* @property {'test.failed'} failed
|
|
26
26
|
* @property {'test.finish'} finished
|
|
27
|
+
* @property {'test.skipped'} skipped
|
|
27
28
|
*/
|
|
28
29
|
test: {
|
|
29
30
|
started: 'test.start', // sync
|
|
@@ -32,6 +33,7 @@ module.exports = {
|
|
|
32
33
|
passed: 'test.passed', // sync
|
|
33
34
|
failed: 'test.failed', // sync
|
|
34
35
|
finished: 'test.finish', // sync
|
|
36
|
+
skipped: 'test.skipped', // sync
|
|
35
37
|
},
|
|
36
38
|
/**
|
|
37
39
|
* @type {object}
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const requireg = require('requireg');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const fsExtra = require('fs-extra');
|
|
3
5
|
|
|
4
6
|
const Helper = require('../helper');
|
|
5
7
|
const Locator = require('../locator');
|
|
@@ -69,7 +71,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
69
71
|
* * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
|
|
70
72
|
* * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to false.
|
|
71
73
|
* * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
|
|
72
|
-
* * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Playwright API](https://github.com/
|
|
74
|
+
* * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/master/docs/api.md#pagewaitfornavigationoptions).
|
|
73
75
|
* * `pressKeyDelay`: (optional, default: '10'). Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
|
|
74
76
|
* * `getPageTimeout` (optional, default: '0') config option to set maximum navigation time in milliseconds.
|
|
75
77
|
* * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
|
|
@@ -94,7 +96,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
94
96
|
* }
|
|
95
97
|
* ```
|
|
96
98
|
*
|
|
97
|
-
* #### Example #2: Wait for DOMContentLoaded event
|
|
99
|
+
* #### Example #2: Wait for DOMContentLoaded event
|
|
98
100
|
*
|
|
99
101
|
* ```js
|
|
100
102
|
* {
|
|
@@ -102,7 +104,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
102
104
|
* Playwright : {
|
|
103
105
|
* url: "http://localhost",
|
|
104
106
|
* restart: false,
|
|
105
|
-
* waitForNavigation:
|
|
107
|
+
* waitForNavigation: "domcontentloaded",
|
|
106
108
|
* waitForAction: 500
|
|
107
109
|
* }
|
|
108
110
|
* }
|
|
@@ -533,7 +535,7 @@ class Playwright extends Helper {
|
|
|
533
535
|
this.browser.on('targetchanged', (target) => {
|
|
534
536
|
this.debugSection('Url', target.url());
|
|
535
537
|
});
|
|
536
|
-
this.browserContext = await this.browser.newContext(this.options.emulate);
|
|
538
|
+
this.browserContext = await this.browser.newContext({ acceptDownloads: true, ...this.options.emulate });
|
|
537
539
|
|
|
538
540
|
const existingPages = await this.browserContext.pages();
|
|
539
541
|
|
|
@@ -993,6 +995,36 @@ class Playwright extends Helper {
|
|
|
993
995
|
return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
|
|
994
996
|
}
|
|
995
997
|
|
|
998
|
+
/**
|
|
999
|
+
* Handles a file download.Aa file name is required to save the file on disk.
|
|
1000
|
+
* Files are saved to "output" directory.
|
|
1001
|
+
*
|
|
1002
|
+
* Should be used with [FileSystem helper](https://codecept.io/helpers/FileSystem) to check that file were downloaded correctly.
|
|
1003
|
+
*
|
|
1004
|
+
* ```js
|
|
1005
|
+
* I.handleDownloads('downloads/avatar.jpg');
|
|
1006
|
+
* I.click('Download Avatar');
|
|
1007
|
+
* I.amInPath('output/downloads');
|
|
1008
|
+
* I.waitForFile('downloads/avatar.jpg', 5);
|
|
1009
|
+
*
|
|
1010
|
+
* ```
|
|
1011
|
+
*
|
|
1012
|
+
* @param {string} [fileName] set filename for downloaded file
|
|
1013
|
+
*/
|
|
1014
|
+
async handleDownloads(fileName = 'downloads') {
|
|
1015
|
+
this.page.waitForEvent('download').then(async (download) => {
|
|
1016
|
+
const filePath = await download.path();
|
|
1017
|
+
const downloadPath = path.join(global.output_dir, fileName || path.basename(filePath));
|
|
1018
|
+
if (!fs.existsSync(path.dirname(downloadPath))) {
|
|
1019
|
+
fs.mkdirSync(path.dirname(downloadPath), '0777');
|
|
1020
|
+
}
|
|
1021
|
+
fs.copyFileSync(filePath, downloadPath);
|
|
1022
|
+
this.debug('Download completed');
|
|
1023
|
+
this.debugSection('Downloaded From', await download.url());
|
|
1024
|
+
this.debugSection('Downloaded To', downloadPath);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
996
1028
|
/**
|
|
997
1029
|
* {{> click }}
|
|
998
1030
|
*
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -232,7 +232,7 @@ class Puppeteer extends Helper {
|
|
|
232
232
|
try {
|
|
233
233
|
requireg('puppeteer');
|
|
234
234
|
} catch (e) {
|
|
235
|
-
return ['puppeteer@^
|
|
235
|
+
return ['puppeteer@^3.0.1'];
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -943,6 +943,36 @@ class Puppeteer extends Helper {
|
|
|
943
943
|
return proceedClick.call(this, locator, context);
|
|
944
944
|
}
|
|
945
945
|
|
|
946
|
+
/**
|
|
947
|
+
* {{> forceClick }}
|
|
948
|
+
*
|
|
949
|
+
* {{ react }}
|
|
950
|
+
*/
|
|
951
|
+
async forceClick(locator, context = null) {
|
|
952
|
+
let matcher = await this.context;
|
|
953
|
+
if (context) {
|
|
954
|
+
const els = await this._locate(context);
|
|
955
|
+
assertElementExists(els, context);
|
|
956
|
+
matcher = els[0];
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const els = await findClickable.call(this, matcher, locator);
|
|
960
|
+
if (context) {
|
|
961
|
+
assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`);
|
|
962
|
+
} else {
|
|
963
|
+
assertElementExists(els, locator, 'Clickable element');
|
|
964
|
+
}
|
|
965
|
+
const elem = els[0];
|
|
966
|
+
return this.executeScript((el) => {
|
|
967
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
968
|
+
document.activeElement.blur();
|
|
969
|
+
}
|
|
970
|
+
const event = document.createEvent('MouseEvent');
|
|
971
|
+
event.initEvent('click', true, true);
|
|
972
|
+
return el.dispatchEvent(event);
|
|
973
|
+
}, elem);
|
|
974
|
+
}
|
|
975
|
+
|
|
946
976
|
/**
|
|
947
977
|
* {{> clickLink }}
|
|
948
978
|
*
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -516,6 +516,11 @@ class WebDriver extends Helper {
|
|
|
516
516
|
if (this.options.multiremote) {
|
|
517
517
|
this.browser = await webdriverio.multiremote(this.options.multiremote);
|
|
518
518
|
} else {
|
|
519
|
+
// remove non w3c capabilities
|
|
520
|
+
delete this.options.capabilities.protocol;
|
|
521
|
+
delete this.options.capabilities.hostname;
|
|
522
|
+
delete this.options.capabilities.port;
|
|
523
|
+
delete this.options.capabilities.path;
|
|
519
524
|
this.browser = await webdriverio.remote(this.options);
|
|
520
525
|
}
|
|
521
526
|
} catch (err) {
|
|
@@ -765,13 +770,15 @@ class WebDriver extends Helper {
|
|
|
765
770
|
* Find a clickable element by providing human readable text:
|
|
766
771
|
*
|
|
767
772
|
* ```js
|
|
768
|
-
* this.helpers
|
|
773
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page');
|
|
774
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page', '.pages');
|
|
769
775
|
* ```
|
|
770
776
|
*
|
|
771
777
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
772
778
|
*/
|
|
773
|
-
async _locateClickable(locator) {
|
|
774
|
-
|
|
779
|
+
async _locateClickable(locator, context) {
|
|
780
|
+
const locateFn = prepareLocateFn.call(this, context);
|
|
781
|
+
return findClickable.call(this, locator, locateFn);
|
|
775
782
|
}
|
|
776
783
|
|
|
777
784
|
/**
|
|
@@ -842,6 +849,32 @@ class WebDriver extends Helper {
|
|
|
842
849
|
return this.browser[clickMethod](getElementId(elem));
|
|
843
850
|
}
|
|
844
851
|
|
|
852
|
+
/**
|
|
853
|
+
* {{> forceClick }}
|
|
854
|
+
*
|
|
855
|
+
* {{ react }}
|
|
856
|
+
*/
|
|
857
|
+
async forceClick(locator, context = null) {
|
|
858
|
+
const locateFn = prepareLocateFn.call(this, context);
|
|
859
|
+
|
|
860
|
+
const res = await findClickable.call(this, locator, locateFn);
|
|
861
|
+
if (context) {
|
|
862
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
|
|
863
|
+
} else {
|
|
864
|
+
assertElementExists(res, locator, 'Clickable element');
|
|
865
|
+
}
|
|
866
|
+
const elem = usingFirstElement(res);
|
|
867
|
+
|
|
868
|
+
return this.executeScript((el) => {
|
|
869
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
870
|
+
document.activeElement.blur();
|
|
871
|
+
}
|
|
872
|
+
const event = document.createEvent('MouseEvent');
|
|
873
|
+
event.initEvent('click', true, true);
|
|
874
|
+
return el.dispatchEvent(event);
|
|
875
|
+
}, elem);
|
|
876
|
+
}
|
|
877
|
+
|
|
845
878
|
/**
|
|
846
879
|
* {{> doubleClick }}
|
|
847
880
|
*
|
|
@@ -1859,6 +1892,19 @@ class WebDriver extends Helper {
|
|
|
1859
1892
|
*/
|
|
1860
1893
|
async waitForEnabled(locator, sec = null) {
|
|
1861
1894
|
const aSec = sec || this.options.waitForTimeout;
|
|
1895
|
+
if (isWebDriver5()) {
|
|
1896
|
+
return this.browser.waitUntil(async () => {
|
|
1897
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
1898
|
+
if (!res || res.length === 0) {
|
|
1899
|
+
return false;
|
|
1900
|
+
}
|
|
1901
|
+
const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)));
|
|
1902
|
+
if (Array.isArray(selected)) {
|
|
1903
|
+
return selected.filter(val => val === true).length > 0;
|
|
1904
|
+
}
|
|
1905
|
+
return selected;
|
|
1906
|
+
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
1907
|
+
}
|
|
1862
1908
|
return this.browser.waitUntil(async () => {
|
|
1863
1909
|
const res = await this.$$(withStrictLocator(locator));
|
|
1864
1910
|
if (!res || res.length === 0) {
|
|
@@ -1869,7 +1915,10 @@ class WebDriver extends Helper {
|
|
|
1869
1915
|
return selected.filter(val => val === true).length > 0;
|
|
1870
1916
|
}
|
|
1871
1917
|
return selected;
|
|
1872
|
-
},
|
|
1918
|
+
}, {
|
|
1919
|
+
timeout: aSec * 1000,
|
|
1920
|
+
timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
|
|
1921
|
+
});
|
|
1873
1922
|
}
|
|
1874
1923
|
|
|
1875
1924
|
/**
|
package/lib/plugin/allure.js
CHANGED
package/lib/plugin/wdio.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const debug = require('debug')('codeceptjs:plugin:wdio');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
2
4
|
|
|
3
5
|
const container = require('../container');
|
|
4
6
|
const mainConfig = require('../config');
|
|
@@ -99,7 +101,14 @@ module.exports = (config) => {
|
|
|
99
101
|
if (Service) {
|
|
100
102
|
if (Service.launcher && typeof Service.launcher === 'function') {
|
|
101
103
|
const Launcher = Service.launcher;
|
|
102
|
-
|
|
104
|
+
|
|
105
|
+
const version = JSON.parse(fs.readFileSync(path.join(require.resolve('webdriverio'), '/../../', 'package.json')).toString()).version;
|
|
106
|
+
if (version.indexOf('5') === 0) {
|
|
107
|
+
launchers.push(new Launcher(config));
|
|
108
|
+
} else {
|
|
109
|
+
const options = { logPath: global.output_dir, installArgs: {}, args: {} };
|
|
110
|
+
launchers.push(new Launcher(options, [config.capabilities], config));
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
if (typeof Service === 'function') {
|
|
105
114
|
services.push(new Service(config, config.capabilities));
|
package/lib/reporter/cli.js
CHANGED
|
@@ -11,11 +11,11 @@ class Cli extends Base {
|
|
|
11
11
|
constructor(runner, opts) {
|
|
12
12
|
super(runner);
|
|
13
13
|
let level = 0;
|
|
14
|
+
this.failedTests = [];
|
|
14
15
|
opts = opts.reporterOptions || opts;
|
|
15
16
|
if (opts.steps) level = 1;
|
|
16
17
|
if (opts.debug) level = 2;
|
|
17
18
|
if (opts.verbose) level = 3;
|
|
18
|
-
|
|
19
19
|
output.level(level);
|
|
20
20
|
output.print(`CodeceptJS v${require('../codecept').version()}`);
|
|
21
21
|
output.print(`Using test root "${global.codecept_dir}"`);
|
|
@@ -42,6 +42,9 @@ class Cli extends Base {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
runner.on('fail', (test, err) => {
|
|
45
|
+
if (test.ctx.currentTest) {
|
|
46
|
+
this.failedTests.push(test.ctx.currentTest.id);
|
|
47
|
+
}
|
|
45
48
|
if (showSteps && test.steps) {
|
|
46
49
|
return output.scenario.failed(test);
|
|
47
50
|
}
|
|
@@ -90,6 +93,24 @@ class Cli extends Base {
|
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
runner.on('suite end', suite => {
|
|
97
|
+
let skippedCount = 0;
|
|
98
|
+
const grep = runner._grep;
|
|
99
|
+
for (const test of suite.tests) {
|
|
100
|
+
if (!test.state && !this.failedTests.includes(test.id)) {
|
|
101
|
+
if (matchTest(grep, test.title)) {
|
|
102
|
+
event.emit(event.test.skipped, test);
|
|
103
|
+
test.state = 'skipped';
|
|
104
|
+
output.test.skipped(test);
|
|
105
|
+
skippedCount += 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.stats.pending += skippedCount;
|
|
111
|
+
this.stats.tests += skippedCount;
|
|
112
|
+
});
|
|
113
|
+
|
|
93
114
|
runner.on('end', this.result.bind(this));
|
|
94
115
|
}
|
|
95
116
|
|
|
@@ -129,6 +150,14 @@ class Cli extends Base {
|
|
|
129
150
|
output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration));
|
|
130
151
|
}
|
|
131
152
|
}
|
|
153
|
+
|
|
154
|
+
function matchTest(grep, test) {
|
|
155
|
+
if (grep) {
|
|
156
|
+
return grep.test(test);
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
module.exports = function (runner, opts) {
|
|
133
162
|
return new Cli(runner, opts);
|
|
134
163
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acceptance",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@codeceptjs/detox-helper": "^1.0.2",
|
|
85
|
+
"@codeceptjs/mock-request": "^0.3.0",
|
|
85
86
|
"@pollyjs/adapter-puppeteer": "^2.6.3",
|
|
86
87
|
"@pollyjs/core": "^2.6.3",
|
|
87
88
|
"@types/inquirer": "^0.0.35",
|
|
@@ -109,9 +110,9 @@
|
|
|
109
110
|
"mocha-parallel-tests": "^2.2.2",
|
|
110
111
|
"nightmare": "^3.0.2",
|
|
111
112
|
"nodemon": "^1.19.4",
|
|
112
|
-
"playwright": "^0.
|
|
113
|
+
"playwright": "^0.14.0",
|
|
113
114
|
"protractor": "^5.4.1",
|
|
114
|
-
"puppeteer": "^
|
|
115
|
+
"puppeteer": "^3.0.0",
|
|
115
116
|
"qrcode-terminal": "^0.12.0",
|
|
116
117
|
"rosie": "^1.6.0",
|
|
117
118
|
"runio.js": "^1.0.20",
|
package/typings/types.d.ts
CHANGED
|
@@ -2498,6 +2498,23 @@ declare namespace CodeceptJS {
|
|
|
2498
2498
|
* @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
|
|
2499
2499
|
*/
|
|
2500
2500
|
dontSeeElementInDOM(locator: CodeceptJS.LocatorOrString): void;
|
|
2501
|
+
/**
|
|
2502
|
+
* Handles a file download.Aa file name is required to save the file on disk.
|
|
2503
|
+
* Files are saved to "output" directory.
|
|
2504
|
+
*
|
|
2505
|
+
* Should be used with [FileSystem helper](https://codecept.io/helpers/FileSystem) to check that file were downloaded correctly.
|
|
2506
|
+
*
|
|
2507
|
+
* ```js
|
|
2508
|
+
* I.handleDownloads('downloads/avatar.jpg');
|
|
2509
|
+
* I.click('Download Avatar');
|
|
2510
|
+
* I.amInPath('output/downloads');
|
|
2511
|
+
* I.waitForFile('downloads/avatar.jpg', 5);
|
|
2512
|
+
*
|
|
2513
|
+
* ```
|
|
2514
|
+
*
|
|
2515
|
+
* @param {string} [fileName] set filename for downloaded file
|
|
2516
|
+
*/
|
|
2517
|
+
handleDownloads(fileName?: string): void;
|
|
2501
2518
|
/**
|
|
2502
2519
|
* Perform a click on a link or a button, given by a locator.
|
|
2503
2520
|
* If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
@@ -5109,6 +5126,39 @@ declare namespace CodeceptJS {
|
|
|
5109
5126
|
* {{ react }}
|
|
5110
5127
|
*/
|
|
5111
5128
|
click(locator: CodeceptJS.LocatorOrString, context?: CodeceptJS.LocatorOrString): void;
|
|
5129
|
+
/**
|
|
5130
|
+
* Perform an emulated click on a link or a button, given by a locator.
|
|
5131
|
+
* Unlike normal click instead of sending native event, emulates a click with JavaScript.
|
|
5132
|
+
* This works on hidden, animated or inactive elements as well.
|
|
5133
|
+
*
|
|
5134
|
+
* If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
5135
|
+
* For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
|
|
5136
|
+
* For images, the "alt" attribute and inner text of any parent links are searched.
|
|
5137
|
+
*
|
|
5138
|
+
* The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
5139
|
+
*
|
|
5140
|
+
* ```js
|
|
5141
|
+
* // simple link
|
|
5142
|
+
* I.forceClick('Logout');
|
|
5143
|
+
* // button of form
|
|
5144
|
+
* I.forceClick('Submit');
|
|
5145
|
+
* // CSS button
|
|
5146
|
+
* I.forceClick('#form input[type=submit]');
|
|
5147
|
+
* // XPath
|
|
5148
|
+
* I.forceClick('//form/*[@type=submit]');
|
|
5149
|
+
* // link in context
|
|
5150
|
+
* I.forceClick('Logout', '#nav');
|
|
5151
|
+
* // using strict locator
|
|
5152
|
+
* I.forceClick({css: 'nav a.login'});
|
|
5153
|
+
* ```
|
|
5154
|
+
*
|
|
5155
|
+
* @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
|
|
5156
|
+
* @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
|
|
5157
|
+
*
|
|
5158
|
+
*
|
|
5159
|
+
* {{ react }}
|
|
5160
|
+
*/
|
|
5161
|
+
forceClick(locator: CodeceptJS.LocatorOrString, context?: CodeceptJS.LocatorOrString): void;
|
|
5112
5162
|
/**
|
|
5113
5163
|
* Performs a click on a link and waits for navigation before moving on.
|
|
5114
5164
|
*
|
|
@@ -7120,7 +7170,8 @@ declare namespace CodeceptJS {
|
|
|
7120
7170
|
* Find a clickable element by providing human readable text:
|
|
7121
7171
|
*
|
|
7122
7172
|
* ```js
|
|
7123
|
-
* this.helpers
|
|
7173
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page');
|
|
7174
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page', '.pages');
|
|
7124
7175
|
* ```
|
|
7125
7176
|
*
|
|
7126
7177
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
@@ -7193,6 +7244,39 @@ declare namespace CodeceptJS {
|
|
|
7193
7244
|
* {{ react }}
|
|
7194
7245
|
*/
|
|
7195
7246
|
click(locator: CodeceptJS.LocatorOrString, context?: CodeceptJS.LocatorOrString): void;
|
|
7247
|
+
/**
|
|
7248
|
+
* Perform an emulated click on a link or a button, given by a locator.
|
|
7249
|
+
* Unlike normal click instead of sending native event, emulates a click with JavaScript.
|
|
7250
|
+
* This works on hidden, animated or inactive elements as well.
|
|
7251
|
+
*
|
|
7252
|
+
* If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
7253
|
+
* For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
|
|
7254
|
+
* For images, the "alt" attribute and inner text of any parent links are searched.
|
|
7255
|
+
*
|
|
7256
|
+
* The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
7257
|
+
*
|
|
7258
|
+
* ```js
|
|
7259
|
+
* // simple link
|
|
7260
|
+
* I.forceClick('Logout');
|
|
7261
|
+
* // button of form
|
|
7262
|
+
* I.forceClick('Submit');
|
|
7263
|
+
* // CSS button
|
|
7264
|
+
* I.forceClick('#form input[type=submit]');
|
|
7265
|
+
* // XPath
|
|
7266
|
+
* I.forceClick('//form/*[@type=submit]');
|
|
7267
|
+
* // link in context
|
|
7268
|
+
* I.forceClick('Logout', '#nav');
|
|
7269
|
+
* // using strict locator
|
|
7270
|
+
* I.forceClick({css: 'nav a.login'});
|
|
7271
|
+
* ```
|
|
7272
|
+
*
|
|
7273
|
+
* @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
|
|
7274
|
+
* @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
|
|
7275
|
+
*
|
|
7276
|
+
*
|
|
7277
|
+
* {{ react }}
|
|
7278
|
+
*/
|
|
7279
|
+
forceClick(locator: CodeceptJS.LocatorOrString, context?: CodeceptJS.LocatorOrString): void;
|
|
7196
7280
|
/**
|
|
7197
7281
|
* Performs a double-click on an element matched by link|button|label|CSS or XPath.
|
|
7198
7282
|
* Context can be specified as second parameter to narrow search.
|
|
@@ -9970,6 +10054,7 @@ declare namespace CodeceptJS {
|
|
|
9970
10054
|
* @property {'test.passed'} passed
|
|
9971
10055
|
* @property {'test.failed'} failed
|
|
9972
10056
|
* @property {'test.finish'} finished
|
|
10057
|
+
* @property {'test.skipped'} skipped
|
|
9973
10058
|
*/
|
|
9974
10059
|
const test: {
|
|
9975
10060
|
started: 'test.start';
|
|
@@ -9978,6 +10063,7 @@ declare namespace CodeceptJS {
|
|
|
9978
10063
|
passed: 'test.passed';
|
|
9979
10064
|
failed: 'test.failed';
|
|
9980
10065
|
finished: 'test.finish';
|
|
10066
|
+
skipped: 'test.skipped';
|
|
9981
10067
|
};
|
|
9982
10068
|
/**
|
|
9983
10069
|
* @type {object}
|