codeceptjs 3.1.2 → 3.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +2 -3
  3. package/bin/codecept.js +1 -0
  4. package/docs/advanced.md +94 -60
  5. package/docs/basics.md +27 -2
  6. package/docs/bdd.md +57 -3
  7. package/docs/build/Appium.js +8 -4
  8. package/docs/build/FileSystem.js +1 -0
  9. package/docs/build/Playwright.js +39 -30
  10. package/docs/build/Protractor.js +9 -24
  11. package/docs/build/Puppeteer.js +10 -28
  12. package/docs/build/REST.js +1 -0
  13. package/docs/build/WebDriver.js +3 -25
  14. package/docs/changelog.md +103 -0
  15. package/docs/commands.md +21 -7
  16. package/docs/custom-helpers.md +1 -36
  17. package/docs/helpers/Appium.md +34 -30
  18. package/docs/helpers/FileSystem.md +1 -1
  19. package/docs/helpers/Playwright.md +16 -18
  20. package/docs/helpers/Puppeteer.md +1 -17
  21. package/docs/helpers/REST.md +3 -1
  22. package/docs/helpers/WebDriver.md +1 -17
  23. package/docs/mobile-react-native-locators.md +3 -0
  24. package/docs/pageobjects.md +2 -0
  25. package/docs/playwright.md +16 -33
  26. package/docs/plugins.md +128 -3
  27. package/docs/reports.md +23 -5
  28. package/lib/actor.js +20 -2
  29. package/lib/codecept.js +2 -0
  30. package/lib/command/info.js +1 -1
  31. package/lib/config.js +13 -1
  32. package/lib/container.js +3 -1
  33. package/lib/data/dataTableArgument.js +35 -0
  34. package/lib/helper/Appium.js +8 -4
  35. package/lib/helper/FileSystem.js +1 -0
  36. package/lib/helper/Playwright.js +39 -20
  37. package/lib/helper/Protractor.js +2 -14
  38. package/lib/helper/Puppeteer.js +3 -18
  39. package/lib/helper/REST.js +1 -0
  40. package/lib/helper/WebDriver.js +3 -15
  41. package/lib/index.js +2 -0
  42. package/lib/interfaces/featureConfig.js +3 -0
  43. package/lib/interfaces/gherkin.js +7 -1
  44. package/lib/interfaces/scenarioConfig.js +4 -0
  45. package/lib/listener/helpers.js +1 -0
  46. package/lib/listener/steps.js +21 -3
  47. package/lib/listener/timeout.js +72 -0
  48. package/lib/locator.js +3 -0
  49. package/lib/mochaFactory.js +2 -3
  50. package/lib/plugin/allure.js +6 -1
  51. package/lib/plugin/coverage.js +1 -1
  52. package/lib/plugin/retryFailedStep.js +4 -3
  53. package/lib/plugin/retryTo.js +130 -0
  54. package/lib/plugin/screenshotOnFail.js +1 -0
  55. package/lib/plugin/stepByStepReport.js +7 -0
  56. package/lib/plugin/stepTimeout.js +91 -0
  57. package/lib/recorder.js +23 -9
  58. package/lib/step.js +58 -0
  59. package/lib/store.js +2 -0
  60. package/lib/ui.js +2 -2
  61. package/package.json +4 -6
  62. package/typings/index.d.ts +8 -1
  63. package/typings/types.d.ts +103 -70
  64. package/docs/angular.md +0 -325
  65. package/docs/helpers/Protractor.md +0 -1658
  66. package/docs/webapi/waitUntil.mustache +0 -11
  67. package/typings/Protractor.d.ts +0 -16
package/docs/reports.md CHANGED
@@ -7,8 +7,8 @@ title: Reporters
7
7
 
8
8
  ## Cli
9
9
 
10
- By default CodeceptJS provides cli reporter with console output.
11
- Test names and failures will be printed to screen.
10
+ By default, CodeceptJS provides cli reporter with console output.
11
+ Test names and failures will be printed out on screen.
12
12
 
13
13
  ```sh
14
14
  GitHub --
@@ -33,8 +33,8 @@ GitHub --
33
33
  Run with --verbose flag to see NodeJS stacktrace
34
34
 
35
35
  ```
36
- npx codeceptjs run --stepsutput add `--steps` option to `run` command:
37
- ```
36
+
37
+ output steps use `--steps` option:
38
38
  ```
