codeceptjs 3.3.3 → 3.3.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/CHANGELOG.md +38 -0
- package/docs/api.md +4 -0
- package/docs/basics.md +2 -0
- package/docs/build/JSONResponse.js +44 -3
- package/docs/build/Playwright.js +21 -4
- package/docs/build/REST.js +6 -2
- package/docs/build/WebDriver.js +5 -5
- package/docs/changelog.md +38 -0
- package/docs/helpers/JSONResponse.md +24 -0
- package/docs/helpers/Playwright.md +42 -36
- package/docs/helpers/REST.md +2 -0
- package/docs/plugins.md +41 -1
- package/docs/secrets.md +30 -0
- package/lib/command/interactive.js +1 -1
- package/lib/helper/JSONResponse.js +44 -3
- package/lib/helper/Playwright.js +21 -4
- package/lib/helper/REST.js +6 -2
- package/lib/helper/WebDriver.js +5 -5
- package/lib/plugin/customLocator.js +50 -3
- package/lib/plugin/retryFailedStep.js +1 -1
- package/lib/plugin/retryTo.js +1 -8
- package/lib/secret.js +30 -0
- package/lib/step.js +1 -1
- package/package.json +4 -4
- package/typings/types.d.ts +28 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
## 3.3.4
|
|
2
|
+
|
|
3
|
+
* Added support for masking fields in objects via `secret` function:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
I.sendPostRequest('/auth', secret({ name: 'jon', password: '123456' }, 'password'));
|
|
7
|
+
```
|
|
8
|
+
* Added [a guide about using of `secret`](/secrets) function
|
|
9
|
+
* [Appium] Use `touchClick` when interacting with elements in iOS. See #3317 by @mikk150
|
|
10
|
+
* [Playwright] Added `cdpConnection` option to connect over CDP. See #3309 by @Hmihaly
|
|
11
|
+
* [customLocator plugin] Allowed to specify multiple attributes for custom locator. Thanks to @aruiz-caritsqa
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
plugins: {
|
|
15
|
+
customLocator: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
prefix: '$',
|
|
18
|
+
attribute: ['data-qa', 'data-test'],
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
* [retryTo plugin] Fixed #3147 using `pollInterval` option. See #3351 by @cyonkee
|
|
23
|
+
* [Playwright] Fixed grabbing of browser console messages and window resize in new tab. Thanks to @mirao
|
|
24
|
+
* [REST] Added `prettyPrintJson` option to print JSON in nice way by @PeterNgTr
|
|
25
|
+
* [JSONResponse] Updated response validation to iterate over array items if response is array. Thanks to @PeterNgTr
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// response.data == [
|
|
29
|
+
// { user: { name: 'jon', email: 'jon@doe.com' } },
|
|
30
|
+
// { user: { name: 'matt', email: 'matt@doe.com' } },
|
|
31
|
+
//]
|
|
32
|
+
|
|
33
|
+
I.seeResponseContainsKeys(['user']);
|
|
34
|
+
I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
35
|
+
I.seeResponseContainsJson({ user: { email: 'matt@doe.com' } });
|
|
36
|
+
I.dontSeeResponseContainsJson({ user: 2 });
|
|
37
|
+
```
|
|
38
|
+
|
|
1
39
|
## 3.3.3
|
|
2
40
|
|
|
3
41
|
* Fixed `DataCloneError: () => could not be cloned` when running data tests in run-workers
|
package/docs/api.md
CHANGED
|
@@ -263,6 +263,8 @@ The most basic thing to check in response is existence of keys in JSON object. U
|
|
|
263
263
|
I.seeResponseContainsKeys(['name', 'email']);
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
> ℹ️ If response is an array, it will check that every element in array have provided keys
|
|
267
|
+
|
|
266
268
|
However, this is a very naive approach. It won't work for arrays or nested objects.
|
|
267
269
|
To check complex JSON structures `JSONResponse` helper uses [`joi`](https://joi.dev) library.
|
|
268
270
|
It has rich API to validate JSON by the schema defined using JavaScript.
|
|
@@ -296,6 +298,8 @@ I.seeResponseContainsJson({
|
|
|
296
298
|
})
|
|
297
299
|
```
|
|
298
300
|
|
|
301
|
+
> ℹ️ If response is an array, it will check that at least one element in array matches JSON
|
|
302
|
+
|
|
299
303
|
To perform arbitrary assertions on a response object use `seeResponseValidByCallback`.
|
|
300
304
|
It allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.
|
|
301
305
|
|
package/docs/basics.md
CHANGED
|
@@ -212,6 +212,8 @@ To fill in sensitive data use the `secret` function, it won't expose actual valu
|
|
|
212
212
|
I.fillField('password', secret('123456'));
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
> ℹ️ Learn more about [masking secret](/secrets/) output
|
|
216
|
+
|
|
215
217
|
### Assertions
|
|
216
218
|
|
|
217
219
|
In order to verify the expected behavior of a web application, its content should be checked.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
1
2
|
const chai = require('chai');
|
|
2
3
|
const joi = require('joi');
|
|
3
4
|
const chaiDeepMatch = require('chai-deep-match');
|
|
@@ -173,12 +174,30 @@ class JSONResponse extends Helper {
|
|
|
173
174
|
*
|
|
174
175
|
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
175
176
|
* ```
|
|
177
|
+
* If an array is received, checks that at least one element contains JSON
|
|
178
|
+
* ```js
|
|
179
|
+
* // response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }]
|
|
180
|
+
*
|
|
181
|
+
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
182
|
+
* ```
|
|
176
183
|
*
|
|
177
184
|
* @param {object} json
|
|
178
185
|
*/
|
|
179
186
|
seeResponseContainsJson(json = {}) {
|
|
180
187
|
this._checkResponseReady();
|
|
181
|
-
|
|
188
|
+
if (Array.isArray(this.response.data)) {
|
|
189
|
+
let fails = 0;
|
|
190
|
+
for (const el of this.response.data) {
|
|
191
|
+
try {
|
|
192
|
+
expect(el).to.deep.match(json);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
fails++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
assert.ok(fails < this.response.data.length, `No elements in array matched ${JSON.stringify(json)}`);
|
|
198
|
+
} else {
|
|
199
|
+
expect(this.response.data).to.deep.match(json);
|
|
200
|
+
}
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/**
|
|
@@ -189,12 +208,22 @@ class JSONResponse extends Helper {
|
|
|
189
208
|
*
|
|
190
209
|
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
191
210
|
* ```
|
|
211
|
+
* If an array is received, checks that no element of array contains json:
|
|
212
|
+
* ```js
|
|
213
|
+
* // response.data == [{ user: 1 }, { user: 3 }]
|
|
214
|
+
*
|
|
215
|
+
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
216
|
+
* ```
|
|
192
217
|
*
|
|
193
218
|
* @param {object} json
|
|
194
219
|
*/
|
|
195
220
|
dontSeeResponseContainsJson(json = {}) {
|
|
196
221
|
this._checkResponseReady();
|
|
197
|
-
|
|
222
|
+
if (Array.isArray(this.response.data)) {
|
|
223
|
+
this.response.data.forEach(data => expect(data).not.to.deep.match(json));
|
|
224
|
+
} else {
|
|
225
|
+
expect(this.response.data).not.to.deep.match(json);
|
|
226
|
+
}
|
|
198
227
|
}
|
|
199
228
|
|
|
200
229
|
/**
|
|
@@ -206,11 +235,23 @@ class JSONResponse extends Helper {
|
|
|
206
235
|
* I.seeResponseContainsKeys(['user']);
|
|
207
236
|
* ```
|
|
208
237
|
*
|
|
238
|
+
* If an array is received, check is performed for each element of array:
|
|
239
|
+
*
|
|
240
|
+
* ```js
|
|
241
|
+
* // response.data == [{ user: 'jon' }, { user: 'matt'}]
|
|
242
|
+
*
|
|
243
|
+
* I.seeResponseContainsKeys(['user']);
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
209
246
|
* @param {array} keys
|
|
210
247
|
*/
|
|
211
248
|
seeResponseContainsKeys(keys = []) {
|
|
212
249
|
this._checkResponseReady();
|
|
213
|
-
|
|
250
|
+
if (Array.isArray(this.response.data)) {
|
|
251
|
+
this.response.data.forEach(data => expect(data).to.include.keys(keys));
|
|
252
|
+
} else {
|
|
253
|
+
expect(this.response.data).to.include.keys(keys);
|
|
254
|
+
}
|
|
214
255
|
}
|
|
215
256
|
|
|
216
257
|
/**
|
package/docs/build/Playwright.js
CHANGED
|
@@ -167,7 +167,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
167
167
|
* Playwright: {
|
|
168
168
|
* url: "http://localhost",
|
|
169
169
|
* chromium: {
|
|
170
|
-
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
170
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a',
|
|
171
|
+
* cdpConnection: false // default is false
|
|
171
172
|
* }
|
|
172
173
|
* }
|
|
173
174
|
* }
|
|
@@ -272,6 +273,7 @@ class Playwright extends Helper {
|
|
|
272
273
|
this.sessionPages = {};
|
|
273
274
|
this.activeSessionName = '';
|
|
274
275
|
this.isElectron = false;
|
|
276
|
+
this.isCDPConnection = false;
|
|
275
277
|
this.electronSessions = [];
|
|
276
278
|
this.storageState = null;
|
|
277
279
|
|
|
@@ -347,6 +349,7 @@ class Playwright extends Helper {
|
|
|
347
349
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
348
350
|
this.isElectron = this.options.browser === 'electron';
|
|
349
351
|
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
352
|
+
this.isCDPConnection = this.playwrightOptions.cdpConnection;
|
|
350
353
|
popupStore.defaultAction = this.options.defaultPopupAction;
|
|
351
354
|
}
|
|
352
355
|
|
|
@@ -699,6 +702,15 @@ class Playwright extends Helper {
|
|
|
699
702
|
async _startBrowser() {
|
|
700
703
|
if (this.isElectron) {
|
|
701
704
|
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
705
|
+
} else if (this.isRemoteBrowser && this.isCDPConnection) {
|
|
706
|
+
try {
|
|
707
|
+
this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions);
|
|
708
|
+
} catch (err) {
|
|
709
|
+
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
710
|
+
throw new RemoteBrowserConnectionRefused(err);
|
|
711
|
+
}
|
|
712
|
+
throw err;
|
|
713
|
+
}
|
|
702
714
|
} else if (this.isRemoteBrowser) {
|
|
703
715
|
try {
|
|
704
716
|
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
@@ -1157,6 +1169,7 @@ class Playwright extends Helper {
|
|
|
1157
1169
|
if (!page) {
|
|
1158
1170
|
throw new Error(`There is no ability to switch to next tab with offset ${num}`);
|
|
1159
1171
|
}
|
|
1172
|
+
targetCreatedHandler.call(this, page);
|
|
1160
1173
|
await this._setPage(page);
|
|
1161
1174
|
return this._waitForAction();
|
|
1162
1175
|
}
|
|
@@ -1239,7 +1252,9 @@ class Playwright extends Helper {
|
|
|
1239
1252
|
if (this.isElectron) {
|
|
1240
1253
|
throw new Error('Cannot open new tabs inside an Electron container');
|
|
1241
1254
|
}
|
|
1242
|
-
|
|
1255
|
+
const page = await this.browserContext.newPage(options);
|
|
1256
|
+
targetCreatedHandler.call(this, page);
|
|
1257
|
+
await this._setPage(page);
|
|
1243
1258
|
return this._waitForAction();
|
|
1244
1259
|
}
|
|
1245
1260
|
|
|
@@ -2071,9 +2086,11 @@ class Playwright extends Helper {
|
|
|
2071
2086
|
* Get JS log from browser.
|
|
2072
2087
|
*
|
|
2073
2088
|
* ```js
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2089
|
+
* const logs = await I.grabBrowserLogs();
|
|
2090
|
+
* const errors = logs.map(l => ({ type: l.type(), text: l.text() })).filter(l => l.type === 'error');
|
|
2091
|
+
* console.log(JSON.stringify(errors));
|
|
2076
2092
|
* ```
|
|
2093
|
+
* [Learn more about console messages](https://playwright.dev/docs/api/class-consolemessage)
|
|
2077
2094
|
* @return {Promise<any[]>}
|
|
2078
2095
|
*/
|
|
2079
2096
|
async grabBrowserLogs() {
|
package/docs/build/REST.js
CHANGED
|
@@ -2,6 +2,7 @@ const axios = require('axios').default;
|
|
|
2
2
|
const Secret = require('../secret');
|
|
3
3
|
|
|
4
4
|
const Helper = require('../helper');
|
|
5
|
+
const { beautify } = require('../utils');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* REST helper allows to send additional requests to the REST API during acceptance tests.
|
|
@@ -10,6 +11,7 @@ const Helper = require('../helper');
|
|
|
10
11
|
* ## Configuration
|
|
11
12
|
*
|
|
12
13
|
* * endpoint: API base URL
|
|
14
|
+
* * prettyPrintJson: pretty print json for response/request on console logs
|
|
13
15
|
* * timeout: timeout for requests in milliseconds. 10000ms by default
|
|
14
16
|
* * defaultHeaders: a list of default headers
|
|
15
17
|
* * onRequest: a async function which can update request object.
|
|
@@ -22,6 +24,7 @@ const Helper = require('../helper');
|
|
|
22
24
|
* helpers: {
|
|
23
25
|
* REST: {
|
|
24
26
|
* endpoint: 'http://site.com/api',
|
|
27
|
+
* prettyPrintJson: true,
|
|
25
28
|
* onRequest: (request) => {
|
|
26
29
|
* request.headers.auth = '123';
|
|
27
30
|
* }
|
|
@@ -49,6 +52,7 @@ class REST extends Helper {
|
|
|
49
52
|
timeout: 10000,
|
|
50
53
|
defaultHeaders: {},
|
|
51
54
|
endpoint: '',
|
|
55
|
+
prettyPrintJson: false,
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
if (this.options.maxContentLength) {
|
|
@@ -137,7 +141,7 @@ class REST extends Helper {
|
|
|
137
141
|
await this.config.onRequest(request);
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
this.debugSection('Request', JSON.stringify(_debugRequest));
|
|
144
|
+
this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest));
|
|
141
145
|
|
|
142
146
|
let response;
|
|
143
147
|
try {
|
|
@@ -150,7 +154,7 @@ class REST extends Helper {
|
|
|
150
154
|
if (this.config.onResponse) {
|
|
151
155
|
await this.config.onResponse(response);
|
|
152
156
|
}
|
|
153
|
-
this.debugSection('Response', JSON.stringify(response.data));
|
|
157
|
+
this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data));
|
|
154
158
|
return response;
|
|
155
159
|
}
|
|
156
160
|
|
package/docs/build/WebDriver.js
CHANGED
|
@@ -942,7 +942,7 @@ class WebDriver extends Helper {
|
|
|
942
942
|
* {{ react }}
|
|
943
943
|
*/
|
|
944
944
|
async click(locator, context = null) {
|
|
945
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
945
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
946
946
|
const locateFn = prepareLocateFn.call(this, context);
|
|
947
947
|
|
|
948
948
|
const res = await findClickable.call(this, locator, locateFn);
|
|
@@ -1296,7 +1296,7 @@ class WebDriver extends Helper {
|
|
|
1296
1296
|
* Appium: not tested
|
|
1297
1297
|
*/
|
|
1298
1298
|
async checkOption(field, context = null) {
|
|
1299
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
1299
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
1300
1300
|
const locateFn = prepareLocateFn.call(this, context);
|
|
1301
1301
|
|
|
1302
1302
|
const res = await findCheckable.call(this, field, locateFn);
|
|
@@ -1327,7 +1327,7 @@ class WebDriver extends Helper {
|
|
|
1327
1327
|
* Appium: not tested
|
|
1328
1328
|
*/
|
|
1329
1329
|
async uncheckOption(field, context = null) {
|
|
1330
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
1330
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
1331
1331
|
const locateFn = prepareLocateFn.call(this, context);
|
|
1332
1332
|
|
|
1333
1333
|
const res = await findCheckable.call(this, field, locateFn);
|
|
@@ -2212,7 +2212,7 @@ class WebDriver extends Helper {
|
|
|
2212
2212
|
assertElementExists(res);
|
|
2213
2213
|
const elem = usingFirstElement(res);
|
|
2214
2214
|
const elementId = getElementId(elem);
|
|
2215
|
-
if (this.browser.isMobile) return this.browser.touchScroll(offsetX, offsetY, elementId);
|
|
2215
|
+
if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(offsetX, offsetY, elementId);
|
|
2216
2216
|
const location = await elem.getLocation();
|
|
2217
2217
|
assertElementExists(location, 'Failed to receive', 'location');
|
|
2218
2218
|
/* eslint-disable prefer-arrow-callback */
|
|
@@ -2220,7 +2220,7 @@ class WebDriver extends Helper {
|
|
|
2220
2220
|
/* eslint-enable */
|
|
2221
2221
|
}
|
|
2222
2222
|
|
|
2223
|
-
if (this.browser.isMobile) return this.browser.touchScroll(locator, offsetX, offsetY);
|
|
2223
|
+
if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(locator, offsetX, offsetY);
|
|
2224
2224
|
|
|
2225
2225
|
/* eslint-disable prefer-arrow-callback, comma-dangle */
|
|
2226
2226
|
return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY);
|
package/docs/changelog.md
CHANGED
|
@@ -7,6 +7,44 @@ layout: Section
|
|
|
7
7
|
|
|
8
8
|
# Releases
|
|
9
9
|
|
|
10
|
+
## 3.3.4
|
|
11
|
+
|
|
12
|
+
* Added support for masking fields in objects via `secret` function:
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
I.sendPostRequest('/auth', secret({ name: 'jon', password: '123456' }, 'password'));
|
|
16
|
+
```
|
|
17
|
+
* Added [a guide about using of `secret`](/secrets) function
|
|
18
|
+
* **[Appium]** Use `touchClick` when interacting with elements in iOS. See [#3317](https://github.com/codeceptjs/CodeceptJS/issues/3317) by **[mikk150](https://github.com/mikk150)**
|
|
19
|
+
* **[Playwright]** Added `cdpConnection` option to connect over CDP. See [#3309](https://github.com/codeceptjs/CodeceptJS/issues/3309) by **[Hmihaly](https://github.com/Hmihaly)**
|
|
20
|
+
* [customLocator plugin] Allowed to specify multiple attributes for custom locator. Thanks to **[aruiz-caritsqa](https://github.com/aruiz-caritsqa)**
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
plugins: {
|
|
24
|
+
customLocator: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
prefix: '$',
|
|
27
|
+
attribute: ['data-qa', 'data-test'],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
* [retryTo plugin] Fixed [#3147](https://github.com/codeceptjs/CodeceptJS/issues/3147) using `pollInterval` option. See [#3351](https://github.com/codeceptjs/CodeceptJS/issues/3351) by **[cyonkee](https://github.com/cyonkee)**
|
|
32
|
+
* **[Playwright]** Fixed grabbing of browser console messages and window resize in new tab. Thanks to **[mirao](https://github.com/mirao)**
|
|
33
|
+
* **[REST]** Added `prettyPrintJson` option to print JSON in nice way by **[PeterNgTr](https://github.com/PeterNgTr)**
|
|
34
|
+
* **[JSONResponse]** Updated response validation to iterate over array items if response is array. Thanks to **[PeterNgTr](https://github.com/PeterNgTr)**
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// response.data == [
|
|
38
|
+
// { user: { name: 'jon', email: 'jon@doe.com' } },
|
|
39
|
+
// { user: { name: 'matt', email: 'matt@doe.com' } },
|
|
40
|
+
//]
|
|
41
|
+
|
|
42
|
+
I.seeResponseContainsKeys(['user']);
|
|
43
|
+
I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
44
|
+
I.seeResponseContainsJson({ user: { email: 'matt@doe.com' } });
|
|
45
|
+
I.dontSeeResponseContainsJson({ user: 2 });
|
|
46
|
+
```
|
|
47
|
+
|
|
10
48
|
## 3.3.3
|
|
11
49
|
|
|
12
50
|
* Fixed `DataCloneError: () => could not be cloned` when running data tests in run-workers
|
|
@@ -92,6 +92,14 @@ Checks for deep inclusion of a provided json in a response data.
|
|
|
92
92
|
I.dontSeeResponseContainsJson({ user: 2 });
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
If an array is received, checks that no element of array contains json:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// response.data == [{ user: 1 }, { user: 3 }]
|
|
99
|
+
|
|
100
|
+
I.dontSeeResponseContainsJson({ user: 2 });
|
|
101
|
+
```
|
|
102
|
+
|
|
95
103
|
#### Parameters
|
|
96
104
|
|
|
97
105
|
- `json` **[object][2]**
|
|
@@ -139,6 +147,14 @@ Checks for deep inclusion of a provided json in a response data.
|
|
|
139
147
|
I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
140
148
|
```
|
|
141
149
|
|
|
150
|
+
If an array is received, checks that at least one element contains JSON
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
// response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }]
|
|
154
|
+
|
|
155
|
+
I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
156
|
+
```
|
|
157
|
+
|
|
142
158
|
#### Parameters
|
|
143
159
|
|
|
144
160
|
- `json` **[object][2]**
|
|
@@ -153,6 +169,14 @@ Checks for deep inclusion of a provided json in a response data.
|
|
|
153
169
|
I.seeResponseContainsKeys(['user']);
|
|
154
170
|
```
|
|
155
171
|
|
|
172
|
+
If an array is received, check is performed for each element of array:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
// response.data == [{ user: 'jon' }, { user: 'matt'}]
|
|
176
|
+
|
|
177
|
+
I.seeResponseContainsKeys(['user']);
|
|
178
|
+
```
|
|
179
|
+
|
|
156
180
|
#### Parameters
|
|
157
181
|
|
|
158
182
|
- `keys` **[array][3]**
|
|
@@ -131,7 +131,8 @@ Traces will be saved to `output/trace`
|
|
|
131
131
|
Playwright: {
|
|
132
132
|
url: "http://localhost",
|
|
133
133
|
chromium: {
|
|
134
|
-
browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
134
|
+
browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a',
|
|
135
|
+
cdpConnection: false // default is false
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
}
|
|
@@ -811,10 +812,13 @@ Returns **[Promise][18]<[Array][19]<[string][12]>>** attribute value
|
|
|
811
812
|
Get JS log from browser.
|
|
812
813
|
|
|
813
814
|
```js
|
|
814
|
-
|
|
815
|
-
|
|
815
|
+
const logs = await I.grabBrowserLogs();
|
|
816
|
+
const errors = logs.map(l => ({ type: l.type(), text: l.text() })).filter(l => l.type === 'error');
|
|
817
|
+
console.log(JSON.stringify(errors));
|
|
816
818
|
```
|
|
817
819
|
|
|
820
|
+
[Learn more about console messages][20]
|
|
821
|
+
|
|
818
822
|
Returns **[Promise][18]<[Array][19]<any>>**
|
|
819
823
|
|
|
820
824
|
### grabCookie
|
|
@@ -1102,7 +1106,7 @@ Returns **[Promise][18]<[Array][19]<[string][12]>>** attribute value
|
|
|
1102
1106
|
Handles a file download.Aa file name is required to save the file on disk.
|
|
1103
1107
|
Files are saved to "output" directory.
|
|
1104
1108
|
|
|
1105
|
-
Should be used with [FileSystem helper][
|
|
1109
|
+
Should be used with [FileSystem helper][21] to check that file were downloaded correctly.
|
|
1106
1110
|
|
|
1107
1111
|
```js
|
|
1108
1112
|
I.handleDownloads('downloads/avatar.jpg');
|
|
@@ -1133,7 +1137,7 @@ I.haveRequestHeaders({
|
|
|
1133
1137
|
|
|
1134
1138
|
### makeApiRequest
|
|
1135
1139
|
|
|
1136
|
-
Performs [api request][
|
|
1140
|
+
Performs [api request][22] using
|
|
1137
1141
|
the cookies from the current browser session.
|
|
1138
1142
|
|
|
1139
1143
|
```js
|
|
@@ -1154,17 +1158,17 @@ Returns **[Promise][18]<[object][10]>** response
|
|
|
1154
1158
|
|
|
1155
1159
|
### mockRoute
|
|
1156
1160
|
|
|
1157
|
-
Mocks network request using [`browserContext.route`][
|
|
1161
|
+
Mocks network request using [`browserContext.route`][23] of Playwright
|
|
1158
1162
|
|
|
1159
1163
|
```js
|
|
1160
1164
|
I.mockRoute(/(.png$)|(.jpg$)/, route => route.abort());
|
|
1161
1165
|
```
|
|
1162
1166
|
|
|
1163
|
-
This method allows intercepting and mocking requests & responses. [Learn more about it][
|
|
1167
|
+
This method allows intercepting and mocking requests & responses. [Learn more about it][24]
|
|
1164
1168
|
|
|
1165
1169
|
#### Parameters
|
|
1166
1170
|
|
|
1167
|
-
- `url` **([string][12] | [RegExp][
|
|
1171
|
+
- `url` **([string][12] | [RegExp][25])?** URL, regex or pattern for to match URL
|
|
1168
1172
|
- `handler` **[function][17]?** a function to process reques
|
|
1169
1173
|
|
|
1170
1174
|
### moveCursorTo
|
|
@@ -1192,7 +1196,7 @@ Open new tab and automatically switched to new tab
|
|
|
1192
1196
|
I.openNewTab();
|
|
1193
1197
|
```
|
|
1194
1198
|
|
|
1195
|
-
You can pass in [page options][
|
|
1199
|
+
You can pass in [page options][26] to emulate device on this page
|
|
1196
1200
|
|
|
1197
1201
|
```js
|
|
1198
1202
|
// enable mobile
|
|
@@ -1207,7 +1211,7 @@ I.openNewTab({ isMobile: true });
|
|
|
1207
1211
|
|
|
1208
1212
|
Presses a key in the browser (on a focused element).
|
|
1209
1213
|
|
|
1210
|
-
_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][
|
|
1214
|
+
_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][27].
|
|
1211
1215
|
|
|
1212
1216
|
```js
|
|
1213
1217
|
I.pressKey('Backspace');
|
|
@@ -1267,13 +1271,13 @@ Some of the supported key names are:
|
|
|
1267
1271
|
#### Parameters
|
|
1268
1272
|
|
|
1269
1273
|
- `key` **([string][12] | [Array][19]<[string][12]>)** key or array of keys to press.
|
|
1270
|
-
[!] returns a _promise_ which is synchronized internally by recorder_Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Puppeteer#1313][
|
|
1274
|
+
[!] returns a _promise_ which is synchronized internally by recorder_Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Puppeteer#1313][28]).
|
|
1271
1275
|
|
|
1272
1276
|
### pressKeyDown
|
|
1273
1277
|
|
|
1274
1278
|
Presses a key in the browser and leaves it in a down state.
|
|
1275
1279
|
|
|
1276
|
-
To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][
|
|
1280
|
+
To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][29]).
|
|
1277
1281
|
|
|
1278
1282
|
```js
|
|
1279
1283
|
I.pressKeyDown('Control');
|
|
@@ -1290,7 +1294,7 @@ I.pressKeyUp('Control');
|
|
|
1290
1294
|
|
|
1291
1295
|
Releases a key in the browser which was previously set to a down state.
|
|
1292
1296
|
|
|
1293
|
-
To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][
|
|
1297
|
+
To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][29]).
|
|
1294
1298
|
|
|
1295
1299
|
```js
|
|
1296
1300
|
I.pressKeyDown('Control');
|
|
@@ -1378,7 +1382,7 @@ I.saveScreenshot('debug.png', true) //resizes to available scrollHeight and scro
|
|
|
1378
1382
|
#### Parameters
|
|
1379
1383
|
|
|
1380
1384
|
- `fileName` **[string][12]** file name to save.
|
|
1381
|
-
- `fullPage` **[boolean][
|
|
1385
|
+
- `fullPage` **[boolean][30]** (optional, `false` by default) flag to enable fullscreen screenshot mode.
|
|
1382
1386
|
[!] returns a _promise_ which is synchronized internally by recorder
|
|
1383
1387
|
|
|
1384
1388
|
### scrollPageToBottom
|
|
@@ -1724,7 +1728,7 @@ If no handler is passed, all mock requests for the rote are disabled.
|
|
|
1724
1728
|
|
|
1725
1729
|
#### Parameters
|
|
1726
1730
|
|
|
1727
|
-
- `url` **([string][12] | [RegExp][
|
|
1731
|
+
- `url` **([string][12] | [RegExp][25])?** URL, regex or pattern for to match URL
|
|
1728
1732
|
- `handler` **[function][17]?** a function to process reques
|
|
1729
1733
|
|
|
1730
1734
|
### switchTo
|
|
@@ -1771,7 +1775,7 @@ I.switchToPreviousTab(2);
|
|
|
1771
1775
|
|
|
1772
1776
|
Types out the given text into an active field.
|
|
1773
1777
|
To slow down typing use a second parameter, to set interval between key presses.
|
|
1774
|
-
_Note:_ Should be used when [`fillField`][
|
|
1778
|
+
_Note:_ Should be used when [`fillField`][27] is not an option.
|
|
1775
1779
|
|
|
1776
1780
|
```js
|
|
1777
1781
|
// passing in a string
|
|
@@ -1808,7 +1812,7 @@ I.uncheckOption('agree', '//form');
|
|
|
1808
1812
|
|
|
1809
1813
|
- `field` **([string][12] | [object][10])** checkbox located by label | name | CSS | XPath | strict locator.
|
|
1810
1814
|
- `context` **([string][12]? | [object][10])** (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
1811
|
-
[!] returns a _promise_ which is synchronized internally by recorder[Additional options][
|
|
1815
|
+
[!] returns a _promise_ which is synchronized internally by recorder[Additional options][31] for uncheck available as 3rd argument.Examples:```js
|
|
1812
1816
|
// click on element at position
|
|
1813
1817
|
I.uncheckOption('Agree', '.signup', { position: { x: 5, y: 5 } })
|
|
1814
1818
|
```> ⚠️ To avoid flakiness, option `force: true` is set by default
|
|
@@ -1821,7 +1825,7 @@ Use Playwright API inside a test.
|
|
|
1821
1825
|
First argument is a description of an action.
|
|
1822
1826
|
Second argument is async function that gets this helper as parameter.
|
|
1823
1827
|
|
|
1824
|
-
{ [`page`][
|
|
1828
|
+
{ [`page`][32], [`browserContext`][33] [`browser`][34] } objects from Playwright API are available.
|
|
1825
1829
|
|
|
1826
1830
|
```js
|
|
1827
1831
|
I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
|
|
@@ -1947,7 +1951,7 @@ I.waitForInvisible('#popup');
|
|
|
1947
1951
|
|
|
1948
1952
|
Waits for navigation to finish. By default takes configured `waitForNavigation` option.
|
|
1949
1953
|
|
|
1950
|
-
See [Playwright's reference][
|
|
1954
|
+
See [Playwright's reference][35]
|
|
1951
1955
|
|
|
1952
1956
|
#### Parameters
|
|
1953
1957
|
|
|
@@ -2027,7 +2031,7 @@ I.waitForVisible('#popup');
|
|
|
2027
2031
|
|
|
2028
2032
|
- `locator` **([string][12] | [object][10])** element located by CSS|XPath|strict locator.
|
|
2029
2033
|
- `sec` **[number][16]** (optional, `1` by default) time in seconds to wait
|
|
2030
|
-
[!] returns a _promise_ which is synchronized internally by recorderThis method accepts [React selectors][
|
|
2034
|
+
[!] returns a _promise_ which is synchronized internally by recorderThis method accepts [React selectors][36].
|
|
2031
2035
|
|
|
2032
2036
|
### waitInUrl
|
|
2033
2037
|
|
|
@@ -2126,34 +2130,36 @@ I.waitUrlEquals('http://127.0.0.1:8000/info');
|
|
|
2126
2130
|
|
|
2127
2131
|
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
|
2128
2132
|
|
|
2129
|
-
[20]: https://
|
|
2133
|
+
[20]: https://playwright.dev/docs/api/class-consolemessage
|
|
2134
|
+
|
|
2135
|
+
[21]: https://codecept.io/helpers/FileSystem
|
|
2130
2136
|
|
|
2131
|
-
[
|
|
2137
|
+
[22]: https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get
|
|
2132
2138
|
|
|
2133
|
-
[
|
|
2139
|
+
[23]: https://playwright.dev/docs/api/class-browsercontext#browser-context-route
|
|
2134
2140
|
|
|
2135
|
-
[
|
|
2141
|
+
[24]: https://playwright.dev/docs/network#handle-requests
|
|
2136
2142
|
|
|
2137
|
-
[
|
|
2143
|
+
[25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
|
2138
2144
|
|
|
2139
|
-
[
|
|
2145
|
+
[26]: https://github.com/microsoft/playwright/blob/main/docs/api.md#browsernewpageoptions
|
|
2140
2146
|
|
|
2141
|
-
[
|
|
2147
|
+
[27]: #fillfield
|
|
2142
2148
|
|
|
2143
|
-
[
|
|
2149
|
+
[28]: https://github.com/GoogleChrome/puppeteer/issues/1313
|
|
2144
2150
|
|
|
2145
|
-
[
|
|
2151
|
+
[29]: #click
|
|
2146
2152
|
|
|
2147
|
-
[
|
|
2153
|
+
[30]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
|
2148
2154
|
|
|
2149
|
-
[
|
|
2155
|
+
[31]: https://playwright.dev/docs/api/class-elementhandle#element-handle-uncheck
|
|
2150
2156
|
|
|
2151
|
-
[
|
|
2157
|
+
[32]: https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md
|
|
2152
2158
|
|
|
2153
|
-
[
|
|
2159
|
+
[33]: https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md
|
|
2154
2160
|
|
|
2155
|
-
[
|
|
2161
|
+
[34]: https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md
|
|
2156
2162
|
|
|
2157
|
-
[
|
|
2163
|
+
[35]: https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions
|
|
2158
2164
|
|
|
2159
|
-
[
|
|
2165
|
+
[36]: https://codecept.io/react
|
package/docs/helpers/REST.md
CHANGED
|
@@ -17,6 +17,7 @@ REST helper allows to send additional requests to the REST API during acceptance
|
|
|
17
17
|
## Configuration
|
|
18
18
|
|
|
19
19
|
- endpoint: API base URL
|
|
20
|
+
- prettyPrintJson: pretty print json for response/request on console logs
|
|
20
21
|
- timeout: timeout for requests in milliseconds. 10000ms by default
|
|
21
22
|
- defaultHeaders: a list of default headers
|
|
22
23
|
- onRequest: a async function which can update request object.
|
|
@@ -29,6 +30,7 @@ REST helper allows to send additional requests to the REST API during acceptance
|
|
|
29
30
|
helpers: {
|
|
30
31
|
REST: {
|
|
31
32
|
endpoint: 'http://site.com/api',
|
|
33
|
+
prettyPrintJson: true,
|
|
32
34
|
onRequest: (request) => {
|
|
33
35
|
request.headers.auth = '123';
|
|
34
36
|
}
|
package/docs/plugins.md
CHANGED
|
@@ -512,6 +512,46 @@ I.seeElement('=user'); // matches => [data-qa=user]
|
|
|
512
512
|
I.click('=sign-up'); // matches => [data-qa=sign-up]
|
|
513
513
|
```
|
|
514
514
|
|
|
515
|
+
Using `data-qa` OR `data-test` attribute with `=` prefix:
|
|
516
|
+
|
|
517
|
+
```js
|
|
518
|
+
// in codecept.conf.js
|
|
519
|
+
plugins: {
|
|
520
|
+
customLocator: {
|
|
521
|
+
enabled: true,
|
|
522
|
+
prefix: '=',
|
|
523
|
+
attribute: ['data-qa', 'data-test'],
|
|
524
|
+
strategy: 'xpath'
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
In a test:
|
|
530
|
+
|
|
531
|
+
```js
|
|
532
|
+
I.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]
|
|
533
|
+
I.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
// in codecept.conf.js
|
|
538
|
+
plugins: {
|
|
539
|
+
customLocator: {
|
|
540
|
+
enabled: true,
|
|
541
|
+
prefix: '=',
|
|
542
|
+
attribute: ['data-qa', 'data-test'],
|
|
543
|
+
strategy: 'css'
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
In a test:
|
|
549
|
+
|
|
550
|
+
```js
|
|
551
|
+
I.seeElement('=user'); // matches => [data-qa=user],[data-test=user]
|
|
552
|
+
I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
|
|
553
|
+
```
|
|
554
|
+
|
|
515
555
|
### Parameters
|
|
516
556
|
|
|
517
557
|
- `config`
|
|
@@ -682,7 +722,7 @@ Run tests with plugin enabled:
|
|
|
682
722
|
plugins: {
|
|
683
723
|
retryFailedStep: {
|
|
684
724
|
enabled: true,
|
|
685
|
-
|
|
725
|
+
ignoredSteps: [
|
|
686
726
|
'scroll*', // ignore all scroll steps
|
|
687
727
|
/Cookie/, // ignore all steps with a Cookie in it (by regexp)
|
|
688
728
|
]
|
package/docs/secrets.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
|
|
3
|
+
It is possible to mask out sensitive data when passing it to steps. This is important when filling password fields, or sending secure keys to API endpoint.
|
|
4
|
+
|
|
5
|
+
Wrap data in `secret` function to mask sensitive values in output and logs.
|
|
6
|
+
|
|
7
|
+
For basic string `secret` just wrap a value into a string:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
I.fillField('password', secret('123456'));
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
When executed it will be printed like this:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
I fill field "password" "*****"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For an object, which can be a payload to POST request, specify which fields should be masked:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
I.sendPostRequest('/login', secret({
|
|
23
|
+
name: 'davert',
|
|
24
|
+
password: '123456'
|
|
25
|
+
}, 'password'))
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The object created from `secret` is as Proxy to the object passed in. When printed password will be replaced with ****.
|
|
29
|
+
|
|
30
|
+
> ⚠️ Only direct properties of the object can be masked via `secret`
|
|
@@ -21,7 +21,7 @@ module.exports = async function (path, options) {
|
|
|
21
21
|
|
|
22
22
|
if (options.verbose) output.level(3);
|
|
23
23
|
|
|
24
|
-
output.print('
|
|
24
|
+
output.print('Starting interactive shell for current suite...');
|
|
25
25
|
recorder.start();
|
|
26
26
|
event.emit(event.suite.before, {
|
|
27
27
|
fullTitle: () => 'Interactive Shell',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
1
2
|
const chai = require('chai');
|
|
2
3
|
const joi = require('joi');
|
|
3
4
|
const chaiDeepMatch = require('chai-deep-match');
|
|
@@ -173,12 +174,30 @@ class JSONResponse extends Helper {
|
|
|
173
174
|
*
|
|
174
175
|
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
175
176
|
* ```
|
|
177
|
+
* If an array is received, checks that at least one element contains JSON
|
|
178
|
+
* ```js
|
|
179
|
+
* // response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }]
|
|
180
|
+
*
|
|
181
|
+
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
182
|
+
* ```
|
|
176
183
|
*
|
|
177
184
|
* @param {object} json
|
|
178
185
|
*/
|
|
179
186
|
seeResponseContainsJson(json = {}) {
|
|
180
187
|
this._checkResponseReady();
|
|
181
|
-
|
|
188
|
+
if (Array.isArray(this.response.data)) {
|
|
189
|
+
let fails = 0;
|
|
190
|
+
for (const el of this.response.data) {
|
|
191
|
+
try {
|
|
192
|
+
expect(el).to.deep.match(json);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
fails++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
assert.ok(fails < this.response.data.length, `No elements in array matched ${JSON.stringify(json)}`);
|
|
198
|
+
} else {
|
|
199
|
+
expect(this.response.data).to.deep.match(json);
|
|
200
|
+
}
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/**
|
|
@@ -189,12 +208,22 @@ class JSONResponse extends Helper {
|
|
|
189
208
|
*
|
|
190
209
|
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
191
210
|
* ```
|
|
211
|
+
* If an array is received, checks that no element of array contains json:
|
|
212
|
+
* ```js
|
|
213
|
+
* // response.data == [{ user: 1 }, { user: 3 }]
|
|
214
|
+
*
|
|
215
|
+
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
216
|
+
* ```
|
|
192
217
|
*
|
|
193
218
|
* @param {object} json
|
|
194
219
|
*/
|
|
195
220
|
dontSeeResponseContainsJson(json = {}) {
|
|
196
221
|
this._checkResponseReady();
|
|
197
|
-
|
|
222
|
+
if (Array.isArray(this.response.data)) {
|
|
223
|
+
this.response.data.forEach(data => expect(data).not.to.deep.match(json));
|
|
224
|
+
} else {
|
|
225
|
+
expect(this.response.data).not.to.deep.match(json);
|
|
226
|
+
}
|
|
198
227
|
}
|
|
199
228
|
|
|
200
229
|
/**
|
|
@@ -206,11 +235,23 @@ class JSONResponse extends Helper {
|
|
|
206
235
|
* I.seeResponseContainsKeys(['user']);
|
|
207
236
|
* ```
|
|
208
237
|
*
|
|
238
|
+
* If an array is received, check is performed for each element of array:
|
|
239
|
+
*
|
|
240
|
+
* ```js
|
|
241
|
+
* // response.data == [{ user: 'jon' }, { user: 'matt'}]
|
|
242
|
+
*
|
|
243
|
+
* I.seeResponseContainsKeys(['user']);
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
209
246
|
* @param {array} keys
|
|
210
247
|
*/
|
|
211
248
|
seeResponseContainsKeys(keys = []) {
|
|
212
249
|
this._checkResponseReady();
|
|
213
|
-
|
|
250
|
+
if (Array.isArray(this.response.data)) {
|
|
251
|
+
this.response.data.forEach(data => expect(data).to.include.keys(keys));
|
|
252
|
+
} else {
|
|
253
|
+
expect(this.response.data).to.include.keys(keys);
|
|
254
|
+
}
|
|
214
255
|
}
|
|
215
256
|
|
|
216
257
|
/**
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -167,7 +167,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
167
167
|
* Playwright: {
|
|
168
168
|
* url: "http://localhost",
|
|
169
169
|
* chromium: {
|
|
170
|
-
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
170
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a',
|
|
171
|
+
* cdpConnection: false // default is false
|
|
171
172
|
* }
|
|
172
173
|
* }
|
|
173
174
|
* }
|
|
@@ -272,6 +273,7 @@ class Playwright extends Helper {
|
|
|
272
273
|
this.sessionPages = {};
|
|
273
274
|
this.activeSessionName = '';
|
|
274
275
|
this.isElectron = false;
|
|
276
|
+
this.isCDPConnection = false;
|
|
275
277
|
this.electronSessions = [];
|
|
276
278
|
this.storageState = null;
|
|
277
279
|
|
|
@@ -347,6 +349,7 @@ class Playwright extends Helper {
|
|
|
347
349
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
348
350
|
this.isElectron = this.options.browser === 'electron';
|
|
349
351
|
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
352
|
+
this.isCDPConnection = this.playwrightOptions.cdpConnection;
|
|
350
353
|
popupStore.defaultAction = this.options.defaultPopupAction;
|
|
351
354
|
}
|
|
352
355
|
|
|
@@ -692,6 +695,15 @@ class Playwright extends Helper {
|
|
|
692
695
|
async _startBrowser() {
|
|
693
696
|
if (this.isElectron) {
|
|
694
697
|
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
698
|
+
} else if (this.isRemoteBrowser && this.isCDPConnection) {
|
|
699
|
+
try {
|
|
700
|
+
this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
703
|
+
throw new RemoteBrowserConnectionRefused(err);
|
|
704
|
+
}
|
|
705
|
+
throw err;
|
|
706
|
+
}
|
|
695
707
|
} else if (this.isRemoteBrowser) {
|
|
696
708
|
try {
|
|
697
709
|
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
@@ -1054,6 +1066,7 @@ class Playwright extends Helper {
|
|
|
1054
1066
|
if (!page) {
|
|
1055
1067
|
throw new Error(`There is no ability to switch to next tab with offset ${num}`);
|
|
1056
1068
|
}
|
|
1069
|
+
targetCreatedHandler.call(this, page);
|
|
1057
1070
|
await this._setPage(page);
|
|
1058
1071
|
return this._waitForAction();
|
|
1059
1072
|
}
|
|
@@ -1136,7 +1149,9 @@ class Playwright extends Helper {
|
|
|
1136
1149
|
if (this.isElectron) {
|
|
1137
1150
|
throw new Error('Cannot open new tabs inside an Electron container');
|
|
1138
1151
|
}
|
|
1139
|
-
|
|
1152
|
+
const page = await this.browserContext.newPage(options);
|
|
1153
|
+
targetCreatedHandler.call(this, page);
|
|
1154
|
+
await this._setPage(page);
|
|
1140
1155
|
return this._waitForAction();
|
|
1141
1156
|
}
|
|
1142
1157
|
|
|
@@ -1558,9 +1573,11 @@ class Playwright extends Helper {
|
|
|
1558
1573
|
* Get JS log from browser.
|
|
1559
1574
|
*
|
|
1560
1575
|
* ```js
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1576
|
+
* const logs = await I.grabBrowserLogs();
|
|
1577
|
+
* const errors = logs.map(l => ({ type: l.type(), text: l.text() })).filter(l => l.type === 'error');
|
|
1578
|
+
* console.log(JSON.stringify(errors));
|
|
1563
1579
|
* ```
|
|
1580
|
+
* [Learn more about console messages](https://playwright.dev/docs/api/class-consolemessage)
|
|
1564
1581
|
* @return {Promise<any[]>}
|
|
1565
1582
|
*/
|
|
1566
1583
|
async grabBrowserLogs() {
|
package/lib/helper/REST.js
CHANGED
|
@@ -2,6 +2,7 @@ const axios = require('axios').default;
|
|
|
2
2
|
const Secret = require('../secret');
|
|
3
3
|
|
|
4
4
|
const Helper = require('../helper');
|
|
5
|
+
const { beautify } = require('../utils');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* REST helper allows to send additional requests to the REST API during acceptance tests.
|
|
@@ -10,6 +11,7 @@ const Helper = require('../helper');
|
|
|
10
11
|
* ## Configuration
|
|
11
12
|
*
|
|
12
13
|
* * endpoint: API base URL
|
|
14
|
+
* * prettyPrintJson: pretty print json for response/request on console logs
|
|
13
15
|
* * timeout: timeout for requests in milliseconds. 10000ms by default
|
|
14
16
|
* * defaultHeaders: a list of default headers
|
|
15
17
|
* * onRequest: a async function which can update request object.
|
|
@@ -22,6 +24,7 @@ const Helper = require('../helper');
|
|
|
22
24
|
* helpers: {
|
|
23
25
|
* REST: {
|
|
24
26
|
* endpoint: 'http://site.com/api',
|
|
27
|
+
* prettyPrintJson: true,
|
|
25
28
|
* onRequest: (request) => {
|
|
26
29
|
* request.headers.auth = '123';
|
|
27
30
|
* }
|
|
@@ -49,6 +52,7 @@ class REST extends Helper {
|
|
|
49
52
|
timeout: 10000,
|
|
50
53
|
defaultHeaders: {},
|
|
51
54
|
endpoint: '',
|
|
55
|
+
prettyPrintJson: false,
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
if (this.options.maxContentLength) {
|
|
@@ -137,7 +141,7 @@ class REST extends Helper {
|
|
|
137
141
|
await this.config.onRequest(request);
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
this.debugSection('Request', JSON.stringify(_debugRequest));
|
|
144
|
+
this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest));
|
|
141
145
|
|
|
142
146
|
let response;
|
|
143
147
|
try {
|
|
@@ -150,7 +154,7 @@ class REST extends Helper {
|
|
|
150
154
|
if (this.config.onResponse) {
|
|
151
155
|
await this.config.onResponse(response);
|
|
152
156
|
}
|
|
153
|
-
this.debugSection('Response', JSON.stringify(response.data));
|
|
157
|
+
this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data));
|
|
154
158
|
return response;
|
|
155
159
|
}
|
|
156
160
|
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -907,7 +907,7 @@ class WebDriver extends Helper {
|
|
|
907
907
|
* {{ react }}
|
|
908
908
|
*/
|
|
909
909
|
async click(locator, context = null) {
|
|
910
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
910
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
911
911
|
const locateFn = prepareLocateFn.call(this, context);
|
|
912
912
|
|
|
913
913
|
const res = await findClickable.call(this, locator, locateFn);
|
|
@@ -1117,7 +1117,7 @@ class WebDriver extends Helper {
|
|
|
1117
1117
|
* Appium: not tested
|
|
1118
1118
|
*/
|
|
1119
1119
|
async checkOption(field, context = null) {
|
|
1120
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
1120
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
1121
1121
|
const locateFn = prepareLocateFn.call(this, context);
|
|
1122
1122
|
|
|
1123
1123
|
const res = await findCheckable.call(this, field, locateFn);
|
|
@@ -1136,7 +1136,7 @@ class WebDriver extends Helper {
|
|
|
1136
1136
|
* Appium: not tested
|
|
1137
1137
|
*/
|
|
1138
1138
|
async uncheckOption(field, context = null) {
|
|
1139
|
-
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
|
|
1139
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
|
|
1140
1140
|
const locateFn = prepareLocateFn.call(this, context);
|
|
1141
1141
|
|
|
1142
1142
|
const res = await findCheckable.call(this, field, locateFn);
|
|
@@ -1623,7 +1623,7 @@ class WebDriver extends Helper {
|
|
|
1623
1623
|
assertElementExists(res);
|
|
1624
1624
|
const elem = usingFirstElement(res);
|
|
1625
1625
|
const elementId = getElementId(elem);
|
|
1626
|
-
if (this.browser.isMobile) return this.browser.touchScroll(offsetX, offsetY, elementId);
|
|
1626
|
+
if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(offsetX, offsetY, elementId);
|
|
1627
1627
|
const location = await elem.getLocation();
|
|
1628
1628
|
assertElementExists(location, 'Failed to receive', 'location');
|
|
1629
1629
|
/* eslint-disable prefer-arrow-callback */
|
|
@@ -1631,7 +1631,7 @@ class WebDriver extends Helper {
|
|
|
1631
1631
|
/* eslint-enable */
|
|
1632
1632
|
}
|
|
1633
1633
|
|
|
1634
|
-
if (this.browser.isMobile) return this.browser.touchScroll(locator, offsetX, offsetY);
|
|
1634
|
+
if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(locator, offsetX, offsetY);
|
|
1635
1635
|
|
|
1636
1636
|
/* eslint-disable prefer-arrow-callback, comma-dangle */
|
|
1637
1637
|
return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY);
|
|
@@ -70,23 +70,70 @@ const defaultConfig = {
|
|
|
70
70
|
* I.seeElement('=user'); // matches => [data-qa=user]
|
|
71
71
|
* I.click('=sign-up'); // matches => [data-qa=sign-up]
|
|
72
72
|
* ```
|
|
73
|
+
*
|
|
74
|
+
* Using `data-qa` OR `data-test` attribute with `=` prefix:
|
|
75
|
+
*
|
|
76
|
+
* ```js
|
|
77
|
+
* // in codecept.conf.js
|
|
78
|
+
* plugins: {
|
|
79
|
+
* customLocator: {
|
|
80
|
+
* enabled: true,
|
|
81
|
+
* prefix: '=',
|
|
82
|
+
* attribute: ['data-qa', 'data-test'],
|
|
83
|
+
* strategy: 'xpath'
|
|
84
|
+
* }
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* In a test:
|
|
89
|
+
*
|
|
90
|
+
* ```js
|
|
91
|
+
* I.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]
|
|
92
|
+
* I.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* ```js
|
|
96
|
+
* // in codecept.conf.js
|
|
97
|
+
* plugins: {
|
|
98
|
+
* customLocator: {
|
|
99
|
+
* enabled: true,
|
|
100
|
+
* prefix: '=',
|
|
101
|
+
* attribute: ['data-qa', 'data-test'],
|
|
102
|
+
* strategy: 'css'
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* In a test:
|
|
108
|
+
*
|
|
109
|
+
* ```js
|
|
110
|
+
* I.seeElement('=user'); // matches => [data-qa=user],[data-test=user]
|
|
111
|
+
* I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
|
|
112
|
+
* ```
|
|
73
113
|
*/
|
|
74
114
|
module.exports = (config) => {
|
|
75
|
-
config =
|
|
115
|
+
config = { ...defaultConfig, ...config };
|
|
76
116
|
|
|
77
117
|
Locator.addFilter((value, locatorObj) => {
|
|
78
118
|
if (typeof value !== 'string') return;
|
|
79
119
|
if (!value.startsWith(config.prefix)) return;
|
|
80
120
|
|
|
121
|
+
if (!['String', 'Array'].includes(config.attribute.constructor.name)) return;
|
|
122
|
+
|
|
81
123
|
const val = value.substr(config.prefix.length);
|
|
82
124
|
|
|
83
125
|
if (config.strategy.toLowerCase() === 'xpath') {
|
|
84
|
-
locatorObj.value = `.//*[
|
|
126
|
+
locatorObj.value = `.//*[${
|
|
127
|
+
[].concat(config.attribute)
|
|
128
|
+
.map((attr) => `@${attr}=${xpathLocator.literal(val)}`)
|
|
129
|
+
.join(' or ')}]`;
|
|
85
130
|
locatorObj.type = 'xpath';
|
|
86
131
|
}
|
|
87
132
|
|
|
88
133
|
if (config.strategy.toLowerCase() === 'css') {
|
|
89
|
-
locatorObj.value =
|
|
134
|
+
locatorObj.value = [].concat(config.attribute)
|
|
135
|
+
.map((attr) => `[${attr}=${val}]`)
|
|
136
|
+
.join(',');
|
|
90
137
|
locatorObj.type = 'css';
|
|
91
138
|
}
|
|
92
139
|
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -101,27 +101,20 @@ module.exports = function (config) {
|
|
|
101
101
|
err = e;
|
|
102
102
|
recorder.session.restore(`retryTo ${tries}`);
|
|
103
103
|
tries++;
|
|
104
|
-
// recorder.session.restore(`retryTo`);
|
|
105
104
|
if (tries <= maxTries) {
|
|
106
105
|
debug(`Error ${err}... Retrying`);
|
|
107
106
|
err = null;
|
|
108
107
|
|
|
109
|
-
recorder.add(`retryTo ${tries}`, () =>
|
|
110
|
-
tryBlock();
|
|
111
|
-
// recorder.add(() => new Promise(done => setTimeout(done, pollInterval)));
|
|
112
|
-
});
|
|
108
|
+
recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval));
|
|
113
109
|
} else {
|
|
114
|
-
// recorder.throw(err);
|
|
115
110
|
done(null);
|
|
116
111
|
}
|
|
117
112
|
});
|
|
118
|
-
// return recorder.promise();
|
|
119
113
|
};
|
|
120
114
|
|
|
121
115
|
recorder.add('retryTo', async () => {
|
|
122
116
|
store.debugMode = true;
|
|
123
117
|
tryBlock();
|
|
124
|
-
// recorder.add(() => recorder.session.restore(`retryTo ${tries-1}`));
|
|
125
118
|
});
|
|
126
119
|
}).then(() => {
|
|
127
120
|
if (err) recorder.throw(err);
|
package/lib/secret.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
const { deepClone } = require('./utils');
|
|
3
|
+
|
|
1
4
|
/** @param {string} secret */
|
|
2
5
|
class Secret {
|
|
3
6
|
constructor(secret) {
|
|
@@ -9,13 +12,40 @@ class Secret {
|
|
|
9
12
|
return this._secret;
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
getMasked() {
|
|
16
|
+
return '*****';
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
/**
|
|
13
20
|
* @param {*} secret
|
|
14
21
|
* @returns {Secret}
|
|
15
22
|
*/
|
|
16
23
|
static secret(secret) {
|
|
24
|
+
if (typeof secret === 'object') {
|
|
25
|
+
const fields = Array.from(arguments);
|
|
26
|
+
fields.shift();
|
|
27
|
+
return secretObject(secret, fields);
|
|
28
|
+
}
|
|
17
29
|
return new Secret(secret);
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
|
|
33
|
+
function secretObject(obj, fieldsToHide = []) {
|
|
34
|
+
const handler = {
|
|
35
|
+
get(obj, prop) {
|
|
36
|
+
if (prop === 'toString') {
|
|
37
|
+
return function () {
|
|
38
|
+
const maskedObject = deepClone(obj);
|
|
39
|
+
fieldsToHide.forEach(f => maskedObject[f] = '****');
|
|
40
|
+
return JSON.stringify(maskedObject);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return obj[prop];
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return new Proxy(obj, handler);
|
|
49
|
+
}
|
|
50
|
+
|
|
21
51
|
module.exports = Secret;
|
package/lib/step.js
CHANGED
|
@@ -168,7 +168,7 @@ class Step {
|
|
|
168
168
|
} else if (typeof arg === 'undefined') {
|
|
169
169
|
return `${arg}`;
|
|
170
170
|
} else if (arg instanceof Secret) {
|
|
171
|
-
return
|
|
171
|
+
return arg.getMasked();
|
|
172
172
|
} else if (arg.toString && arg.toString() !== '[object Object]') {
|
|
173
173
|
return arg.toString();
|
|
174
174
|
} else if (typeof arg === 'object') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.4",
|
|
4
4
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acceptance",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"uuid": "^8.3.2"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@codeceptjs/detox-helper": "^1.0.2",
|
|
93
|
+
"@codeceptjs/detox-helper": "^1.0.2",
|
|
94
94
|
"@codeceptjs/mock-request": "^0.3.1",
|
|
95
95
|
"@faker-js/faker": "^5.5.3",
|
|
96
96
|
"@pollyjs/adapter-puppeteer": "^5.1.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"@wdio/selenium-standalone-service": "^5.16.10",
|
|
102
102
|
"@wdio/utils": "^5.23.0",
|
|
103
103
|
"apollo-server-express": "^2.25.3",
|
|
104
|
-
"chai-as-promised": "^
|
|
104
|
+
"chai-as-promised": "^7.1.1",
|
|
105
105
|
"chai-subset": "^1.6.0",
|
|
106
106
|
"contributor-faces": "^1.0.3",
|
|
107
107
|
"documentation": "^12.3.0",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"mocha-parallel-tests": "^2.3.0",
|
|
123
123
|
"nightmare": "^3.0.2",
|
|
124
124
|
"nodemon": "^1.19.4",
|
|
125
|
-
"playwright": "^1.
|
|
125
|
+
"playwright": "^1.23.2",
|
|
126
126
|
"puppeteer": "^10.4.0",
|
|
127
127
|
"qrcode-terminal": "^0.12.0",
|
|
128
128
|
"rosie": "^1.6.0",
|
package/typings/types.d.ts
CHANGED
|
@@ -1562,6 +1562,12 @@ declare namespace CodeceptJS {
|
|
|
1562
1562
|
*
|
|
1563
1563
|
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
1564
1564
|
* ```
|
|
1565
|
+
* If an array is received, checks that at least one element contains JSON
|
|
1566
|
+
* ```js
|
|
1567
|
+
* // response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }]
|
|
1568
|
+
*
|
|
1569
|
+
* I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } });
|
|
1570
|
+
* ```
|
|
1565
1571
|
*/
|
|
1566
1572
|
seeResponseContainsJson(json: any): void;
|
|
1567
1573
|
/**
|
|
@@ -1572,6 +1578,12 @@ declare namespace CodeceptJS {
|
|
|
1572
1578
|
*
|
|
1573
1579
|
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
1574
1580
|
* ```
|
|
1581
|
+
* If an array is received, checks that no element of array contains json:
|
|
1582
|
+
* ```js
|
|
1583
|
+
* // response.data == [{ user: 1 }, { user: 3 }]
|
|
1584
|
+
*
|
|
1585
|
+
* I.dontSeeResponseContainsJson({ user: 2 });
|
|
1586
|
+
* ```
|
|
1575
1587
|
*/
|
|
1576
1588
|
dontSeeResponseContainsJson(json: any): void;
|
|
1577
1589
|
/**
|
|
@@ -1582,6 +1594,14 @@ declare namespace CodeceptJS {
|
|
|
1582
1594
|
*
|
|
1583
1595
|
* I.seeResponseContainsKeys(['user']);
|
|
1584
1596
|
* ```
|
|
1597
|
+
*
|
|
1598
|
+
* If an array is received, check is performed for each element of array:
|
|
1599
|
+
*
|
|
1600
|
+
* ```js
|
|
1601
|
+
* // response.data == [{ user: 'jon' }, { user: 'matt'}]
|
|
1602
|
+
*
|
|
1603
|
+
* I.seeResponseContainsKeys(['user']);
|
|
1604
|
+
* ```
|
|
1585
1605
|
*/
|
|
1586
1606
|
seeResponseContainsKeys(keys: any[]): void;
|
|
1587
1607
|
/**
|
|
@@ -2755,7 +2775,8 @@ declare namespace CodeceptJS {
|
|
|
2755
2775
|
* Playwright: {
|
|
2756
2776
|
* url: "http://localhost",
|
|
2757
2777
|
* chromium: {
|
|
2758
|
-
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
2778
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a',
|
|
2779
|
+
* cdpConnection: false // default is false
|
|
2759
2780
|
* }
|
|
2760
2781
|
* }
|
|
2761
2782
|
* }
|
|
@@ -3753,9 +3774,11 @@ declare namespace CodeceptJS {
|
|
|
3753
3774
|
* Get JS log from browser.
|
|
3754
3775
|
*
|
|
3755
3776
|
* ```js
|
|
3756
|
-
*
|
|
3757
|
-
*
|
|
3777
|
+
* const logs = await I.grabBrowserLogs();
|
|
3778
|
+
* const errors = logs.map(l => ({ type: l.type(), text: l.text() })).filter(l => l.type === 'error');
|
|
3779
|
+
* console.log(JSON.stringify(errors));
|
|
3758
3780
|
* ```
|
|
3781
|
+
* [Learn more about console messages](https://playwright.dev/docs/api/class-consolemessage)
|
|
3759
3782
|
*/
|
|
3760
3783
|
grabBrowserLogs(): Promise<any[]>;
|
|
3761
3784
|
/**
|
|
@@ -7436,6 +7459,7 @@ declare namespace CodeceptJS {
|
|
|
7436
7459
|
* ## Configuration
|
|
7437
7460
|
*
|
|
7438
7461
|
* * endpoint: API base URL
|
|
7462
|
+
* * prettyPrintJson: pretty print json for response/request on console logs
|
|
7439
7463
|
* * timeout: timeout for requests in milliseconds. 10000ms by default
|
|
7440
7464
|
* * defaultHeaders: a list of default headers
|
|
7441
7465
|
* * onRequest: a async function which can update request object.
|
|
@@ -7448,6 +7472,7 @@ declare namespace CodeceptJS {
|
|
|
7448
7472
|
* helpers: {
|
|
7449
7473
|
* REST: {
|
|
7450
7474
|
* endpoint: 'http://site.com/api',
|
|
7475
|
+
* prettyPrintJson: true,
|
|
7451
7476
|
* onRequest: (request) => {
|
|
7452
7477
|
* request.headers.auth = '123';
|
|
7453
7478
|
* }
|