codeceptjs 3.5.4-beta.1 → 3.5.4
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 +0 -2
- package/docs/build/Appium.js +8 -6
- package/docs/build/GraphQL.js +25 -0
- package/docs/build/Nightmare.js +11 -6
- package/docs/build/Playwright.js +425 -193
- package/docs/build/Protractor.js +13 -8
- package/docs/build/Puppeteer.js +20 -14
- package/docs/build/TestCafe.js +17 -10
- package/docs/build/WebDriver.js +41 -37
- package/docs/changelog.md +220 -0
- package/docs/community-helpers.md +8 -4
- package/docs/examples.md +8 -2
- package/docs/helpers/Appium.md +2 -2
- package/docs/helpers/GraphQL.md +21 -0
- package/docs/helpers/Nightmare.md +1258 -0
- package/docs/helpers/Playwright.md +223 -119
- package/docs/helpers/Protractor.md +1709 -0
- package/docs/helpers/Puppeteer.md +3 -3
- package/docs/helpers/TestCafe.md +2 -2
- package/docs/helpers/WebDriver.md +3 -3
- package/docs/playwright.md +24 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/wiki/Books-&-Posts.md +0 -0
- package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
- package/docs/wiki/Examples.md +8 -2
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
- package/docs/wiki/Home.md +0 -0
- package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
- package/docs/wiki/Release-Process.md +0 -0
- package/docs/wiki/Roadmap.md +0 -0
- package/docs/wiki/Tests.md +0 -0
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
- package/docs/wiki/Videos.md +0 -0
- package/lib/command/definitions.js +2 -7
- package/lib/command/run-multiple/collection.js +17 -5
- package/lib/helper/Appium.js +6 -4
- package/lib/helper/GraphQL.js +25 -0
- package/lib/helper/Nightmare.js +1415 -0
- package/lib/helper/Playwright.js +321 -54
- package/lib/helper/Protractor.js +1837 -0
- package/lib/helper/Puppeteer.js +18 -12
- package/lib/helper/TestCafe.js +15 -8
- package/lib/helper/WebDriver.js +39 -35
- package/lib/helper/clientscripts/nightmare.js +213 -0
- package/lib/helper/errors/ElementNotFound.js +2 -1
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/interfaces/bdd.js +1 -1
- package/lib/mochaFactory.js +2 -1
- package/lib/pause.js +5 -4
- package/lib/plugin/heal.js +2 -3
- package/lib/plugin/selenoid.js +6 -1
- package/lib/step.js +27 -10
- package/lib/utils.js +4 -0
- package/lib/workers.js +3 -1
- package/package.json +13 -13
- package/typings/promiseBasedTypes.d.ts +145 -126
- package/typings/types.d.ts +152 -133
- package/CHANGELOG.md +0 -2519
- package/docs/build/Polly.js +0 -42
- package/docs/build/SeleniumWebdriver.js +0 -76
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
screenshotOutputFolder,
|
|
24
24
|
getNormalizedKeyAttributeValue,
|
|
25
25
|
isModifierKey,
|
|
26
|
-
requireWithFallback,
|
|
26
|
+
requireWithFallback, normalizeSpacesInString,
|
|
27
27
|
} = require('../utils');
|
|
28
28
|
const {
|
|
29
29
|
isColorProperty,
|
|
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
|
|
|
69
69
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
|
|
70
70
|
* @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
|
|
71
71
|
* @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
|
|
72
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
72
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
73
73
|
*/
|
|
74
74
|
const config = {};
|
|
75
75
|
|
|
@@ -231,6 +231,7 @@ class Puppeteer extends Helper {
|
|
|
231
231
|
keepBrowserState: false,
|
|
232
232
|
show: false,
|
|
233
233
|
defaultPopupAction: 'accept',
|
|
234
|
+
highlightElement: false,
|
|
234
235
|
};
|
|
235
236
|
|
|
236
237
|
return Object.assign(defaults, config);
|
|
@@ -765,8 +766,11 @@ class Puppeteer extends Helper {
|
|
|
765
766
|
const body = document.body;
|
|
766
767
|
const html = document.documentElement;
|
|
767
768
|
window.scrollTo(0, Math.max(
|
|
768
|
-
body.scrollHeight,
|
|
769
|
-
|
|
769
|
+
body.scrollHeight,
|
|
770
|
+
body.offsetHeight,
|
|
771
|
+
html.clientHeight,
|
|
772
|
+
html.scrollHeight,
|
|
773
|
+
html.offsetHeight,
|
|
770
774
|
));
|
|
771
775
|
});
|
|
772
776
|
}
|
|
@@ -1318,7 +1322,7 @@ class Puppeteer extends Helper {
|
|
|
1318
1322
|
await this._evaluateHandeInContext(el => el.innerHTML = '', el);
|
|
1319
1323
|
}
|
|
1320
1324
|
|
|
1321
|
-
highlightActiveElement.call(this, el, this.
|
|
1325
|
+
highlightActiveElement.call(this, el, await this._getContext());
|
|
1322
1326
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1323
1327
|
|
|
1324
1328
|
return this._waitForAction();
|
|
@@ -1339,7 +1343,7 @@ class Puppeteer extends Helper {
|
|
|
1339
1343
|
async appendField(field, value) {
|
|
1340
1344
|
const els = await findVisibleFields.call(this, field);
|
|
1341
1345
|
assertElementExists(els, field, 'Field');
|
|
1342
|
-
highlightActiveElement.call(this, els[0], this.
|
|
1346
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
1343
1347
|
await els[0].press('End');
|
|
1344
1348
|
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1345
1349
|
return this._waitForAction();
|
|
@@ -1349,14 +1353,16 @@ class Puppeteer extends Helper {
|
|
|
1349
1353
|
* {{> seeInField }}
|
|
1350
1354
|
*/
|
|
1351
1355
|
async seeInField(field, value) {
|
|
1352
|
-
|
|
1356
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1357
|
+
return proceedSeeInField.call(this, 'assert', field, _value);
|
|
1353
1358
|
}
|
|
1354
1359
|
|
|
1355
1360
|
/**
|
|
1356
1361
|
* {{> dontSeeInField }}
|
|
1357
1362
|
*/
|
|
1358
1363
|
async dontSeeInField(field, value) {
|
|
1359
|
-
|
|
1364
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1365
|
+
return proceedSeeInField.call(this, 'negate', field, _value);
|
|
1360
1366
|
}
|
|
1361
1367
|
|
|
1362
1368
|
/**
|
|
@@ -1386,7 +1392,7 @@ class Puppeteer extends Helper {
|
|
|
1386
1392
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
1387
1393
|
throw new Error('Element is not <select>');
|
|
1388
1394
|
}
|
|
1389
|
-
highlightActiveElement.call(this, els[0], this.
|
|
1395
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
1390
1396
|
if (!Array.isArray(option)) option = [option];
|
|
1391
1397
|
|
|
1392
1398
|
for (const key in option) {
|
|
@@ -2359,7 +2365,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2359
2365
|
assertElementExists(els, locator, 'Clickable element');
|
|
2360
2366
|
}
|
|
2361
2367
|
|
|
2362
|
-
highlightActiveElement.call(this, els[0], this.
|
|
2368
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
2363
2369
|
|
|
2364
2370
|
await els[0].click(options);
|
|
2365
2371
|
const promises = [];
|
|
@@ -2419,7 +2425,7 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
2419
2425
|
if (strict) {
|
|
2420
2426
|
return allText.map(elText => equals(description)[assertType](text, elText));
|
|
2421
2427
|
}
|
|
2422
|
-
return stringIncludes(description)[assertType](text, allText.join(' | '));
|
|
2428
|
+
return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
|
|
2423
2429
|
}
|
|
2424
2430
|
|
|
2425
2431
|
async function findCheckable(locator, context) {
|
|
@@ -2714,7 +2720,7 @@ function getNormalizedKey(key) {
|
|
|
2714
2720
|
}
|
|
2715
2721
|
|
|
2716
2722
|
function highlightActiveElement(element, context) {
|
|
2717
|
-
if (!this.options.
|
|
2723
|
+
if (!this.options.highlightElement && !store.debugMode) return;
|
|
2718
2724
|
|
|
2719
2725
|
highlightElement(element, context);
|
|
2720
2726
|
}
|
package/lib/helper/TestCafe.js
CHANGED
|
@@ -20,7 +20,7 @@ const { urlEquals } = require('../assert/equal');
|
|
|
20
20
|
const { empty } = require('../assert/empty');
|
|
21
21
|
const { truth } = require('../assert/truth');
|
|
22
22
|
const {
|
|
23
|
-
xpathLocator,
|
|
23
|
+
xpathLocator, normalizeSpacesInString,
|
|
24
24
|
} = require('../utils');
|
|
25
25
|
const Locator = require('../locator');
|
|
26
26
|
|
|
@@ -637,9 +637,9 @@ class TestCafe extends Helper {
|
|
|
637
637
|
async see(text, context = null) {
|
|
638
638
|
let els;
|
|
639
639
|
if (context) {
|
|
640
|
-
els = (await findElements.call(this, this.context, context)).withText(text);
|
|
640
|
+
els = (await findElements.call(this, this.context, context)).withText(normalizeSpacesInString(text));
|
|
641
641
|
} else {
|
|
642
|
-
els = (await findElements.call(this, this.context, '*')).withText(text);
|
|
642
|
+
els = (await findElements.call(this, this.context, '*')).withText(normalizeSpacesInString(text));
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
return this.t
|
|
@@ -727,13 +727,14 @@ class TestCafe extends Helper {
|
|
|
727
727
|
* {{> seeInField }}
|
|
728
728
|
*/
|
|
729
729
|
async seeInField(field, value) {
|
|
730
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
730
731
|
// const expectedValue = findElements.call(this, this.context, field).value;
|
|
731
732
|
const els = await findFields.call(this, field);
|
|
732
733
|
assertElementExists(els, field, 'Field');
|
|
733
734
|
const el = await els.nth(0);
|
|
734
735
|
|
|
735
736
|
return this.t
|
|
736
|
-
.expect(await el.value).eql(
|
|
737
|
+
.expect(await el.value).eql(_value)
|
|
737
738
|
.catch(mapError);
|
|
738
739
|
}
|
|
739
740
|
|
|
@@ -741,13 +742,14 @@ class TestCafe extends Helper {
|
|
|
741
742
|
* {{> dontSeeInField }}
|
|
742
743
|
*/
|
|
743
744
|
async dontSeeInField(field, value) {
|
|
745
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
744
746
|
// const expectedValue = findElements.call(this, this.context, field).value;
|
|
745
747
|
const els = await findFields.call(this, field);
|
|
746
748
|
assertElementExists(els, field, 'Field');
|
|
747
749
|
const el = await els.nth(0);
|
|
748
750
|
|
|
749
751
|
return this.t
|
|
750
|
-
.expect(el.value).notEql(
|
|
752
|
+
.expect(el.value).notEql(_value)
|
|
751
753
|
.catch(mapError);
|
|
752
754
|
}
|
|
753
755
|
|
|
@@ -960,8 +962,11 @@ class TestCafe extends Helper {
|
|
|
960
962
|
const body = document.body;
|
|
961
963
|
const html = document.documentElement;
|
|
962
964
|
window.scrollTo(0, Math.max(
|
|
963
|
-
body.scrollHeight,
|
|
964
|
-
|
|
965
|
+
body.scrollHeight,
|
|
966
|
+
body.offsetHeight,
|
|
967
|
+
html.clientHeight,
|
|
968
|
+
html.scrollHeight,
|
|
969
|
+
html.offsetHeight,
|
|
965
970
|
));
|
|
966
971
|
}).with({ boundTestRun: this.t })().catch(mapError);
|
|
967
972
|
}
|
|
@@ -1220,7 +1225,9 @@ class TestCafe extends Helper {
|
|
|
1220
1225
|
}
|
|
1221
1226
|
|
|
1222
1227
|
async function waitForFunction(browserFn, waitTimeout) {
|
|
1223
|
-
const pause = () => new Promise((done =>
|
|
1228
|
+
const pause = () => new Promise((done => {
|
|
1229
|
+
setTimeout(done, 50);
|
|
1230
|
+
}));
|
|
1224
1231
|
|
|
1225
1232
|
const start = Date.now();
|
|
1226
1233
|
// eslint-disable-next-line no-constant-condition
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -62,7 +62,7 @@ const webRoot = 'body';
|
|
|
62
62
|
* @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
|
|
63
63
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
|
|
64
64
|
* @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
|
|
65
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
65
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
66
66
|
*/
|
|
67
67
|
const config = {};
|
|
68
68
|
|
|
@@ -429,6 +429,7 @@ class WebDriver extends Helper {
|
|
|
429
429
|
keepCookies: false,
|
|
430
430
|
keepBrowserState: false,
|
|
431
431
|
deprecationWarnings: false,
|
|
432
|
+
highlightElement: false,
|
|
432
433
|
};
|
|
433
434
|
|
|
434
435
|
// override defaults with config
|
|
@@ -1347,7 +1348,8 @@ class WebDriver extends Helper {
|
|
|
1347
1348
|
*
|
|
1348
1349
|
*/
|
|
1349
1350
|
async seeInField(field, value) {
|
|
1350
|
-
|
|
1351
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1352
|
+
return proceedSeeField.call(this, 'assert', field, _value);
|
|
1351
1353
|
}
|
|
1352
1354
|
|
|
1353
1355
|
/**
|
|
@@ -1355,7 +1357,8 @@ class WebDriver extends Helper {
|
|
|
1355
1357
|
*
|
|
1356
1358
|
*/
|
|
1357
1359
|
async dontSeeInField(field, value) {
|
|
1358
|
-
|
|
1360
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1361
|
+
return proceedSeeField.call(this, 'negate', field, _value);
|
|
1359
1362
|
}
|
|
1360
1363
|
|
|
1361
1364
|
/**
|
|
@@ -2062,7 +2065,9 @@ class WebDriver extends Helper {
|
|
|
2062
2065
|
* {{> wait }}
|
|
2063
2066
|
*/
|
|
2064
2067
|
async wait(sec) {
|
|
2065
|
-
return new Promise(resolve =>
|
|
2068
|
+
return new Promise(resolve => {
|
|
2069
|
+
setTimeout(resolve, sec * 1000);
|
|
2070
|
+
});
|
|
2066
2071
|
}
|
|
2067
2072
|
|
|
2068
2073
|
/**
|
|
@@ -2167,20 +2172,18 @@ class WebDriver extends Helper {
|
|
|
2167
2172
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2168
2173
|
const _context = context || this.root;
|
|
2169
2174
|
|
|
2170
|
-
return this.browser.waitUntil(
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
},
|
|
2183
|
-
);
|
|
2175
|
+
return this.browser.waitUntil(async () => {
|
|
2176
|
+
const res = await this.$$(withStrictLocator.call(this, _context));
|
|
2177
|
+
if (!res || res.length === 0) return false;
|
|
2178
|
+
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2179
|
+
if (Array.isArray(selected)) {
|
|
2180
|
+
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
|
|
2181
|
+
}
|
|
2182
|
+
return selected.indexOf(text) >= 0;
|
|
2183
|
+
}, {
|
|
2184
|
+
timeout: aSec * 1000,
|
|
2185
|
+
timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2186
|
+
});
|
|
2184
2187
|
}
|
|
2185
2188
|
|
|
2186
2189
|
/**
|
|
@@ -2190,20 +2193,18 @@ class WebDriver extends Helper {
|
|
|
2190
2193
|
const client = this.browser;
|
|
2191
2194
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2192
2195
|
|
|
2193
|
-
return client.waitUntil(
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
},
|
|
2206
|
-
);
|
|
2196
|
+
return client.waitUntil(async () => {
|
|
2197
|
+
const res = await findFields.call(this, field);
|
|
2198
|
+
if (!res || res.length === 0) return false;
|
|
2199
|
+
const selected = await forEachAsync(res, async el => el.getValue());
|
|
2200
|
+
if (Array.isArray(selected)) {
|
|
2201
|
+
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
|
|
2202
|
+
}
|
|
2203
|
+
return selected.indexOf(value) >= 0;
|
|
2204
|
+
}, {
|
|
2205
|
+
timeout: aSec * 1000,
|
|
2206
|
+
timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2207
|
+
});
|
|
2207
2208
|
}
|
|
2208
2209
|
|
|
2209
2210
|
/**
|
|
@@ -2411,8 +2412,11 @@ class WebDriver extends Helper {
|
|
|
2411
2412
|
const body = document.body;
|
|
2412
2413
|
const html = document.documentElement;
|
|
2413
2414
|
window.scrollTo(0, Math.max(
|
|
2414
|
-
body.scrollHeight,
|
|
2415
|
-
|
|
2415
|
+
body.scrollHeight,
|
|
2416
|
+
body.offsetHeight,
|
|
2417
|
+
html.clientHeight,
|
|
2418
|
+
html.scrollHeight,
|
|
2419
|
+
html.offsetHeight
|
|
2416
2420
|
));
|
|
2417
2421
|
});
|
|
2418
2422
|
/* eslint-enable */
|
|
@@ -2914,7 +2918,7 @@ function isModifierKey(key) {
|
|
|
2914
2918
|
}
|
|
2915
2919
|
|
|
2916
2920
|
function highlightActiveElement(element) {
|
|
2917
|
-
if (!this.options.
|
|
2921
|
+
if (!this.options.highlightElement && !store.debugMode) return;
|
|
2918
2922
|
|
|
2919
2923
|
highlightElement(element, this.browser);
|
|
2920
2924
|
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
if (!window.codeceptjs) {
|
|
2
|
+
/**
|
|
3
|
+
* @alias CodeceptJS.browserCodecept
|
|
4
|
+
* @namespace
|
|
5
|
+
*/
|
|
6
|
+
const codeceptjs = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* all found elements are stored here for reuse
|
|
10
|
+
* @inner
|
|
11
|
+
* @type {Node[]}
|
|
12
|
+
*/
|
|
13
|
+
codeceptjs.elements = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* global context changer
|
|
17
|
+
* @inner
|
|
18
|
+
* @type {?Node}
|
|
19
|
+
*/
|
|
20
|
+
codeceptjs.within = null;
|
|
21
|
+
|
|
22
|
+
// save
|
|
23
|
+
const storeElement = function (el) {
|
|
24
|
+
if (!el) return;
|
|
25
|
+
return codeceptjs.elements.push(el) - 1; // return index
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const storeElements = function (els) {
|
|
29
|
+
return els.map(el => storeElement(el));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* finders
|
|
34
|
+
* @param {number} id
|
|
35
|
+
* @return {Node}
|
|
36
|
+
*/
|
|
37
|
+
codeceptjs.fetchElement = function (id) {
|
|
38
|
+
if (!this.elements[id]) throw new Error(`Element (${id}) is not accessible`);
|
|
39
|
+
return this.elements[id];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} by
|
|
44
|
+
* @param {CodeceptJS.ILocator} locator
|
|
45
|
+
* @param {*} [contextEl]
|
|
46
|
+
* @return {number[]}
|
|
47
|
+
*/
|
|
48
|
+
codeceptjs.findAndStoreElements = function (by, locator, contextEl) {
|
|
49
|
+
return storeElements(this.findElements(by, locator, contextEl));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} by
|
|
54
|
+
* @param {CodeceptJS.ILocator} locator
|
|
55
|
+
* @param {*} [contextEl]
|
|
56
|
+
* @return {number | undefined}
|
|
57
|
+
*/
|
|
58
|
+
codeceptjs.findAndStoreElement = function (by, locator, contextEl) {
|
|
59
|
+
return storeElement(this.findElement(by, locator, contextEl));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} by
|
|
64
|
+
* @param {CodeceptJS.ILocator} locator
|
|
65
|
+
*/
|
|
66
|
+
codeceptjs.setWithin = function (by, locator) {
|
|
67
|
+
this.within = this.findElement(by, locator);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} by
|
|
72
|
+
* @param {CodeceptJS.ILocator} locator
|
|
73
|
+
* @param {*} [contextEl]
|
|
74
|
+
* @return {Node[]}
|
|
75
|
+
*/
|
|
76
|
+
codeceptjs.findElements = function (by, locator, contextEl) {
|
|
77
|
+
let context;
|
|
78
|
+
if (typeof contextEl !== 'number') {
|
|
79
|
+
context = this.within || document;
|
|
80
|
+
} else {
|
|
81
|
+
context = this.fetchElement(contextEl);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (by === 'name') {
|
|
85
|
+
by = 'css';
|
|
86
|
+
locator = `*[name="${locator}"]`;
|
|
87
|
+
}
|
|
88
|
+
if (by === 'id') {
|
|
89
|
+
by = 'css';
|
|
90
|
+
locator = `#${locator}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (by === 'css') {
|
|
94
|
+
const found = context.querySelectorAll(locator);
|
|
95
|
+
const res = [];
|
|
96
|
+
for (let i = 0; i < found.length; i++) {
|
|
97
|
+
res.push(found[i]);
|
|
98
|
+
}
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (by === 'xpath') {
|
|
103
|
+
const found = document.evaluate(locator, context, null, 5, null);
|
|
104
|
+
const res = [];
|
|
105
|
+
let current = null;
|
|
106
|
+
while (current = found.iterateNext()) {
|
|
107
|
+
res.push(current);
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (by === 'frame') {
|
|
113
|
+
if (!Array.isArray(locator)) locator = [locator];
|
|
114
|
+
return [locator.reduce((parent, child) => parent.querySelector(child).contentDocument, window.document).querySelector('body')];
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} by
|
|
121
|
+
* @param {CodeceptJS.ILocator} locator
|
|
122
|
+
* @param {*} [context]
|
|
123
|
+
* @return {?Node}
|
|
124
|
+
*/
|
|
125
|
+
codeceptjs.findElement = function (by, locator, context) {
|
|
126
|
+
return this.findElements(by, locator, context)[0] || null;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// actions
|
|
130
|
+
/**
|
|
131
|
+
* @param {number} el
|
|
132
|
+
* @return {boolean}
|
|
133
|
+
*/
|
|
134
|
+
codeceptjs.clickEl = function (el) {
|
|
135
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
136
|
+
document.activeElement.blur();
|
|
137
|
+
}
|
|
138
|
+
const event = document.createEvent('MouseEvent');
|
|
139
|
+
event.initEvent('click', true, true);
|
|
140
|
+
return this.fetchElement(el).dispatchEvent(event);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** @param {number} el */
|
|
144
|
+
codeceptjs.doubleClickEl = function (el) {
|
|
145
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
146
|
+
document.activeElement.blur();
|
|
147
|
+
}
|
|
148
|
+
const event = document.createEvent('MouseEvent');
|
|
149
|
+
event.initEvent('dblclick', true, true);
|
|
150
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {number} el
|
|
155
|
+
* @param {number | undefined} x
|
|
156
|
+
* @param {number | undefined} y
|
|
157
|
+
*/
|
|
158
|
+
codeceptjs.hoverEl = function (el, x, y) {
|
|
159
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
160
|
+
document.activeElement.blur();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const event = new MouseEvent('mouseover', {
|
|
164
|
+
bubbles: true,
|
|
165
|
+
cancelable: true,
|
|
166
|
+
screenX: x,
|
|
167
|
+
screenY: y,
|
|
168
|
+
clientX: x,
|
|
169
|
+
clientY: y,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** @param {number} el */
|
|
176
|
+
codeceptjs.rightClickEl = function (el) {
|
|
177
|
+
const event = new MouseEvent('contextmenu', {
|
|
178
|
+
bubbles: true,
|
|
179
|
+
cancelable: true,
|
|
180
|
+
view: window,
|
|
181
|
+
buttons: 2,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {number} el
|
|
189
|
+
* @return {boolean | undefined}
|
|
190
|
+
*/
|
|
191
|
+
codeceptjs.checkEl = function (el) {
|
|
192
|
+
const element = this.fetchElement(el);
|
|
193
|
+
const event = document.createEvent('HTMLEvents');
|
|
194
|
+
if (element.checked) return;
|
|
195
|
+
element.checked = true;
|
|
196
|
+
event.initEvent('change', true, true);
|
|
197
|
+
return element.dispatchEvent(event);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {number} el
|
|
202
|
+
* @return {boolean}
|
|
203
|
+
*/
|
|
204
|
+
codeceptjs.unCheckEl = function (el) {
|
|
205
|
+
const element = this.fetchElement(el);
|
|
206
|
+
const event = document.createEvent('HTMLEvents');
|
|
207
|
+
element.checked = false;
|
|
208
|
+
event.initEvent('change', true, true);
|
|
209
|
+
return element.dispatchEvent(event);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
window.codeceptjs = codeceptjs;
|
|
213
|
+
}
|
|
@@ -5,7 +5,8 @@ const Locator = require('../../locator');
|
|
|
5
5
|
*/
|
|
6
6
|
class ElementNotFound {
|
|
7
7
|
constructor(
|
|
8
|
-
locator,
|
|
8
|
+
locator,
|
|
9
|
+
prefixMessage = 'Element',
|
|
9
10
|
postfixMessage = 'was not found by text|CSS|XPath',
|
|
10
11
|
) {
|
|
11
12
|
if (typeof locator === 'object') {
|
|
@@ -8,7 +8,7 @@ module.exports.highlightElement = (element, context) => {
|
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
10
|
// Playwright, Puppeteer
|
|
11
|
-
context.evaluate(clientSideHighlightFn, element);
|
|
11
|
+
context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err));
|
|
12
12
|
} catch (e) {
|
|
13
13
|
// WebDriver
|
|
14
14
|
try {
|
package/lib/interfaces/bdd.js
CHANGED
|
@@ -30,7 +30,7 @@ const parameterTypeRegistry = new ParameterTypeRegistry();
|
|
|
30
30
|
const matchStep = (step) => {
|
|
31
31
|
for (const stepName in steps) {
|
|
32
32
|
if (stepName.indexOf('/') === 0) {
|
|
33
|
-
const regExpArr = stepName.match(
|
|
33
|
+
const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || [];
|
|
34
34
|
const res = step.match(new RegExp(regExpArr[1], regExpArr[2]));
|
|
35
35
|
if (res) {
|
|
36
36
|
const fn = steps[stepName];
|
package/lib/mochaFactory.js
CHANGED
|
@@ -97,7 +97,8 @@ class MochaFactory {
|
|
|
97
97
|
const attributes = Object.getOwnPropertyDescriptor(reporterOptions, 'codeceptjs-cli-reporter');
|
|
98
98
|
if (reporterOptions['codeceptjs-cli-reporter'] && attributes) {
|
|
99
99
|
Object.defineProperty(
|
|
100
|
-
reporterOptions,
|
|
100
|
+
reporterOptions,
|
|
101
|
+
'codeceptjs/lib/cli',
|
|
101
102
|
attributes,
|
|
102
103
|
);
|
|
103
104
|
delete reporterOptions['codeceptjs-cli-reporter'];
|
package/lib/pause.js
CHANGED
|
@@ -67,6 +67,7 @@ function pauseSession(passedObject = {}) {
|
|
|
67
67
|
});
|
|
68
68
|
return new Promise(((resolve) => {
|
|
69
69
|
finish = resolve;
|
|
70
|
+
// eslint-disable-next-line
|
|
70
71
|
return askForStep();
|
|
71
72
|
}));
|
|
72
73
|
}
|
|
@@ -114,13 +115,13 @@ async function parseInput(cmd) {
|
|
|
114
115
|
isAiCommand = true;
|
|
115
116
|
executeCommand = executeCommand.then(async () => {
|
|
116
117
|
try {
|
|
117
|
-
const html = await res;
|
|
118
|
+
const html = await res;
|
|
118
119
|
aiAssistant.setHtmlContext(html);
|
|
119
120
|
} catch (err) {
|
|
120
121
|
output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
|
|
121
122
|
return;
|
|
122
123
|
} finally {
|
|
123
|
-
output.level(currentOutputLevel);
|
|
124
|
+
output.level(currentOutputLevel);
|
|
124
125
|
}
|
|
125
126
|
// aiAssistant.mockResponse("```js\nI.click('Sign in');\n```");
|
|
126
127
|
const spinner = ora("Processing OpenAI request...").start();
|
|
@@ -148,12 +149,12 @@ async function parseInput(cmd) {
|
|
|
148
149
|
})
|
|
149
150
|
|
|
150
151
|
const val = await executeCommand;
|
|
151
|
-
|
|
152
|
+
|
|
152
153
|
if (isCustomCommand) {
|
|
153
154
|
if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line
|
|
154
155
|
$res = val;
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
|
|
158
159
|
output.print(output.styles.success(' OK '), cmd);
|
|
159
160
|
}
|
package/lib/plugin/heal.js
CHANGED
|
@@ -147,11 +147,10 @@ module.exports = function (config = {}) {
|
|
|
147
147
|
async function tryToHeal(failedStep, err) {
|
|
148
148
|
output.debug(`Running OpenAI to heal ${failedStep.toCode()} step`);
|
|
149
149
|
|
|
150
|
-
const codeSnippets = await aiAssistant.healFailedStep(
|
|
151
|
-
failedStep, err, currentTest,
|
|
152
|
-
);
|
|
150
|
+
const codeSnippets = await aiAssistant.healFailedStep(failedStep, err, currentTest);
|
|
153
151
|
|
|
154
152
|
output.debug(`Received ${codeSnippets.length} suggestions from OpenAI`);
|
|
153
|
+
const I = Container.support('I'); // eslint-disable-line
|
|
155
154
|
|
|
156
155
|
for (const codeSnippet of codeSnippets) {
|
|
157
156
|
try {
|
package/lib/plugin/selenoid.js
CHANGED
|
@@ -58,7 +58,12 @@ let seleniumUrl = 'http://localhost:$port$';
|
|
|
58
58
|
const supportedHelpers = ['WebDriver'];
|
|
59
59
|
const SELENOID_START_TIMEOUT = 2000;
|
|
60
60
|
const SELENOID_STOP_TIMEOUT = 10000;
|
|
61
|
-
const wait = time => new Promise((res) =>
|
|
61
|
+
const wait = time => new Promise((res) => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
res();
|
|
65
|
+
}, time);
|
|
66
|
+
});
|
|
62
67
|
|
|
63
68
|
/**
|
|
64
69
|
* [Selenoid](https://aerokube.com/selenoid/) plugin automatically starts browsers and video recording.
|