39
39
  npx codeceptjs run --steps
40
40
  ```
@@ -201,7 +201,7 @@ npx codeceptjs dry-run --debug -p allure
201
201
 
202
202
  ## ReportPortal
203
203
 
204
- Allure is a great reportin tool, however, if you are running tests on different machines it is hard to merge its XML result files to build a proper report. So, for enterprise grade reporting we recommend using [ReportPortal](https://reportportal.io).
204
+ Allure is a great reporting tool, however, if you are running tests on different machines it is hard to merge its XML result files to build a proper report. So, for enterprise grade reporting we recommend using [ReportPortal](https://reportportal.io).
205
205
 
206
206
  ![](https://camo.githubusercontent.com/6550c0365f1d0ce1e29c53f1860b12957d1fc529/68747470733a2f2f692e6962622e636f2f516d353247306e2f53637265656e73686f742d323031392d30342d31312d61742d31352d35372d34302e706e67)
207
207
 
@@ -376,3 +376,21 @@ npx codeceptjs run --reporter mocha-multi
376
376
  ```
377
377
 
378
378
  This will give you cli with steps in console and HTML report in `output` directory.
379
+
380
+
381
+ ## Testrail
382
+
383
+ Testrail integration with CodeceptJS is now so seamless. The test run is created automatically afterwards. The screenshots of failed tests are also attached to test results.
384
+
385
+ Try to use [codeceptjs-testrail](https://www.npmjs.com/package/codeceptjs-testrail) plugin
386
+
387
+ Install it via NPM:
388
+
389
+ ```sh
390
+ npm i codeceptjs-testrail --save
391
+ ```
392
+
393
+ ![Attachemnt for failed case](http://g.recordit.co/ajaa2QRlnW.gif)
394
+
395
+ Now there is new feature, add the configuration to test run of test plan
396
+ ![Attachemnt for failed case](http://g.recordit.co/uQLvQUq7cT.gif)
package/lib/actor.js CHANGED
@@ -27,8 +27,26 @@ class Actor {
27
27
  }
28
28
 
29
29
  /**
30
+ * set the maximum execution time for the next step
30
31
  * @function
31
- * @param {*} opts
32
+ * @param {number} timeout - step timeout in seconds
33
+ * @return {this}
34
+ * @inner
35
+ */
36
+ limitTime(timeout) {
37
+ if (!store.timeouts) return this;
38
+
39
+ event.dispatcher.prependOnceListener(event.step.before, (step) => {
40
+ output.log(`Timeout to ${step}: ${timeout}s`);
41
+ step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
42
+ });
43
+
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * @function
49
+ * @param {*} [opts]
32
50
  * @return {this}
33
51
  * @inner
34
52
  */
@@ -114,7 +132,7 @@ function recordStep(step, args) {
114
132
  step.startTime = Date.now();
115
133
  }
116
134
  return val = step.run(...args);
117
- });
135
+ }, false, undefined, step.getTimeout());
118
136
 
119
137
  event.emit(event.step.after, step);
120
138
 
package/lib/codecept.js CHANGED
@@ -77,6 +77,7 @@ class Codecept {
77
77
  global.inject = container.support;
78
78
  global.share = container.share;
79
79
  global.secret = require('./secret').secret;
80
+ global.codecept_debug = output.debug;
80
81
  global.codeceptjs = require('./index'); // load all objects
81
82
 
82
83
  // BDD
@@ -95,6 +96,7 @@ class Codecept {
95
96
  runHook(require('./listener/steps'));
96
97
  runHook(require('./listener/config'));
97
98
  runHook(require('./listener/helpers'));
99
+ runHook(require('./listener/timeout'));
98
100
  runHook(require('./listener/exit'));
99
101
 
100
102
  // custom hooks
@@ -32,7 +32,7 @@ module.exports = async function (path) {
32
32
  }
33
33
  }
34
34
  output.print('***************************************');
35
- output.print('If you have questions ask them in our Slack: shorturl.at/cuKU8');
35
+ output.print('If you have questions ask them in our Slack: http://bit.ly/chat-codeceptjs');
36
36
  output.print('Or ask them on our discussion board: https://codecept.discourse.group/');
37
37
  output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues');
38
38
  output.print('***************************************');
package/lib/config.js CHANGED
@@ -13,6 +13,7 @@ const defaultConfig = {
13
13
  include: {},
14
14
  mocha: {},
15
15
  bootstrap: null,
16
+ timeout: null,
16
17
  teardown: null,
17
18
  hooks: [],
18
19
  gherkin: {},
@@ -21,6 +22,17 @@ const defaultConfig = {
21
22
  enabled: true, // will be disabled by default in 2.0
22
23
  },
23
24
  },
25
+ stepTimeout: 0,
26
+ stepTimeoutOverride: [
27
+ {
28
+ pattern: 'wait.*',
29
+ timeout: 0,
30
+ },
31
+ {
32
+ pattern: 'amOnPage',
33
+ timeout: 0,
34
+ },
35
+ ],
24
36
  };
