codeceptjs 3.3.7-beta.1 → 3.3.7

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 (62) hide show
  1. package/docs/basics.md +2 -3
  2. package/docs/bdd.md +33 -0
  3. package/docs/best.md +8 -8
  4. package/docs/build/Appium.js +60 -22
  5. package/docs/build/GraphQL.js +6 -6
  6. package/docs/build/Nightmare.js +4 -4
  7. package/docs/build/Playwright.js +102 -39
  8. package/docs/build/Polly.js +0 -0
  9. package/docs/build/Protractor.js +25 -25
  10. package/docs/build/Puppeteer.js +7 -7
  11. package/docs/build/SeleniumWebdriver.js +0 -0
  12. package/docs/build/TestCafe.js +12 -12
  13. package/docs/build/WebDriver.js +32 -32
  14. package/docs/changelog.md +36 -1
  15. package/docs/helpers/Appium.md +103 -79
  16. package/docs/helpers/GraphQL.md +6 -6
  17. package/docs/helpers/Playwright.md +280 -245
  18. package/docs/helpers/Puppeteer.md +1 -1
  19. package/docs/helpers/REST.md +1 -1
  20. package/docs/helpers/WebDriver.md +2 -2
  21. package/docs/playwright.md +14 -0
  22. package/docs/quickstart.md +40 -13
  23. package/docs/translation.md +83 -56
  24. package/docs/typescript.md +49 -3
  25. package/docs/wiki/Books-&-Posts.md +0 -0
  26. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -0
  27. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -0
  28. package/docs/wiki/Examples.md +0 -0
  29. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
  30. package/docs/wiki/Home.md +0 -0
  31. package/docs/wiki/Release-Process.md +0 -0
  32. package/docs/wiki/Roadmap.md +0 -0
  33. package/docs/wiki/Tests.md +0 -0
  34. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
  35. package/docs/wiki/Videos.md +0 -0
  36. package/lib/codecept.js +3 -1
  37. package/lib/command/definitions.js +26 -2
  38. package/lib/command/generate.js +23 -9
  39. package/lib/command/init.js +32 -7
  40. package/lib/command/run.js +5 -1
  41. package/lib/container.js +15 -15
  42. package/lib/helper/Appium.js +60 -22
  43. package/lib/helper/GraphQL.js +6 -6
  44. package/lib/helper/Nightmare.js +4 -4
  45. package/lib/helper/Playwright.js +102 -41
  46. package/lib/helper/Protractor.js +25 -25
  47. package/lib/helper/Puppeteer.js +7 -7
  48. package/lib/helper/TestCafe.js +12 -12
  49. package/lib/helper/WebDriver.js +32 -32
  50. package/lib/helper/errors/ElementNotFound.js +1 -1
  51. package/lib/helper/extras/PlaywrightRestartOpts.js +0 -2
  52. package/lib/interfaces/bdd.js +26 -1
  53. package/lib/listener/artifacts.js +19 -0
  54. package/lib/rerun.js +12 -13
  55. package/lib/step.js +5 -5
  56. package/lib/translation.js +32 -0
  57. package/package.json +11 -17
  58. package/translations/ru-RU.js +1 -0
  59. package/typings/index.d.ts +29 -1
  60. package/typings/promiseBasedTypes.d.ts +10466 -0
  61. package/typings/types.d.ts +62 -12
  62. package/CHANGELOG.md +0 -2340
package/docs/basics.md CHANGED
@@ -23,14 +23,14 @@ The `I` object is an **actor**, an abstraction for a testing user. The `I` is a
23
23
 
24
24
  ## Architecture
25
25
 
26
- CodeceptJS bypasses execution commands to helpers. Depending on the helper enabled, your tests will be executed differently. If you need cross-browser support you should choose Selenium-based WebDriver or TestCafé. If you are interested in speed - you should use Chrome-based Puppeteer.
26
+ CodeceptJS bypasses execution commands to helpers. Depending on the helper enabled, your tests will be executed differently.
27
27
 
28
28
  The following is a diagram of the CodeceptJS architecture:
29
29
 
30
30
  ![architecture](/img/architecture.png)
31
31
 
32
32
  All helpers share the same API, so it's easy to migrate tests from one backend to another.
33
- However, because of the difference in backends and their limitations, they are not guaranteed to be compatible with each other. For instance, you can't set request headers in WebDriver or Protractor, but you can do so in Puppeteer or Nightmare.
33
+ However, because of the difference in backends and their limitations, they are not guaranteed to be compatible with each other. For instance, you can't set request headers in WebDriver but you can do so in Playwright or Puppeteer.
34
34
 
