codeceptjs 3.0.6 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +92 -8
- package/README.md +9 -1
- package/bin/codecept.js +28 -17
- package/docs/build/Appium.js +69 -0
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Playwright.js +271 -63
- package/docs/build/Protractor.js +2 -0
- package/docs/build/Puppeteer.js +56 -18
- package/docs/build/REST.js +16 -3
- package/docs/build/WebDriver.js +82 -16
- package/docs/changelog.md +93 -9
- package/docs/configuration.md +15 -2
- package/docs/email.md +8 -8
- package/docs/examples.md +3 -3
- package/docs/helpers/Appium.md +66 -68
- package/docs/helpers/MockRequest.md +3 -3
- package/docs/helpers/Playwright.md +269 -203
- package/docs/helpers/Puppeteer.md +17 -1
- package/docs/helpers/REST.md +23 -9
- package/docs/helpers/WebDriver.md +3 -2
- package/docs/locators.md +27 -0
- package/docs/mobile.md +2 -1
- package/docs/nightmare.md +0 -5
- package/docs/parallel.md +14 -7
- package/docs/playwright.md +178 -11
- package/docs/plugins.md +61 -69
- package/docs/react.md +1 -1
- package/docs/reports.md +5 -4
- package/lib/actor.js +1 -2
- package/lib/codecept.js +13 -2
- package/lib/command/definitions.js +8 -1
- package/lib/command/interactive.js +4 -2
- package/lib/command/run-multiple/collection.js +4 -0
- package/lib/container.js +3 -3
- package/lib/helper/Appium.js +41 -0
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Playwright.js +218 -70
- package/lib/helper/Protractor.js +2 -0
- package/lib/helper/Puppeteer.js +56 -18
- package/lib/helper/REST.js +12 -0
- package/lib/helper/WebDriver.js +82 -16
- package/lib/helper/errors/ConnectionRefused.js +1 -1
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +44 -32
- package/lib/interfaces/gherkin.js +1 -0
- package/lib/listener/exit.js +2 -4
- package/lib/listener/helpers.js +3 -4
- package/lib/locator.js +7 -0
- package/lib/mochaFactory.js +11 -6
- package/lib/output.js +5 -2
- package/lib/plugin/allure.js +7 -18
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
- package/lib/plugin/customLocator.js +2 -2
- package/lib/plugin/screenshotOnFail.js +5 -0
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/step.js +4 -2
- package/lib/ui.js +6 -2
- package/package.json +5 -4
- package/typings/index.d.ts +44 -21
- package/typings/types.d.ts +137 -16
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -129,8 +129,9 @@ const consoleLogStore = new Console();
|
|
|
129
129
|
* }
|
|
130
130
|
* }
|
|
131
131
|
* ```
|
|
132
|
+
* > Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
132
133
|
*
|
|
133
|
-
* #### Example #5: Target URL with provided basic authentication
|
|
134
|
+
* #### Example #5: Target URL with provided basic authentication
|
|
134
135
|
*
|
|
135
136
|
* ```js
|
|
136
137
|
* {
|
|
@@ -143,10 +144,25 @@ const consoleLogStore = new Console();
|
|
|
143
144
|
* }
|
|
144
145
|
* }
|
|
145
146
|
* ```
|
|
147
|
+
* #### Troubleshooting
|
|
146
148
|
*
|
|
149
|
+
* Error Message: `No usable sandbox!`
|
|
150
|
+
*
|
|
151
|
+
* When running Puppeteer on CI try to disable sandbox if you see that message
|
|
152
|
+
*
|
|
153
|
+
* ```
|
|
154
|
+
* helpers: {
|
|
155
|
+
* Puppeteer: {
|
|
156
|
+
* url: 'http://localhost',
|
|
157
|
+
* show: false,
|
|
158
|
+
* chrome: {
|
|
159
|
+
* args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
160
|
+
* }
|
|
161
|
+
* },
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
147
164
|
*
|
|
148
165
|
*
|
|
149
|
-
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
150
166
|
*
|
|
151
167
|
* ## Access From Helpers
|
|
152
168
|
*
|
|
@@ -670,7 +686,7 @@ class Puppeteer extends Helper {
|
|
|
670
686
|
assertElementExists(els);
|
|
671
687
|
|
|
672
688
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
673
|
-
const { x, y } = await els[0]
|
|
689
|
+
const { x, y } = await getClickablePoint(els[0]);
|
|
674
690
|
await this.page.mouse.move(x + offsetX, y + offsetY);
|
|
675
691
|
return this._waitForAction();
|
|
676
692
|
}
|
|
@@ -726,7 +742,7 @@ class Puppeteer extends Helper {
|
|
|
726
742
|
const els = await this._locate(locator);
|
|
727
743
|
assertElementExists(els, locator, 'Element');
|
|
728
744
|
await els[0]._scrollIntoViewIfNeeded();
|
|
729
|
-
const elementCoordinates = await els[0]
|
|
745
|
+
const elementCoordinates = await getClickablePoint(els[0]);
|
|
730
746
|
await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
|
|
731
747
|
} else {
|
|
732
748
|
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY);
|
|
@@ -942,7 +958,10 @@ class Puppeteer extends Helper {
|
|
|
942
958
|
*/
|
|
943
959
|
async seeElement(locator) {
|
|
944
960
|
let els = await this._locate(locator);
|
|
945
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
961
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
962
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
963
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
964
|
+
|
|
946
965
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
947
966
|
}
|
|
948
967
|
|
|
@@ -952,7 +971,10 @@ class Puppeteer extends Helper {
|
|
|
952
971
|
*/
|
|
953
972
|
async dontSeeElement(locator) {
|
|
954
973
|
let els = await this._locate(locator);
|
|
955
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
974
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
975
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
976
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
977
|
+
|
|
956
978
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
957
979
|
}
|
|
958
980
|
|
|
@@ -1239,7 +1261,7 @@ class Puppeteer extends Helper {
|
|
|
1239
1261
|
* {{ react }}
|
|
1240
1262
|
*/
|
|
1241
1263
|
async fillField(field, value) {
|
|
1242
|
-
const els = await
|
|
1264
|
+
const els = await findVisibleFields.call(this, field);
|
|
1243
1265
|
assertElementExists(els, field, 'Field');
|
|
1244
1266
|
const el = els[0];
|
|
1245
1267
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -1266,7 +1288,7 @@ class Puppeteer extends Helper {
|
|
|
1266
1288
|
* {{ react }}
|
|
1267
1289
|
*/
|
|
1268
1290
|
async appendField(field, value) {
|
|
1269
|
-
const els = await
|
|
1291
|
+
const els = await findVisibleFields.call(this, field);
|
|
1270
1292
|
assertElementExists(els, field, 'Field');
|
|
1271
1293
|
await els[0].press('End');
|
|
1272
1294
|
await els[0].type(value, { delay: this.options.pressKeyDelay });
|
|
@@ -1308,7 +1330,7 @@ class Puppeteer extends Helper {
|
|
|
1308
1330
|
* {{> selectOption }}
|
|
1309
1331
|
*/
|
|
1310
1332
|
async selectOption(select, option) {
|
|
1311
|
-
const els = await
|
|
1333
|
+
const els = await findVisibleFields.call(this, select);
|
|
1312
1334
|
assertElementExists(els, select, 'Selectable field');
|
|
1313
1335
|
const el = els[0];
|
|
1314
1336
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
@@ -1342,7 +1364,10 @@ class Puppeteer extends Helper {
|
|
|
1342
1364
|
*/
|
|
1343
1365
|
async grabNumberOfVisibleElements(locator) {
|
|
1344
1366
|
let els = await this._locate(locator);
|
|
1345
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1367
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1368
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1369
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1370
|
+
|
|
1346
1371
|
return els.filter(v => v).length;
|
|
1347
1372
|
}
|
|
1348
1373
|
|
|
@@ -1727,8 +1752,8 @@ class Puppeteer extends Helper {
|
|
|
1727
1752
|
const src = await this._locate(locator);
|
|
1728
1753
|
assertElementExists(src, locator, 'Slider Element');
|
|
1729
1754
|
|
|
1730
|
-
// Note: Using
|
|
1731
|
-
const sliderSource = await src[0]
|
|
1755
|
+
// Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
|
|
1756
|
+
const sliderSource = await getClickablePoint(src[0]);
|
|
1732
1757
|
|
|
1733
1758
|
// Drag start point
|
|
1734
1759
|
await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 });
|
|
@@ -2264,7 +2289,7 @@ class Puppeteer extends Helper {
|
|
|
2264
2289
|
module.exports = Puppeteer;
|
|
2265
2290
|
|
|
2266
2291
|
async function findElements(matcher, locator) {
|
|
2267
|
-
if (locator.react) return findReact(matcher, locator);
|
|
2292
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
2268
2293
|
locator = new Locator(locator, 'css');
|
|
2269
2294
|
if (!locator.isXPath()) return matcher.$$(locator.simplify());
|
|
2270
2295
|
return matcher.$x(locator.value);
|
|
@@ -2293,7 +2318,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2293
2318
|
}
|
|
2294
2319
|
|
|
2295
2320
|
async function findClickable(matcher, locator) {
|
|
2296
|
-
if (locator.react) return findReact(matcher, locator);
|
|
2321
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
2297
2322
|
locator = new Locator(locator);
|
|
2298
2323
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
2299
2324
|
|
|
@@ -2376,6 +2401,12 @@ async function proceedIsChecked(assertType, option) {
|
|
|
2376
2401
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
|
|
2377
2402
|
}
|
|
2378
2403
|
|
|
2404
|
+
async function findVisibleFields(locator) {
|
|
2405
|
+
const els = await findFields.call(this, locator);
|
|
2406
|
+
const visible = await Promise.all(els.map(el => el.boundingBox()));
|
|
2407
|
+
return els.filter((el, index) => visible[index]);
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2379
2410
|
async function findFields(locator) {
|
|
2380
2411
|
const matchedLocator = new Locator(locator);
|
|
2381
2412
|
if (!matchedLocator.isFuzzy()) {
|
|
@@ -2406,9 +2437,9 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
2406
2437
|
const dst = await this._locate(destinationLocator);
|
|
2407
2438
|
assertElementExists(dst, destinationLocator, 'Destination Element');
|
|
2408
2439
|
|
|
2409
|
-
// Note: Using
|
|
2410
|
-
const dragSource = await src[0]
|
|
2411
|
-
const dragDestination = await dst[0]
|
|
2440
|
+
// Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
|
|
2441
|
+
const dragSource = await getClickablePoint(src[0]);
|
|
2442
|
+
const dragDestination = await getClickablePoint(dst[0]);
|
|
2412
2443
|
|
|
2413
2444
|
// Drag start point
|
|
2414
2445
|
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
|
|
@@ -2422,7 +2453,7 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
2422
2453
|
}
|
|
2423
2454
|
|
|
2424
2455
|
async function proceedSeeInField(assertType, field, value) {
|
|
2425
|
-
const els = await
|
|
2456
|
+
const els = await findVisibleFields.call(this, field);
|
|
2426
2457
|
assertElementExists(els, field, 'Field');
|
|
2427
2458
|
const el = els[0];
|
|
2428
2459
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -2555,6 +2586,13 @@ async function targetCreatedHandler(page) {
|
|
|
2555
2586
|
}
|
|
2556
2587
|
}
|
|
2557
2588
|
|
|
2589
|
+
// BC compatibility for Puppeteer < 10
|
|
2590
|
+
async function getClickablePoint(el) {
|
|
2591
|
+
if (el.clickablePoint) return el.clickablePoint();
|
|
2592
|
+
if (el._clickablePoint) return el._clickablePoint();
|
|
2593
|
+
return null;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2558
2596
|
// List of key values to key definitions
|
|
2559
2597
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
|
|
2560
2598
|
const keyDefinitionMap = {
|
package/lib/helper/REST.js
CHANGED
|
@@ -75,6 +75,8 @@ class REST extends Helper {
|
|
|
75
75
|
* Executes axios request
|
|
76
76
|
*
|
|
77
77
|
* @param {*} request
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<*>} response
|
|
78
80
|
*/
|
|
79
81
|
async _executeRequest(request) {
|
|
80
82
|
const _debugRequest = { ...request };
|
|
@@ -143,6 +145,8 @@ class REST extends Helper {
|
|
|
143
145
|
*
|
|
144
146
|
* @param {*} url
|
|
145
147
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
148
|
+
*
|
|
149
|
+
* @returns {Promise<*>} response
|
|
146
150
|
*/
|
|
147
151
|
async sendGetRequest(url, headers = {}) {
|
|
148
152
|
const request = {
|
|
@@ -166,6 +170,8 @@ class REST extends Helper {
|
|
|
166
170
|
* @param {*} url
|
|
167
171
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
168
172
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
173
|
+
*
|
|
174
|
+
* @returns {Promise<*>} response
|
|
169
175
|
*/
|
|
170
176
|
async sendPostRequest(url, payload = {}, headers = {}) {
|
|
171
177
|
const request = {
|
|
@@ -197,6 +203,8 @@ class REST extends Helper {
|
|
|
197
203
|
* @param {string} url
|
|
198
204
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
199
205
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
206
|
+
*
|
|
207
|
+
* @returns {Promise<*>} response
|
|
200
208
|
*/
|
|
201
209
|
async sendPatchRequest(url, payload = {}, headers = {}) {
|
|
202
210
|
const request = {
|
|
@@ -228,6 +236,8 @@ class REST extends Helper {
|
|
|
228
236
|
* @param {string} url
|
|
229
237
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
230
238
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
239
|
+
*
|
|
240
|
+
* @returns {Promise<*>} response
|
|
231
241
|
*/
|
|
232
242
|
async sendPutRequest(url, payload = {}, headers = {}) {
|
|
233
243
|
const request = {
|
|
@@ -254,6 +264,8 @@ class REST extends Helper {
|
|
|
254
264
|
*
|
|
255
265
|
* @param {*} url
|
|
256
266
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
267
|
+
*
|
|
268
|
+
* @returns {Promise<*>} response
|
|
257
269
|
*/
|
|
258
270
|
async sendDeleteRequest(url, headers = {}) {
|
|
259
271
|
const request = {
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -396,6 +396,7 @@ class WebDriver extends Helper {
|
|
|
396
396
|
this.isRunning = false;
|
|
397
397
|
this.sessionWindows = {};
|
|
398
398
|
this.activeSessionName = '';
|
|
399
|
+
this.customLocatorStrategies = config.customLocatorStrategies;
|
|
399
400
|
|
|
400
401
|
this._setConfig(config);
|
|
401
402
|
|
|
@@ -503,6 +504,33 @@ class WebDriver extends Helper {
|
|
|
503
504
|
}
|
|
504
505
|
}
|
|
505
506
|
|
|
507
|
+
_lookupCustomLocator(customStrategy) {
|
|
508
|
+
if (typeof (this.customLocatorStrategies) !== 'object') {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const strategy = this.customLocatorStrategies[customStrategy];
|
|
512
|
+
return typeof (strategy) === 'function' ? strategy : null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
_isCustomLocator(locator) {
|
|
516
|
+
const locatorObj = new Locator(locator);
|
|
517
|
+
if (locatorObj.isCustom()) {
|
|
518
|
+
const customLocator = this._lookupCustomLocator(locatorObj.type);
|
|
519
|
+
if (customLocator) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async _res(locator) {
|
|
528
|
+
const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
|
|
529
|
+
? await this._locate(locator)
|
|
530
|
+
: await this.$$(withStrictLocator(locator));
|
|
531
|
+
return res;
|
|
532
|
+
}
|
|
533
|
+
|
|
506
534
|
async _startBrowser() {
|
|
507
535
|
try {
|
|
508
536
|
if (this.options.multiremote) {
|
|
@@ -530,9 +558,22 @@ class WebDriver extends Helper {
|
|
|
530
558
|
await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
|
|
531
559
|
|
|
532
560
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
561
|
+
|
|
562
|
+
if (this._isCustomLocatorStrategyDefined()) {
|
|
563
|
+
Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
|
|
564
|
+
this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
|
|
565
|
+
const locatorFunction = this._lookupCustomLocator(customLocator);
|
|
566
|
+
this.browser.addLocatorStrategy(customLocator, locatorFunction);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
533
570
|
return this.browser;
|
|
534
571
|
}
|
|
535
572
|
|
|
573
|
+
_isCustomLocatorStrategyDefined() {
|
|
574
|
+
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
|
|
575
|
+
}
|
|
576
|
+
|
|
536
577
|
async _stopBrowser() {
|
|
537
578
|
if (this.browser && this.isRunning) await this.browser.deleteSession();
|
|
538
579
|
}
|
|
@@ -755,17 +796,34 @@ class WebDriver extends Helper {
|
|
|
755
796
|
}
|
|
756
797
|
|
|
757
798
|
if (!this.options.smartWait || !smartWait) {
|
|
799
|
+
if (this._isCustomLocator(locator)) {
|
|
800
|
+
const locatorObj = new Locator(locator);
|
|
801
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
802
|
+
}
|
|
803
|
+
|
|
758
804
|
const els = await this.$$(withStrictLocator(locator));
|
|
759
805
|
return els;
|
|
760
806
|
}
|
|
761
807
|
|
|
762
808
|
await this._smartWait(locator);
|
|
763
809
|
|
|
810
|
+
if (this._isCustomLocator(locator)) {
|
|
811
|
+
const locatorObj = new Locator(locator);
|
|
812
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
813
|
+
}
|
|
814
|
+
|
|
764
815
|
const els = await this.$$(withStrictLocator(locator));
|
|
765
816
|
await this.defineTimeout({ implicit: 0 });
|
|
766
817
|
return els;
|
|
767
818
|
}
|
|
768
819
|
|
|
820
|
+
_grabCustomLocator(locator) {
|
|
821
|
+
if (typeof locator === 'string') {
|
|
822
|
+
locator = new Locator(locator);
|
|
823
|
+
}
|
|
824
|
+
return locator.value ? locator.value : locator.custom;
|
|
825
|
+
}
|
|
826
|
+
|
|
769
827
|
/**
|
|
770
828
|
* Find a checkbox by providing human readable text:
|
|
771
829
|
*
|
|
@@ -962,6 +1020,7 @@ class WebDriver extends Helper {
|
|
|
962
1020
|
/**
|
|
963
1021
|
* {{> fillField }}
|
|
964
1022
|
* {{ react }}
|
|
1023
|
+
* {{ custom }}
|
|
965
1024
|
*
|
|
966
1025
|
*/
|
|
967
1026
|
async fillField(field, value) {
|
|
@@ -1193,7 +1252,6 @@ class WebDriver extends Helper {
|
|
|
1193
1252
|
|
|
1194
1253
|
/**
|
|
1195
1254
|
* {{> grabAttributeFromAll }}
|
|
1196
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1197
1255
|
*/
|
|
1198
1256
|
async grabAttributeFromAll(locator, attr) {
|
|
1199
1257
|
const res = await this._locate(locator, true);
|
|
@@ -1204,7 +1262,6 @@ class WebDriver extends Helper {
|
|
|
1204
1262
|
|
|
1205
1263
|
/**
|
|
1206
1264
|
* {{> grabAttributeFrom }}
|
|
1207
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1208
1265
|
*/
|
|
1209
1266
|
async grabAttributeFrom(locator, attr) {
|
|
1210
1267
|
const attrs = await this.grabAttributeFromAll(locator, attr);
|
|
@@ -1335,7 +1392,7 @@ class WebDriver extends Helper {
|
|
|
1335
1392
|
*
|
|
1336
1393
|
*/
|
|
1337
1394
|
async seeElementInDOM(locator) {
|
|
1338
|
-
const res = await this
|
|
1395
|
+
const res = await this._res(locator);
|
|
1339
1396
|
return empty('elements').negate(res);
|
|
1340
1397
|
}
|
|
1341
1398
|
|
|
@@ -1344,7 +1401,7 @@ class WebDriver extends Helper {
|
|
|
1344
1401
|
*
|
|
1345
1402
|
*/
|
|
1346
1403
|
async dontSeeElementInDOM(locator) {
|
|
1347
|
-
const res = await this
|
|
1404
|
+
const res = await this._res(locator);
|
|
1348
1405
|
return empty('elements').assert(res);
|
|
1349
1406
|
}
|
|
1350
1407
|
|
|
@@ -1991,7 +2048,7 @@ class WebDriver extends Helper {
|
|
|
1991
2048
|
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
1992
2049
|
}
|
|
1993
2050
|
return this.browser.waitUntil(async () => {
|
|
1994
|
-
const res = await this
|
|
2051
|
+
const res = await this._res(locator);
|
|
1995
2052
|
if (!res || res.length === 0) {
|
|
1996
2053
|
return false;
|
|
1997
2054
|
}
|
|
@@ -2018,7 +2075,7 @@ class WebDriver extends Helper {
|
|
|
2018
2075
|
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
|
|
2019
2076
|
}
|
|
2020
2077
|
return this.browser.waitUntil(async () => {
|
|
2021
|
-
const res = await this
|
|
2078
|
+
const res = await this._res(locator);
|
|
2022
2079
|
return res && res.length;
|
|
2023
2080
|
}, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
|
|
2024
2081
|
}
|
|
@@ -2188,9 +2245,7 @@ class WebDriver extends Helper {
|
|
|
2188
2245
|
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
2189
2246
|
}
|
|
2190
2247
|
return this.browser.waitUntil(async () => {
|
|
2191
|
-
const res =
|
|
2192
|
-
? await this._locate(withStrictLocator(locator))
|
|
2193
|
-
: await this.$$(withStrictLocator(locator));
|
|
2248
|
+
const res = await this._res(locator);
|
|
2194
2249
|
if (!res || res.length === 0) return false;
|
|
2195
2250
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2196
2251
|
if (Array.isArray(selected)) {
|
|
@@ -2217,7 +2272,7 @@ class WebDriver extends Helper {
|
|
|
2217
2272
|
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
2218
2273
|
}
|
|
2219
2274
|
return this.browser.waitUntil(async () => {
|
|
2220
|
-
const res = await this
|
|
2275
|
+
const res = await this._res(locator);
|
|
2221
2276
|
if (!res || res.length === 0) return false;
|
|
2222
2277
|
let selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2223
2278
|
|
|
@@ -2241,7 +2296,7 @@ class WebDriver extends Helper {
|
|
|
2241
2296
|
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
2242
2297
|
}
|
|
2243
2298
|
return this.browser.waitUntil(async () => {
|
|
2244
|
-
const res = await this
|
|
2299
|
+
const res = await this._res(locator);
|
|
2245
2300
|
if (!res || res.length === 0) return true;
|
|
2246
2301
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2247
2302
|
return !selected.length;
|
|
@@ -2262,7 +2317,7 @@ class WebDriver extends Helper {
|
|
|
2262
2317
|
const aSec = sec || this.options.waitForTimeout;
|
|
2263
2318
|
if (isWebDriver5()) {
|
|
2264
2319
|
return this.browser.waitUntil(async () => {
|
|
2265
|
-
const res = await this
|
|
2320
|
+
const res = await this._res(locator);
|
|
2266
2321
|
if (!res || res.length === 0) {
|
|
2267
2322
|
return true;
|
|
2268
2323
|
}
|
|
@@ -2270,7 +2325,7 @@ class WebDriver extends Helper {
|
|
|
2270
2325
|
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
2271
2326
|
}
|
|
2272
2327
|
return this.browser.waitUntil(async () => {
|
|
2273
|
-
const res = await this
|
|
2328
|
+
const res = await this._res(locator);
|
|
2274
2329
|
if (!res || res.length === 0) {
|
|
2275
2330
|
return true;
|
|
2276
2331
|
}
|
|
@@ -2543,12 +2598,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
2543
2598
|
}
|
|
2544
2599
|
|
|
2545
2600
|
const smartWaitEnabled = assertType === 'assert';
|
|
2546
|
-
|
|
2547
2601
|
const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
|
|
2548
2602
|
assertElementExists(res, context);
|
|
2549
|
-
|
|
2550
2603
|
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2551
|
-
|
|
2552
2604
|
if (strict) {
|
|
2553
2605
|
if (Array.isArray(selected) && selected.length !== 0) {
|
|
2554
2606
|
return selected.map(elText => equals(description)[assertType](text, elText));
|
|
@@ -2616,6 +2668,11 @@ async function filterAsync(array, callback) {
|
|
|
2616
2668
|
|
|
2617
2669
|
async function findClickable(locator, locateFn) {
|
|
2618
2670
|
locator = new Locator(locator);
|
|
2671
|
+
|
|
2672
|
+
if (this._isCustomLocator(locator)) {
|
|
2673
|
+
return locateFn(locator.value);
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2619
2676
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
2620
2677
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
2621
2678
|
|
|
@@ -2637,6 +2694,10 @@ async function findClickable(locator, locateFn) {
|
|
|
2637
2694
|
async function findFields(locator) {
|
|
2638
2695
|
locator = new Locator(locator);
|
|
2639
2696
|
|
|
2697
|
+
if (this._isCustomLocator(locator)) {
|
|
2698
|
+
return this._locate(locator);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2640
2701
|
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
|
|
2641
2702
|
if (!locator.isFuzzy()) return this._locate(locator, true);
|
|
2642
2703
|
|
|
@@ -2742,6 +2803,10 @@ async function findCheckable(locator, locateFn) {
|
|
|
2742
2803
|
let els;
|
|
2743
2804
|
locator = new Locator(locator);
|
|
2744
2805
|
|
|
2806
|
+
if (this._isCustomLocator(locator)) {
|
|
2807
|
+
return locateFn(locator.value);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2745
2810
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
2746
2811
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
2747
2812
|
|
|
@@ -2787,6 +2852,7 @@ function getElementId(el) {
|
|
|
2787
2852
|
if (el.ELEMENT) {
|
|
2788
2853
|
return el.ELEMENT;
|
|
2789
2854
|
}
|
|
2855
|
+
|
|
2790
2856
|
return null;
|
|
2791
2857
|
}
|
|
2792
2858
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function ConnectionRefused(err) {
|
|
2
2
|
this.message = "Can't connect to WebDriver.\n";
|
|
3
3
|
this.message += `${err}\n\n`;
|
|
4
|
-
this.message += 'Please make sure Selenium Server
|
|
4
|
+
this.message += 'Please make sure Selenium Server is running and accessible';
|
|
5
5
|
this.stack = err.stack;
|
|
6
6
|
}
|
|
7
7
|
|
|
@@ -4,50 +4,62 @@ let resqScript;
|
|
|
4
4
|
|
|
5
5
|
module.exports = async function findReact(matcher, locator) {
|
|
6
6
|
if (!resqScript) resqScript = fs.readFileSync(require.resolve('resq'));
|
|
7
|
-
await matcher.
|
|
8
|
-
await matcher
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (Object.keys(state).length) {
|
|
15
|
-
elements = elements.byState(state);
|
|
16
|
-
}
|
|
7
|
+
await matcher.evaluate(resqScript.toString());
|
|
8
|
+
await matcher
|
|
9
|
+
.evaluate(() => window.resq.waitToLoadReact());
|
|
10
|
+
const arrayHandle = await matcher.evaluateHandle(
|
|
11
|
+
(obj) => {
|
|
12
|
+
const { selector, props, state } = obj;
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
let elements = window.resq.resq$$(selector);
|
|
15
|
+
if (Object.keys(props).length) {
|
|
16
|
+
elements = elements.byProps(props);
|
|
17
|
+
}
|
|
18
|
+
if (Object.keys(state).length) {
|
|
19
|
+
elements = elements.byState(state);
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let nodes = [];
|
|
22
|
+
if (!elements.length) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// resq returns an array of HTMLElements if the React component is a fragment
|
|
27
|
+
// this avoids having nested arrays of nodes which the driver does not understand
|
|
28
|
+
// [[div, div], [div, div]] => [div, div, div, div]
|
|
29
|
+
let nodes = [];
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
isFragment =
|
|
32
|
-
node = element.children;
|
|
33
|
-
}
|
|
31
|
+
elements.forEach((element) => {
|
|
32
|
+
let { node, isFragment } = element;
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
});
|
|
34
|
+
if (!node) {
|
|
35
|
+
isFragment = true;
|
|
36
|
+
node = element.children;
|
|
37
|
+
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
if (isFragment) {
|
|
40
|
+
nodes = nodes.concat(node);
|
|
41
|
+
} else {
|
|
42
|
+
nodes.push(node);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return [...nodes];
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
selector: locator.react,
|
|
50
|
+
props: locator.props || {},
|
|
51
|
+
state: locator.state || {},
|
|
52
|
+
},
|
|
53
|
+
);
|
|
44
54
|
|
|
45
55
|
const properties = await arrayHandle.getProperties();
|
|
46
56
|
await arrayHandle.dispose();
|
|
47
57
|
const result = [];
|
|
48
58
|
for (const property of properties.values()) {
|
|
49
59
|
const elementHandle = property.asElement();
|
|
50
|
-
if (elementHandle) {
|
|
60
|
+
if (elementHandle) {
|
|
61
|
+
result.push(elementHandle);
|
|
62
|
+
}
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
return result;
|
|
@@ -83,6 +83,7 @@ module.exports = (text) => {
|
|
|
83
83
|
for (const index in example.cells) {
|
|
84
84
|
const placeholder = fields[index];
|
|
85
85
|
const value = transform('gherkin.examples', example.cells[index].value);
|
|
86
|
+
example.cells[index].value = value;
|
|
86
87
|
current[placeholder] = value;
|
|
87
88
|
exampleSteps = exampleSteps.map((step) => {
|
|
88
89
|
step = { ...step };
|
package/lib/listener/exit.js
CHANGED
|
@@ -18,13 +18,11 @@ module.exports = function () {
|
|
|
18
18
|
failedTests = failedTests.filter(failed => id !== failed);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
process.on('beforeExit', (code) => {
|
|
22
22
|
if (failedTests.length) {
|
|
23
|
-
|
|
23
|
+
code = 1;
|
|
24
24
|
}
|
|
25
|
-
});
|
|
26
25
|
|
|
27
|
-
process.on('beforeExit', (code) => {
|
|
28
26
|
if (code) {
|
|
29
27
|
process.exit(code);
|
|
30
28
|
}
|
package/lib/listener/helpers.js
CHANGED
|
@@ -11,11 +11,10 @@ module.exports = function () {
|
|
|
11
11
|
|
|
12
12
|
const runHelpersHook = (hook, param) => {
|
|
13
13
|
if (store.dryRun) return;
|
|
14
|
-
Object.
|
|
15
|
-
if (
|
|
16
|
-
|
|
14
|
+
Object.values(helpers).forEach((helper) => {
|
|
15
|
+
if (helper[hook]) {
|
|
16
|
+
helper[hook](param);
|
|
17
17
|
}
|
|
18
|
-
helpers[key][hook](param);
|
|
19
18
|
});
|
|
20
19
|
};
|
|
21
20
|
|
package/lib/locator.js
CHANGED