25
37
 
26
38
  let hooks = [];
@@ -128,7 +140,7 @@ function loadConfigFile(configFile) {
128
140
  const extensionName = path.extname(configFile);
129
141
 
130
142
  // .conf.js config file
131
- if (extensionName === '.js' || extensionName === '.ts') {
143
+ if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
132
144
  return Config.create(require(configFile).config);
133
145
  }
134
146
 
package/lib/container.js CHANGED
@@ -7,6 +7,7 @@ const MochaFactory = require('./mochaFactory');
7
7
  const recorder = require('./recorder');
8
8
  const event = require('./event');
9
9
  const WorkerStorage = require('./workerStorage');
10
+ const store = require('./store');
10
11
 
11
12
  let container = {
12
13
  helpers: {},
@@ -45,6 +46,7 @@ class Container {
45
46
  container.support = createSupportObjects(config.include || {});
46
47
  container.plugins = createPlugins(config.plugins || {}, opts);
47
48
  if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []);
49
+ if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts;
48
50
  }
49
51
 
50
52
  /**
@@ -233,7 +235,7 @@ function createSupportObjects(config) {
233
235
  const currentObject = objects[object];
234
236
  Object.keys(currentObject).forEach((method) => {
235
237
  const currentMethod = currentObject[method];
236
- if (currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
238
+ if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
237
239
  objects[object][method] = asyncWrapper(currentMethod);
238
240
  }
239
241
  });
@@ -1,4 +1,9 @@
1
+ /**
2
+ * DataTableArgument class to store the Cucumber data table from
3
+ * a step as an object with methods that can be used to access the data.
4
+ */
1
5
  class DataTableArgument {
6
+ /** @param {*} gherkinDataTable */
2
7
  constructor(gherkinDataTable) {
3
8
  this.rawData = gherkinDataTable.rows.map((row) => {
4
9
  return row.cells.map((cell) => {
@@ -7,16 +12,25 @@ class DataTableArgument {
7
12
  });
8
13
  }
9
14
 
15
+ /** Returns the table as a 2-D array
16
+ * @returns {string[][]}
17
+ */
10
18
  raw() {
11
19
  return this.rawData.slice(0);
12
20
  }
13
21
 
22
+ /** Returns the table as a 2-D array, without the first row
23
+ * @returns {string[][]}
24
+ */
14
25
  rows() {
15
26
  const copy = this.raw();
16
27
  copy.shift();
17
28
  return copy;
18
29
  }
19
30
 
31
+ /** Returns an array of objects where each row is converted to an object (column header is the key)
32
+ * @returns {any[]}
33
+ */
20
34
  hashes() {
21
35
  const copy = this.raw();
22
36
  const header = copy.shift();
@@ -26,6 +40,27 @@ class DataTableArgument {
26
40
  return r;
27
41
  });
28
42
  }
43
+
44
+ /** Returns an object where each row corresponds to an entry
45
+ * (first column is the key, second column is the value)
46
+ * @returns {Record<string, string>}
47
+ */
48
+ rowsHash() {
49
+ const rows = this.raw();
50
+ const everyRowHasTwoColumns = rows.every((row) => row.length === 2);
51
+ if (!everyRowHasTwoColumns) {
52
+ throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns');
53
+ }
54
+ /** @type {Record<string, string>} */
55
+ const result = {};
56
+ rows.forEach((x) => (result[x[0]] = x[1]));
57
+ return result;
58
+ }
59
+
60
+ /** Transposed the data */
61
+ transpose() {
62
+ this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]));
63
+ }
29
64
  }
30
65
 
31
66
  module.exports = DataTableArgument;