35
35
  **Pick one helper, as it defines how tests are executed.** If requirements change it's easy to migrate to another.
36
36
 
@@ -290,7 +290,6 @@ I.waitForElement('#agree_button', 30); // secs
290
290
  // clicks a button only when it is visible
291
291
  I.click('#agree_button');
292
292
  ```
293
- > ℹ See [helpers reference](/reference) for a complete list of all available commands for the helper you use.
294
293
 
295
294
  ## How It Works
296
295
 
package/docs/bdd.md CHANGED
@@ -406,6 +406,39 @@ npx codeceptjs run --grep "@important"
406
406
 
407
407
  Tag should be placed before *Scenario:* or before *Feature:* keyword. In the last case all scenarios of that feature will be added to corresponding group.
408
408
 
409
+ ### Custom types
410
+
411
+ If you need parameter text in more advanced way and you like using [Cucumber expressions](https://docs.cucumber.io/cucumber/cucumber-expressions/) better that regular expressions, use `DefineParameterType` function. You can extend Cucumber Expressions so they automatically convert output parameters to your own types or transforms the match from the regexp.
412
+
413
+ ```js
414
+ DefineParameterType({
415
+ name: 'popup_type',
416
+ regexp: /critical|non-critical/,
417
+ transformer: (match) => {
418
+ return match === 'critical' ? '[class$="error"]'
419
+ : '[class$="warning"]';
420
+ },
421
+ };);
422
+
423
+ Given('I see {popup_type} popup', (popup) => {
424
+ I.seeElement(popup);
425
+ });
426
+ ```
427
+
428
+ ```gherkin
429
+ Scenario: Display error message if user doesn't have permissions
430
+ Given I on "Main" page without permissons
431
+ Then I see error popup
432
+ ```
433
+
434
+ #### Parameters
435
+
436
+ * `name` **[string]** The name the parameter type will be recognised by in output parameters.
437
+ * `regexp` **([string] | [RegExp])** A regexp that will match the parameter. May include capture groups.
438
+ * `transformer` **[function]** A function or method that transforms the match from the regexp.
439
+ * `useForSnippets` **[boolean]** Defaults to `true`. That means this parameter type will be used to generate snippets for undefined steps.
440
+ * `preferForRegexpMatch` **[boolean]** Defaults to `false`. Set to true if you have step definitions that use regular expressions, and you want this parameter type to take precedence over others during a match.
441
+
409
442
  ## Configuration
410
443
 
411
444
  * `gherkin`
package/docs/best.md CHANGED
@@ -90,7 +90,7 @@ However, it's recommended to not overengineer and keep tests simple. If a test c
90
90
 
91
91
  ```js
92
92
  class CheckoutForm {
93
-
93
+
94
94
  fillBillingInformation(data = {}) {
95
95
  // take data in a flexible format
96
96
  // iterate over fields to fill them all
@@ -99,7 +99,7 @@ class CheckoutForm {
99
99
  }
100
100
  }
101
101
 
102
- }
102
+ }
103
103
  module.exports = new CheckoutForm();
104
104
  module.exports.CheckoutForm = CheckoutForm; // for inheritance
105
105
  ```
@@ -108,7 +108,7 @@ module.exports.CheckoutForm = CheckoutForm; // for inheritance
108
108
 
109
109
  ```js
