codeceptjs 3.0.6 â 3.1.2
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 +92 -8
- package/README.md +9 -1
- package/bin/codecept.js +28 -17
- package/docs/build/Appium.js +69 -0
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Playwright.js +271 -63
- package/docs/build/Protractor.js +2 -0
- package/docs/build/Puppeteer.js +56 -18
- package/docs/build/REST.js +16 -3
- package/docs/build/WebDriver.js +82 -16
- package/docs/changelog.md +93 -9
- package/docs/configuration.md +15 -2
- package/docs/email.md +8 -8
- package/docs/examples.md +3 -3
- package/docs/helpers/Appium.md +66 -68
- package/docs/helpers/MockRequest.md +3 -3
- package/docs/helpers/Playwright.md +269 -203
- package/docs/helpers/Puppeteer.md +17 -1
- package/docs/helpers/REST.md +23 -9
- package/docs/helpers/WebDriver.md +3 -2
- package/docs/locators.md +27 -0
- package/docs/mobile.md +2 -1
- package/docs/nightmare.md +0 -5
- package/docs/parallel.md +14 -7
- package/docs/playwright.md +178 -11
- package/docs/plugins.md +61 -69
- package/docs/react.md +1 -1
- package/docs/reports.md +5 -4
- package/lib/actor.js +1 -2
- package/lib/codecept.js +13 -2
- package/lib/command/definitions.js +8 -1
- package/lib/command/interactive.js +4 -2
- package/lib/command/run-multiple/collection.js +4 -0
- package/lib/container.js +3 -3
- package/lib/helper/Appium.js +41 -0
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Playwright.js +218 -70
- package/lib/helper/Protractor.js +2 -0
- package/lib/helper/Puppeteer.js +56 -18
- package/lib/helper/REST.js +12 -0
- package/lib/helper/WebDriver.js +82 -16
- package/lib/helper/errors/ConnectionRefused.js +1 -1
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +44 -32
- package/lib/interfaces/gherkin.js +1 -0
- package/lib/listener/exit.js +2 -4
- package/lib/listener/helpers.js +3 -4
- package/lib/locator.js +7 -0
- package/lib/mochaFactory.js +11 -6
- package/lib/output.js +5 -2
- package/lib/plugin/allure.js +7 -18
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/{puppeteerCoverage.js â coverage.js} +10 -22
- package/lib/plugin/customLocator.js +2 -2
- package/lib/plugin/screenshotOnFail.js +5 -0
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/step.js +4 -2
- package/lib/ui.js +6 -2
- package/package.json +5 -4
- package/typings/index.d.ts +44 -21
- package/typings/types.d.ts +137 -16
package/docs/build/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
|
*
|
|
@@ -701,7 +717,7 @@ class Puppeteer extends Helper {
|
|
|
701
717
|
assertElementExists(els);
|
|
702
718
|
|
|
703
719
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
704
|
-
const { x, y } = await els[0]
|
|
720
|
+
const { x, y } = await getClickablePoint(els[0]);
|
|
705
721
|
await this.page.mouse.move(x + offsetX, y + offsetY);
|
|
706
722
|
return this._waitForAction();
|
|
707
723
|
}
|
|
@@ -790,7 +806,7 @@ class Puppeteer extends Helper {
|
|
|
790
806
|
const els = await this._locate(locator);
|
|
791
807
|
assertElementExists(els, locator, 'Element');
|
|
792
808
|
await els[0]._scrollIntoViewIfNeeded();
|
|
793
|
-
const elementCoordinates = await els[0]
|
|
809
|
+
const elementCoordinates = await getClickablePoint(els[0]);
|
|
794
810
|
await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
|
|
795
811
|
} else {
|
|
796
812
|
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY);
|
|
@@ -1047,7 +1063,10 @@ class Puppeteer extends Helper {
|
|
|
1047
1063
|
*/
|
|
1048
1064
|
async seeElement(locator) {
|
|
1049
1065
|
let els = await this._locate(locator);
|
|
1050
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1066
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1067
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1068
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1069
|
+
|
|
1051
1070
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
1052
1071
|
}
|
|
1053
1072
|
|
|
@@ -1063,7 +1082,10 @@ class Puppeteer extends Helper {
|
|
|
1063
1082
|
*/
|
|
1064
1083
|
async dontSeeElement(locator) {
|
|
1065
1084
|
let els = await this._locate(locator);
|
|
1066
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1085
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1086
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1087
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1088
|
+
|
|
1067
1089
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
1068
1090
|
}
|
|
1069
1091
|
|
|
@@ -1598,7 +1620,7 @@ class Puppeteer extends Helper {
|
|
|
1598
1620
|
* {{ react }}
|
|
1599
1621
|
*/
|
|
1600
1622
|
async fillField(field, value) {
|
|
1601
|
-
const els = await
|
|
1623
|
+
const els = await findVisibleFields.call(this, field);
|
|
1602
1624
|
assertElementExists(els, field, 'Field');
|
|
1603
1625
|
const el = els[0];
|
|
1604
1626
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -1640,7 +1662,7 @@ class Puppeteer extends Helper {
|
|
|
1640
1662
|
* {{ react }}
|
|
1641
1663
|
*/
|
|
1642
1664
|
async appendField(field, value) {
|
|
1643
|
-
const els = await
|
|
1665
|
+
const els = await findVisibleFields.call(this, field);
|
|
1644
1666
|
assertElementExists(els, field, 'Field');
|
|
1645
1667
|
await els[0].press('End');
|
|
1646
1668
|
await els[0].type(value, { delay: this.options.pressKeyDelay });
|
|
@@ -1732,7 +1754,7 @@ class Puppeteer extends Helper {
|
|
|
1732
1754
|
*
|
|
1733
1755
|
*/
|
|
1734
1756
|
async selectOption(select, option) {
|
|
1735
|
-
const els = await
|
|
1757
|
+
const els = await findVisibleFields.call(this, select);
|
|
1736
1758
|
assertElementExists(els, select, 'Selectable field');
|
|
1737
1759
|
const el = els[0];
|
|
1738
1760
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
@@ -1774,7 +1796,10 @@ class Puppeteer extends Helper {
|
|
|
1774
1796
|
*/
|
|
1775
1797
|
async grabNumberOfVisibleElements(locator) {
|
|
1776
1798
|
let els = await this._locate(locator);
|
|
1777
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1799
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1800
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1801
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1802
|
+
|
|
1778
1803
|
return els.filter(v => v).length;
|
|
1779
1804
|
}
|
|
1780
1805
|
|
|
@@ -2452,8 +2477,8 @@ class Puppeteer extends Helper {
|
|
|
2452
2477
|
const src = await this._locate(locator);
|
|
2453
2478
|
assertElementExists(src, locator, 'Slider Element');
|
|
2454
2479
|
|
|
2455
|
-
// Note: Using
|
|
2456
|
-
const sliderSource = await src[0]
|
|
2480
|
+
// Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
|
|
2481
|
+
const sliderSource = await getClickablePoint(src[0]);
|
|
2457
2482
|
|
|
2458
2483
|
// Drag start point
|
|
2459
2484
|
await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 });
|
|
@@ -3202,7 +3227,7 @@ class Puppeteer extends Helper {
|
|
|
3202
3227
|
module.exports = Puppeteer;
|
|
3203
3228
|
|
|
3204
3229
|
async function findElements(matcher, locator) {
|
|
3205
|
-
if (locator.react) return findReact(matcher, locator);
|
|
3230
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
3206
3231
|
locator = new Locator(locator, 'css');
|
|
3207
3232
|
if (!locator.isXPath()) return matcher.$$(locator.simplify());
|
|
3208
3233
|
return matcher.$x(locator.value);
|
|
@@ -3231,7 +3256,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3231
3256
|
}
|
|
3232
3257
|
|
|
3233
3258
|
async function findClickable(matcher, locator) {
|
|
3234
|
-
if (locator.react) return findReact(matcher, locator);
|
|
3259
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
3235
3260
|
locator = new Locator(locator);
|
|
3236
3261
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
3237
3262
|
|
|
@@ -3314,6 +3339,12 @@ async function proceedIsChecked(assertType, option) {
|
|
|
3314
3339
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
|
|
3315
3340
|
}
|
|
3316
3341
|
|
|
3342
|
+
async function findVisibleFields(locator) {
|
|
3343
|
+
const els = await findFields.call(this, locator);
|
|
3344
|
+
const visible = await Promise.all(els.map(el => el.boundingBox()));
|
|
3345
|
+
return els.filter((el, index) => visible[index]);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3317
3348
|
async function findFields(locator) {
|
|
3318
3349
|
const matchedLocator = new Locator(locator);
|
|
3319
3350
|
if (!matchedLocator.isFuzzy()) {
|
|
@@ -3344,9 +3375,9 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
3344
3375
|
const dst = await this._locate(destinationLocator);
|
|
3345
3376
|
assertElementExists(dst, destinationLocator, 'Destination Element');
|
|
3346
3377
|
|
|
3347
|
-
// Note: Using
|
|
3348
|
-
const dragSource = await src[0]
|
|
3349
|
-
const dragDestination = await dst[0]
|
|
3378
|
+
// Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
|
|
3379
|
+
const dragSource = await getClickablePoint(src[0]);
|
|
3380
|
+
const dragDestination = await getClickablePoint(dst[0]);
|
|
3350
3381
|
|
|
3351
3382
|
// Drag start point
|
|
3352
3383
|
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
|
|
@@ -3360,7 +3391,7 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
3360
3391
|
}
|
|
3361
3392
|
|
|
3362
3393
|
async function proceedSeeInField(assertType, field, value) {
|
|
3363
|
-
const els = await
|
|
3394
|
+
const els = await findVisibleFields.call(this, field);
|
|
3364
3395
|
assertElementExists(els, field, 'Field');
|
|
3365
3396
|
const el = els[0];
|
|
3366
3397
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -3493,6 +3524,13 @@ async function targetCreatedHandler(page) {
|
|
|
3493
3524
|
}
|
|
3494
3525
|
}
|
|
3495
3526
|
|
|
3527
|
+
// BC compatibility for Puppeteer < 10
|
|
3528
|
+
async function getClickablePoint(el) {
|
|
3529
|
+
if (el.clickablePoint) return el.clickablePoint();
|
|
3530
|
+
if (el._clickablePoint) return el._clickablePoint();
|
|
3531
|
+
return null;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3496
3534
|
// List of key values to key definitions
|
|
3497
3535
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
|
|
3498
3536
|
const keyDefinitionMap = {
|
package/docs/build/REST.js
CHANGED
|
@@ -59,7 +59,8 @@ class REST extends Helper {
|
|
|
59
59
|
|
|
60
60
|
this.options = { ...this.options, ...config };
|
|
61
61
|
this.headers = { ...this.options.defaultHeaders };
|
|
62
|
-
axios
|
|
62
|
+
this.axios = axios.create();
|
|
63
|
+
this.axios.defaults.headers = this.options.defaultHeaders;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
static _checkRequirements() {
|
|
@@ -74,10 +75,12 @@ class REST extends Helper {
|
|
|
74
75
|
* Executes axios request
|
|
75
76
|
*
|
|
76
77
|
* @param {*} request
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<*>} response
|
|
77
80
|
*/
|
|
78
81
|
async _executeRequest(request) {
|
|
79
82
|
const _debugRequest = { ...request };
|
|
80
|
-
axios.defaults.timeout = request.timeout || this.options.timeout;
|
|
83
|
+
this.axios.defaults.timeout = request.timeout || this.options.timeout;
|
|
81
84
|
|
|
82
85
|
if (this.headers && this.headers.auth) {
|
|
83
86
|
request.auth = this.headers.auth;
|
|
@@ -102,7 +105,7 @@ class REST extends Helper {
|
|
|
102
105
|
|
|
103
106
|
let response;
|
|
104
107
|
try {
|
|
105
|
-
response = await axios(request);
|
|
108
|
+
response = await this.axios(request);
|
|
106
109
|
} catch (err) {
|
|
107
110
|
if (!err.response) throw err;
|
|
108
111
|
this.debugSection('Response', `Response error. Status code: ${err.response.status}`);
|
|
@@ -142,6 +145,8 @@ class REST extends Helper {
|
|
|
142
145
|
*
|
|
143
146
|
* @param {*} url
|
|
144
147
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
148
|
+
*
|
|
149
|
+
* @returns {Promise<*>} response
|
|
145
150
|
*/
|
|
146
151
|
async sendGetRequest(url, headers = {}) {
|
|
147
152
|
const request = {
|
|
@@ -165,6 +170,8 @@ class REST extends Helper {
|
|
|
165
170
|
* @param {*} url
|
|
166
171
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
167
172
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
173
|
+
*
|
|
174
|
+
* @returns {Promise<*>} response
|
|
168
175
|
*/
|
|
169
176
|
async sendPostRequest(url, payload = {}, headers = {}) {
|
|
170
177
|
const request = {
|
|
@@ -196,6 +203,8 @@ class REST extends Helper {
|
|
|
196
203
|
* @param {string} url
|
|
197
204
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
198
205
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
206
|
+
*
|
|
207
|
+
* @returns {Promise<*>} response
|
|
199
208
|
*/
|
|
200
209
|
async sendPatchRequest(url, payload = {}, headers = {}) {
|
|
201
210
|
const request = {
|
|
@@ -227,6 +236,8 @@ class REST extends Helper {
|
|
|
227
236
|
* @param {string} url
|
|
228
237
|
* @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
|
|
229
238
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
239
|
+
*
|
|
240
|
+
* @returns {Promise<*>} response
|
|
230
241
|
*/
|
|
231
242
|
async sendPutRequest(url, payload = {}, headers = {}) {
|
|
232
243
|
const request = {
|
|
@@ -253,6 +264,8 @@ class REST extends Helper {
|
|
|
253
264
|
*
|
|
254
265
|
* @param {*} url
|
|
255
266
|
* @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
|
|
267
|
+
*
|
|
268
|
+
* @returns {Promise<*>} response
|
|
256
269
|
*/
|
|
257
270
|
async sendDeleteRequest(url, headers = {}) {
|
|
258
271
|
const request = {
|
package/docs/build/WebDriver.js
CHANGED
|
@@ -396,6 +396,7 @@ class WebDriver extends Helper {
|
|
|
396
396
|
this.isRunning = false;
|
|
397
397
|
this.sessionWindows = {};
|
|
398
398
|
this.activeSessionName = '';
|
|
399
|
+
this.customLocatorStrategies = config.customLocatorStrategies;
|
|
399
400
|
|
|
400
401
|
this._setConfig(config);
|
|
401
402
|
|
|
@@ -503,6 +504,33 @@ class WebDriver extends Helper {
|
|
|
503
504
|
}
|
|
504
505
|
}
|
|
505
506
|
|
|
507
|
+
_lookupCustomLocator(customStrategy) {
|
|
508
|
+
if (typeof (this.customLocatorStrategies) !== 'object') {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const strategy = this.customLocatorStrategies[customStrategy];
|
|
512
|
+
return typeof (strategy) === 'function' ? strategy : null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
_isCustomLocator(locator) {
|
|
516
|
+
const locatorObj = new Locator(locator);
|
|
517
|
+
if (locatorObj.isCustom()) {
|
|
518
|
+
const customLocator = this._lookupCustomLocator(locatorObj.type);
|
|
519
|
+
if (customLocator) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async _res(locator) {
|
|
528
|
+
const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
|
|
529
|
+
? await this._locate(locator)
|
|
530
|
+
: await this.$$(withStrictLocator(locator));
|
|
531
|
+
return res;
|
|
532
|
+
}
|
|
533
|
+
|
|
506
534
|
async _startBrowser() {
|
|
507
535
|
try {
|
|
508
536
|
if (this.options.multiremote) {
|
|
@@ -530,9 +558,22 @@ class WebDriver extends Helper {
|
|
|
530
558
|
await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
|
|
531
559
|
|
|
532
560
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
561
|
+
|
|
562
|
+
if (this._isCustomLocatorStrategyDefined()) {
|
|
563
|
+
Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
|
|
564
|
+
this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
|
|
565
|
+
const locatorFunction = this._lookupCustomLocator(customLocator);
|
|
566
|
+
this.browser.addLocatorStrategy(customLocator, locatorFunction);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
533
570
|
return this.browser;
|
|
534
571
|
}
|
|
535
572
|
|
|
573
|
+
_isCustomLocatorStrategyDefined() {
|
|
574
|
+
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
|
|
575
|
+
}
|
|
576
|
+
|
|
536
577
|
async _stopBrowser() {
|
|
537
578
|
if (this.browser && this.isRunning) await this.browser.deleteSession();
|
|
538
579
|
}
|
|
@@ -755,17 +796,34 @@ class WebDriver extends Helper {
|
|
|
755
796
|
}
|
|
756
797
|
|
|
757
798
|
if (!this.options.smartWait || !smartWait) {
|
|
799
|
+
if (this._isCustomLocator(locator)) {
|
|
800
|
+
const locatorObj = new Locator(locator);
|
|
801
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
802
|
+
}
|
|
803
|
+
|
|
758
804
|
const els = await this.$$(withStrictLocator(locator));
|
|
759
805
|
return els;
|
|
760
806
|
}
|
|
761
807
|
|
|
762
808
|
await this._smartWait(locator);
|
|
763
809
|
|
|
810
|
+
if (this._isCustomLocator(locator)) {
|
|
811
|
+
const locatorObj = new Locator(locator);
|
|
812
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
813
|
+
}
|
|
814
|
+
|
|
764
815
|
const els = await this.$$(withStrictLocator(locator));
|
|
765
816
|
await this.defineTimeout({ implicit: 0 });
|
|
766
817
|
return els;
|
|
767
818
|
}
|
|
768
819
|
|
|
820
|
+
_grabCustomLocator(locator) {
|
|
821
|
+
if (typeof locator === 'string') {
|
|
822
|
+
locator = new Locator(locator);
|
|
823
|
+
}
|
|
824
|
+
return locator.value ? locator.value : locator.custom;
|
|
825
|
+
}
|
|
826
|
+
|
|
769
827
|
/**
|
|
770
828
|
* Find a checkbox by providing human readable text:
|
|
771
829
|
*
|
|
@@ -1079,6 +1137,7 @@ class WebDriver extends Helper {
|
|
|
1079
1137
|
* @param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
1080
1138
|
*
|
|
1081
1139
|
* {{ react }}
|
|
1140
|
+
* {{ custom }}
|
|
1082
1141
|
*
|
|
1083
1142
|
*/
|
|
1084
1143
|
async fillField(field, value) {
|
|
@@ -1462,7 +1521,6 @@ class WebDriver extends Helper {
|
|
|
1462
1521
|
* @param {string} attr attribute name.
|
|
1463
1522
|
* @returns {Promise<string[]>} attribute value
|
|
1464
1523
|
*
|
|
1465
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1466
1524
|
*/
|
|
1467
1525
|
async grabAttributeFromAll(locator, attr) {
|
|
1468
1526
|
const res = await this._locate(locator, true);
|
|
@@ -1483,7 +1541,6 @@ class WebDriver extends Helper {
|
|
|
1483
1541
|
* @param {string} attr attribute name.
|
|
1484
1542
|
* @returns {Promise<string>} attribute value
|
|
1485
1543
|
*
|
|
1486
|
-
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
|
|
1487
1544
|
*/
|
|
1488
1545
|
async grabAttributeFrom(locator, attr) {
|
|
1489
1546
|
const attrs = await this.grabAttributeFromAll(locator, attr);
|
|
@@ -1723,7 +1780,7 @@ class WebDriver extends Helper {
|
|
|
1723
1780
|
*
|
|
1724
1781
|
*/
|
|
1725
1782
|
async seeElementInDOM(locator) {
|
|
1726
|
-
const res = await this
|
|
1783
|
+
const res = await this._res(locator);
|
|
1727
1784
|
return empty('elements').negate(res);
|
|
1728
1785
|
}
|
|
1729
1786
|
|
|
@@ -1738,7 +1795,7 @@ class WebDriver extends Helper {
|
|
|
1738
1795
|
*
|
|
1739
1796
|
*/
|
|
1740
1797
|
async dontSeeElementInDOM(locator) {
|
|
1741
|
-
const res = await this
|
|
1798
|
+
const res = await this._res(locator);
|
|
1742
1799
|
return empty('elements').assert(res);
|
|
1743
1800
|
}
|
|
1744
1801
|
|
|
@@ -2787,7 +2844,7 @@ class WebDriver extends Helper {
|
|
|
2787
2844
|
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
2788
2845
|
}
|
|
2789
2846
|
return this.browser.waitUntil(async () => {
|
|
2790
|
-
const res = await this
|
|
2847
|
+
const res = await this._res(locator);
|
|
2791
2848
|
if (!res || res.length === 0) {
|
|
2792
2849
|
return false;
|
|
2793
2850
|
}
|
|
@@ -2823,7 +2880,7 @@ class WebDriver extends Helper {
|
|
|
2823
2880
|
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
|
|
2824
2881
|
}
|
|
2825
2882
|
return this.browser.waitUntil(async () => {
|
|
2826
|
-
const res = await this
|
|
2883
|
+
const res = await this._res(locator);
|
|
2827
2884
|
return res && res.length;
|
|
2828
2885
|
}, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
|
|
2829
2886
|
}
|
|
@@ -3046,9 +3103,7 @@ class WebDriver extends Helper {
|
|
|
3046
3103
|
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
3047
3104
|
}
|
|
3048
3105
|
return this.browser.waitUntil(async () => {
|
|
3049
|
-
const res =
|
|
3050
|
-
? await this._locate(withStrictLocator(locator))
|
|
3051
|
-
: await this.$$(withStrictLocator(locator));
|
|
3106
|
+
const res = await this._res(locator);
|
|
3052
3107
|
if (!res || res.length === 0) return false;
|
|
3053
3108
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
3054
3109
|
if (Array.isArray(selected)) {
|
|
@@ -3083,7 +3138,7 @@ class WebDriver extends Helper {
|
|
|
3083
3138
|
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
3084
3139
|
}
|
|
3085
3140
|
return this.browser.waitUntil(async () => {
|
|
3086
|
-
const res = await this
|
|
3141
|
+
const res = await this._res(locator);
|
|
3087
3142
|
if (!res || res.length === 0) return false;
|
|
3088
3143
|
let selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
3089
3144
|
|
|
@@ -3115,7 +3170,7 @@ class WebDriver extends Helper {
|
|
|
3115
3170
|
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
3116
3171
|
}
|
|
3117
3172
|
return this.browser.waitUntil(async () => {
|
|
3118
|
-
const res = await this
|
|
3173
|
+
const res = await this._res(locator);
|
|
3119
3174
|
if (!res || res.length === 0) return true;
|
|
3120
3175
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
3121
3176
|
return !selected.length;
|
|
@@ -3152,7 +3207,7 @@ class WebDriver extends Helper {
|
|
|
3152
3207
|
const aSec = sec || this.options.waitForTimeout;
|
|
3153
3208
|
if (isWebDriver5()) {
|
|
3154
3209
|
return this.browser.waitUntil(async () => {
|
|
3155
|
-
const res = await this
|
|
3210
|
+
const res = await this._res(locator);
|
|
3156
3211
|
if (!res || res.length === 0) {
|
|
3157
3212
|
return true;
|
|
3158
3213
|
}
|
|
@@ -3160,7 +3215,7 @@ class WebDriver extends Helper {
|
|
|
3160
3215
|
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
3161
3216
|
}
|
|
3162
3217
|
return this.browser.waitUntil(async () => {
|
|
3163
|
-
const res = await this
|
|
3218
|
+
const res = await this._res(locator);
|
|
3164
3219
|
if (!res || res.length === 0) {
|
|
3165
3220
|
return true;
|
|
3166
3221
|
}
|
|
@@ -3563,12 +3618,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3563
3618
|
}
|
|
3564
3619
|
|
|
3565
3620
|
const smartWaitEnabled = assertType === 'assert';
|
|
3566
|
-
|
|
3567
3621
|
const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
|
|
3568
3622
|
assertElementExists(res, context);
|
|
3569
|
-
|
|
3570
3623
|
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
3571
|
-
|
|
3572
3624
|
if (strict) {
|
|
3573
3625
|
if (Array.isArray(selected) && selected.length !== 0) {
|
|
3574
3626
|
return selected.map(elText => equals(description)[assertType](text, elText));
|
|
@@ -3636,6 +3688,11 @@ async function filterAsync(array, callback) {
|
|
|
3636
3688
|
|
|
3637
3689
|
async function findClickable(locator, locateFn) {
|
|
3638
3690
|
locator = new Locator(locator);
|
|
3691
|
+
|
|
3692
|
+
if (this._isCustomLocator(locator)) {
|
|
3693
|
+
return locateFn(locator.value);
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3639
3696
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
3640
3697
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
3641
3698
|
|
|
@@ -3657,6 +3714,10 @@ async function findClickable(locator, locateFn) {
|
|
|
3657
3714
|
async function findFields(locator) {
|
|
3658
3715
|
locator = new Locator(locator);
|
|
3659
3716
|
|
|
3717
|
+
if (this._isCustomLocator(locator)) {
|
|
3718
|
+
return this._locate(locator);
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3660
3721
|
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
|
|
3661
3722
|
if (!locator.isFuzzy()) return this._locate(locator, true);
|
|
3662
3723
|
|
|
@@ -3762,6 +3823,10 @@ async function findCheckable(locator, locateFn) {
|
|
|
3762
3823
|
let els;
|
|
3763
3824
|
locator = new Locator(locator);
|
|
3764
3825
|
|
|
3826
|
+
if (this._isCustomLocator(locator)) {
|
|
3827
|
+
return locateFn(locator.value);
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3765
3830
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
3766
3831
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
3767
3832
|
|
|
@@ -3807,6 +3872,7 @@ function getElementId(el) {
|
|
|
3807
3872
|
if (el.ELEMENT) {
|
|
3808
3873
|
return el.ELEMENT;
|
|
3809
3874
|
}
|
|
3875
|
+
|
|
3810
3876
|
return null;
|
|
3811
3877
|
}
|
|
3812
3878
|
|
package/docs/changelog.md
CHANGED
|
@@ -7,13 +7,97 @@ layout: Section
|
|
|
7
7
|
|
|
8
8
|
# Releases
|
|
9
9
|
|
|
10
|
+
## 3.1.2
|
|
11
|
+
|
|
12
|
+
đŠī¸ Features:
|
|
13
|
+
|
|
14
|
+
* Added `coverage` plugin to generate code coverage for Playwright & Puppeteer. By **[anirudh-modi](https://github.com/anirudh-modi)**
|
|
15
|
+
* Added `subtitle` plugin to generate subtitles for videos recorded with Playwright. By **[anirudh-modi](https://github.com/anirudh-modi)**
|
|
16
|
+
* Configuration: `config.tests` to accept array of file patterns. See [#2994](https://github.com/codeceptjs/CodeceptJS/issues/2994) by **[monsteramba](https://github.com/monsteramba)**
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
exports.config = {
|
|
20
|
+
tests: ['./*_test.js','./sampleTest.js'],
|
|
21
|
+
// ...
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
* Notification is shown for test files without `Feature()`. See [#3011](https://github.com/codeceptjs/CodeceptJS/issues/3011) by **[PeterNgTr](https://github.com/PeterNgTr)**
|
|
25
|
+
|
|
26
|
+
đ Bugfixes:
|
|
27
|
+
|
|
28
|
+
* **[Playwright]** Fixed [#2986](https://github.com/codeceptjs/CodeceptJS/issues/2986) error is thrown when deleting a missing video. Fix by **[hatufacci](https://github.com/hatufacci)**
|
|
29
|
+
* Fixed false positive result when invalid function is called in a helper. See [#2997](https://github.com/codeceptjs/CodeceptJS/issues/2997) by **[abhimanyupandian](https://github.com/abhimanyupandian)**
|
|
30
|
+
* **[Appium]** Removed full page mode for `saveScreenshot`. See [#3002](https://github.com/codeceptjs/CodeceptJS/issues/3002) by **[nlespiaucq](https://github.com/nlespiaucq)**
|
|
31
|
+
* **[Playwright]** Fixed [#3003](https://github.com/codeceptjs/CodeceptJS/issues/3003) saving trace for a test with a long name. Fix by **[hatufacci](https://github.com/hatufacci)**
|
|
32
|
+
|
|
33
|
+
đą Other:
|
|
34
|
+
|
|
35
|
+
* Deprecated `puppeteerCoverage` plugin in favor of `coverage` plugin.
|
|
36
|
+
|
|
37
|
+
## 3.1.1
|
|
38
|
+
|
|
39
|
+
* **[Appium]** Fixed [#2759](https://github.com/codeceptjs/CodeceptJS/issues/2759)
|
|
40
|
+
`grabNumberOfVisibleElements`, `grabAttributeFrom`, `grabAttributeFromAll` to allow id locators.
|
|
41
|
+
|
|
42
|
+
## 3.1.0
|
|
43
|
+
|
|
44
|
+
* **[Plawyright]** Updated to Playwright 1.13
|
|
45
|
+
* **[Playwright]** **Possible breaking change**: `BrowserContext` is initialized before each test and closed after. This behavior matches recommendation from Playwright team to use different contexts for tests.
|
|
46
|
+
* **[Puppeteer]** Updated to Puppeteer 10.2.
|
|
47
|
+
* **[Protractor]** Helper deprecated
|
|
48
|
+
|
|
49
|
+
đŠī¸ Features:
|
|
50
|
+
|
|
51
|
+
* **[Playwright]** Added recording of [video](https://codecept.io/playwright/#video) and [traces](https://codecept.io/playwright/#trace) by **[davertmik](https://github.com/davertmik)**
|
|
52
|
+
* **[Playwritght]** [Mocking requests](https://codecept.io/playwright/#mocking-network-requests) implemented via `route` API of Playwright by **[davertmik](https://github.com/davertmik)**
|
|
53
|
+
* **[Playwright]** Added **support for [React locators](https://codecept.io/react/#locators)** in [#2912](https://github.com/codeceptjs/CodeceptJS/issues/2912) by **[AAAstorga](https://github.com/AAAstorga)**
|
|
54
|
+
|
|
55
|
+
đ Bugfixes:
|
|
56
|
+
|
|
57
|
+
* **[Puppeteer]** Fixed [#2244](https://github.com/codeceptjs/CodeceptJS/issues/2244) `els[0]._clickablePoint is not a function` by **[karunandrii](https://github.com/karunandrii)**.
|
|
58
|
+
* **[Puppeteer]** Fixed `fillField` to check for invisible elements. See [#2916](https://github.com/codeceptjs/CodeceptJS/issues/2916) by **[anne-open-xchange](https://github.com/anne-open-xchange)**
|
|
59
|
+
* **[Playwright]** Reset of dialog event listener before registration of new one. [#2946](https://github.com/codeceptjs/CodeceptJS/issues/2946) by **[nikocanvacom](https://github.com/nikocanvacom)**
|
|
60
|
+
* Fixed running Gherkin features with `run-multiple` using chunks. See [#2900](https://github.com/codeceptjs/CodeceptJS/issues/2900) by **[andrenoberto](https://github.com/andrenoberto)**
|
|
61
|
+
* Fixed [#2937](https://github.com/codeceptjs/CodeceptJS/issues/2937) broken typings for subfolders on Windows by **[jancorvus](https://github.com/jancorvus)**
|
|
62
|
+
* Fixed issue where cucumberJsonReporter not working with fakerTransform plugin. See [#2942](https://github.com/codeceptjs/CodeceptJS/issues/2942) by **[ilangv](https://github.com/ilangv)**
|
|
63
|
+
* Fixed [#2952](https://github.com/codeceptjs/CodeceptJS/issues/2952) finished job with status code 0 when playwright cannot connect to remote wss url. By **[davertmik](https://github.com/davertmik)**
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## 3.0.7
|
|
67
|
+
|
|
68
|
+
đ Documentation fixes:
|
|
69
|
+
|
|
70
|
+
* Remove broken link from `Nightmare helper`. See [#2860](https://github.com/codeceptjs/CodeceptJS/issues/2860) by **[Arhell](https://github.com/Arhell)**
|
|
71
|
+
* Fixed broken links in `playwright.md`. See [#2848](https://github.com/codeceptjs/CodeceptJS/issues/2848) by **[johnhoodjr](https://github.com/johnhoodjr)**
|
|
72
|
+
* Fix mocha-multi config example. See [#2881](https://github.com/codeceptjs/CodeceptJS/issues/2881) by **[rimesc](https://github.com/rimesc)**
|
|
73
|
+
* Fix small errors in email documentation file. See [#2884](https://github.com/codeceptjs/CodeceptJS/issues/2884) by **[mkrtchian](https://github.com/mkrtchian)**
|
|
74
|
+
* Improve documentation for `Sharing Data Between Workers` section. See [#2891](https://github.com/codeceptjs/CodeceptJS/issues/2891) by **[ngraf](https://github.com/ngraf)**
|
|
75
|
+
|
|
76
|
+
đŠī¸ Features:
|
|
77
|
+
|
|
78
|
+
* **[WebDriver]** Shadow DOM Support for `Webdriver`. See [#2741](https://github.com/codeceptjs/CodeceptJS/issues/2741) by **[gkushang](https://github.com/gkushang)**
|
|
79
|
+
* [Release management] Introduce the versioning automatically, it follows the semantics versioning. See [#2883](https://github.com/codeceptjs/CodeceptJS/issues/2883) by **[PeterNgTr](https://github.com/PeterNgTr)**
|
|
80
|
+
* Adding opts into `Scenario.skip` that it would be useful for building reports. See [#2867](https://github.com/codeceptjs/CodeceptJS/issues/2867) by **[AlexKo4](https://github.com/AlexKo4)**
|
|
81
|
+
* Added support for attaching screenshots to [cucumberJsonReporter](https://github.com/ktryniszewski-mdsol/codeceptjs-cucumber-json-reporter) See [#2888](https://github.com/codeceptjs/CodeceptJS/issues/2888) by **[fijijavis](https://github.com/fijijavis)**
|
|
82
|
+
* Supported config file for `codeceptjs shell` command. See [#2895](https://github.com/codeceptjs/CodeceptJS/issues/2895) by **[PeterNgTr](https://github.com/PeterNgTr)**:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
npx codeceptjs shell -c foo.conf.js
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Bug fixes:
|
|
89
|
+
* **[GraphQL]** Use a helper-specific instance of Axios to avoid contaminating global defaults. See [#2868](https://github.com/codeceptjs/CodeceptJS/issues/2868) by **[vanvoljg](https://github.com/vanvoljg)**
|
|
90
|
+
* A default system color is used when passing non supported system color when using I.say(). See [#2874](https://github.com/codeceptjs/CodeceptJS/issues/2874) by **[PeterNgTr](https://github.com/PeterNgTr)**
|
|
91
|
+
* **[Playwright]** Avoid the timout due to calling the click on invisible elements. See [#2875](https://github.com/codeceptjs/CodeceptJS/issues/2875) by cbayer97
|
|
92
|
+
|
|
93
|
+
|
|
10
94
|
## 3.0.6
|
|
11
95
|
|
|
12
|
-
* **[Playwright]** Added `electron` as a browser to config. See [#2834](https://github.com/codeceptjs/CodeceptJS/issues/2834) by **[cbayer97](https://github.com/cbayer97)**
|
|
96
|
+
* **[Playwright]** Added `electron` as a browser to config. See [#2834](https://github.com/codeceptjs/CodeceptJS/issues/2834) by **[cbayer97](https://github.com/cbayer97)**
|
|
13
97
|
* **[Playwright]** Implemented `launchPersistentContext` to be able to launch persistent remote browsers. See [#2817](https://github.com/codeceptjs/CodeceptJS/issues/2817) by **[brunoqueiros](https://github.com/brunoqueiros)**. Fixes [#2376](https://github.com/codeceptjs/CodeceptJS/issues/2376).
|
|
14
98
|
* Fixed printing logs and stack traces for `run-workers`. See [#2857](https://github.com/codeceptjs/CodeceptJS/issues/2857) by **[haveac1gar](https://github.com/haveac1gar)**. Fixes [#2621](https://github.com/codeceptjs/CodeceptJS/issues/2621), [#2852](https://github.com/codeceptjs/CodeceptJS/issues/2852)
|
|
15
|
-
* Emit custom messages from worker to the main thread. See [#2824](https://github.com/codeceptjs/CodeceptJS/issues/2824) by **[jccguimaraes](https://github.com/jccguimaraes)**
|
|
16
|
-
* Improved workers processes output. See [#2804](https://github.com/codeceptjs/CodeceptJS/issues/2804) by **[drfiresign](https://github.com/drfiresign)**
|
|
99
|
+
* Emit custom messages from worker to the main thread. See [#2824](https://github.com/codeceptjs/CodeceptJS/issues/2824) by **[jccguimaraes](https://github.com/jccguimaraes)**
|
|
100
|
+
* Improved workers processes output. See [#2804](https://github.com/codeceptjs/CodeceptJS/issues/2804) by **[drfiresign](https://github.com/drfiresign)**
|
|
17
101
|
* BDD. Added ability to use an array of feature files inside config in `gherkin.features`. See [#2814](https://github.com/codeceptjs/CodeceptJS/issues/2814) by **[jbergeronjr](https://github.com/jbergeronjr)**
|
|
18
102
|
|
|
19
103
|
```js
|
|
@@ -22,8 +106,8 @@ layout: Section
|
|
|
22
106
|
"./features/api_features/*.feature"
|
|
23
107
|
],
|
|
24
108
|
```
|
|
25
|
-
* Added `getQueueId` to reporter to rerun a specific promise. See [#2837](https://github.com/codeceptjs/CodeceptJS/issues/2837) by **[jonatask](https://github.com/jonatask)**
|
|
26
|
-
* **Added `fakerTransform` plugin** to use faker data in Gherkin scenarios. See [#2854](https://github.com/codeceptjs/CodeceptJS/issues/2854) by **[adrielcodeco](https://github.com/adrielcodeco)**
|
|
109
|
+
* Added `getQueueId` to reporter to rerun a specific promise. See [#2837](https://github.com/codeceptjs/CodeceptJS/issues/2837) by **[jonatask](https://github.com/jonatask)**
|
|
110
|
+
* **Added `fakerTransform` plugin** to use faker data in Gherkin scenarios. See [#2854](https://github.com/codeceptjs/CodeceptJS/issues/2854) by **[adrielcodeco](https://github.com/adrielcodeco)**
|
|
27
111
|
|
|
28
112
|
```feature
|
|
29
113
|
Scenario Outline: ...
|
|
@@ -35,7 +119,7 @@ Scenario Outline: ...
|
|
|
35
119
|
| productName | customer | email | anythingMore |
|
|
36
120
|
| {{commerce.product}} | Dr. {{name.findName}} | {{internet.email}} | staticData |
|
|
37
121
|
```
|
|
38
|
-
* **[REST]** Use class instance of axios, not the global instance, to avoid contaminating global configuration. [#2846](https://github.com/codeceptjs/CodeceptJS/issues/2846) by **[vanvoljg](https://github.com/vanvoljg)**
|
|
122
|
+
* **[REST]** Use class instance of axios, not the global instance, to avoid contaminating global configuration. [#2846](https://github.com/codeceptjs/CodeceptJS/issues/2846) by **[vanvoljg](https://github.com/vanvoljg)**
|
|
39
123
|
* **[Appium]** Added `tunnelIdentifier` config option to provide tunnel for SauceLabs. See [#2832](https://github.com/codeceptjs/CodeceptJS/issues/2832) by **[gurjeetbains](https://github.com/gurjeetbains)**
|
|
40
124
|
|
|
41
125
|
## 3.0.5
|
|
@@ -43,9 +127,9 @@ Scenario Outline: ...
|
|
|
43
127
|
|
|
44
128
|
Features:
|
|
45
129
|
|
|
46
|
-
* **[Official Docker image for CodeceptJS v3](https://hub.docker.com/r/codeceptjs/codeceptjs)**. New Docker image is based on official Playwright image and supports Playwright, Puppeteer, WebDriver engines. Thanks **[VikentyShevyrin](https://github.com/VikentyShevyrin)**
|
|
130
|
+
* **[Official Docker image for CodeceptJS v3](https://hub.docker.com/r/codeceptjs/codeceptjs)**. New Docker image is based on official Playwright image and supports Playwright, Puppeteer, WebDriver engines. Thanks **[VikentyShevyrin](https://github.com/VikentyShevyrin)**
|
|
47
131
|
* Better support for Typescript `codecept.conf.ts` configuration files. See [#2750](https://github.com/codeceptjs/CodeceptJS/issues/2750) by **[elaichenkov](https://github.com/elaichenkov)**
|
|
48
|
-
* Propagate more events for custom parallel script. See [#2796](https://github.com/codeceptjs/CodeceptJS/issues/2796) by **[jccguimaraes](https://github.com/jccguimaraes)**
|
|
132
|
+
* Propagate more events for custom parallel script. See [#2796](https://github.com/codeceptjs/CodeceptJS/issues/2796) by **[jccguimaraes](https://github.com/jccguimaraes)**
|
|
49
133
|
* [mocha-junit-reporter] Now supports attachments, see documentation for details. See [#2675](https://github.com/codeceptjs/CodeceptJS/issues/2675) by **[Shard](https://github.com/Shard)**
|
|
50
134
|
* CustomLocators interface for TypeScript to extend from LocatorOrString. See [#2798](https://github.com/codeceptjs/CodeceptJS/issues/2798) by **[danielrentz](https://github.com/danielrentz)**
|
|
51
135
|
* **[REST]** Mask sensitive data from log messages.
|
|
@@ -144,7 +228,7 @@ Scenario('title', (I, loginPage) => {});
|
|
|
144
228
|
Scenario('title', ({ I, loginPage }) => {});
|
|
145
229
|
```
|
|
146
230
|
|
|
147
|
-
* **BREAKING** Replaced bootstrap/teardown scripts to accept only functions or async functions. Async function with callback (with done parameter) should be replaced with async/await. [See our
|
|
231
|
+
* **BREAKING** Replaced bootstrap/teardown scripts to accept only functions or async functions. Async function with callback (with done parameter) should be replaced with async/await. [See our upgrade guide](https://bit.ly/codecept3Up).
|
|
148
232
|
* **[TypeScript guide](/typescript)** and [boilerplate project](https://github.com/codeceptjs/typescript-boilerplate)
|
|
149
233
|
* [tryTo](/plugins/#tryto) and [pauseOnFail](/plugins/#pauseOnFail) plugins installed by default
|
|
150
234
|
* Introduced one-line installer:
|