@@ -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);
@@ -91,6 +91,7 @@ class FileSystem extends Helper {
91
91
  * I.amInPath('output/downloads');
92
92
  * I.seeFileNameMatching('.pdf');
93
93
  * ```
94
+ * @param {string} text
94
95
  */
95
96
  seeFileNameMatching(text) {
96
97
  assert.ok(
@@ -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
@@ -351,9 +365,11 @@ class Playwright extends Helper {
351
365
  return err.message.includes('context');
352
366
  },
353
367
  });
368
+
354
369
  if (this.options.restart && !this.options.manualStart) await this._startBrowser();
355
370
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
356
371
 
372
+ this.isAuthenticated = false;
357
373
  if (this.isElectron) {
358
374
  this.browserContext = this.browser.context();
359
375
  } else if (this.userDataDir) {
@@ -364,8 +380,14 @@ class Playwright extends Helper {
364
380
  acceptDownloads: true,
365
381
  ...this.options.emulate,
366
382
  };
383
+ if (this.options.basicAuth) {
384
+ contextOptions.httpCredentials = this.options.basicAuth;
385
+ this.isAuthenticated = true;
386
+ }
367
387
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
368
388
  if (this.storageState) contextOptions.storageState = this.storageState;
389
+ if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
390
+ if (this.options.locale) contextOptions.locale = this.options.locale;
369
391
  this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
370
392
  }
371
393
 
@@ -557,9 +579,14 @@ class Playwright extends Helper {
557
579
  this.page = page;
558
580
  if (!page) return;
559
581
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
582
+
583
+ page.on('crash', async () => {
584
+ console.log('ERROR: Page has crashed, closing page!');
585
+ await page.close();
586
+ });
560
587
  this.context = await this.page;
561
588
  this.contextLocator = null;
562
- if (this.config.browser === 'chrome') {
589
+ if (this.options.browser === 'chrome') {
563
590
  await page.bringToFront();
564
591
  }
565
592
  }
@@ -714,9 +741,9 @@ class Playwright extends Helper {
714
741
  url = this.options.url + url;
715
742
  }
716
743
 
717
- if (this.config.basicAuth && (this.isAuthenticated !== true)) {
744
+ if (this.options.basicAuth && (this.isAuthenticated !== true)) {
718
745
  if (url.includes(this.options.url)) {
719
- await this.browserContext.setHTTPCredentials(this.config.basicAuth);
746
+ await this.browserContext.setHTTPCredentials(this.options.basicAuth);
720
747
  this.isAuthenticated = true;
721
748
  }
722
749
  }
@@ -775,7 +802,7 @@ class Playwright extends Helper {
775
802
  if (!customHeaders) {
776
803
  throw new Error('Cannot send empty headers.');
777
804
  }
778
- return this.page.setExtraHTTPHeaders(customHeaders);
805
+ return this.browserContext.setExtraHTTPHeaders(customHeaders);
779
806
  }
780
807
 
781
808
  /**
@@ -1851,6 +1878,11 @@ class Playwright extends Helper {
1851
1878
 
1852
1879
  async _failed(test) {
1853
1880
  await this._withinEnd();
1881
+
1882
+ if (!test.artifacts) {
1883
+ test.artifacts = {};
1884
+ }
1885
+
1854
1886
  if (this.options.recordVideo && this.page.video()) {
1855
1887
  test.artifacts.video = await this.page.video().path();
1856
1888
  }
@@ -2136,11 +2168,11 @@ class Playwright extends Helper {
2136
2168
  }
2137
2169
 
2138
2170
  /**
2139
- * Waits for a network request.
2171
+ * Waits for a network response.
2140
2172
  *
2141
2173
  * ```js
2142
2174
  * I.waitForResponse('http://example.com/resource');
2143
- * I.waitForResponse(request => request.url() === 'http://example.com' && request.method() === 'GET');
2175
+ * I.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);
2144
2176
  * ```
2145
2177
  *
2146
2178
  * @param {string|function} urlOrPredicate
@@ -2226,16 +2258,6 @@ class Playwright extends Helper {
2226
2258
  return this.page.waitForNavigation(opts);
2227
2259
  }
2228
2260
 
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
2261
  async waitUntilExists(locator, sec) {
2240
2262
  console.log(`waitUntilExists deprecated:
2241
2263
  * use 'waitForElement' to wait for element to be attached
