codeceptjs 3.1.0 → 3.2.0

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 (71) hide show
  1. package/CHANGELOG.md +129 -3
  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 +1 -1
  6. package/docs/bdd.md +55 -1
  7. package/docs/build/Appium.js +106 -34
  8. package/docs/build/FileSystem.js +1 -0
  9. package/docs/build/Nightmare.js +48 -48
  10. package/docs/build/Playwright.js +97 -94
  11. package/docs/build/Protractor.js +68 -81
  12. package/docs/build/Puppeteer.js +91 -93
  13. package/docs/build/REST.js +1 -0
  14. package/docs/build/TestCafe.js +44 -44
  15. package/docs/build/WebDriver.js +71 -95
  16. package/docs/changelog.md +144 -2
  17. package/docs/commands.md +21 -7
  18. package/docs/configuration.md +15 -2
  19. package/docs/custom-helpers.md +1 -36
  20. package/docs/helpers/Appium.md +97 -95
  21. package/docs/helpers/FileSystem.md +1 -1
  22. package/docs/helpers/Playwright.md +16 -18
  23. package/docs/helpers/Puppeteer.md +18 -18
  24. package/docs/helpers/REST.md +3 -1
  25. package/docs/helpers/WebDriver.md +3 -19
  26. package/docs/mobile-react-native-locators.md +3 -0
  27. package/docs/playwright.md +40 -0
  28. package/docs/plugins.md +185 -68
  29. package/docs/reports.md +23 -5
  30. package/lib/actor.js +20 -2
  31. package/lib/codecept.js +15 -2
  32. package/lib/command/info.js +1 -1
  33. package/lib/config.js +13 -1
  34. package/lib/container.js +3 -1
  35. package/lib/data/dataTableArgument.js +35 -0
  36. package/lib/helper/Appium.js +49 -4
  37. package/lib/helper/FileSystem.js +1 -0
  38. package/lib/helper/Playwright.js +35 -22
  39. package/lib/helper/Protractor.js +2 -14
  40. package/lib/helper/Puppeteer.js +20 -19
  41. package/lib/helper/REST.js +1 -0
  42. package/lib/helper/WebDriver.js +2 -16
  43. package/lib/index.js +2 -0
  44. package/lib/interfaces/featureConfig.js +3 -0
  45. package/lib/interfaces/gherkin.js +7 -1
  46. package/lib/interfaces/scenarioConfig.js +4 -0
  47. package/lib/listener/helpers.js +1 -0
  48. package/lib/listener/steps.js +21 -3
  49. package/lib/listener/timeout.js +71 -0
  50. package/lib/locator.js +3 -0
  51. package/lib/mochaFactory.js +13 -9
  52. package/lib/plugin/allure.js +6 -1
  53. package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
  54. package/lib/plugin/customLocator.js +2 -2
  55. package/lib/plugin/retryTo.js +130 -0
  56. package/lib/plugin/screenshotOnFail.js +1 -0
  57. package/lib/plugin/stepByStepReport.js +7 -0
  58. package/lib/plugin/stepTimeout.js +90 -0
  59. package/lib/plugin/subtitles.js +88 -0
  60. package/lib/plugin/tryTo.js +1 -1
  61. package/lib/recorder.js +21 -8
  62. package/lib/step.js +7 -2
  63. package/lib/store.js +2 -0
  64. package/lib/ui.js +2 -2
  65. package/package.json +6 -7
  66. package/typings/index.d.ts +8 -1
  67. package/typings/types.d.ts +198 -82
  68. package/docs/angular.md +0 -325
  69. package/docs/helpers/Protractor.md +0 -1658
  70. package/docs/webapi/waitUntil.mustache +0 -11
  71. package/typings/Protractor.d.ts +0 -16
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.totalTimeout = timeout * 1000;
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.totalTimeout);
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
@@ -130,7 +132,16 @@ class Codecept {
130
132
  let patterns = [pattern];
131
133
  if (!pattern) {
132
134
  patterns = [];
133
- if (this.config.tests && !this.opts.features) patterns.push(this.config.tests);
135
+
136
+ // If the user wants to test a specific set of test files as an array or string.
137
+ if (this.config.tests && !this.opts.features) {
138
+ if (Array.isArray(this.config.tests)) {
139
+ patterns.push(...this.config.tests);
140
+ } else {
141
+ patterns.push(this.config.tests);
142
+ }
143
+ }
144
+
134
145
  if (this.config.gherkin.features && !this.opts.tests) {
135
146
  if (Array.isArray(this.config.gherkin.features)) {
136
147
  this.config.gherkin.features.forEach(feature => {
@@ -147,7 +158,9 @@ class Codecept {
147
158
  if (!fsPath.isAbsolute(file)) {
148
159
  file = fsPath.join(global.codecept_dir, file);
149
160
  }
150
- this.testFiles.push(fsPath.resolve(file));
161
+ if (!this.testFiles.includes(fsPath.resolve(file))) {
162
+ this.testFiles.push(fsPath.resolve(file));
163
+ }
151
164
  });
152
165
  }
153
166
  }
@@ -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);
@@ -1352,6 +1356,33 @@ class Appium extends Webdriver {
1352
1356
  return super.grabTextFrom(parseLocator.call(this, locator));
1353
1357
  }
1354
1358
 
1359
+ /**
1360
+ * {{> grabNumberOfVisibleElements }}
1361
+ */
1362
+ async grabNumberOfVisibleElements(locator) {
1363
+ if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
1364
+ return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
1365
+ }
1366
+
1367
+ /**
1368
+ * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1369
+ *
1370
+ * {{> grabAttributeFrom }}
1371
+ */
1372
+ async grabAttributeFrom(locator, attr) {
1373
+ if (this.isWeb) return super.grabAttributeFrom(locator, attr);
1374
+ return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
1375
+ }
1376
+
1377
+ /**
1378
+ * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1379
+ * {{> grabAttributeFromAll }}
1380
+ */
1381
+ async grabAttributeFromAll(locator, attr) {
1382
+ if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
1383
+ return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
1384
+ }
1385
+
1355
1386
  /**
1356
1387
  * {{> grabValueFromAll }}
1357
1388
  *
@@ -1370,6 +1401,20 @@ class Appium extends Webdriver {
1370
1401
  return super.grabValueFrom(parseLocator.call(this, locator));
1371
1402
  }
1372
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
+
1373
1418
  /**
1374
1419
  * {{> scrollIntoView }}
1375
1420
  *
@@ -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
@@ -354,6 +368,7 @@ class Playwright extends Helper {
354
368
  if (this.options.restart && !this.options.manualStart) await this._startBrowser();
355
369
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
356
370
 
371
+ this.isAuthenticated = false;
357
372
  if (this.isElectron) {
358
373
  this.browserContext = this.browser.context();
359
374
  } else if (this.userDataDir) {
@@ -364,8 +379,14 @@ class Playwright extends Helper {
364
379
  acceptDownloads: true,
365
380
  ...this.options.emulate,
366
381
  };
382
+ if (this.options.basicAuth) {
383
+ contextOptions.httpCredentials = this.options.basicAuth;
384
+ this.isAuthenticated = true;
385
+ }
367
386
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
368
387
  if (this.storageState) contextOptions.storageState = this.storageState;
388
+ if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
389
+ if (this.options.locale) contextOptions.locale = this.options.locale;
369
390
  this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
370
391
  }
371
392
 
@@ -559,7 +580,7 @@ class Playwright extends Helper {
559
580
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
560
581
  this.context = await this.page;
561
582
  this.contextLocator = null;
562
- if (this.config.browser === 'chrome') {
583
+ if (this.options.browser === 'chrome') {
563
584
  await page.bringToFront();
564
585
  }
565
586
  }
@@ -714,9 +735,9 @@ class Playwright extends Helper {
714
735
  url = this.options.url + url;
715
736
  }
716
737
 
717
- if (this.config.basicAuth && (this.isAuthenticated !== true)) {
738
+ if (this.options.basicAuth && (this.isAuthenticated !== true)) {
718
739
  if (url.includes(this.options.url)) {
719
- await this.browserContext.setHTTPCredentials(this.config.basicAuth);
740
+ await this.browserContext.setHTTPCredentials(this.options.basicAuth);
720
741
  this.isAuthenticated = true;
721
742
  }
722
743
  }
@@ -775,7 +796,7 @@ class Playwright extends Helper {
775
796
  if (!customHeaders) {
776
797
  throw new Error('Cannot send empty headers.');
777
798
  }
778
- return this.page.setExtraHTTPHeaders(customHeaders);
799
+ return this.browserContext.setExtraHTTPHeaders(customHeaders);
779
800
  }
780
801
 
781
802
  /**
@@ -1851,12 +1872,17 @@ class Playwright extends Helper {
1851
1872
 
1852
1873
  async _failed(test) {
1853
1874
  await this._withinEnd();
1875
+
1876
+ if (!test.artifacts) {
1877
+ test.artifacts = {};
1878
+ }
1879
+
1854
1880
  if (this.options.recordVideo && this.page.video()) {
1855
1881
  test.artifacts.video = await this.page.video().path();
1856
1882
  }
1857
1883
 
1858
1884
  if (this.options.trace) {
1859
- const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
1885
+ const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
1860
1886
  await this.browserContext.tracing.stop({ path });
1861
1887
  test.artifacts.trace = path;
1862
1888
  }
@@ -1867,7 +1893,7 @@ class Playwright extends Helper {
1867
1893
  if (this.options.keepVideoForPassedTests) {
1868
1894
  test.artifacts.video = await this.page.video().path();
1869
1895
  } else {
1870
- this.page.video().delete();
1896
+ this.page.video().delete().catch(e => {});
1871
1897
  }
1872
1898
  }
1873
1899
 
@@ -2136,11 +2162,11 @@ class Playwright extends Helper {
2136
2162
  }
2137
2163
 
2138
2164
  /**
2139
- * Waits for a network request.
2165
+ * Waits for a network response.
2140
2166
  *
2141
2167
  * ```js
2142
2168
  * I.waitForResponse('http://example.com/resource');
2143
- * I.waitForResponse(request => request.url() === 'http://example.com' && request.method() === 'GET');
2169
+ * I.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);
2144
2170
  * ```
2145
2171
  *
2146
2172
  * @param {string|function} urlOrPredicate
@@ -2226,16 +2252,6 @@ class Playwright extends Helper {
2226
2252
  return this.page.waitForNavigation(opts);
2227
2253
  }
2228
2254
 
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
2255
  async waitUntilExists(locator, sec) {
2240
2256
  console.log(`waitUntilExists deprecated:
2241
2257
  * use 'waitForElement' to wait for element to be attached
@@ -2652,13 +2668,10 @@ async function targetCreatedHandler(page) {
2652
2668
  });
2653
2669
  });
2654
2670
  page.on('console', (msg) => {
2655
- this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '));
2671
+ this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
2656
2672
  consoleLogStore.add(msg);
2657
2673
  });
2658
2674
 
2659
- if (this.options.userAgent) {
2660
- await page.setUserAgent(this.options.userAgent);
2661
- }
2662
2675
  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
2663
2676
  await page.setViewportSize(parseWindowSize(this.options.windowSize));
2664
2677
  }
@@ -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
  */
@@ -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
  *
@@ -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
- * Checks that title is equal to provided one.
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
@@ -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
 
@@ -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);
@@ -1252,7 +1252,6 @@ class WebDriver extends Helper {
1252
1252
 
1253
1253
  /**
1254
1254
  * {{> grabAttributeFromAll }}
1255
- * Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1256
1255
  */
1257
1256
  async grabAttributeFromAll(locator, attr) {
1258
1257
  const res = await this._locate(locator, true);
@@ -1263,7 +1262,6 @@ class WebDriver extends Helper {
1263
1262
 
1264
1263
  /**
1265
1264
  * {{> grabAttributeFrom }}
1266
- * Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1267
1265
  */
1268
1266
  async grabAttributeFrom(locator, attr) {
1269
1267
  const attrs = await this.grabAttributeFromAll(locator, attr);
@@ -2355,18 +2353,6 @@ class WebDriver extends Helper {
2355
2353
  return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
2356
2354
  }
2357
2355
 
2358
- /**
2359
- * {{> waitUntil }}
2360
- */
2361
- async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) {
2362
- const aSec = sec || this.options.waitForTimeout;
2363
- const _interval = typeof interval === 'number' ? interval * 1000 : null;
2364
- if (isWebDriver5()) {
2365
- return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
2366
- }
2367
- return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval });
2368
- }
2369
-
2370
2356
  /**
2371
2357
  * {{> switchTo }}
2372
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
  }