codeceptjs 2.2.0 → 2.2.1
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 +30 -1
- package/README.md +15 -22
- package/bin/codecept.js +3 -1
- package/docs/advanced.md +1 -1
- package/docs/angular.md +6 -9
- package/docs/basics.md +388 -86
- package/docs/bdd.md +4 -3
- package/docs/build/Nightmare.js +3 -0
- package/docs/build/Polly.js +26 -12
- package/docs/build/Puppeteer.js +14 -13
- package/docs/build/TestCafe.js +101 -2
- package/docs/build/WebDriver.js +53 -52
- package/docs/changelog.md +86 -57
- package/docs/detox.md +235 -0
- package/docs/helpers/Detox.md +579 -0
- package/docs/helpers/Polly.md +13 -3
- package/docs/helpers/Puppeteer.md +155 -156
- package/docs/helpers/TestCafe.md +53 -0
- package/docs/helpers/WebDriver.md +209 -204
- package/docs/locators.md +2 -0
- package/docs/mobile.md +5 -1
- package/docs/puppeteer.md +59 -13
- package/docs/quickstart.md +47 -12
- package/docs/testcafe.md +157 -0
- package/docs/webdriver.md +453 -0
- package/lib/command/definitions.js +152 -7
- package/lib/command/gherkin/snippets.js +19 -8
- package/lib/command/init.js +30 -22
- package/lib/command/utils.js +1 -1
- package/lib/container.js +36 -10
- package/lib/data/dataScenarioConfig.js +18 -0
- package/lib/helper/Nightmare.js +3 -0
- package/lib/helper/Polly.js +26 -12
- package/lib/helper/Puppeteer.js +14 -13
- package/lib/helper/TestCafe.js +72 -2
- package/lib/helper/WebDriver.js +53 -52
- package/lib/helper/testcafe/testcafe-utils.js +3 -2
- package/lib/interfaces/scenarioConfig.js +2 -2
- package/lib/listener/config.js +2 -2
- package/lib/plugin/allure.js +3 -0
- package/lib/step.js +5 -2
- package/lib/ui.js +1 -1
- package/lib/utils.js +13 -21
- package/package.json +14 -12
|
@@ -8,6 +8,7 @@ const { Parser } = require('gherkin');
|
|
|
8
8
|
const glob = require('glob');
|
|
9
9
|
const fsPath = require('path');
|
|
10
10
|
const fs = require('fs');
|
|
11
|
+
const escapeStringRegexp = require('escape-string-regexp');
|
|
11
12
|
|
|
12
13
|
const parser = new Parser();
|
|
13
14
|
parser.stopAtFirstError = false;
|
|
@@ -30,7 +31,7 @@ module.exports = function (genPath, options) {
|
|
|
30
31
|
output.error('No gherkin steps defined in config. Exiting');
|
|
31
32
|
process.exit(1);
|
|
32
33
|
}
|
|
33
|
-
if (!config.gherkin.features) {
|
|
34
|
+
if (!options.feature && !config.gherkin.features) {
|
|
34
35
|
output.error('No gherkin features defined in config. Exiting');
|
|
35
36
|
process.exit(1);
|
|
36
37
|
}
|
|
@@ -40,7 +41,7 @@ module.exports = function (genPath, options) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const files = [];
|
|
43
|
-
glob.sync(config.gherkin.features, { cwd: global.codecept_dir }).forEach((file) => {
|
|
44
|
+
glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach((file) => {
|
|
44
45
|
if (!fsPath.isAbsolute(file)) {
|
|
45
46
|
file = fsPath.join(global.codecept_dir, file);
|
|
46
47
|
}
|
|
@@ -62,11 +63,21 @@ module.exports = function (genPath, options) {
|
|
|
62
63
|
try {
|
|
63
64
|
matchStep(step.text);
|
|
64
65
|
} catch (err) {
|
|
65
|
-
let stepLine
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
let stepLine;
|
|
67
|
+
if (/[{}()/]/.test(step.text)) {
|
|
68
|
+
stepLine = escapeStringRegexp(step.text)
|
|
69
|
+
.replace(/\//g, '\\/')
|
|
70
|
+
.replace(/\"(.*?)\"/g, '"(.*?)"')
|
|
71
|
+
.replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)')
|
|
72
|
+
.replace(/ (\d+) /, ' (\\d+) ');
|
|
73
|
+
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true });
|
|
74
|
+
} else {
|
|
75
|
+
stepLine = step.text
|
|
76
|
+
.replace(/\"(.*?)\"/g, '{string}')
|
|
77
|
+
.replace(/(\d+\.\d+)/, '{float}')
|
|
78
|
+
.replace(/ (\d+) /, ' {int} ');
|
|
79
|
+
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false });
|
|
80
|
+
}
|
|
70
81
|
newSteps.push(stepLine);
|
|
71
82
|
}
|
|
72
83
|
}
|
|
@@ -99,7 +110,7 @@ module.exports = function (genPath, options) {
|
|
|
99
110
|
.filter((value, index, self) => self.indexOf(value) === index)
|
|
100
111
|
.map((step) => {
|
|
101
112
|
return `
|
|
102
|
-
${step.type}('${step}', () => {
|
|
113
|
+
${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
|
|
103
114
|
// From "${step.file}" ${JSON.stringify(step.location)}
|
|
104
115
|
throw new Error('Not implemented yet');
|
|
105
116
|
});`;
|
package/lib/command/init.js
CHANGED
|
@@ -4,7 +4,8 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { fileExists, beautify } = require('../utils');
|
|
6
6
|
const inquirer = require('inquirer');
|
|
7
|
-
const getTestRoot = require('./utils')
|
|
7
|
+
const { getTestRoot } = require('./utils');
|
|
8
|
+
const generateDefinitions = require('./definitions');
|
|
8
9
|
const isLocal = require('../utils').installedLocally();
|
|
9
10
|
const mkdirp = require('mkdirp');
|
|
10
11
|
const { inspect } = require('util');
|
|
@@ -123,7 +124,7 @@ module.exports = function (initPath) {
|
|
|
123
124
|
// append file mask to the end of tests
|
|
124
125
|
if (!config.tests.match(/\*(.*?)$/)) {
|
|
125
126
|
config.tests = `${config.tests.replace(/\/+$/, '')}/*_test.js`;
|
|
126
|
-
console.
|
|
127
|
+
console.print(`Adding default test mask: ${config.tests}`);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
if (result.translation !== noTranslation) config.translation = result.translation;
|
|
@@ -158,42 +159,48 @@ module.exports = function (initPath) {
|
|
|
158
159
|
}
|
|
159
160
|
fs.writeFileSync(stepFile, defaultActor);
|
|
160
161
|
config.include.I = result.steps_file;
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const tsconfig = {
|
|
165
|
-
compilerOption: {
|
|
166
|
-
allowJs: true,
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
const tsconfigJson = beautify(JSON.stringify(tsconfig));
|
|
170
|
-
const tsconfigFile = path.join(testsPath, 'tsconfig.json');
|
|
171
|
-
if (fileExists(tsconfigFile)) {
|
|
172
|
-
print(`tsconfig.json has already exists at ${tsconfigFile}`);
|
|
173
|
-
} else {
|
|
174
|
-
fs.writeFileSync(tsconfigFile, tsconfigJson);
|
|
175
|
-
success(`TypeScript project configuration file created at ${tsconfigFile}`);
|
|
162
|
+
print(`Steps file created at ${stepFile}`);
|
|
176
163
|
}
|
|
177
164
|
|
|
178
165
|
fs.writeFileSync(configFile, beautify(`exports.config = ${inspect(config, false, 4, false)}`), 'utf-8');
|
|
179
|
-
|
|
166
|
+
print(`Config created at ${configFile}`);
|
|
180
167
|
|
|
181
168
|
if (config.output) {
|
|
182
169
|
if (!fileExists(config.output)) {
|
|
183
170
|
mkdirp.sync(path.join(testsPath, config.output));
|
|
184
|
-
|
|
171
|
+
print(`Directory for temporary output files created at '${config.output}'`);
|
|
185
172
|
} else {
|
|
186
173
|
print(`Directory for temporary output files is already created at '${config.output}'`);
|
|
187
174
|
}
|
|
188
175
|
}
|
|
189
|
-
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
const jsconfig = {
|
|
179
|
+
compilerOption: {
|
|
180
|
+
allowJs: true,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
const jsconfigJson = beautify(JSON.stringify(jsconfig));
|
|
184
|
+
const jsconfigFile = path.join(testsPath, 'jsconfig.json');
|
|
185
|
+
if (fileExists(jsconfigFile)) {
|
|
186
|
+
print(`jsconfig.json has already exists at ${jsconfigFile}`);
|
|
187
|
+
} else {
|
|
188
|
+
fs.writeFileSync(jsconfigFile, jsconfigJson);
|
|
189
|
+
print(`Intellisense enabled in ${jsconfigFile}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
generateDefinitions(testsPath, {});
|
|
193
|
+
|
|
194
|
+
print('');
|
|
195
|
+
success(' Almost done! Next step:');
|
|
196
|
+
success(' Create your first test by executing `npx codeceptjs gt` command ');
|
|
190
197
|
|
|
191
198
|
if (packages) {
|
|
192
199
|
print('\n--');
|
|
193
200
|
if (isLocal) {
|
|
194
|
-
|
|
201
|
+
success(`Please install dependent packages locally: ${colors.bold(`npm install --save-dev ${packages.join(' ')}`)}`);
|
|
195
202
|
} else {
|
|
196
|
-
|
|
203
|
+
success(`Please install dependent packages globally: [sudo] ${colors.bold(`npm install -g ${packages.join(' ')}`)}`);
|
|
197
204
|
}
|
|
198
205
|
}
|
|
199
206
|
};
|
|
@@ -208,6 +215,7 @@ module.exports = function (initPath) {
|
|
|
208
215
|
config.helpers[helperName][configName] = helperResult[key];
|
|
209
216
|
});
|
|
210
217
|
|
|
218
|
+
print('');
|
|
211
219
|
finish();
|
|
212
220
|
});
|
|
213
221
|
});
|
package/lib/command/utils.js
CHANGED
package/lib/container.js
CHANGED
|
@@ -161,22 +161,22 @@ function createHelpers(config) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
function createSupportObjects(config) {
|
|
164
|
-
const objects =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
});
|
|
164
|
+
const objects = {};
|
|
165
|
+
|
|
166
|
+
for (const name in config) {
|
|
167
|
+
objects[name] = {}; // placeholders
|
|
168
|
+
}
|
|
171
169
|
|
|
172
170
|
if (!config.I) {
|
|
173
|
-
|
|
171
|
+
objects.I = require('./actor')();
|
|
174
172
|
|
|
175
173
|
if (container.translation.I !== 'I') {
|
|
176
|
-
|
|
174
|
+
objects[container.translation.I] = objects.I;
|
|
177
175
|
}
|
|
178
176
|
}
|
|
179
177
|
|
|
178
|
+
container.support = objects;
|
|
179
|
+
|
|
180
180
|
function lazyLoad(name) {
|
|
181
181
|
let newObj = getSupportObject(config, name);
|
|
182
182
|
try {
|
|
@@ -190,6 +190,7 @@ function createSupportObjects(config) {
|
|
|
190
190
|
}
|
|
191
191
|
return newObj;
|
|
192
192
|
}
|
|
193
|
+
|
|
193
194
|
const asyncWrapper = function (f) {
|
|
194
195
|
return function () {
|
|
195
196
|
return f.apply(this, arguments).catch((e) => {
|
|
@@ -209,7 +210,32 @@ function createSupportObjects(config) {
|
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
212
|
-
return
|
|
213
|
+
return new Proxy({}, {
|
|
214
|
+
has(target, key) {
|
|
215
|
+
return key in config;
|
|
216
|
+
},
|
|
217
|
+
ownKeys() {
|
|
218
|
+
return Reflect.ownKeys(config);
|
|
219
|
+
},
|
|
220
|
+
get(target, key) {
|
|
221
|
+
// configured but not in support object, yet: load the module
|
|
222
|
+
if (key in objects && !(key in target)) {
|
|
223
|
+
// load default I
|
|
224
|
+
if (key in objects && !(key in config)) {
|
|
225
|
+
return target[key] = objects[key];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// load new object
|
|
229
|
+
const object = lazyLoad(key);
|
|
230
|
+
// check that object is a real object and not an array
|
|
231
|
+
if (Object.prototype.toString.call(object) === '[object Object]') {
|
|
232
|
+
return target[key] = Object.assign(objects[key], object);
|
|
233
|
+
}
|
|
234
|
+
target[key] = object;
|
|
235
|
+
}
|
|
236
|
+
return target[key];
|
|
237
|
+
},
|
|
238
|
+
});
|
|
213
239
|
}
|
|
214
240
|
|
|
215
241
|
function createPlugins(config, options = {}) {
|
|
@@ -61,6 +61,24 @@ class DataScenarioConfig {
|
|
|
61
61
|
this.scenarios.forEach(scenario => scenario.tag(tagName));
|
|
62
62
|
return this;
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pass in additional objects to inject into test
|
|
67
|
+
* @param {*} obj
|
|
68
|
+
*/
|
|
69
|
+
inject(obj) {
|
|
70
|
+
this.scenarios.forEach(scenario => scenario.inject(obj));
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
|
|
76
|
+
* @param {*} dependencies
|
|
77
|
+
*/
|
|
78
|
+
injectDependencies(dependencies) {
|
|
79
|
+
this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies));
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
module.exports = DataScenarioConfig;
|
package/lib/helper/Nightmare.js
CHANGED
|
@@ -81,6 +81,9 @@ class Nightmare extends Helper {
|
|
|
81
81
|
static _config() {
|
|
82
82
|
return [
|
|
83
83
|
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
84
|
+
{
|
|
85
|
+
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
86
|
+
},
|
|
84
87
|
];
|
|
85
88
|
}
|
|
86
89
|
|
package/lib/helper/Polly.js
CHANGED
|
@@ -58,7 +58,7 @@ class Polly extends Helper {
|
|
|
58
58
|
|
|
59
59
|
// Start mocking network requests/responses
|
|
60
60
|
async _startMocking(title = 'Test') {
|
|
61
|
-
if (!this.helpers &&
|
|
61
|
+
if (!(this.helpers && this.helpers.Puppeteer)) {
|
|
62
62
|
throw new Error('Puppeteer is the only supported helper right now');
|
|
63
63
|
}
|
|
64
64
|
await this._connectPuppeteer(title);
|
|
@@ -67,20 +67,21 @@ class Polly extends Helper {
|
|
|
67
67
|
// Connect Puppeteer helper to mock future requests.
|
|
68
68
|
async _connectPuppeteer(title) {
|
|
69
69
|
const adapter = require('@pollyjs/adapter-puppeteer');
|
|
70
|
-
|
|
71
70
|
PollyJS.register(adapter);
|
|
71
|
+
|
|
72
72
|
const { page } = this.helpers.Puppeteer;
|
|
73
|
+
if (!page) {
|
|
74
|
+
throw new Error('Looks like, there is no open tab');
|
|
75
|
+
}
|
|
73
76
|
await page.setRequestInterception(true);
|
|
74
77
|
|
|
75
78
|
this.polly = new PollyJS(title, {
|
|
79
|
+
mode: 'passthrough',
|
|
76
80
|
adapters: ['puppeteer'],
|
|
77
81
|
adapterOptions: {
|
|
78
82
|
puppeteer: { page },
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
|
-
|
|
82
|
-
// By default let pass through all network requests
|
|
83
|
-
if (this.polly) this.polly.server.any().passthrough();
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -90,6 +91,7 @@ class Polly extends Helper {
|
|
|
90
91
|
* I.mockRequest('GET', '/api/users', 200);
|
|
91
92
|
* I.mockRequest('ANY', '/secretsRoutes/*', 403);
|
|
92
93
|
* I.mockRequest('POST', '/secrets', { secrets: 'fakeSecrets' });
|
|
94
|
+
* I.mockRequest('GET', '/api/users/1', 404, 'User not found');
|
|
93
95
|
* ```
|
|
94
96
|
*
|
|
95
97
|
* Multiple requests
|
|
@@ -97,17 +99,27 @@ class Polly extends Helper {
|
|
|
97
99
|
* ```js
|
|
98
100
|
* I.mockRequest('GET', ['/secrets', '/v2/secrets'], 403);
|
|
99
101
|
* ```
|
|
102
|
+
* @param {string} method request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`.
|
|
103
|
+
* @param {string|array} oneOrMoreUrls url(s) to mock. Can be exact URL, a pattern, or an array of URLs.
|
|
104
|
+
* @param {number|string|object} dataOrStatusCode status code when number provided. A response body otherwise
|
|
105
|
+
* @param {string|object} additionalData response body when a status code is set by previous parameter.
|
|
106
|
+
*
|
|
100
107
|
*/
|
|
101
|
-
async mockRequest(method, oneOrMoreUrls, dataOrStatusCode) {
|
|
108
|
+
async mockRequest(method, oneOrMoreUrls, dataOrStatusCode, additionalData = null) {
|
|
102
109
|
await this._checkAndStartMocking();
|
|
110
|
+
const puppeteerConfigUrl = this.helpers.Puppeteer && this.helpers.Puppeteer.options.url;
|
|
111
|
+
|
|
103
112
|
const handler = this._getRouteHandler(
|
|
104
113
|
method,
|
|
105
114
|
oneOrMoreUrls,
|
|
106
|
-
this.options.url,
|
|
115
|
+
this.options.url || puppeteerConfigUrl,
|
|
107
116
|
);
|
|
108
117
|
|
|
109
118
|
if (typeof dataOrStatusCode === 'number') {
|
|
110
119
|
const statusCode = dataOrStatusCode;
|
|
120
|
+
if (additionalData) {
|
|
121
|
+
return handler.intercept((_, res) => res.status(statusCode).send(additionalData));
|
|
122
|
+
}
|
|
111
123
|
return handler.intercept((_, res) => res.sendStatus(statusCode));
|
|
112
124
|
}
|
|
113
125
|
const data = dataOrStatusCode;
|
|
@@ -148,16 +160,18 @@ class Polly extends Helper {
|
|
|
148
160
|
*/
|
|
149
161
|
async stopMocking() {
|
|
150
162
|
if (!this._checkIfMockingStarted()) return;
|
|
151
|
-
|
|
152
163
|
await this._disconnectPuppeteer();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
|
|
165
|
+
const { polly } = this;
|
|
166
|
+
if (!polly) return;
|
|
167
|
+
await polly.flush();
|
|
168
|
+
await polly.stop();
|
|
169
|
+
delete this.polly;
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
async _disconnectPuppeteer() {
|
|
159
173
|
const { page } = this.helpers.Puppeteer;
|
|
160
|
-
await page.setRequestInterception(false);
|
|
174
|
+
if (page) await page.setRequestInterception(false);
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
177
|
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -57,12 +57,13 @@ const consoleLogStore = new Console();
|
|
|
57
57
|
* * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to false.
|
|
58
58
|
* * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
|
|
59
59
|
* * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions). Array values are accepted as well.
|
|
60
|
+
* * `pressKeyDelay`: (optional, default: '10'). Delay between key presses in ms. Used when calling Puppeteers page.type(...) in fillField/appendField
|
|
60
61
|
* * `getPageTimeout` (optional, default: '0') config option to set maximum navigation time in milliseconds.
|
|
61
62
|
* * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
|
|
62
63
|
* * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
|
|
63
64
|
* * `userAgent`: (optional) user-agent string.
|
|
64
65
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
|
|
65
|
-
* * `browser`: (optional, default: chrome) - can be changed to `firefox` when using [puppeteer-firefox](
|
|
66
|
+
* * `browser`: (optional, default: chrome) - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
|
|
66
67
|
* * `chrome`: (optional) pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
|
|
67
68
|
*
|
|
68
69
|
*
|
|
@@ -159,6 +160,7 @@ class Puppeteer extends Helper {
|
|
|
159
160
|
browser: 'chrome',
|
|
160
161
|
waitForAction: 100,
|
|
161
162
|
waitForTimeout: 1000,
|
|
163
|
+
pressKeyDelay: 10,
|
|
162
164
|
fullPageScreenshots: false,
|
|
163
165
|
disableScreenshots: false,
|
|
164
166
|
uniqueScreenshotNames: false,
|
|
@@ -189,6 +191,9 @@ class Puppeteer extends Helper {
|
|
|
189
191
|
static _config() {
|
|
190
192
|
return [
|
|
191
193
|
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
194
|
+
{
|
|
195
|
+
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
196
|
+
},
|
|
192
197
|
];
|
|
193
198
|
}
|
|
194
199
|
|
|
@@ -599,7 +604,7 @@ class Puppeteer extends Helper {
|
|
|
599
604
|
* {{> scrollPageToTop }}
|
|
600
605
|
*/
|
|
601
606
|
scrollPageToTop() {
|
|
602
|
-
return this.
|
|
607
|
+
return this.executeScript(() => {
|
|
603
608
|
window.scrollTo(0, 0);
|
|
604
609
|
});
|
|
605
610
|
}
|
|
@@ -608,7 +613,7 @@ class Puppeteer extends Helper {
|
|
|
608
613
|
* {{> scrollPageToBottom }}
|
|
609
614
|
*/
|
|
610
615
|
scrollPageToBottom() {
|
|
611
|
-
return this.
|
|
616
|
+
return this.executeScript(() => {
|
|
612
617
|
const body = document.body;
|
|
613
618
|
const html = document.documentElement;
|
|
614
619
|
window.scrollTo(0, Math.max(
|
|
@@ -627,20 +632,16 @@ class Puppeteer extends Helper {
|
|
|
627
632
|
offsetX = locator;
|
|
628
633
|
locator = null;
|
|
629
634
|
}
|
|
630
|
-
|
|
631
|
-
let y = 0;
|
|
635
|
+
|
|
632
636
|
if (locator) {
|
|
633
637
|
const els = await this._locate(locator);
|
|
634
638
|
assertElementExists(els, locator, 'Element');
|
|
635
639
|
await els[0]._scrollIntoViewIfNeeded();
|
|
636
640
|
const elementCoordinates = await els[0]._clickablePoint();
|
|
637
|
-
x
|
|
638
|
-
|
|
641
|
+
await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
|
|
642
|
+
} else {
|
|
643
|
+
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY);
|
|
639
644
|
}
|
|
640
|
-
|
|
641
|
-
await this.page.evaluate((x, y) => {
|
|
642
|
-
window.scrollTo(x, y);
|
|
643
|
-
}, x + offsetX, y + offsetY);
|
|
644
645
|
return this._waitForAction();
|
|
645
646
|
}
|
|
646
647
|
|
|
@@ -1086,7 +1087,7 @@ class Puppeteer extends Helper {
|
|
|
1086
1087
|
} else if (editable) {
|
|
1087
1088
|
await this._evaluateHandeInContext(el => el.innerHTML = '', el);
|
|
1088
1089
|
}
|
|
1089
|
-
await el.type(value.toString(), { delay:
|
|
1090
|
+
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1090
1091
|
return this._waitForAction();
|
|
1091
1092
|
}
|
|
1092
1093
|
|
|
@@ -1107,7 +1108,7 @@ class Puppeteer extends Helper {
|
|
|
1107
1108
|
const els = await findFields.call(this, field);
|
|
1108
1109
|
assertElementExists(els, field, 'Field');
|
|
1109
1110
|
await els[0].press('End');
|
|
1110
|
-
await els[0].type(value, { delay:
|
|
1111
|
+
await els[0].type(value, { delay: this.options.pressKeyDelay });
|
|
1111
1112
|
return this._waitForAction();
|
|
1112
1113
|
}
|
|
1113
1114
|
|
package/lib/helper/TestCafe.js
CHANGED
|
@@ -124,6 +124,10 @@ class TestCafe extends Helper {
|
|
|
124
124
|
static _config() {
|
|
125
125
|
return [
|
|
126
126
|
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
127
|
+
{ name: 'browser', message: 'Browser to be used', default: 'chrome' },
|
|
128
|
+
{
|
|
129
|
+
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
130
|
+
},
|
|
127
131
|
];
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -248,7 +252,7 @@ class TestCafe extends Helper {
|
|
|
248
252
|
async _withinBegin(locator) {
|
|
249
253
|
const els = await this._locate(locator);
|
|
250
254
|
assertElementExists(els, locator);
|
|
251
|
-
this.context = els.nth(0);
|
|
255
|
+
this.context = await els.nth(0);
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
async _withinEnd() {
|
|
@@ -636,7 +640,7 @@ class TestCafe extends Helper {
|
|
|
636
640
|
const el = await els.nth(0);
|
|
637
641
|
|
|
638
642
|
return this.t
|
|
639
|
-
.expect(el.value).eql(value)
|
|
643
|
+
.expect(await el.value).eql(value)
|
|
640
644
|
.catch(mapError);
|
|
641
645
|
}
|
|
642
646
|
|
|
@@ -780,10 +784,76 @@ class TestCafe extends Helper {
|
|
|
780
784
|
return ClientFunction(() => document.location.href).with({ boundTestRun: this.t })();
|
|
781
785
|
}
|
|
782
786
|
|
|
787
|
+
/**
|
|
788
|
+
* {{> grabPageScrollPosition }}
|
|
789
|
+
*/
|
|
790
|
+
async grabPageScrollPosition() {
|
|
791
|
+
return ClientFunction(() => ({ x: window.pageXOffset, y: window.pageYOffset })).with({ boundTestRun: this.t })();
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* {{> scrollPageToTop }}
|
|
796
|
+
*/
|
|
797
|
+
scrollPageToTop() {
|
|
798
|
+
return ClientFunction(() => window.scrollTo(0, 0)).with({ boundTestRun: this.t })().catch(mapError);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* {{> scrollPageToBottom }}
|
|
803
|
+
*/
|
|
804
|
+
scrollPageToBottom() {
|
|
805
|
+
return ClientFunction(() => {
|
|
806
|
+
const body = document.body;
|
|
807
|
+
const html = document.documentElement;
|
|
808
|
+
window.scrollTo(0, Math.max(
|
|
809
|
+
body.scrollHeight, body.offsetHeight,
|
|
810
|
+
html.clientHeight, html.scrollHeight, html.offsetHeight,
|
|
811
|
+
));
|
|
812
|
+
}).with({ boundTestRun: this.t })().catch(mapError);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* {{> scrollTo }}
|
|
817
|
+
*/
|
|
818
|
+
async scrollTo(locator, offsetX = 0, offsetY = 0) {
|
|
819
|
+
if (typeof locator === 'number' && typeof offsetX === 'number') {
|
|
820
|
+
offsetY = offsetX;
|
|
821
|
+
offsetX = locator;
|
|
822
|
+
locator = null;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const scrollBy = ClientFunction((offset) => {
|
|
826
|
+
if (window && window.scrollBy && offset) {
|
|
827
|
+
window.scrollBy(offset.x, offset.y);
|
|
828
|
+
}
|
|
829
|
+
}).with({ boundTestRun: this.t });
|
|
830
|
+
|
|
831
|
+
if (locator) {
|
|
832
|
+
const els = await this._locate(locator);
|
|
833
|
+
assertElementExists(els, locator, 'Element');
|
|
834
|
+
const el = await els.nth(0);
|
|
835
|
+
const x = (await el.offsetLeft) + offsetX;
|
|
836
|
+
const y = (await el.offsetTop) + offsetY;
|
|
837
|
+
|
|
838
|
+
return scrollBy({ x, y }).catch(mapError);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const x = offsetX;
|
|
842
|
+
const y = offsetY;
|
|
843
|
+
return scrollBy({ x, y }).catch(mapError);
|
|
844
|
+
}
|
|
845
|
+
|
|
783
846
|
/**
|
|
784
847
|
* {{> switchTo }}
|
|
785
848
|
*/
|
|
786
849
|
async switchTo(locator) {
|
|
850
|
+
if (Number.isInteger(locator)) {
|
|
851
|
+
throw new Error('Not supported switching to iframe by number');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!locator) {
|
|
855
|
+
return this.t.switchToMainWindow();
|
|
856
|
+
}
|
|
787
857
|
return this.t.switchToIframe(findElements.call(this, this.context, locator));
|
|
788
858
|
}
|
|
789
859
|
|