@@ -2652,13 +2674,10 @@ async function targetCreatedHandler(page) {
2652
2674
  });
2653
2675
  });
2654
2676
  page.on('console', (msg) => {
2655
- this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '));
2677
+ this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
2656
2678
  consoleLogStore.add(msg);
2657
2679
  });
2658
2680
 
2659
- if (this.options.userAgent) {
2660
- await page.setUserAgent(this.options.userAgent);
2661
- }
2662
2681
  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
2663
2682
  await page.setViewportSize(parseWindowSize(this.options.windowSize));
2664
2683
  }
@@ -838,11 +838,7 @@ class Protractor extends Helper {
838
838
  }
839
839
 
840
840
  /**
841
- * Checks that title is equal to provided one.
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
- * {{> seeInCurrentUrl }}
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
  */
@@ -264,7 +264,7 @@ class Puppeteer extends Helper {
264
264
  async _before() {
265
265
  this.sessionPages = {};
266
266
  recorder.retry({
267
- retries: 5,
267
+ retries: 3,
268
268
  when: err => {
269
269
  if (!err || typeof (err.message) !== 'string') {
270
270
  return false;
@@ -554,10 +554,9 @@ class Puppeteer extends Helper {
554
554
  this.context = null;
555
555
  popupStore.clear();
556
556
  this.isAuthenticated = false;
557
+ await this.browser.close();
557
558
  if (this.isRemoteBrowser) {
558
559
  await this.browser.disconnect();
559
- } else {
560
- await this.browser.close();
561
560
  }
562
561
  }
563
562
 
@@ -774,11 +773,7 @@ class Puppeteer extends Helper {
774
773
  }
775
774
 
776
775
  /**
777
- * Checks that title is equal to provided one.
778
- *
779
- * ```js
780
- * I.seeTitleEquals('Test title.');
781
- * ```
776
+ * {{> seeTitleEquals }}
782
777
  */
783
778
  async seeTitleEquals(text) {
784
779
  const title = await this.page.title();
@@ -2220,16 +2215,6 @@ class Puppeteer extends Helper {
2220
2215
  return this.page.waitForNavigation(opts);
2221
2216
  }
2222
2217
 
2223
- /**
2224
- * {{> waitUntil }}
2225
- */
2226
- async waitUntil(fn, sec = null) {
2227
- console.log('This method will remove in CodeceptJS 1.4; use `waitForFunction` instead!');
2228
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2229
- const context = await this._getContext();
2230
- return context.waitForFunction(fn, { timeout: waitTimeout });
2231
- }
2232
-
2233
2218
  async waitUntilExists(locator, sec) {
2234
2219
  console.log(`waitUntilExists deprecated:
2235
2220
  * use 'waitForElement' to wait for element to be attached
@@ -131,6 +131,7 @@ class REST extends Helper {
131
131
  * I.setRequestTimeout(10000); // In milliseconds
132
132
  * ```
133
133
  *
134
+ * @param {number} newTimeout - timeout in milliseconds
134
135
  */
135
136
  setRequestTimeout(newTimeout) {
136
137
  this.options.timeout = newTimeout;
@@ -481,7 +481,7 @@ class WebDriver extends Helper {
481
481
  try {
482
482
  require('webdriverio');
483
483
  } catch (e) {
484
- return ['webdriverio@^5.2.2'];
484
+ return ['webdriverio@^6.12.1'];
485
485
  }
486
486
  }
487
487
 
@@ -582,7 +582,7 @@ class WebDriver extends Helper {
582
582
  this.context = this.root;
583
583
  if (this.options.restart && !this.options.manualStart) return this._startBrowser();
584
584
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
585
- this.$$ = this.browser.$$.bind(this.browser);
585
+ if (this.browser) this.$$ = this.browser.$$.bind(this.browser);
586
586
  return this.browser;
587
587
  }
588
588
 
@@ -875,7 +875,7 @@ class WebDriver extends Helper {
875
875
  * I.defineTimeout({ implicit: 10000, pageLoad: 10000, script: 5000 });
876
876
  * ```
877
877
  *
878
- * @param {WebdriverIO.Timeouts} timeouts WebDriver timeouts object.
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
  }
@@ -1,3 +1,4 @@
1
+ const path = require('path');
1
2
  const event = require('../event');
2
3
  const container = require('../container');
3
4
  const recorder = require('../recorder');