110
110
  class DropDownComponent {
111
-
111
+
112
112
  selectFirstItem(locator) {
113
113
  I.click(locator);
114
114
  I.click('#dropdown-items li');
@@ -133,17 +133,17 @@ class DatePicker {
133
133
  I.click(locator);
134
134
  I.click('.currentDate', '.date-picker');
135
135
  }
136
-
136
+
137
137
  selectInNextMonth(locator, date = '15') {
138
138
  I.click(locator);
139
139
  I.click('show next month', '.date-picker')
140
140
  I.click(date, '.date-picker')
141
141
  }
142
-
142
+
143
143
  }
144
144
 
145
145
 
146
- module.exports = new DatePicker;
146
+ module.exports = new DatePicker();
147
147
  module.exports.DatePicker = DatePicker; // for inheritance
148
148
  ```
149
149
 
@@ -187,7 +187,7 @@ include them like this:
187
187
 
188
188
  ```js
189
189
  // inside codecept conf file
190
- bootstrap: () => {
190
+ bootstrap: () => {
191
191
  codeceptjs.container.append({
192
192
  testUser: {
193
193
  email: 'test@test.com',
@@ -202,7 +202,7 @@ bootstrap: () => {
202
202
  ```js
203
203
  include: {
204
204
  // ...
205
- testData: './config/testData'
205
+ testData: './config/testData'
206
206
 
207
207
  }
208
208
  ```
@@ -2,7 +2,7 @@ let webdriverio;
2
2
  let wdioV4;
3
3
 
4
4
  const fs = require('fs');
5
- const axios = require('axios');
5
+ const axios = require('axios').default;
6
6
 
7
7
  const Webdriver = require('./WebDriver');
8
8
  const AssertionFailedError = require('../assert/error');
@@ -13,6 +13,10 @@ const ConnectionRefused = require('./errors/ConnectionRefused');
13
13
 
14
14
  const mobileRoot = '//*';
15
15
  const webRoot = 'body';
16
+ const supportedPlatform = {
17
+ android: 'Android',
18
+ iOS: 'iOS',
19
+ };
16
20
 
17
21
  /**
18
22
  * Appium helper extends [Webriver](http://codecept.io/helpers/WebDriver/) helper.
@@ -131,6 +135,7 @@ class Appium extends Webdriver {
131
135
  super(config);
132
136
 
133
137
  this.isRunning = false;
138
+ this.axios = axios.create();
134
139
 
135
140
  webdriverio = require('webdriverio');
136
141
  (!webdriverio.VERSION || webdriverio.VERSION.indexOf('4') !== 0) ? wdioV4 = false : wdioV4 = true;
@@ -185,7 +190,7 @@ class Appium extends Webdriver {
185
190
  config.capabilities.app = config.app || config.capabilities.app;
186
191
  config.capabilities.platformName = config.platform || config.capabilities.platformName;
187
192
  config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel
188
- config.waitForTimeout /= 1000; // convert to seconds
193
+ config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds
189
194
 
190
195
  // [CodeceptJS compatible] transform host to hostname
191
196
  config.hostname = config.host || config.hostname;
@@ -215,8 +220,8 @@ class Appium extends Webdriver {
215
220
  name: 'platform',
216
221
  message: 'Mobile Platform',
217
222
  type: 'list',
218
- choices: ['iOS', 'Android'],
219
- default: 'Android',
223
+ choices: ['iOS', supportedPlatform.android],
224
+ default: supportedPlatform.android,
220
225
  }, {
221
226
  name: 'device',
222
227
  message: 'Device to run tests on',
@@ -427,6 +432,23 @@ class Appium extends Webdriver {
427
432
  fn();
428
433
  }
429
434
 
435
+ /**
436
+ * Returns app installation status.
437
+ *
438
+ * ```js
439
+ * I.checkIfAppIsInstalled("com.example.android.apis");
440
+ * ```
441
+ *
442
+ * @param {string} bundleId String ID of bundled app
443
+ * @return {Promise<boolean>}
444
+ *
445
+ * Appium: support only Android
446
+ */
447
+ async checkIfAppIsInstalled(bundleId) {
448
+ onlyForApps.call(this, supportedPlatform.android);
449
+ return this.browser.isAppInstalled(bundleId);
450
+ }
451
+
430
452
  /**
431
453
  * Check if an app is installed.
432
454
  *
@@ -440,7 +462,7 @@ class Appium extends Webdriver {
440
462
  * Appium: support only Android
441
463
  */
442
464
  async seeAppIsInstalled(bundleId) {
443
- onlyForApps.call(this, 'Android');
465
+ onlyForApps.call(this, supportedPlatform.android);
444
466
  const res = await this.browser.isAppInstalled(bundleId);
445
467
  return truth(`app ${bundleId}`, 'to be installed').assert(res);
446
468
  }
@@ -458,7 +480,7 @@ class Appium extends Webdriver {
458
480
  * Appium: support only Android
459
481
  */
460
482
  async seeAppIsNotInstalled(bundleId) {
461
- onlyForApps.call(this, 'Android');
483
+ onlyForApps.call(this, supportedPlatform.android);
462
484
  const res = await this.browser.isAppInstalled(bundleId);
463
485
  return truth(`app ${bundleId}`, 'to be installed').negate(res);
464
486
  }
@@ -475,7 +497,7 @@ class Appium extends Webdriver {
475
497
  * Appium: support only Android
476
498
  */
477
499
  async installApp(path) {
478
- onlyForApps.call(this, 'Android');
500
+ onlyForApps.call(this, supportedPlatform.android);
479
501
  return this.browser.installApp(path);
480
502
  }
481
503
 
@@ -492,19 +514,35 @@ class Appium extends Webdriver {
492
514
  * @param {string} [bundleId] ID of bundle
493
515
  */
494
516
  async removeApp(appId, bundleId) {
495
- onlyForApps.call(this, 'Android');
517
+ onlyForApps.call(this, supportedPlatform.android);
496
518
 
497
519
  if (wdioV4) {
498
520
  return this.browser.removeApp(bundleId);
499
521
  }
500
522
 
501
- return axios({
523
+ return this.axios({
502
524
  method: 'post',
503
525
  url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/device/remove_app`,
504
526
  data: { appId, bundleId },
505
527
  });
506
528
  }
507
529
 
530
+ /**
531
+ * Reset the currently running app for current session.
532
+ *
533
+ * ```js
534
+ * I.resetApp();
535
+ * ```
536
+ *
537
+ */
538
+ async resetApp() {
539
+ onlyForApps.call(this);
540
+ return this.axios({
541
+ method: 'post',
542
+ url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset`,
543
+ });
544
+ }
545
+
508
546
  /**
509
547
  * Check current activity on an Android device.
510
548
  *
@@ -517,7 +555,7 @@ class Appium extends Webdriver {
517
555
  * Appium: support only Android
518
556
  */
519
557
  async seeCurrentActivityIs(currentActivity) {
520
- onlyForApps.call(this, 'Android');
558
+ onlyForApps.call(this, supportedPlatform.android);
521
559
  const res = await this.browser.getCurrentActivity();
522
560
  return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity);
523
561
  }
@@ -534,7 +572,7 @@ class Appium extends Webdriver {
534
572
  * Appium: support only Android
535
573
  */
536
574
  async seeDeviceIsLocked() {
537
- onlyForApps.call(this, 'Android');
575
+ onlyForApps.call(this, supportedPlatform.android);
538
576
  const res = await this.browser.isLocked();
539
577
  return truth('device', 'to be locked').assert(res);
540
578
  }
@@ -551,7 +589,7 @@ class Appium extends Webdriver {
551
589
  * Appium: support only Android
552
590
  */
553
591
  async seeDeviceIsUnlocked() {
554
- onlyForApps.call(this, 'Android');
592
+ onlyForApps.call(this, supportedPlatform.android);
555
593
  const res = await this.browser.isLocked();
556
594
  return truth('device', 'to be locked').negate(res);
557
595
  }
@@ -579,7 +617,7 @@ class Appium extends Webdriver {
579
617
  currentOrientation = res;
580
618
  }
581
619
 
582
- const res = await axios({
620
+ const res = await this.axios({
583
621
  method: 'get',
584
622
  url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
585
623
  });
@@ -606,7 +644,7 @@ class Appium extends Webdriver {
606
644
  return this.browser.setOrientation(orientation);
607
645
  }
608
646
 
609
- return axios({
647
+ return this.axios({
610
648
  method: 'post',
611
649
  url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
612
650
  data: { orientation },
@@ -657,7 +695,7 @@ class Appium extends Webdriver {
657
695
  * Appium: support only Android
658
696
  */
659
697
  async grabCurrentActivity() {
660
- onlyForApps.call(this, 'Android');
698
+ onlyForApps.call(this, supportedPlatform.android);
661
699
  return this.browser.getCurrentActivity();
662
700
  }
663
701
 
@@ -675,7 +713,7 @@ class Appium extends Webdriver {
675
713
  * Appium: support only Android
676
714
  */
677
715
  async grabNetworkConnection() {
678
- onlyForApps.call(this, 'Android');
716
+ onlyForApps.call(this, supportedPlatform.android);
679
717
  const res = await this.browser.getNetworkConnection();
680
718
  return {
681
719
  value: res,
@@ -795,7 +833,7 @@ class Appium extends Webdriver {
795
833
  * @return {Promise<void>}
796
834
  */
797
835
  async startActivity(appPackage, appActivity) {
798
- onlyForApps.call(this, 'Android');
836
+ onlyForApps.call(this, supportedPlatform.android);
799
837
  return this.browser.startActivity(appPackage, appActivity);
800
838
  }
801
839
 
@@ -820,7 +858,7 @@ class Appium extends Webdriver {
820
858
  * Appium: support only Android
821
859
  */
822
860
  async setNetworkConnection(value) {
823
- onlyForApps.call(this, 'Android');
861
+ onlyForApps.call(this, supportedPlatform.android);
824
862
  return this.browser.setNetworkConnection(value);
825
863
  }
826
864
 
@@ -877,7 +915,7 @@ class Appium extends Webdriver {
877
915
  * Appium: support only Android
878
916
  */
879
917
  async sendDeviceKeyEvent(keyValue) {
880
- onlyForApps.call(this, 'Android');
918
+ onlyForApps.call(this, supportedPlatform.android);
881
919
  if (wdioV4) {
882
920
  return this.browser.sendKeyEvent(keyValue);
883
921
  }
@@ -896,7 +934,7 @@ class Appium extends Webdriver {
896
934
  * Appium: support only Android
897
935
  */
898
936
  async openNotifications() {
899
- onlyForApps.call(this, 'Android');
937
+ onlyForApps.call(this, supportedPlatform.android);
900
938
  return this.browser.openNotifications();
901
939
  }
902
940
 
@@ -965,7 +1003,7 @@ class Appium extends Webdriver {
965
1003
  onlyForApps.call(this);
966
1004
  const res = await this.browser.$(parseLocator.call(this, locator));
967
1005
  // if (!res.length) throw new ElementNotFound(locator, 'was not found in UI');
968
- return this.performSwipe(await res.getLocation(), { x: await res.getLocation().x + xoffset, y: await res.getLocation().y + yoffset });
1006
+ return this.performSwipe(await res.getLocation(), { x: (await res.getLocation()).x + xoffset, y: (await res.getLocation()).y + yoffset });
969
1007
  }
970
1008
  /* eslint-enable */
971
1009
 
@@ -1148,7 +1186,7 @@ class Appium extends Webdriver {
1148
1186
  direction = 'swipeRight';
1149
1187
  break;
1150
1188
  }
1151
- timeout = timeout || this.options.waitForTimeout;
1189
+ timeout = timeout || this.options.waitForTimeoutInSeconds;
1152
1190
 
1153
1191
  const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`;
1154
1192
  const browser = this.browser;
@@ -139,9 +139,9 @@ class GraphQL extends Helper {
139
139
  * ```
140
140
  *
141
141
  * @param {String} query
142
- * @param {object} variables that may go along with the query
143
- * @param {object} options are additional query options
144
- * @param {object} headers
142
+ * @param {object} [variables] that may go along with the query
143
+ * @param {object} [options] are additional query options
144
+ * @param {object} [headers]
145
145
  * @return Promise<any>
146
146
  */
147
147
  async sendQuery(query, variables, options = {}, headers = {}) {
@@ -179,9 +179,9 @@ class GraphQL extends Helper {
179
179
  * ```
180
180
  *
181
181
  * @param {String} mutation
182
- * @param {object} variables that may go along with the mutation
183
- * @param {object} options are additional query options
184
- * @param {object} headers
182
+ * @param {object} [variables] that may go along with the mutation
183
+ * @param {object} [options] are additional query options
184
+ * @param {object} [headers]
185
185
  * @return Promise<any>
186
186
  */
187
187
  async sendMutation(mutation, variables, options = {}, headers = {}) {
@@ -128,7 +128,7 @@ class Nightmare extends Helper {
128
128
  this.evaluate_now((by, locator, contextEl) => {
129
129
  const res = window.codeceptjs.findAndStoreElement(by, locator, contextEl);
130
130
  if (res === null) {
131
- throw new Error(`Element ${locator} couldn't be located by ${by}`);
131
+ throw new Error(`Element ${(new Locator(locator))} couldn't be located by ${by}`);
132
132
  }
133
133
  return res;
134
134
  }, done, by, value, contextEl);
@@ -708,7 +708,7 @@ class Nightmare extends Helper {
708
708
  */
709
709
  async seeNumberOfElements(locator, num) {
710
710
  const elements = await this._locate(locator);
711
- return equals(`expected number of elements (${locator}) is ${num}, but found ${elements.length}`).assert(elements.length, num);
711
+ return equals(`expected number of elements (${(new Locator(locator))}) is ${num}, but found ${elements.length}`).assert(elements.length, num);
712
712
  }
713
713
 
714
714
  /**
@@ -726,7 +726,7 @@ class Nightmare extends Helper {
726
726
  */
727
727
  async seeNumberOfVisibleElements(locator, num) {
728
728
  const res = await this.grabNumberOfVisibleElements(locator);
729
- return equals(`expected number of visible elements (${locator}) is ${num}, but found ${res}`).assert(res, num);
729
+ return equals(`expected number of visible elements (${(new Locator(locator))}) is ${num}, but found ${res}`).assert(res, num);
730
730
  }
731
731
 
732
732
  /**
@@ -1839,7 +1839,7 @@ class Nightmare extends Helper {
1839
1839
  height: Math.floor(rect.height),
1840
1840
  };
1841
1841
 
1842
- this.debug(`Screenshot of ${locator} element has been saved to ${outputFile}`);
1842
+ this.debug(`Screenshot of ${(new Locator(locator))} element has been saved to ${outputFile}`);
1843
1843
  // take the screenshot
1844
1844
  await this.browser.screenshot(outputFile, button_clip);
1845
1845
  }