codeceptjs 3.1.1 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/README.md +2 -3
- package/bin/codecept.js +1 -0
- package/docs/advanced.md +94 -60
- package/docs/basics.md +1 -1
- package/docs/bdd.md +55 -1
- package/docs/build/Appium.js +22 -4
- package/docs/build/FileSystem.js +1 -0
- package/docs/build/Playwright.js +40 -42
- package/docs/build/Protractor.js +9 -24
- package/docs/build/Puppeteer.js +28 -30
- package/docs/build/REST.js +1 -0
- package/docs/build/WebDriver.js +2 -24
- package/docs/changelog.md +120 -0
- package/docs/commands.md +21 -7
- package/docs/configuration.md +15 -2
- package/docs/custom-helpers.md +1 -36
- package/docs/helpers/Appium.md +49 -50
- package/docs/helpers/FileSystem.md +1 -1
- package/docs/helpers/Playwright.md +16 -18
- package/docs/helpers/Puppeteer.md +18 -18
- package/docs/helpers/REST.md +3 -1
- package/docs/helpers/WebDriver.md +1 -17
- package/docs/mobile-react-native-locators.md +3 -0
- package/docs/playwright.md +40 -0
- package/docs/plugins.md +187 -70
- package/docs/reports.md +23 -5
- package/lib/actor.js +20 -2
- package/lib/codecept.js +15 -2
- package/lib/command/info.js +1 -1
- package/lib/config.js +13 -1
- package/lib/container.js +3 -1
- package/lib/data/dataTableArgument.js +35 -0
- package/lib/helper/Appium.js +22 -4
- package/lib/helper/FileSystem.js +1 -0
- package/lib/helper/Playwright.js +40 -32
- package/lib/helper/Protractor.js +2 -14
- package/lib/helper/Puppeteer.js +21 -20
- package/lib/helper/REST.js +1 -0
- package/lib/helper/WebDriver.js +2 -14
- package/lib/index.js +2 -0
- package/lib/interfaces/featureConfig.js +3 -0
- package/lib/interfaces/gherkin.js +7 -1
- package/lib/interfaces/scenarioConfig.js +4 -0
- package/lib/listener/helpers.js +1 -0
- package/lib/listener/steps.js +21 -3
- package/lib/listener/timeout.js +71 -0
- package/lib/locator.js +3 -0
- package/lib/mochaFactory.js +13 -9
- package/lib/plugin/allure.js +6 -1
- package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
- package/lib/plugin/customLocator.js +2 -2
- package/lib/plugin/retryFailedStep.js +4 -3
- package/lib/plugin/retryTo.js +130 -0
- package/lib/plugin/screenshotOnFail.js +1 -0
- package/lib/plugin/stepByStepReport.js +7 -0
- package/lib/plugin/stepTimeout.js +90 -0
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/recorder.js +21 -8
- package/lib/step.js +7 -2
- package/lib/store.js +2 -0
- package/lib/ui.js +2 -2
- package/package.json +6 -7
- package/typings/index.d.ts +8 -1
- package/typings/types.d.ts +104 -71
- package/docs/angular.md +0 -325
- package/docs/helpers/Protractor.md +0 -1658
- package/docs/webapi/waitUntil.mustache +0 -11
- package/typings/Protractor.d.ts +0 -16
package/lib/helper/Appium.js
CHANGED
|
@@ -481,10 +481,11 @@ class Appium extends Webdriver {
|
|
|
481
481
|
* ```js
|
|
482
482
|
* I.removeApp('appName', 'com.example.android.apis');
|
|
483
483
|
* ```
|
|
484
|
-
* @param {string} appId
|
|
485
|
-
* @param {string} bundleId String ID of bundle
|
|
486
484
|
*
|
|
487
485
|
* Appium: support only Android
|
|
486
|
+
*
|
|
487
|
+
* @param {string} appId
|
|
488
|
+
* @param {string} [bundleId] ID of bundle
|
|
488
489
|
*/
|
|
489
490
|
async removeApp(appId, bundleId) {
|
|
490
491
|
onlyForApps.call(this, 'Android');
|
|
@@ -820,9 +821,10 @@ class Appium extends Webdriver {
|
|
|
820
821
|
* I.hideDeviceKeyboard('pressKey', 'Done');
|
|
821
822
|
* ```
|
|
822
823
|
*
|
|
823
|
-
* @param {'tapOutside' | 'pressKey'} strategy desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
|
|
824
|
-
*
|
|
825
824
|
* Appium: support Android and iOS
|
|
825
|
+
*
|
|
826
|
+
* @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
|
|
827
|
+
* @param {string} [key] Optional key
|
|
826
828
|
*/
|
|
827
829
|
async hideDeviceKeyboard(strategy, key) {
|
|
828
830
|
onlyForApps.call(this);
|
|
@@ -1162,6 +1164,8 @@ class Appium extends Webdriver {
|
|
|
1162
1164
|
* ```
|
|
1163
1165
|
*
|
|
1164
1166
|
* Appium: support Android and iOS
|
|
1167
|
+
*
|
|
1168
|
+
* @param {Array} actions Array of touch actions
|
|
1165
1169
|
*/
|
|
1166
1170
|
async touchPerform(actions) {
|
|
1167
1171
|
onlyForApps.call(this);
|
|
@@ -1397,6 +1401,20 @@ class Appium extends Webdriver {
|
|
|
1397
1401
|
return super.grabValueFrom(parseLocator.call(this, locator));
|
|
1398
1402
|
}
|
|
1399
1403
|
|
|
1404
|
+
/**
|
|
1405
|
+
* Saves a screenshot to ouput folder (set in codecept.json or codecept.conf.js).
|
|
1406
|
+
* Filename is relative to output folder.
|
|
1407
|
+
*
|
|
1408
|
+
* ```js
|
|
1409
|
+
* I.saveScreenshot('debug.png');
|
|
1410
|
+
* ```
|
|
1411
|
+
*
|
|
1412
|
+
* @param {string} fileName file name to save.
|
|
1413
|
+
*/
|
|
1414
|
+
async saveScreenshot(fileName) {
|
|
1415
|
+
return super.saveScreenshot(fileName, false);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1400
1418
|
/**
|
|
1401
1419
|
* {{> scrollIntoView }}
|
|
1402
1420
|
*
|
package/lib/helper/FileSystem.js
CHANGED
package/lib/helper/Playwright.js
CHANGED
|
@@ -80,6 +80,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
80
80
|
* * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
|
|
81
81
|
* * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
|
|
82
82
|
* * `userAgent`: (optional) user-agent string.
|
|
83
|
+
* * `locale`: (optional) locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
|
|
83
84
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
84
85
|
* * `chromium`: (optional) pass additional chromium options
|
|
85
86
|
* * `electron`: (optional) pass additional electron options
|
|
@@ -197,6 +198,19 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
197
198
|
* }
|
|
198
199
|
* ```
|
|
199
200
|
*
|
|
201
|
+
* #### Example #7: Launch test with a specifc user locale
|
|
202
|
+
*
|
|
203
|
+
* ```js
|
|
204
|
+
* {
|
|
205
|
+
* helpers: {
|
|
206
|
+
* Playwright : {
|
|
207
|
+
* url: "http://localhost",
|
|
208
|
+
* locale: "fr-FR",
|
|
209
|
+
* }
|
|
210
|
+
* }
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
200
214
|
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
201
215
|
*
|
|
202
216
|
* ## Access From Helpers
|
|
@@ -341,19 +355,10 @@ class Playwright extends Helper {
|
|
|
341
355
|
}
|
|
342
356
|
|
|
343
357
|
async _before() {
|
|
344
|
-
recorder.retry({
|
|
345
|
-
retries: 5,
|
|
346
|
-
when: err => {
|
|
347
|
-
if (!err || typeof (err.message) !== 'string') {
|
|
348
|
-
return false;
|
|
349
|
-
}
|
|
350
|
-
// ignore context errors
|
|
351
|
-
return err.message.includes('context');
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
358
|
if (this.options.restart && !this.options.manualStart) await this._startBrowser();
|
|
355
359
|
if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
|
|
356
360
|
|
|
361
|
+
this.isAuthenticated = false;
|
|
357
362
|
if (this.isElectron) {
|
|
358
363
|
this.browserContext = this.browser.context();
|
|
359
364
|
} else if (this.userDataDir) {
|
|
@@ -364,8 +369,14 @@ class Playwright extends Helper {
|
|
|
364
369
|
acceptDownloads: true,
|
|
365
370
|
...this.options.emulate,
|
|
366
371
|
};
|
|
372
|
+
if (this.options.basicAuth) {
|
|
373
|
+
contextOptions.httpCredentials = this.options.basicAuth;
|
|
374
|
+
this.isAuthenticated = true;
|
|
375
|
+
}
|
|
367
376
|
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
|
|
368
377
|
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
378
|
+
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
379
|
+
if (this.options.locale) contextOptions.locale = this.options.locale;
|
|
369
380
|
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
370
381
|
}
|
|
371
382
|
|
|
@@ -557,9 +568,14 @@ class Playwright extends Helper {
|
|
|
557
568
|
this.page = page;
|
|
558
569
|
if (!page) return;
|
|
559
570
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
571
|
+
|
|
572
|
+
page.on('crash', async () => {
|
|
573
|
+
console.log('ERROR: Page has crashed, closing page!');
|
|
574
|
+
await page.close();
|
|
575
|
+
});
|
|
560
576
|
this.context = await this.page;
|
|
561
577
|
this.contextLocator = null;
|
|
562
|
-
if (this.
|
|
578
|
+
if (this.options.browser === 'chrome') {
|
|
563
579
|
await page.bringToFront();
|
|
564
580
|
}
|
|
565
581
|
}
|
|
@@ -714,9 +730,9 @@ class Playwright extends Helper {
|
|
|
714
730
|
url = this.options.url + url;
|
|
715
731
|
}
|
|
716
732
|
|
|
717
|
-
if (this.
|
|
733
|
+
if (this.options.basicAuth && (this.isAuthenticated !== true)) {
|
|
718
734
|
if (url.includes(this.options.url)) {
|
|
719
|
-
await this.browserContext.setHTTPCredentials(this.
|
|
735
|
+
await this.browserContext.setHTTPCredentials(this.options.basicAuth);
|
|
720
736
|
this.isAuthenticated = true;
|
|
721
737
|
}
|
|
722
738
|
}
|
|
@@ -775,7 +791,7 @@ class Playwright extends Helper {
|
|
|
775
791
|
if (!customHeaders) {
|
|
776
792
|
throw new Error('Cannot send empty headers.');
|
|
777
793
|
}
|
|
778
|
-
return this.
|
|
794
|
+
return this.browserContext.setExtraHTTPHeaders(customHeaders);
|
|
779
795
|
}
|
|
780
796
|
|
|
781
797
|
/**
|
|
@@ -1851,12 +1867,17 @@ class Playwright extends Helper {
|
|
|
1851
1867
|
|
|
1852
1868
|
async _failed(test) {
|
|
1853
1869
|
await this._withinEnd();
|
|
1870
|
+
|
|
1871
|
+
if (!test.artifacts) {
|
|
1872
|
+
test.artifacts = {};
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1854
1875
|
if (this.options.recordVideo && this.page.video()) {
|
|
1855
1876
|
test.artifacts.video = await this.page.video().path();
|
|
1856
1877
|
}
|
|
1857
1878
|
|
|
1858
1879
|
if (this.options.trace) {
|
|
1859
|
-
const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
|
|
1880
|
+
const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
|
|
1860
1881
|
await this.browserContext.tracing.stop({ path });
|
|
1861
1882
|
test.artifacts.trace = path;
|
|
1862
1883
|
}
|
|
@@ -1867,7 +1888,7 @@ class Playwright extends Helper {
|
|
|
1867
1888
|
if (this.options.keepVideoForPassedTests) {
|
|
1868
1889
|
test.artifacts.video = await this.page.video().path();
|
|
1869
1890
|
} else {
|
|
1870
|
-
this.page.video().delete();
|
|
1891
|
+
this.page.video().delete().catch(e => {});
|
|
1871
1892
|
}
|
|
1872
1893
|
}
|
|
1873
1894
|
|
|
@@ -2136,11 +2157,11 @@ class Playwright extends Helper {
|
|
|
2136
2157
|
}
|
|
2137
2158
|
|
|
2138
2159
|
/**
|
|
2139
|
-
* Waits for a network
|
|
2160
|
+
* Waits for a network response.
|
|
2140
2161
|
*
|
|
2141
2162
|
* ```js
|
|
2142
2163
|
* I.waitForResponse('http://example.com/resource');
|
|
2143
|
-
* I.waitForResponse(
|
|
2164
|
+
* I.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);
|
|
2144
2165
|
* ```
|
|
2145
2166
|
*
|
|
2146
2167
|
* @param {string|function} urlOrPredicate
|
|
@@ -2226,16 +2247,6 @@ class Playwright extends Helper {
|
|
|
2226
2247
|
return this.page.waitForNavigation(opts);
|
|
2227
2248
|
}
|
|
2228
2249
|
|
|
2229
|
-
/**
|
|
2230
|
-
* {{> waitUntil }}
|
|
2231
|
-
*/
|
|
2232
|
-
async waitUntil(fn, sec = null) {
|
|
2233
|
-
console.log('This method will remove in CodeceptJS 1.4; use `waitForFunction` instead!');
|
|
2234
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2235
|
-
const context = await this._getContext();
|
|
2236
|
-
return context.waitForFunction(fn, { timeout: waitTimeout });
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
2250
|
async waitUntilExists(locator, sec) {
|
|
2240
2251
|
console.log(`waitUntilExists deprecated:
|
|
2241
2252
|
* use 'waitForElement' to wait for element to be attached
|
|
@@ -2652,13 +2663,10 @@ async function targetCreatedHandler(page) {
|
|
|
2652
2663
|
});
|
|
2653
2664
|
});
|
|
2654
2665
|
page.on('console', (msg) => {
|
|
2655
|
-
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '));
|
|
2666
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
|
|
2656
2667
|
consoleLogStore.add(msg);
|
|
2657
2668
|
});
|
|
2658
2669
|
|
|
2659
|
-
if (this.options.userAgent) {
|
|
2660
|
-
await page.setUserAgent(this.options.userAgent);
|
|
2661
|
-
}
|
|
2662
2670
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
2663
2671
|
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
2664
2672
|
}
|
package/lib/helper/Protractor.js
CHANGED
|
@@ -838,11 +838,7 @@ class Protractor extends Helper {
|
|
|
838
838
|
}
|
|
839
839
|
|
|
840
840
|
/**
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* ```js
|
|
844
|
-
* I.seeTitleEquals('Test title.');
|
|
845
|
-
* ```
|
|
841
|
+
* {{> seeTitleEquals }}
|
|
846
842
|
*/
|
|
847
843
|
async seeTitleEquals(text) {
|
|
848
844
|
const title = await this.browser.getTitle();
|
|
@@ -1018,7 +1014,7 @@ class Protractor extends Helper {
|
|
|
1018
1014
|
}
|
|
1019
1015
|
|
|
1020
1016
|
/**
|
|
1021
|
-
|
|
1017
|
+
* {{> seeInCurrentUrl }}
|
|
1022
1018
|
*/
|
|
1023
1019
|
async seeInCurrentUrl(url) {
|
|
1024
1020
|
return this.browser.getCurrentUrl().then(currentUrl => stringIncludes('url').assert(url, currentUrl));
|
|
@@ -1498,14 +1494,6 @@ class Protractor extends Helper {
|
|
|
1498
1494
|
return this.browser.wait(() => this.browser.executeScript.call(this.browser, fn, ...args), aSec * 1000);
|
|
1499
1495
|
}
|
|
1500
1496
|
|
|
1501
|
-
/**
|
|
1502
|
-
* {{> waitUntil }}
|
|
1503
|
-
*/
|
|
1504
|
-
async waitUntil(fn, sec = null, timeoutMsg = null) {
|
|
1505
|
-
const aSec = sec || this.options.waitForTimeout;
|
|
1506
|
-
return this.browser.wait(fn, aSec * 1000, timeoutMsg);
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1497
|
/**
|
|
1510
1498
|
* {{> waitInUrl }}
|
|
1511
1499
|
*/
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -129,8 +129,9 @@ const consoleLogStore = new Console();
|
|
|
129
129
|
* }
|
|
130
130
|
* }
|
|
131
131
|
* ```
|
|
132
|
+
* > Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
132
133
|
*
|
|
133
|
-
* #### Example #5: Target URL with provided basic authentication
|
|
134
|
+
* #### Example #5: Target URL with provided basic authentication
|
|
134
135
|
*
|
|
135
136
|
* ```js
|
|
136
137
|
* {
|
|
@@ -143,10 +144,25 @@ const consoleLogStore = new Console();
|
|
|
143
144
|
* }
|
|
144
145
|
* }
|
|
145
146
|
* ```
|
|
147
|
+
* #### Troubleshooting
|
|
146
148
|
*
|
|
149
|
+
* Error Message: `No usable sandbox!`
|
|
150
|
+
*
|
|
151
|
+
* When running Puppeteer on CI try to disable sandbox if you see that message
|
|
152
|
+
*
|
|
153
|
+
* ```
|
|
154
|
+
* helpers: {
|
|
155
|
+
* Puppeteer: {
|
|
156
|
+
* url: 'http://localhost',
|
|
157
|
+
* show: false,
|
|
158
|
+
* chrome: {
|
|
159
|
+
* args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
160
|
+
* }
|
|
161
|
+
* },
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
147
164
|
*
|
|
148
165
|
*
|
|
149
|
-
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
150
166
|
*
|
|
151
167
|
* ## Access From Helpers
|
|
152
168
|
*
|
|
@@ -248,7 +264,7 @@ class Puppeteer extends Helper {
|
|
|
248
264
|
async _before() {
|
|
249
265
|
this.sessionPages = {};
|
|
250
266
|
recorder.retry({
|
|
251
|
-
retries:
|
|
267
|
+
retries: 3,
|
|
252
268
|
when: err => {
|
|
253
269
|
if (!err || typeof (err.message) !== 'string') {
|
|
254
270
|
return false;
|
|
@@ -538,10 +554,9 @@ class Puppeteer extends Helper {
|
|
|
538
554
|
this.context = null;
|
|
539
555
|
popupStore.clear();
|
|
540
556
|
this.isAuthenticated = false;
|
|
557
|
+
await this.browser.close();
|
|
541
558
|
if (this.isRemoteBrowser) {
|
|
542
559
|
await this.browser.disconnect();
|
|
543
|
-
} else {
|
|
544
|
-
await this.browser.close();
|
|
545
560
|
}
|
|
546
561
|
}
|
|
547
562
|
|
|
@@ -758,11 +773,7 @@ class Puppeteer extends Helper {
|
|
|
758
773
|
}
|
|
759
774
|
|
|
760
775
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* ```js
|
|
764
|
-
* I.seeTitleEquals('Test title.');
|
|
765
|
-
* ```
|
|
776
|
+
* {{> seeTitleEquals }}
|
|
766
777
|
*/
|
|
767
778
|
async seeTitleEquals(text) {
|
|
768
779
|
const title = await this.page.title();
|
|
@@ -2204,16 +2215,6 @@ class Puppeteer extends Helper {
|
|
|
2204
2215
|
return this.page.waitForNavigation(opts);
|
|
2205
2216
|
}
|
|
2206
2217
|
|
|
2207
|
-
/**
|
|
2208
|
-
* {{> waitUntil }}
|
|
2209
|
-
*/
|
|
2210
|
-
async waitUntil(fn, sec = null) {
|
|
2211
|
-
console.log('This method will remove in CodeceptJS 1.4; use `waitForFunction` instead!');
|
|
2212
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2213
|
-
const context = await this._getContext();
|
|
2214
|
-
return context.waitForFunction(fn, { timeout: waitTimeout });
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
2218
|
async waitUntilExists(locator, sec) {
|
|
2218
2219
|
console.log(`waitUntilExists deprecated:
|
|
2219
2220
|
* use 'waitForElement' to wait for element to be attached
|
package/lib/helper/REST.js
CHANGED
package/lib/helper/WebDriver.js
CHANGED
|
@@ -481,7 +481,7 @@ class WebDriver extends Helper {
|
|
|
481
481
|
try {
|
|
482
482
|
require('webdriverio');
|
|
483
483
|
} catch (e) {
|
|
484
|
-
return ['webdriverio@^
|
|
484
|
+
return ['webdriverio@^6.12.1'];
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
@@ -875,7 +875,7 @@ class WebDriver extends Helper {
|
|
|
875
875
|
* I.defineTimeout({ implicit: 10000, pageLoad: 10000, script: 5000 });
|
|
876
876
|
* ```
|
|
877
877
|
*
|
|
878
|
-
* @param {
|
|
878
|
+
* @param {*} timeouts WebDriver timeouts object.
|
|
879
879
|
*/
|
|
880
880
|
defineTimeout(timeouts) {
|
|
881
881
|
return this._defineBrowserTimeout(this.browser, timeouts);
|
|
@@ -2353,18 +2353,6 @@ class WebDriver extends Helper {
|
|
|
2353
2353
|
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
|
|
2354
2354
|
}
|
|
2355
2355
|
|
|
2356
|
-
/**
|
|
2357
|
-
* {{> waitUntil }}
|
|
2358
|
-
*/
|
|
2359
|
-
async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) {
|
|
2360
|
-
const aSec = sec || this.options.waitForTimeout;
|
|
2361
|
-
const _interval = typeof interval === 'number' ? interval * 1000 : null;
|
|
2362
|
-
if (isWebDriver5()) {
|
|
2363
|
-
return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
|
|
2364
|
-
}
|
|
2365
|
-
return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval });
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
2356
|
/**
|
|
2369
2357
|
* {{> switchTo }}
|
|
2370
2358
|
*/
|
package/lib/index.js
CHANGED
|
@@ -32,6 +32,8 @@ module.exports = {
|
|
|
32
32
|
within: require('./within'),
|
|
33
33
|
/** @type {typeof CodeceptJS.DataTable} */
|
|
34
34
|
dataTable: require('./data/table'),
|
|
35
|
+
/** @type {typeof CodeceptJS.DataTableArgument} */
|
|
36
|
+
dataTableArgument: require('./data/dataTableArgument'),
|
|
35
37
|
/** @type {typeof CodeceptJS.store} */
|
|
36
38
|
store: require('./store'),
|
|
37
39
|
/** @type {typeof CodeceptJS.Locator} */
|
|
@@ -21,6 +21,9 @@ class FeatureConfig {
|
|
|
21
21
|
* @returns {this}
|
|
22
22
|
*/
|
|
23
23
|
timeout(timeout) {
|
|
24
|
+
console.log(`Feature('${this.suite.title}').timeout(${timeout}) is deprecated!`);
|
|
25
|
+
console.log(`Please use Feature('${this.suite.title}', { timeout: ${timeout / 1000} }) instead`);
|
|
26
|
+
console.log('Timeout should be set in seconds');
|
|
24
27
|
this.suite.timeout(timeout);
|
|
25
28
|
return this;
|
|
26
29
|
}
|
|
@@ -11,15 +11,19 @@ const transform = require('../transform');
|
|
|
11
11
|
const parser = new Parser();
|
|
12
12
|
parser.stopAtFirstError = false;
|
|
13
13
|
|
|
14
|
-
module.exports = (text) => {
|
|
14
|
+
module.exports = (text, file) => {
|
|
15
15
|
const ast = parser.parse(text);
|
|
16
16
|
|
|
17
|
+
if (!ast.feature) {
|
|
18
|
+
throw new Error(`No 'Features' available in Gherkin '${file}' provided!`);
|
|
19
|
+
}
|
|
17
20
|
const suite = new Suite(ast.feature.name, new Context());
|
|
18
21
|
const tags = ast.feature.tags.map(t => t.name);
|
|
19
22
|
suite.title = `${suite.title} ${tags.join(' ')}`.trim();
|
|
20
23
|
suite.tags = tags || [];
|
|
21
24
|
suite.comment = ast.feature.description;
|
|
22
25
|
suite.feature = ast.feature;
|
|
26
|
+
suite.file = file;
|
|
23
27
|
suite.timeout(0);
|
|
24
28
|
|
|
25
29
|
suite.beforeEach('codeceptjs.before', () => scenario.setup(suite));
|
|
@@ -95,6 +99,7 @@ module.exports = (text) => {
|
|
|
95
99
|
const title = `${child.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
|
|
96
100
|
const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
|
|
97
101
|
test.tags = suite.tags.concat(tags);
|
|
102
|
+
test.file = file;
|
|
98
103
|
suite.addTest(scenario.test(test));
|
|
99
104
|
}
|
|
100
105
|
}
|
|
@@ -104,6 +109,7 @@ module.exports = (text) => {
|
|
|
104
109
|
const title = `${child.name} ${tags.join(' ')}`.trim();
|
|
105
110
|
const test = new Test(title, async () => runSteps(child.steps));
|
|
106
111
|
test.tags = suite.tags.concat(tags);
|
|
112
|
+
test.file = file;
|
|
107
113
|
suite.addTest(scenario.test(test));
|
|
108
114
|
}
|
|
109
115
|
|
|
@@ -45,6 +45,10 @@ class ScenarioConfig {
|
|
|
45
45
|
* @returns {this}
|
|
46
46
|
*/
|
|
47
47
|
timeout(timeout) {
|
|
48
|
+
console.log(`Scenario('${this.test.title}', () => {}).timeout(${timeout}) is deprecated!`);
|
|
49
|
+
console.log(`Please use Scenario('${this.test.title}', { timeout: ${timeout / 1000} }, () => {}) instead`);
|
|
50
|
+
console.log('Timeout should be set in seconds');
|
|
51
|
+
|
|
48
52
|
this.test.timeout(timeout);
|
|
49
53
|
return this;
|
|
50
54
|
}
|
package/lib/listener/helpers.js
CHANGED
package/lib/listener/steps.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const debug = require('debug')('codeceptjs:steps');
|
|
1
2
|
const event = require('../event');
|
|
2
3
|
const store = require('../store');
|
|
4
|
+
const output = require('../output');
|
|
3
5
|
|
|
4
6
|
let currentTest;
|
|
5
7
|
let currentHook;
|
|
@@ -9,6 +11,7 @@ let currentHook;
|
|
|
9
11
|
*/
|
|
10
12
|
module.exports = function () {
|
|
11
13
|
event.dispatcher.on(event.test.before, (test) => {
|
|
14
|
+
test.startedAt = +new Date();
|
|
12
15
|
test.artifacts = {};
|
|
13
16
|
});
|
|
14
17
|
|
|
@@ -19,17 +22,23 @@ module.exports = function () {
|
|
|
19
22
|
else currentTest.retryNum += 1;
|
|
20
23
|
});
|
|
21
24
|
|
|
22
|
-
event.dispatcher.on(event.test.after, () => {
|
|
25
|
+
event.dispatcher.on(event.test.after, (test) => {
|
|
23
26
|
currentTest = null;
|
|
24
27
|
});
|
|
25
28
|
|
|
26
|
-
event.dispatcher.on(event.
|
|
27
|
-
currentHook = null;
|
|
29
|
+
event.dispatcher.on(event.test.finished, (test) => {
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
event.dispatcher.on(event.hook.started, (suite) => {
|
|
31
33
|
currentHook = suite.ctx.test;
|
|
32
34
|
currentHook.steps = [];
|
|
35
|
+
|
|
36
|
+
if (suite.ctx && suite.ctx.test) output.log(`--- STARTED ${suite.ctx.test.title} ---`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
event.dispatcher.on(event.hook.passed, (suite) => {
|
|
40
|
+
currentHook = null;
|
|
41
|
+
if (suite.ctx && suite.ctx.test) output.log(`--- ENDED ${suite.ctx.test.title} ---`);
|
|
33
42
|
});
|
|
34
43
|
|
|
35
44
|
event.dispatcher.on(event.test.failed, () => {
|
|
@@ -51,6 +60,7 @@ module.exports = function () {
|
|
|
51
60
|
});
|
|
52
61
|
|
|
53
62
|
event.dispatcher.on(event.test.passed, () => {
|
|
63
|
+
if (!currentTest) return;
|
|
54
64
|
// To be sure that passed test will be passed in report
|
|
55
65
|
delete currentTest.err;
|
|
56
66
|
currentTest.state = 'passed';
|
|
@@ -58,10 +68,18 @@ module.exports = function () {
|
|
|
58
68
|
|
|
59
69
|
event.dispatcher.on(event.step.started, (step) => {
|
|
60
70
|
if (store.debugMode) return;
|
|
71
|
+
step.startedAt = +new Date();
|
|
61
72
|
if (currentHook && Array.isArray(currentHook.steps)) {
|
|
62
73
|
return currentHook.steps.push(step);
|
|
63
74
|
}
|
|
64
75
|
if (!currentTest || !currentTest.steps) return;
|
|
65
76
|
currentTest.steps.push(step);
|
|
66
77
|
});
|
|
78
|
+
|
|
79
|
+
event.dispatcher.on(event.step.finished, (step) => {
|
|
80
|
+
if (store.debugMode) return;
|
|
81
|
+
step.finishedAt = +new Date();
|
|
82
|
+
if (step.startedAt) step.duration = step.finishedAt - step.startedAt;
|
|
83
|
+
debug(`Step '${step}' finished; Duration: ${step.duration}ms`);
|
|
84
|
+
});
|
|
67
85
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const event = require('../event');
|
|
2
|
+
const output = require('../output');
|
|
3
|
+
const recorder = require('../recorder');
|
|
4
|
+
const Config = require('../config');
|
|
5
|
+
const { timeouts } = require('../store');
|
|
6
|
+
|
|
7
|
+
module.exports = function () {
|
|
8
|
+
let timeout;
|
|
9
|
+
let timeoutStack = [];
|
|
10
|
+
let currentTest;
|
|
11
|
+
let currentTimeout;
|
|
12
|
+
|
|
13
|
+
if (!timeouts) {
|
|
14
|
+
console.log('Timeouts were disabled');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
event.dispatcher.on(event.suite.before, (suite) => {
|
|
19
|
+
timeoutStack = [];
|
|
20
|
+
const globalTimeout = Config.get('timeout');
|
|
21
|
+
if (globalTimeout) {
|
|
22
|
+
if (globalTimeout >= 1000) {
|
|
23
|
+
console.log(`Warning: Timeout was set to ${globalTimeout}secs.\nGlobal timeout should be specified in seconds.`);
|
|
24
|
+
}
|
|
25
|
+
timeoutStack.push(globalTimeout);
|
|
26
|
+
}
|
|
27
|
+
if (suite.totalTimeout) timeoutStack.push(suite.totalTimeout);
|
|
28
|
+
output.log(`Timeouts: ${timeoutStack}`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
event.dispatcher.on(event.test.before, (test) => {
|
|
32
|
+
currentTest = test;
|
|
33
|
+
timeout = test.totalTimeout || timeoutStack[timeoutStack.length - 1];
|
|
34
|
+
if (!timeout) return;
|
|
35
|
+
currentTimeout = timeout;
|
|
36
|
+
output.debug(`Test Timeout: ${timeout}s`);
|
|
37
|
+
timeout *= 1000;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
event.dispatcher.on(event.test.passed, (test) => {
|
|
41
|
+
currentTest = null;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
event.dispatcher.on(event.test.failed, (test) => {
|
|
45
|
+
currentTest = null;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
event.dispatcher.on(event.step.before, (step) => {
|
|
49
|
+
if (typeof timeout !== 'number') return;
|
|
50
|
+
|
|
51
|
+
if (timeout < 0) {
|
|
52
|
+
step.totalTimeout = 0.01;
|
|
53
|
+
} else {
|
|
54
|
+
step.totalTimeout = timeout;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
event.dispatcher.on(event.step.finished, (step) => {
|
|
59
|
+
timeout -= step.duration;
|
|
60
|
+
|
|
61
|
+
if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
|
|
62
|
+
if (currentTest && currentTest.callback) {
|
|
63
|
+
recorder.reset();
|
|
64
|
+
// replace mocha timeout with custom timeout
|
|
65
|
+
currentTest.timeout(0);
|
|
66
|
+
currentTest.callback(new Error(`Timeout ${currentTimeout}s exceeded (with Before hook)`));
|
|
67
|
+
currentTest.timedOut = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
package/lib/locator.js
CHANGED
|
@@ -313,11 +313,14 @@ Locator.build = (locator) => {
|
|
|
313
313
|
|
|
314
314
|
/**
|
|
315
315
|
* Filters to modify locators
|
|
316
|
+
* @type {Array<function(CodeceptJS.LocatorOrString, Locator): void>}
|
|
316
317
|
*/
|
|
317
318
|
Locator.filters = [];
|
|
318
319
|
|
|
319
320
|
/**
|
|
320
321
|
* Appends new `Locator` filter to an `Locator.filters` array, and returns the new length of the array.
|
|
322
|
+
* @param {function(CodeceptJS.LocatorOrString, Locator): void} fn
|
|
323
|
+
* @returns {number}
|
|
321
324
|
*/
|
|
322
325
|
Locator.addFilter = fn => Locator.filters.push(fn);
|
|
323
326
|
|