codeceptjs 3.5.4 → 3.5.6
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/docs/build/Appium.js +40 -1
- package/docs/build/FileSystem.js +1 -1
- package/docs/build/Nightmare.js +4 -0
- package/docs/build/Playwright.js +59 -41
- package/docs/build/Protractor.js +4 -0
- package/docs/build/Puppeteer.js +21 -10
- package/docs/build/TestCafe.js +2 -0
- package/docs/build/WebDriver.js +8 -4
- package/docs/changelog.md +1 -170
- package/docs/community-helpers.md +4 -8
- package/docs/examples.md +2 -8
- package/docs/helpers/Appium.md +37 -0
- package/docs/helpers/FileSystem.md +1 -1
- package/docs/helpers/Nightmare.md +26 -24
- package/docs/helpers/Playwright.md +1 -1
- package/docs/helpers/Protractor.md +4 -2
- package/docs/helpers/Puppeteer.md +29 -27
- package/docs/helpers/TestCafe.md +16 -15
- package/docs/helpers/WebDriver.md +32 -30
- package/docs/webapi/executeAsyncScript.mustache +2 -0
- package/docs/webapi/executeScript.mustache +2 -0
- package/lib/codecept.js +4 -0
- package/lib/command/info.js +24 -0
- package/lib/command/init.js +40 -4
- package/lib/command/run-workers.js +5 -0
- package/lib/command/run.js +7 -0
- package/lib/data/context.js +14 -6
- package/lib/helper/Appium.js +40 -1
- package/lib/helper/FileSystem.js +1 -1
- package/lib/helper/Playwright.js +58 -40
- package/lib/helper/Puppeteer.js +17 -10
- package/lib/helper/WebDriver.js +4 -4
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/pause.js +1 -2
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/retryTo.js +0 -2
- package/lib/plugin/tryTo.js +0 -3
- package/lib/session.js +1 -1
- package/package.json +88 -88
- package/translations/fr-FR.js +13 -1
- package/typings/promiseBasedTypes.d.ts +19 -1
- package/typings/types.d.ts +36 -16
package/lib/command/info.js
CHANGED
|
@@ -37,3 +37,27 @@ module.exports = async function (path) {
|
|
|
37
37
|
output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues');
|
|
38
38
|
output.print('***************************************');
|
|
39
39
|
};
|
|
40
|
+
|
|
41
|
+
module.exports.getMachineInfo = async () => {
|
|
42
|
+
const info = {
|
|
43
|
+
nodeInfo: await envinfo.helpers.getNodeInfo(),
|
|
44
|
+
osInfo: await envinfo.helpers.getOSInfo(),
|
|
45
|
+
cpuInfo: await envinfo.helpers.getCPUInfo(),
|
|
46
|
+
chromeInfo: await envinfo.helpers.getChromeInfo(),
|
|
47
|
+
edgeInfo: await envinfo.helpers.getEdgeInfo(),
|
|
48
|
+
firefoxInfo: await envinfo.helpers.getFirefoxInfo(),
|
|
49
|
+
safariInfo: await envinfo.helpers.getSafariInfo(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
output.print('***************************************');
|
|
53
|
+
for (const [key, value] of Object.entries(info)) {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
output.print(`${key}: ${value[1]}`);
|
|
56
|
+
} else {
|
|
57
|
+
output.print(`${key}: ${JSON.stringify(value, null, ' ')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
output.print('If you need more detailed info, just run this: npx codeceptjs info');
|
|
61
|
+
output.print('***************************************');
|
|
62
|
+
return info;
|
|
63
|
+
};
|
package/lib/command/init.js
CHANGED
|
@@ -201,7 +201,13 @@ module.exports = function (initPath) {
|
|
|
201
201
|
// no extra step file for typescript (as it doesn't match TS conventions)
|
|
202
202
|
const stepFile = `./steps_file.${extension}`;
|
|
203
203
|
fs.writeFileSync(path.join(testsPath, stepFile), extension === 'ts' ? defaultActorTs : defaultActor);
|
|
204
|
-
|
|
204
|
+
|
|
205
|
+
if (isTypeScript) {
|
|
206
|
+
config.include = _actorTranslation('./steps_file', config.translation);
|
|
207
|
+
} else {
|
|
208
|
+
config.include = _actorTranslation(stepFile, config.translation);
|
|
209
|
+
}
|
|
210
|
+
|
|
205
211
|
print(`Steps file created at ${stepFile}`);
|
|
206
212
|
|
|
207
213
|
let configSource;
|
|
@@ -316,7 +322,7 @@ module.exports = function (initPath) {
|
|
|
316
322
|
};
|
|
317
323
|
|
|
318
324
|
print('Configure helpers...');
|
|
319
|
-
inquirer.prompt(helperConfigs).then((helperResult) => {
|
|
325
|
+
inquirer.prompt(helperConfigs).then(async (helperResult) => {
|
|
320
326
|
if (helperResult.Playwright_browser === 'electron') {
|
|
321
327
|
delete helperResult.Playwright_url;
|
|
322
328
|
delete helperResult.Playwright_show;
|
|
@@ -336,12 +342,12 @@ module.exports = function (initPath) {
|
|
|
336
342
|
});
|
|
337
343
|
|
|
338
344
|
print('');
|
|
339
|
-
finish();
|
|
345
|
+
await finish();
|
|
340
346
|
});
|
|
341
347
|
});
|
|
342
348
|
};
|
|
343
349
|
|
|
344
|
-
function install(dependencies
|
|
350
|
+
function install(dependencies) {
|
|
345
351
|
let command;
|
|
346
352
|
let args;
|
|
347
353
|
|
|
@@ -374,9 +380,39 @@ function install(dependencies, verbose) {
|
|
|
374
380
|
].concat(dependencies);
|
|
375
381
|
}
|
|
376
382
|
|
|
383
|
+
if (process.env._INIT_DRY_RUN_INSTALL) {
|
|
384
|
+
args.push('--dry-run');
|
|
385
|
+
}
|
|
386
|
+
|
|
377
387
|
const { status } = spawn.sync(command, args, { stdio: 'inherit' });
|
|
378
388
|
if (status !== 0) {
|
|
379
389
|
throw new Error(`${command} ${args.join(' ')} failed`);
|
|
380
390
|
}
|
|
381
391
|
return true;
|
|
382
392
|
}
|
|
393
|
+
|
|
394
|
+
function _actorTranslation(stepFile, translationSelected) {
|
|
395
|
+
let actor;
|
|
396
|
+
|
|
397
|
+
for (const translationAvailable of translations) {
|
|
398
|
+
if (actor) {
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (translationSelected === translationAvailable) {
|
|
403
|
+
const nameOfActor = require('../../translations')[translationAvailable].I;
|
|
404
|
+
|
|
405
|
+
actor = {
|
|
406
|
+
[nameOfActor]: stepFile,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!actor) {
|
|
412
|
+
actor = {
|
|
413
|
+
I: stepFile,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return actor;
|
|
418
|
+
}
|
|
@@ -39,6 +39,11 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
+
if (options.verbose) {
|
|
43
|
+
global.debugMode = true;
|
|
44
|
+
const { getMachineInfo } = require('./info');
|
|
45
|
+
await getMachineInfo();
|
|
46
|
+
}
|
|
42
47
|
await workers.bootstrapAll();
|
|
43
48
|
await workers.run();
|
|
44
49
|
} catch (err) {
|
package/lib/command/run.js
CHANGED
|
@@ -28,6 +28,13 @@ module.exports = async function (test, options) {
|
|
|
28
28
|
codecept.init(testRoot);
|
|
29
29
|
await codecept.bootstrap();
|
|
30
30
|
codecept.loadTests(test);
|
|
31
|
+
|
|
32
|
+
if (options.verbose) {
|
|
33
|
+
global.debugMode = true;
|
|
34
|
+
const { getMachineInfo } = require('./info');
|
|
35
|
+
await getMachineInfo();
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
await codecept.run();
|
|
32
39
|
} catch (err) {
|
|
33
40
|
printError(err);
|
package/lib/data/context.js
CHANGED
|
@@ -23,6 +23,7 @@ module.exports = function (context) {
|
|
|
23
23
|
.inject({ current: dataRow.data }));
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
|
+
maskSecretInTitle(scenarios);
|
|
26
27
|
return new DataScenarioConfig(scenarios);
|
|
27
28
|
},
|
|
28
29
|
only: {
|
|
@@ -42,6 +43,7 @@ module.exports = function (context) {
|
|
|
42
43
|
.inject({ current: dataRow.data }));
|
|
43
44
|
}
|
|
44
45
|
});
|
|
46
|
+
maskSecretInTitle(scenarios);
|
|
45
47
|
return new DataScenarioConfig(scenarios);
|
|
46
48
|
},
|
|
47
49
|
},
|
|
@@ -71,12 +73,6 @@ function replaceTitle(title, dataRow) {
|
|
|
71
73
|
// it should be printed
|
|
72
74
|
if (Object.prototype.toString.call(dataRow.data) === (Object()).toString()
|
|
73
75
|
&& dataRow.data.toString() !== (Object()).toString()) {
|
|
74
|
-
Object.entries(dataRow.data).forEach(entry => {
|
|
75
|
-
const [key, value] = entry;
|
|
76
|
-
if (value instanceof Secret) {
|
|
77
|
-
dataRow.data[key] = value.getMasked();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
76
|
return `${title} | ${dataRow.data}`;
|
|
81
77
|
}
|
|
82
78
|
|
|
@@ -119,3 +115,15 @@ function detectDataType(dataTable) {
|
|
|
119
115
|
|
|
120
116
|
throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function');
|
|
121
117
|
}
|
|
118
|
+
|
|
119
|
+
function maskSecretInTitle(scenarios) {
|
|
120
|
+
scenarios.forEach(scenario => {
|
|
121
|
+
const res = [];
|
|
122
|
+
|
|
123
|
+
scenario.test.title.split(',').forEach(item => {
|
|
124
|
+
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
scenario.test.title = res.join(',');
|
|
128
|
+
});
|
|
129
|
+
}
|
package/lib/helper/Appium.js
CHANGED
|
@@ -117,6 +117,43 @@ const vendorPrefix = {
|
|
|
117
117
|
* }
|
|
118
118
|
* ```
|
|
119
119
|
*
|
|
120
|
+
* Example Android App using Appiumv2 on BrowserStack:
|
|
121
|
+
*
|
|
122
|
+
* ```js
|
|
123
|
+
* {
|
|
124
|
+
* helpers: {
|
|
125
|
+
* Appium: {
|
|
126
|
+
* appiumV2: true,
|
|
127
|
+
* host: "hub-cloud.browserstack.com",
|
|
128
|
+
* port: 4444,
|
|
129
|
+
* user: process.env.BROWSERSTACK_USER,
|
|
130
|
+
* key: process.env.BROWSERSTACK_KEY,
|
|
131
|
+
* app: `bs://c700ce60cf1gjhgjh3ae8ed9770ghjg5a55b8e022f13c5827cg`,
|
|
132
|
+
* browser: '',
|
|
133
|
+
* desiredCapabilities: {
|
|
134
|
+
* 'appPackage': data.packageName,
|
|
135
|
+
* 'deviceName': process.env.DEVICE || 'Google Pixel 3',
|
|
136
|
+
* 'platformName': process.env.PLATFORM || 'android',
|
|
137
|
+
* 'platformVersion': process.env.OS_VERSION || '10.0',
|
|
138
|
+
* 'automationName': process.env.ENGINE || 'UIAutomator2',
|
|
139
|
+
* 'newCommandTimeout': 300000,
|
|
140
|
+
* 'androidDeviceReadyTimeout': 300000,
|
|
141
|
+
* 'androidInstallTimeout': 90000,
|
|
142
|
+
* 'appWaitDuration': 300000,
|
|
143
|
+
* 'autoGrantPermissions': true,
|
|
144
|
+
* 'gpsEnabled': true,
|
|
145
|
+
* 'isHeadless': false,
|
|
146
|
+
* 'noReset': false,
|
|
147
|
+
* 'noSign': true,
|
|
148
|
+
* 'bstack:options' : {
|
|
149
|
+
* "appiumVersion" : "2.0.1",
|
|
150
|
+
* },
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
120
157
|
* Additional configuration params can be used from <https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md>
|
|
121
158
|
*
|
|
122
159
|
* ## Access From Helpers
|
|
@@ -234,7 +271,9 @@ class Appium extends Webdriver {
|
|
|
234
271
|
const _convertedCaps = {};
|
|
235
272
|
for (const [key, value] of Object.entries(capabilities)) {
|
|
236
273
|
if (!key.startsWith(vendorPrefix.appium)) {
|
|
237
|
-
|
|
274
|
+
if (key !== 'platformName') {
|
|
275
|
+
_convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
|
|
276
|
+
}
|
|
238
277
|
} else {
|
|
239
278
|
_convertedCaps[`${key}`] = value;
|
|
240
279
|
}
|
package/lib/helper/FileSystem.js
CHANGED
package/lib/helper/Playwright.js
CHANGED
|
@@ -47,7 +47,6 @@ const {
|
|
|
47
47
|
setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
|
|
48
48
|
} = require('./extras/PlaywrightRestartOpts');
|
|
49
49
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
50
|
-
const { highlightElement } = require('./scripts/highlightElement');
|
|
51
50
|
|
|
52
51
|
const pathSeparator = path.sep;
|
|
53
52
|
|
|
@@ -94,7 +93,7 @@ const pathSeparator = path.sep;
|
|
|
94
93
|
* @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type).
|
|
95
94
|
* @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
|
|
96
95
|
* @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
|
|
97
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
96
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
98
97
|
*/
|
|
99
98
|
const config = {};
|
|
100
99
|
|
|
@@ -482,6 +481,7 @@ class Playwright extends Helper {
|
|
|
482
481
|
contextOptions.httpCredentials = this.options.basicAuth;
|
|
483
482
|
this.isAuthenticated = true;
|
|
484
483
|
}
|
|
484
|
+
if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP;
|
|
485
485
|
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
|
|
486
486
|
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
487
487
|
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
@@ -826,8 +826,9 @@ class Playwright extends Helper {
|
|
|
826
826
|
|
|
827
827
|
async _stopBrowser() {
|
|
828
828
|
this.withinLocator = null;
|
|
829
|
-
this._setPage(null);
|
|
829
|
+
await this._setPage(null);
|
|
830
830
|
this.context = null;
|
|
831
|
+
this.frame = null;
|
|
831
832
|
popupStore.clear();
|
|
832
833
|
await this.browser.close();
|
|
833
834
|
}
|
|
@@ -849,8 +850,8 @@ class Playwright extends Helper {
|
|
|
849
850
|
await this.switchTo(null);
|
|
850
851
|
return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve());
|
|
851
852
|
}
|
|
852
|
-
await this.switchTo(
|
|
853
|
-
this.withinLocator = new Locator(
|
|
853
|
+
await this.switchTo(frame);
|
|
854
|
+
this.withinLocator = new Locator(frame);
|
|
854
855
|
return;
|
|
855
856
|
}
|
|
856
857
|
|
|
@@ -866,6 +867,7 @@ class Playwright extends Helper {
|
|
|
866
867
|
this.withinLocator = null;
|
|
867
868
|
this.context = await this.page;
|
|
868
869
|
this.contextLocator = null;
|
|
870
|
+
this.frame = null;
|
|
869
871
|
}
|
|
870
872
|
|
|
871
873
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -1155,6 +1157,9 @@ class Playwright extends Helper {
|
|
|
1155
1157
|
*/
|
|
1156
1158
|
async _locate(locator) {
|
|
1157
1159
|
const context = await this.context || await this._getContext();
|
|
1160
|
+
|
|
1161
|
+
if (this.frame) return findElements(this.frame, locator);
|
|
1162
|
+
|
|
1158
1163
|
return findElements(context, locator);
|
|
1159
1164
|
}
|
|
1160
1165
|
|
|
@@ -1578,7 +1583,7 @@ class Playwright extends Helper {
|
|
|
1578
1583
|
|
|
1579
1584
|
await el.clear();
|
|
1580
1585
|
|
|
1581
|
-
highlightActiveElement.call(this, el
|
|
1586
|
+
await highlightActiveElement.call(this, el);
|
|
1582
1587
|
|
|
1583
1588
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1584
1589
|
|
|
@@ -1608,7 +1613,7 @@ class Playwright extends Helper {
|
|
|
1608
1613
|
|
|
1609
1614
|
const el = els[0];
|
|
1610
1615
|
|
|
1611
|
-
highlightActiveElement.call(this, el
|
|
1616
|
+
await highlightActiveElement.call(this, el);
|
|
1612
1617
|
|
|
1613
1618
|
await el.clear();
|
|
1614
1619
|
|
|
@@ -1623,7 +1628,7 @@ class Playwright extends Helper {
|
|
|
1623
1628
|
async appendField(field, value) {
|
|
1624
1629
|
const els = await findFields.call(this, field);
|
|
1625
1630
|
assertElementExists(els, field, 'Field');
|
|
1626
|
-
highlightActiveElement.call(this, els[0]
|
|
1631
|
+
await highlightActiveElement.call(this, els[0]);
|
|
1627
1632
|
await els[0].press('End');
|
|
1628
1633
|
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1629
1634
|
return this._waitForAction();
|
|
@@ -1669,7 +1674,7 @@ class Playwright extends Helper {
|
|
|
1669
1674
|
assertElementExists(els, select, 'Selectable field');
|
|
1670
1675
|
const el = els[0];
|
|
1671
1676
|
|
|
1672
|
-
highlightActiveElement.call(this, el
|
|
1677
|
+
await highlightActiveElement.call(this, el);
|
|
1673
1678
|
|
|
1674
1679
|
if (!Array.isArray(option)) option = [option];
|
|
1675
1680
|
|
|
@@ -1881,11 +1886,11 @@ class Playwright extends Helper {
|
|
|
1881
1886
|
* @returns {Promise<any>}
|
|
1882
1887
|
*/
|
|
1883
1888
|
async executeScript(fn, arg) {
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1889
|
+
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
1890
|
+
// switching to iframe context
|
|
1891
|
+
return this.context.locator(':root').evaluate(fn, arg);
|
|
1887
1892
|
}
|
|
1888
|
-
return
|
|
1893
|
+
return this.page.evaluate.apply(this.page, [fn, arg]);
|
|
1889
1894
|
}
|
|
1890
1895
|
|
|
1891
1896
|
/**
|
|
@@ -2407,7 +2412,7 @@ class Playwright extends Helper {
|
|
|
2407
2412
|
}
|
|
2408
2413
|
|
|
2409
2414
|
async _getContext() {
|
|
2410
|
-
if (this.context && this.context.constructor.name === '
|
|
2415
|
+
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2411
2416
|
return this.context;
|
|
2412
2417
|
}
|
|
2413
2418
|
return this.page;
|
|
@@ -2480,6 +2485,14 @@ class Playwright extends Helper {
|
|
|
2480
2485
|
}, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
|
|
2481
2486
|
}
|
|
2482
2487
|
} else {
|
|
2488
|
+
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2489
|
+
if (this.frame) {
|
|
2490
|
+
const { setTimeout } = require('timers/promises');
|
|
2491
|
+
await setTimeout(waitTimeout);
|
|
2492
|
+
waiter = await this.frame.locator(`:has-text('${text}')`).first().isVisible();
|
|
2493
|
+
if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2483
2496
|
waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, text, { timeout: waitTimeout });
|
|
2484
2497
|
}
|
|
2485
2498
|
return waiter.catch((err) => {
|
|
@@ -2534,39 +2547,42 @@ class Playwright extends Helper {
|
|
|
2534
2547
|
}
|
|
2535
2548
|
|
|
2536
2549
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2537
|
-
this.context =
|
|
2550
|
+
this.context = await this.page.frameLocator('iframe').nth(locator);
|
|
2538
2551
|
this.contextLocator = locator;
|
|
2539
2552
|
} else {
|
|
2540
2553
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2541
2554
|
}
|
|
2542
2555
|
return;
|
|
2543
2556
|
}
|
|
2544
|
-
let contentFrame;
|
|
2545
2557
|
|
|
2546
2558
|
if (!locator) {
|
|
2547
|
-
this.context =
|
|
2559
|
+
this.context = this.page;
|
|
2548
2560
|
this.contextLocator = null;
|
|
2561
|
+
this.frame = null;
|
|
2549
2562
|
return;
|
|
2550
2563
|
}
|
|
2551
2564
|
|
|
2552
2565
|
// iframe by selector
|
|
2553
|
-
|
|
2554
|
-
|
|
2566
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
2567
|
+
const frame = await this._locateElement(locator);
|
|
2568
|
+
|
|
2569
|
+
if (!frame) {
|
|
2570
|
+
throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`);
|
|
2571
|
+
}
|
|
2555
2572
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
} else if (locator.toLowerCase().includes('name=')) {
|
|
2561
|
-
const frameName = locator.split('=')[1].replace(/"/g, '').replaceAll(/]/g, '');
|
|
2562
|
-
contentFrame = await this.page.frame(frameName);
|
|
2573
|
+
if (this.frame) {
|
|
2574
|
+
this.frame = await this.frame.frameLocator(locator);
|
|
2575
|
+
} else {
|
|
2576
|
+
this.frame = await this.page.frameLocator(locator);
|
|
2563
2577
|
}
|
|
2564
2578
|
|
|
2579
|
+
const contentFrame = this.frame;
|
|
2580
|
+
|
|
2565
2581
|
if (contentFrame) {
|
|
2566
2582
|
this.context = contentFrame;
|
|
2567
2583
|
this.contextLocator = null;
|
|
2568
2584
|
} else {
|
|
2569
|
-
this.context =
|
|
2585
|
+
this.context = this.page.frame(this.page.frames()[1].name());
|
|
2570
2586
|
this.contextLocator = locator;
|
|
2571
2587
|
}
|
|
2572
2588
|
}
|
|
@@ -3256,7 +3272,7 @@ async function findElement(matcher, locator) {
|
|
|
3256
3272
|
if (locator.react) return findReact(matcher, locator);
|
|
3257
3273
|
locator = new Locator(locator, 'css');
|
|
3258
3274
|
|
|
3259
|
-
return matcher.locator(buildLocatorString(locator));
|
|
3275
|
+
return matcher.locator(buildLocatorString(locator)).first();
|
|
3260
3276
|
}
|
|
3261
3277
|
|
|
3262
3278
|
async function getVisibleElements(elements) {
|
|
@@ -3286,7 +3302,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3286
3302
|
assertElementExists(els, locator, 'Clickable element');
|
|
3287
3303
|
}
|
|
3288
3304
|
|
|
3289
|
-
highlightActiveElement.call(this, els[0]
|
|
3305
|
+
await highlightActiveElement.call(this, els[0]);
|
|
3290
3306
|
|
|
3291
3307
|
/*
|
|
3292
3308
|
using the force true options itself but instead dispatching a click
|
|
@@ -3336,13 +3352,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3336
3352
|
let allText;
|
|
3337
3353
|
|
|
3338
3354
|
if (!context) {
|
|
3339
|
-
|
|
3340
|
-
if (el && !el.getProperty) {
|
|
3341
|
-
// Fallback to body
|
|
3342
|
-
el = await this.page.$('body');
|
|
3343
|
-
}
|
|
3355
|
+
const el = await this.context;
|
|
3344
3356
|
|
|
3345
|
-
allText = [await el.innerText()];
|
|
3357
|
+
allText = [await el.locator('body').innerText()];
|
|
3346
3358
|
description = 'web application';
|
|
3347
3359
|
} else {
|
|
3348
3360
|
const locator = new Locator(context, 'css');
|
|
@@ -3514,7 +3526,9 @@ async function elementSelected(element) {
|
|
|
3514
3526
|
|
|
3515
3527
|
function isFrameLocator(locator) {
|
|
3516
3528
|
locator = new Locator(locator);
|
|
3517
|
-
if (locator.isFrame())
|
|
3529
|
+
if (locator.isFrame()) {
|
|
3530
|
+
return locator.value;
|
|
3531
|
+
}
|
|
3518
3532
|
return false;
|
|
3519
3533
|
}
|
|
3520
3534
|
|
|
@@ -3709,10 +3723,14 @@ async function saveTraceForContext(context, name) {
|
|
|
3709
3723
|
return fileName;
|
|
3710
3724
|
}
|
|
3711
3725
|
|
|
3712
|
-
function highlightActiveElement(element
|
|
3713
|
-
if (
|
|
3714
|
-
|
|
3715
|
-
|
|
3726
|
+
async function highlightActiveElement(element) {
|
|
3727
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
3728
|
+
await element.evaluate(el => {
|
|
3729
|
+
const prevStyle = el.style.boxShadow;
|
|
3730
|
+
el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)';
|
|
3731
|
+
setTimeout(() => el.style.boxShadow = prevStyle, 2000);
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3716
3734
|
}
|
|
3717
3735
|
|
|
3718
3736
|
const createAdvancedTestResults = (url, dataToCheck, requests) => {
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
|
|
|
69
69
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
|
|
70
70
|
* @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
|
|
71
71
|
* @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
|
|
72
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
72
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
73
73
|
*/
|
|
74
74
|
const config = {};
|
|
75
75
|
|
|
@@ -604,8 +604,8 @@ class Puppeteer extends Helper {
|
|
|
604
604
|
return this.switchTo(null)
|
|
605
605
|
.then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
|
|
606
606
|
}
|
|
607
|
-
await this.switchTo(
|
|
608
|
-
this.withinLocator = new Locator(
|
|
607
|
+
await this.switchTo(frame);
|
|
608
|
+
this.withinLocator = new Locator(frame);
|
|
609
609
|
return;
|
|
610
610
|
}
|
|
611
611
|
|
|
@@ -2011,7 +2011,7 @@ class Puppeteer extends Helper {
|
|
|
2011
2011
|
assertElementExists(els, locator);
|
|
2012
2012
|
|
|
2013
2013
|
return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
|
|
2014
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2014
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2015
2015
|
throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
|
|
2016
2016
|
} else {
|
|
2017
2017
|
throw e;
|
|
@@ -2115,7 +2115,7 @@ class Puppeteer extends Helper {
|
|
|
2115
2115
|
return currUrl.indexOf(urlPart) > -1;
|
|
2116
2116
|
}, { timeout: waitTimeout }, urlPart).catch(async (e) => {
|
|
2117
2117
|
const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
|
|
2118
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2118
|
+
if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2119
2119
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
|
|
2120
2120
|
} else {
|
|
2121
2121
|
throw e;
|
|
@@ -2139,7 +2139,7 @@ class Puppeteer extends Helper {
|
|
|
2139
2139
|
return currUrl.indexOf(urlPart) > -1;
|
|
2140
2140
|
}, { timeout: waitTimeout }, urlPart).catch(async (e) => {
|
|
2141
2141
|
const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
|
|
2142
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2142
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2143
2143
|
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
|
|
2144
2144
|
} else {
|
|
2145
2145
|
throw e;
|
|
@@ -2348,6 +2348,10 @@ async function findElements(matcher, locator) {
|
|
|
2348
2348
|
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
2349
2349
|
locator = new Locator(locator, 'css');
|
|
2350
2350
|
if (!locator.isXPath()) return matcher.$$(locator.simplify());
|
|
2351
|
+
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
|
|
2352
|
+
if (puppeteer.default?.defaultBrowserRevision) {
|
|
2353
|
+
return matcher.$$(`xpath/${locator.value}`);
|
|
2354
|
+
}
|
|
2351
2355
|
return matcher.$x(locator.value);
|
|
2352
2356
|
}
|
|
2353
2357
|
|
|
@@ -2602,7 +2606,10 @@ async function elementSelected(element) {
|
|
|
2602
2606
|
|
|
2603
2607
|
function isFrameLocator(locator) {
|
|
2604
2608
|
locator = new Locator(locator);
|
|
2605
|
-
if (locator.isFrame())
|
|
2609
|
+
if (locator.isFrame()) {
|
|
2610
|
+
const _locator = new Locator(locator);
|
|
2611
|
+
return _locator.value;
|
|
2612
|
+
}
|
|
2606
2613
|
return false;
|
|
2607
2614
|
}
|
|
2608
2615
|
|
|
@@ -2720,7 +2727,7 @@ function getNormalizedKey(key) {
|
|
|
2720
2727
|
}
|
|
2721
2728
|
|
|
2722
2729
|
function highlightActiveElement(element, context) {
|
|
2723
|
-
if (
|
|
2724
|
-
|
|
2725
|
-
|
|
2730
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
2731
|
+
highlightElement(element, context);
|
|
2732
|
+
}
|
|
2726
2733
|
}
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -62,7 +62,7 @@ const webRoot = 'body';
|
|
|
62
62
|
* @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
|
|
63
63
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
|
|
64
64
|
* @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
|
|
65
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
65
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
66
66
|
*/
|
|
67
67
|
const config = {};
|
|
68
68
|
|
|
@@ -2918,9 +2918,9 @@ function isModifierKey(key) {
|
|
|
2918
2918
|
}
|
|
2919
2919
|
|
|
2920
2920
|
function highlightActiveElement(element) {
|
|
2921
|
-
if (
|
|
2922
|
-
|
|
2923
|
-
|
|
2921
|
+
if (this.options.highlightElement && global.debugMode) {
|
|
2922
|
+
highlightElement(element, this.browser);
|
|
2923
|
+
}
|
|
2924
2924
|
}
|
|
2925
2925
|
|
|
2926
2926
|
function prepareLocateFn(context) {
|
package/lib/pause.js
CHANGED
|
@@ -22,6 +22,7 @@ const aiAssistant = new AiAssistant();
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Pauses test execution and starts interactive shell
|
|
25
|
+
* @param {Object<string, *>} [passedObject]
|
|
25
26
|
*/
|
|
26
27
|
const pause = function (passedObject = {}) {
|
|
27
28
|
if (store.dryRun) return;
|
|
@@ -77,7 +78,6 @@ async function parseInput(cmd) {
|
|
|
77
78
|
rl.pause();
|
|
78
79
|
next = false;
|
|
79
80
|
recorder.session.start('pause');
|
|
80
|
-
store.debugMode = false;
|
|
81
81
|
if (cmd === '') next = true;
|
|
82
82
|
if (!cmd || cmd === 'resume' || cmd === 'exit') {
|
|
83
83
|
finish();
|
|
@@ -97,7 +97,6 @@ async function parseInput(cmd) {
|
|
|
97
97
|
return cmd;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
store.debugMode = true;
|
|
101
100
|
let isCustomCommand = false;
|
|
102
101
|
let lastError = null;
|
|
103
102
|
let isAiCommand = false;
|
package/lib/plugin/autoLogin.js
CHANGED
|
@@ -251,20 +251,16 @@ module.exports = function (config) {
|
|
|
251
251
|
} else {
|
|
252
252
|
userSession.login(I);
|
|
253
253
|
}
|
|
254
|
-
store.debugMode = true;
|
|
255
254
|
const cookies = await userSession.fetch(I);
|
|
256
255
|
if (config.saveToFile) {
|
|
257
256
|
debug(`Saved user session into file for ${name}`);
|
|
258
257
|
fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies));
|
|
259
258
|
}
|
|
260
259
|
store[`${name}_session`] = cookies;
|
|
261
|
-
store.debugMode = false;
|
|
262
260
|
};
|
|
263
261
|
|
|
264
262
|
if (!cookies) return loginAndSave();
|
|
265
263
|
|
|
266
|
-
store.debugMode = true;
|
|
267
|
-
|
|
268
264
|
recorder.session.start('check login');
|
|
269
265
|
if (shouldAwait) {
|
|
270
266
|
await userSession.restore(I, cookies);
|
|
@@ -287,7 +283,6 @@ module.exports = function (config) {
|
|
|
287
283
|
});
|
|
288
284
|
});
|
|
289
285
|
recorder.add(() => {
|
|
290
|
-
store.debugMode = false;
|
|
291
286
|
recorder.session.restore('check login');
|
|
292
287
|
});
|
|
293
288
|
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -83,7 +83,6 @@ module.exports = function (config) {
|
|
|
83
83
|
return retryTo;
|
|
84
84
|
|
|
85
85
|
function retryTo(callback, maxTries, pollInterval = undefined) {
|
|
86
|
-
const mode = store.debugMode;
|
|
87
86
|
let tries = 1;
|
|
88
87
|
if (!pollInterval) pollInterval = config.pollInterval;
|
|
89
88
|
|
|
@@ -113,7 +112,6 @@ module.exports = function (config) {
|
|
|
113
112
|
};
|
|
114
113
|
|
|
115
114
|
recorder.add('retryTo', async () => {
|
|
116
|
-
store.debugMode = true;
|
|
117
115
|
tryBlock();
|
|
118
116
|
});
|
|
119
117
|
}).then(() => {
|