codeceptjs 3.1.0 → 3.2.0
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 +129 -3
- package/README.md +2 -3
- package/bin/codecept.js +1 -0
- package/docs/advanced.md +94 -60
- package/docs/basics.md +1 -1
- package/docs/bdd.md +55 -1
- package/docs/build/Appium.js +106 -34
- package/docs/build/FileSystem.js +1 -0
- package/docs/build/Nightmare.js +48 -48
- package/docs/build/Playwright.js +97 -94
- package/docs/build/Protractor.js +68 -81
- package/docs/build/Puppeteer.js +91 -93
- package/docs/build/REST.js +1 -0
- package/docs/build/TestCafe.js +44 -44
- package/docs/build/WebDriver.js +71 -95
- package/docs/changelog.md +144 -2
- package/docs/commands.md +21 -7
- package/docs/configuration.md +15 -2
- package/docs/custom-helpers.md +1 -36
- package/docs/helpers/Appium.md +97 -95
- package/docs/helpers/FileSystem.md +1 -1
- package/docs/helpers/Playwright.md +16 -18
- package/docs/helpers/Puppeteer.md +18 -18
- package/docs/helpers/REST.md +3 -1
- package/docs/helpers/WebDriver.md +3 -19
- package/docs/mobile-react-native-locators.md +3 -0
- package/docs/playwright.md +40 -0
- package/docs/plugins.md +185 -68
- package/docs/reports.md +23 -5
- package/lib/actor.js +20 -2
- package/lib/codecept.js +15 -2
- package/lib/command/info.js +1 -1
- package/lib/config.js +13 -1
- package/lib/container.js +3 -1
- package/lib/data/dataTableArgument.js +35 -0
- package/lib/helper/Appium.js +49 -4
- package/lib/helper/FileSystem.js +1 -0
- package/lib/helper/Playwright.js +35 -22
- package/lib/helper/Protractor.js +2 -14
- package/lib/helper/Puppeteer.js +20 -19
- package/lib/helper/REST.js +1 -0
- package/lib/helper/WebDriver.js +2 -16
- package/lib/index.js +2 -0
- package/lib/interfaces/featureConfig.js +3 -0
- package/lib/interfaces/gherkin.js +7 -1
- package/lib/interfaces/scenarioConfig.js +4 -0
- package/lib/listener/helpers.js +1 -0
- package/lib/listener/steps.js +21 -3
- package/lib/listener/timeout.js +71 -0
- package/lib/locator.js +3 -0
- package/lib/mochaFactory.js +13 -9
- package/lib/plugin/allure.js +6 -1
- package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
- package/lib/plugin/customLocator.js +2 -2
- package/lib/plugin/retryTo.js +130 -0
- package/lib/plugin/screenshotOnFail.js +1 -0
- package/lib/plugin/stepByStepReport.js +7 -0
- package/lib/plugin/stepTimeout.js +90 -0
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/recorder.js +21 -8
- package/lib/step.js +7 -2
- package/lib/store.js +2 -0
- package/lib/ui.js +2 -2
- package/package.json +6 -7
- package/typings/index.d.ts +8 -1
- package/typings/types.d.ts +198 -82
- package/docs/angular.md +0 -325
- package/docs/helpers/Protractor.md +0 -1658
- package/docs/webapi/waitUntil.mustache +0 -11
- package/typings/Protractor.d.ts +0 -16
package/lib/actor.js
CHANGED
|
@@ -27,8 +27,26 @@ class Actor {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
+
* set the maximum execution time for the next step
|
|
30
31
|
* @function
|
|
31
|
-
* @param {
|
|
32
|
+
* @param {number} timeout - step timeout in seconds
|
|
33
|
+
* @return {this}
|
|
34
|
+
* @inner
|
|
35
|
+
*/
|
|
36
|
+
limitTime(timeout) {
|
|
37
|
+
if (!store.timeouts) return this;
|
|
38
|
+
|
|
39
|
+
event.dispatcher.prependOnceListener(event.step.before, (step) => {
|
|
40
|
+
output.log(`Timeout to ${step}: ${timeout}s`);
|
|
41
|
+
step.totalTimeout = timeout * 1000;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @function
|
|
49
|
+
* @param {*} [opts]
|
|
32
50
|
* @return {this}
|
|
33
51
|
* @inner
|
|
34
52
|
*/
|
|
@@ -114,7 +132,7 @@ function recordStep(step, args) {
|
|
|
114
132
|
step.startTime = Date.now();
|
|
115
133
|
}
|
|
116
134
|
return val = step.run(...args);
|
|
117
|
-
});
|
|
135
|
+
}, false, undefined, step.totalTimeout);
|
|
118
136
|
|
|
119
137
|
event.emit(event.step.after, step);
|
|
120
138
|
|
package/lib/codecept.js
CHANGED
|
@@ -77,6 +77,7 @@ class Codecept {
|
|
|
77
77
|
global.inject = container.support;
|
|
78
78
|
global.share = container.share;
|
|
79
79
|
global.secret = require('./secret').secret;
|
|
80
|
+
global.codecept_debug = output.debug;
|
|
80
81
|
global.codeceptjs = require('./index'); // load all objects
|
|
81
82
|
|
|
82
83
|
// BDD
|
|
@@ -95,6 +96,7 @@ class Codecept {
|
|
|
95
96
|
runHook(require('./listener/steps'));
|
|
96
97
|
runHook(require('./listener/config'));
|
|
97
98
|
runHook(require('./listener/helpers'));
|
|
99
|
+
runHook(require('./listener/timeout'));
|
|
98
100
|
runHook(require('./listener/exit'));
|
|
99
101
|
|
|
100
102
|
// custom hooks
|
|
@@ -130,7 +132,16 @@ class Codecept {
|
|
|
130
132
|
let patterns = [pattern];
|
|
131
133
|
if (!pattern) {
|
|
132
134
|
patterns = [];
|
|
133
|
-
|
|
135
|
+
|
|
136
|
+
// If the user wants to test a specific set of test files as an array or string.
|
|
137
|
+
if (this.config.tests && !this.opts.features) {
|
|
138
|
+
if (Array.isArray(this.config.tests)) {
|
|
139
|
+
patterns.push(...this.config.tests);
|
|
140
|
+
} else {
|
|
141
|
+
patterns.push(this.config.tests);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
134
145
|
if (this.config.gherkin.features && !this.opts.tests) {
|
|
135
146
|
if (Array.isArray(this.config.gherkin.features)) {
|
|
136
147
|
this.config.gherkin.features.forEach(feature => {
|
|
@@ -147,7 +158,9 @@ class Codecept {
|
|
|
147
158
|
if (!fsPath.isAbsolute(file)) {
|
|
148
159
|
file = fsPath.join(global.codecept_dir, file);
|
|
149
160
|
}
|
|
150
|
-
this.testFiles.
|
|
161
|
+
if (!this.testFiles.includes(fsPath.resolve(file))) {
|
|
162
|
+
this.testFiles.push(fsPath.resolve(file));
|
|
163
|
+
}
|
|
151
164
|
});
|
|
152
165
|
}
|
|
153
166
|
}
|
package/lib/command/info.js
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = async function (path) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
output.print('***************************************');
|
|
35
|
-
output.print('If you have questions ask them in our Slack:
|
|
35
|
+
output.print('If you have questions ask them in our Slack: http://bit.ly/chat-codeceptjs');
|
|
36
36
|
output.print('Or ask them on our discussion board: https://codecept.discourse.group/');
|
|
37
37
|
output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues');
|
|
38
38
|
output.print('***************************************');
|
package/lib/config.js
CHANGED
|
@@ -13,6 +13,7 @@ const defaultConfig = {
|
|
|
13
13
|
include: {},
|
|
14
14
|
mocha: {},
|
|
15
15
|
bootstrap: null,
|
|
16
|
+
timeout: null,
|
|
16
17
|
teardown: null,
|
|
17
18
|
hooks: [],
|
|
18
19
|
gherkin: {},
|
|
@@ -21,6 +22,17 @@ const defaultConfig = {
|
|
|
21
22
|
enabled: true, // will be disabled by default in 2.0
|
|
22
23
|
},
|
|
23
24
|
},
|
|
25
|
+
stepTimeout: 0,
|
|
26
|
+
stepTimeoutOverride: [
|
|
27
|
+
{
|
|
28
|
+
pattern: 'wait.*',
|
|
29
|
+
timeout: 0,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: 'amOnPage',
|
|
33
|
+
timeout: 0,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
let hooks = [];
|
|
@@ -128,7 +140,7 @@ function loadConfigFile(configFile) {
|
|
|
128
140
|
const extensionName = path.extname(configFile);
|
|
129
141
|
|
|
130
142
|
// .conf.js config file
|
|
131
|
-
if (extensionName === '.js' || extensionName === '.ts') {
|
|
143
|
+
if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
|
|
132
144
|
return Config.create(require(configFile).config);
|
|
133
145
|
}
|
|
134
146
|
|
package/lib/container.js
CHANGED
|
@@ -7,6 +7,7 @@ const MochaFactory = require('./mochaFactory');
|
|
|
7
7
|
const recorder = require('./recorder');
|
|
8
8
|
const event = require('./event');
|
|
9
9
|
const WorkerStorage = require('./workerStorage');
|
|
10
|
+
const store = require('./store');
|
|
10
11
|
|
|
11
12
|
let container = {
|
|
12
13
|
helpers: {},
|
|
@@ -45,6 +46,7 @@ class Container {
|
|
|
45
46
|
container.support = createSupportObjects(config.include || {});
|
|
46
47
|
container.plugins = createPlugins(config.plugins || {}, opts);
|
|
47
48
|
if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []);
|
|
49
|
+
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -233,7 +235,7 @@ function createSupportObjects(config) {
|
|
|
233
235
|
const currentObject = objects[object];
|
|
234
236
|
Object.keys(currentObject).forEach((method) => {
|
|
235
237
|
const currentMethod = currentObject[method];
|
|
236
|
-
if (currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
|
|
238
|
+
if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
|
|
237
239
|
objects[object][method] = asyncWrapper(currentMethod);
|
|
238
240
|
}
|
|
239
241
|
});
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTableArgument class to store the Cucumber data table from
|
|
3
|
+
* a step as an object with methods that can be used to access the data.
|
|
4
|
+
*/
|
|
1
5
|
class DataTableArgument {
|
|
6
|
+
/** @param {*} gherkinDataTable */
|
|
2
7
|
constructor(gherkinDataTable) {
|
|
3
8
|
this.rawData = gherkinDataTable.rows.map((row) => {
|
|
4
9
|
return row.cells.map((cell) => {
|
|
@@ -7,16 +12,25 @@ class DataTableArgument {
|
|
|
7
12
|
});
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
/** Returns the table as a 2-D array
|
|
16
|
+
* @returns {string[][]}
|
|
17
|
+
*/
|
|
10
18
|
raw() {
|
|
11
19
|
return this.rawData.slice(0);
|
|
12
20
|
}
|
|
13
21
|
|
|
22
|
+
/** Returns the table as a 2-D array, without the first row
|
|
23
|
+
* @returns {string[][]}
|
|
24
|
+
*/
|
|
14
25
|
rows() {
|
|
15
26
|
const copy = this.raw();
|
|
16
27
|
copy.shift();
|
|
17
28
|
return copy;
|
|
18
29
|
}
|
|
19
30
|
|
|
31
|
+
/** Returns an array of objects where each row is converted to an object (column header is the key)
|
|
32
|
+
* @returns {any[]}
|
|
33
|
+
*/
|
|
20
34
|
hashes() {
|
|
21
35
|
const copy = this.raw();
|
|
22
36
|
const header = copy.shift();
|
|
@@ -26,6 +40,27 @@ class DataTableArgument {
|
|
|
26
40
|
return r;
|
|
27
41
|
});
|
|
28
42
|
}
|
|
43
|
+
|
|
44
|
+
/** Returns an object where each row corresponds to an entry
|
|
45
|
+
* (first column is the key, second column is the value)
|
|
46
|
+
* @returns {Record<string, string>}
|
|
47
|
+
*/
|
|
48
|
+
rowsHash() {
|
|
49
|
+
const rows = this.raw();
|
|
50
|
+
const everyRowHasTwoColumns = rows.every((row) => row.length === 2);
|
|
51
|
+
if (!everyRowHasTwoColumns) {
|
|
52
|
+
throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns');
|
|
53
|
+
}
|
|
54
|
+
/** @type {Record<string, string>} */
|
|
55
|
+
const result = {};
|
|
56
|
+
rows.forEach((x) => (result[x[0]] = x[1]));
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Transposed the data */
|
|
61
|
+
transpose() {
|
|
62
|
+
this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]));
|
|
63
|
+
}
|
|
29
64
|
}
|
|
30
65
|
|
|
31
66
|
module.exports = DataTableArgument;
|
package/lib/helper/Appium.js
CHANGED
|
@@ -481,10 +481,11 @@ class Appium extends Webdriver {
|
|
|
481
481
|
* ```js
|
|
482
482
|
* I.removeApp('appName', 'com.example.android.apis');
|
|
483
483
|
* ```
|
|
484
|
-
* @param {string} appId
|
|
485
|
-
* @param {string} bundleId String ID of bundle
|
|
486
484
|
*
|
|
487
485
|
* Appium: support only Android
|
|
486
|
+
*
|
|
487
|
+
* @param {string} appId
|
|
488
|
+
* @param {string} [bundleId] ID of bundle
|
|
488
489
|
*/
|
|
489
490
|
async removeApp(appId, bundleId) {
|
|
490
491
|
onlyForApps.call(this, 'Android');
|
|
@@ -820,9 +821,10 @@ class Appium extends Webdriver {
|
|
|
820
821
|
* I.hideDeviceKeyboard('pressKey', 'Done');
|
|
821
822
|
* ```
|
|
822
823
|
*
|
|
823
|
-
* @param {'tapOutside' | 'pressKey'} strategy desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
|
|
824
|
-
*
|
|
825
824
|
* Appium: support Android and iOS
|
|
825
|
+
*
|
|
826
|
+
* @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
|
|
827
|
+
* @param {string} [key] Optional key
|
|
826
828
|
*/
|
|
827
829
|
async hideDeviceKeyboard(strategy, key) {
|
|
828
830
|
onlyForApps.call(this);
|
|
@@ -1162,6 +1164,8 @@ class Appium extends Webdriver {
|
|
|
1162
1164
|
* ```
|
|
1163
1165
|
*
|
|
1164
1166
|
* Appium: support Android and iOS
|
|
1167
|
+
*
|
|
1168
|
+
* @param {Array} actions Array of touch actions
|
|
1165
1169
|
*/
|
|
1166
1170
|
async touchPerform(actions) {
|
|
1167
1171
|
onlyForApps.call(this);
|
|
@@ -1352,6 +1356,33 @@ class Appium extends Webdriver {
|
|
|
1352
1356
|
return super.grabTextFrom(parseLocator.call(this, locator));
|
|
1353
1357
|
}
|
|
1354
1358
|
|
|
1359
|
+
/**
|
|
1360
|
+
* {{> grabNumberOfVisibleElements }}
|
|
1361
|
+
*/
|
|
1362
|
+
async grabNumberOfVisibleElements(locator) {
|
|
1363
|
+
if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
|
|
1364
|
+
return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1369
|
+
*
|
|
1370
|
+
* {{> grabAttributeFrom }}
|
|
1371
|
+
*/
|
|
1372
|
+
async grabAttributeFrom(locator, attr) {
|
|
1373
|
+
if (this.isWeb) return super.grabAttributeFrom(locator, attr);
|
|
1374
|
+
return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1379
|
+
* {{> grabAttributeFromAll }}
|
|
1380
|
+
*/
|
|
1381
|
+
async grabAttributeFromAll(locator, attr) {
|
|
1382
|
+
if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
|
|
1383
|
+
return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1355
1386
|
/**
|
|
1356
1387
|
* {{> grabValueFromAll }}
|
|
1357
1388
|
*
|
|
@@ -1370,6 +1401,20 @@ class Appium extends Webdriver {
|
|
|
1370
1401
|
return super.grabValueFrom(parseLocator.call(this, locator));
|
|
1371
1402
|
}
|
|
1372
1403
|
|
|
1404
|
+
/**
|
|
1405
|
+
* Saves a screenshot to ouput folder (set in codecept.json or codecept.conf.js).
|
|
1406
|
+
* Filename is relative to output folder.
|
|
1407
|
+
*
|
|
1408
|
+
* ```js
|
|
1409
|
+
* I.saveScreenshot('debug.png');
|
|
1410
|
+
* ```
|
|
1411
|
+
*
|
|
1412
|
+
* @param {string} fileName file name to save.
|
|
1413
|
+
*/
|
|
1414
|
+
async saveScreenshot(fileName) {
|
|
1415
|
+
return super.saveScreenshot(fileName, false);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1373
1418
|
/**
|
|
1374
1419
|
* {{> scrollIntoView }}
|
|
1375
1420
|
*
|
package/lib/helper/FileSystem.js
CHANGED
package/lib/helper/Playwright.js
CHANGED
|
@@ -80,6 +80,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
80
80
|
* * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
|
|
81
81
|
* * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
|
|
82
82
|
* * `userAgent`: (optional) user-agent string.
|
|
83
|
+
* * `locale`: (optional) locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
|
|
83
84
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
84
85
|
* * `chromium`: (optional) pass additional chromium options
|
|
85
86
|
* * `electron`: (optional) pass additional electron options
|
|
@@ -197,6 +198,19 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
197
198
|
* }
|
|
198
199
|
* ```
|
|
199
200
|
*
|
|
201
|
+
* #### Example #7: Launch test with a specifc user locale
|
|
202
|
+
*
|
|
203
|
+
* ```js
|
|
204
|
+
* {
|
|
205
|
+
* helpers: {
|
|
206
|
+
* Playwright : {
|
|
207
|
+
* url: "http://localhost",
|
|
208
|
+
* locale: "fr-FR",
|
|
209
|
+
* }
|
|
210
|
+
* }
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
200
214
|
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
201
215
|
*
|
|
202
216
|
* ## Access From Helpers
|
|
@@ -354,6 +368,7 @@ class Playwright extends Helper {
|
|
|
354
368
|
if (this.options.restart && !this.options.manualStart) await this._startBrowser();
|
|
355
369
|
if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
|
|
356
370
|
|
|
371
|
+
this.isAuthenticated = false;
|
|
357
372
|
if (this.isElectron) {
|
|
358
373
|
this.browserContext = this.browser.context();
|
|
359
374
|
} else if (this.userDataDir) {
|
|
@@ -364,8 +379,14 @@ class Playwright extends Helper {
|
|
|
364
379
|
acceptDownloads: true,
|
|
365
380
|
...this.options.emulate,
|
|
366
381
|
};
|
|
382
|
+
if (this.options.basicAuth) {
|
|
383
|
+
contextOptions.httpCredentials = this.options.basicAuth;
|
|
384
|
+
this.isAuthenticated = true;
|
|
385
|
+
}
|
|
367
386
|
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
|
|
368
387
|
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
388
|
+
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
389
|
+
if (this.options.locale) contextOptions.locale = this.options.locale;
|
|
369
390
|
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
370
391
|
}
|
|
371
392
|
|
|
@@ -559,7 +580,7 @@ class Playwright extends Helper {
|
|
|
559
580
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
560
581
|
this.context = await this.page;
|
|
561
582
|
this.contextLocator = null;
|
|
562
|
-
if (this.
|
|
583
|
+
if (this.options.browser === 'chrome') {
|
|
563
584
|
await page.bringToFront();
|
|
564
585
|
}
|
|
565
586
|
}
|
|
@@ -714,9 +735,9 @@ class Playwright extends Helper {
|
|
|
714
735
|
url = this.options.url + url;
|
|
715
736
|
}
|
|
716
737
|
|
|
717
|
-
if (this.
|
|
738
|
+
if (this.options.basicAuth && (this.isAuthenticated !== true)) {
|
|
718
739
|
if (url.includes(this.options.url)) {
|
|
719
|
-
await this.browserContext.setHTTPCredentials(this.
|
|
740
|
+
await this.browserContext.setHTTPCredentials(this.options.basicAuth);
|
|
720
741
|
this.isAuthenticated = true;
|
|
721
742
|
}
|
|
722
743
|
}
|
|
@@ -775,7 +796,7 @@ class Playwright extends Helper {
|
|
|
775
796
|
if (!customHeaders) {
|
|
776
797
|
throw new Error('Cannot send empty headers.');
|
|
777
798
|
}
|
|
778
|
-
return this.
|
|
799
|
+
return this.browserContext.setExtraHTTPHeaders(customHeaders);
|
|
779
800
|
}
|
|
780
801
|
|
|
781
802
|
/**
|
|
@@ -1851,12 +1872,17 @@ class Playwright extends Helper {
|
|
|
1851
1872
|
|
|
1852
1873
|
async _failed(test) {
|
|
1853
1874
|
await this._withinEnd();
|
|
1875
|
+
|
|
1876
|
+
if (!test.artifacts) {
|
|
1877
|
+
test.artifacts = {};
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1854
1880
|
if (this.options.recordVideo && this.page.video()) {
|
|
1855
1881
|
test.artifacts.video = await this.page.video().path();
|
|
1856
1882
|
}
|
|
1857
1883
|
|
|
1858
1884
|
if (this.options.trace) {
|
|
1859
|
-
const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
|
|
1885
|
+
const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
|
|
1860
1886
|
await this.browserContext.tracing.stop({ path });
|
|
1861
1887
|
test.artifacts.trace = path;
|
|
1862
1888
|
}
|
|
@@ -1867,7 +1893,7 @@ class Playwright extends Helper {
|
|
|
1867
1893
|
if (this.options.keepVideoForPassedTests) {
|
|
1868
1894
|
test.artifacts.video = await this.page.video().path();
|
|
1869
1895
|
} else {
|
|
1870
|
-
this.page.video().delete();
|
|
1896
|
+
this.page.video().delete().catch(e => {});
|
|
1871
1897
|
}
|
|
1872
1898
|
}
|
|
1873
1899
|
|
|
@@ -2136,11 +2162,11 @@ class Playwright extends Helper {
|
|
|
2136
2162
|
}
|
|
2137
2163
|
|
|
2138
2164
|
/**
|
|
2139
|
-
* Waits for a network
|
|
2165
|
+
* Waits for a network response.
|
|
2140
2166
|
*
|
|
2141
2167
|
* ```js
|
|
2142
2168
|
* I.waitForResponse('http://example.com/resource');
|
|
2143
|
-
* I.waitForResponse(
|
|
2169
|
+
* I.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);
|
|
2144
2170
|
* ```
|
|
2145
2171
|
*
|
|
2146
2172
|
* @param {string|function} urlOrPredicate
|
|
@@ -2226,16 +2252,6 @@ class Playwright extends Helper {
|
|
|
2226
2252
|
return this.page.waitForNavigation(opts);
|
|
2227
2253
|
}
|
|
2228
2254
|
|
|
2229
|
-
/**
|
|
2230
|
-
* {{> waitUntil }}
|
|
2231
|
-
*/
|
|
2232
|
-
async waitUntil(fn, sec = null) {
|
|
2233
|
-
console.log('This method will remove in CodeceptJS 1.4; use `waitForFunction` instead!');
|
|
2234
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2235
|
-
const context = await this._getContext();
|
|
2236
|
-
return context.waitForFunction(fn, { timeout: waitTimeout });
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
2255
|
async waitUntilExists(locator, sec) {
|
|
2240
2256
|
console.log(`waitUntilExists deprecated:
|
|
2241
2257
|
* use 'waitForElement' to wait for element to be attached
|
|
@@ -2652,13 +2668,10 @@ async function targetCreatedHandler(page) {
|
|
|
2652
2668
|
});
|
|
2653
2669
|
});
|
|
2654
2670
|
page.on('console', (msg) => {
|
|
2655
|
-
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '));
|
|
2671
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
|
|
2656
2672
|
consoleLogStore.add(msg);
|
|
2657
2673
|
});
|
|
2658
2674
|
|
|
2659
|
-
if (this.options.userAgent) {
|
|
2660
|
-
await page.setUserAgent(this.options.userAgent);
|
|
2661
|
-
}
|
|
2662
2675
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
2663
2676
|
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
2664
2677
|
}
|
package/lib/helper/Protractor.js
CHANGED
|
@@ -838,11 +838,7 @@ class Protractor extends Helper {
|
|
|
838
838
|
}
|
|
839
839
|
|
|
840
840
|
/**
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* ```js
|
|
844
|
-
* I.seeTitleEquals('Test title.');
|
|
845
|
-
* ```
|
|
841
|
+
* {{> seeTitleEquals }}
|
|
846
842
|
*/
|
|
847
843
|
async seeTitleEquals(text) {
|
|
848
844
|
const title = await this.browser.getTitle();
|
|
@@ -1018,7 +1014,7 @@ class Protractor extends Helper {
|
|
|
1018
1014
|
}
|
|
1019
1015
|
|
|
1020
1016
|
/**
|
|
1021
|
-
|
|
1017
|
+
* {{> seeInCurrentUrl }}
|
|
1022
1018
|
*/
|
|
1023
1019
|
async seeInCurrentUrl(url) {
|
|
1024
1020
|
return this.browser.getCurrentUrl().then(currentUrl => stringIncludes('url').assert(url, currentUrl));
|
|
@@ -1498,14 +1494,6 @@ class Protractor extends Helper {
|
|
|
1498
1494
|
return this.browser.wait(() => this.browser.executeScript.call(this.browser, fn, ...args), aSec * 1000);
|
|
1499
1495
|
}
|
|
1500
1496
|
|
|
1501
|
-
/**
|
|
1502
|
-
* {{> waitUntil }}
|
|
1503
|
-
*/
|
|
1504
|
-
async waitUntil(fn, sec = null, timeoutMsg = null) {
|
|
1505
|
-
const aSec = sec || this.options.waitForTimeout;
|
|
1506
|
-
return this.browser.wait(fn, aSec * 1000, timeoutMsg);
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1497
|
/**
|
|
1510
1498
|
* {{> waitInUrl }}
|
|
1511
1499
|
*/
|
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
|
*
|
|
@@ -538,10 +554,9 @@ class Puppeteer extends Helper {
|
|
|
538
554
|
this.context = null;
|
|
539
555
|
popupStore.clear();
|
|
540
556
|
this.isAuthenticated = false;
|
|
557
|
+
await this.browser.close();
|
|
541
558
|
if (this.isRemoteBrowser) {
|
|
542
559
|
await this.browser.disconnect();
|
|
543
|
-
} else {
|
|
544
|
-
await this.browser.close();
|
|
545
560
|
}
|
|
546
561
|
}
|
|
547
562
|
|
|
@@ -758,11 +773,7 @@ class Puppeteer extends Helper {
|
|
|
758
773
|
}
|
|
759
774
|
|
|
760
775
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* ```js
|
|
764
|
-
* I.seeTitleEquals('Test title.');
|
|
765
|
-
* ```
|
|
776
|
+
* {{> seeTitleEquals }}
|
|
766
777
|
*/
|
|
767
778
|
async seeTitleEquals(text) {
|
|
768
779
|
const title = await this.page.title();
|
|
@@ -2204,16 +2215,6 @@ class Puppeteer extends Helper {
|
|
|
2204
2215
|
return this.page.waitForNavigation(opts);
|
|
2205
2216
|
}
|
|
2206
2217
|
|
|
2207
|
-
/**
|
|
2208
|
-
* {{> waitUntil }}
|
|
2209
|
-
*/
|
|
2210
|
-
async waitUntil(fn, sec = null) {
|
|
2211
|
-
console.log('This method will remove in CodeceptJS 1.4; use `waitForFunction` instead!');
|
|
2212
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2213
|
-
const context = await this._getContext();
|
|
2214
|
-
return context.waitForFunction(fn, { timeout: waitTimeout });
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
2218
|
async waitUntilExists(locator, sec) {
|
|
2218
2219
|
console.log(`waitUntilExists deprecated:
|
|
2219
2220
|
* use 'waitForElement' to wait for element to be attached
|
package/lib/helper/REST.js
CHANGED
package/lib/helper/WebDriver.js
CHANGED
|
@@ -481,7 +481,7 @@ class WebDriver extends Helper {
|
|
|
481
481
|
try {
|
|
482
482
|
require('webdriverio');
|
|
483
483
|
} catch (e) {
|
|
484
|
-
return ['webdriverio@^
|
|
484
|
+
return ['webdriverio@^6.12.1'];
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
@@ -875,7 +875,7 @@ class WebDriver extends Helper {
|
|
|
875
875
|
* I.defineTimeout({ implicit: 10000, pageLoad: 10000, script: 5000 });
|
|
876
876
|
* ```
|
|
877
877
|
*
|
|
878
|
-
* @param {
|
|
878
|
+
* @param {*} timeouts WebDriver timeouts object.
|
|
879
879
|
*/
|
|
880
880
|
defineTimeout(timeouts) {
|
|
881
881
|
return this._defineBrowserTimeout(this.browser, timeouts);
|
|
@@ -1252,7 +1252,6 @@ class WebDriver extends Helper {
|
|
|
1252
1252
|
|
|
1253
1253
|
/**
|
|
1254
1254
|
* {{> grabAttributeFromAll }}
|
|
1255
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1256
1255
|
*/
|
|
1257
1256
|
async grabAttributeFromAll(locator, attr) {
|
|
1258
1257
|
const res = await this._locate(locator, true);
|
|
@@ -1263,7 +1262,6 @@ class WebDriver extends Helper {
|
|
|
1263
1262
|
|
|
1264
1263
|
/**
|
|
1265
1264
|
* {{> grabAttributeFrom }}
|
|
1266
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1267
1265
|
*/
|
|
1268
1266
|
async grabAttributeFrom(locator, attr) {
|
|
1269
1267
|
const attrs = await this.grabAttributeFromAll(locator, attr);
|
|
@@ -2355,18 +2353,6 @@ class WebDriver extends Helper {
|
|
|
2355
2353
|
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
|
|
2356
2354
|
}
|
|
2357
2355
|
|
|
2358
|
-
/**
|
|
2359
|
-
* {{> waitUntil }}
|
|
2360
|
-
*/
|
|
2361
|
-
async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) {
|
|
2362
|
-
const aSec = sec || this.options.waitForTimeout;
|
|
2363
|
-
const _interval = typeof interval === 'number' ? interval * 1000 : null;
|
|
2364
|
-
if (isWebDriver5()) {
|
|
2365
|
-
return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
|
|
2366
|
-
}
|
|
2367
|
-
return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval });
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
2356
|
/**
|
|
2371
2357
|
* {{> switchTo }}
|
|
2372
2358
|
*/
|
package/lib/index.js
CHANGED
|
@@ -32,6 +32,8 @@ module.exports = {
|
|
|
32
32
|
within: require('./within'),
|
|
33
33
|
/** @type {typeof CodeceptJS.DataTable} */
|
|
34
34
|
dataTable: require('./data/table'),
|
|
35
|
+
/** @type {typeof CodeceptJS.DataTableArgument} */
|
|
36
|
+
dataTableArgument: require('./data/dataTableArgument'),
|
|
35
37
|
/** @type {typeof CodeceptJS.store} */
|
|
36
38
|
store: require('./store'),
|
|
37
39
|
/** @type {typeof CodeceptJS.Locator} */
|
|
@@ -21,6 +21,9 @@ class FeatureConfig {
|
|
|
21
21
|
* @returns {this}
|
|
22
22
|
*/
|
|
23
23
|
timeout(timeout) {
|
|
24
|
+
console.log(`Feature('${this.suite.title}').timeout(${timeout}) is deprecated!`);
|
|
25
|
+
console.log(`Please use Feature('${this.suite.title}', { timeout: ${timeout / 1000} }) instead`);
|
|
26
|
+
console.log('Timeout should be set in seconds');
|
|
24
27
|
this.suite.timeout(timeout);
|
|
25
28
|
return this;
|
|
26
29
|
}
|