codeceptjs 3.3.5 → 3.3.7-beta.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/bin/codecept.js +40 -3
  3. package/docs/basics.md +24 -1
  4. package/docs/build/Appium.js +41 -22
  5. package/docs/build/FileSystem.js +1 -1
  6. package/docs/build/Nightmare.js +106 -57
  7. package/docs/build/Playwright.js +187 -111
  8. package/docs/build/Protractor.js +132 -70
  9. package/docs/build/Puppeteer.js +143 -76
  10. package/docs/build/REST.js +2 -2
  11. package/docs/build/TestCafe.js +107 -57
  12. package/docs/build/WebDriver.js +162 -89
  13. package/docs/changelog.md +37 -7
  14. package/docs/commands.md +5 -3
  15. package/docs/configuration.md +5 -5
  16. package/docs/helpers/Appium.md +25 -23
  17. package/docs/helpers/FileSystem.md +1 -1
  18. package/docs/helpers/Nightmare.md +57 -57
  19. package/docs/helpers/Playwright.md +76 -75
  20. package/docs/helpers/Puppeteer.md +76 -85
  21. package/docs/helpers/REST.md +1 -1
  22. package/docs/helpers/TestCafe.md +57 -57
  23. package/docs/helpers/WebDriver.md +84 -97
  24. package/docs/quickstart.md +1 -1
  25. package/docs/reports.md +1 -1
  26. package/docs/secrets.md +1 -1
  27. package/docs/webapi/appendField.mustache +1 -1
  28. package/docs/webapi/attachFile.mustache +3 -3
  29. package/docs/webapi/checkOption.mustache +1 -1
  30. package/docs/webapi/clearCookie.mustache +1 -1
  31. package/docs/webapi/clearField.mustache +1 -1
  32. package/docs/webapi/click.mustache +1 -1
  33. package/docs/webapi/clickLink.mustache +1 -1
  34. package/docs/webapi/closeCurrentTab.mustache +1 -1
  35. package/docs/webapi/closeOtherTabs.mustache +1 -1
  36. package/docs/webapi/dontSee.mustache +1 -1
  37. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
  38. package/docs/webapi/dontSeeCookie.mustache +1 -1
  39. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +1 -1
  40. package/docs/webapi/dontSeeElement.mustache +1 -1
  41. package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
  42. package/docs/webapi/dontSeeInCurrentUrl.mustache +1 -1
  43. package/docs/webapi/dontSeeInField.mustache +1 -1
  44. package/docs/webapi/dontSeeInSource.mustache +1 -1
  45. package/docs/webapi/dontSeeInTitle.mustache +1 -1
  46. package/docs/webapi/doubleClick.mustache +1 -1
  47. package/docs/webapi/downloadFile.mustache +1 -1
  48. package/docs/webapi/dragAndDrop.mustache +1 -1
  49. package/docs/webapi/dragSlider.mustache +1 -1
  50. package/docs/webapi/executeAsyncScript.mustache +1 -1
  51. package/docs/webapi/executeScript.mustache +1 -1
  52. package/docs/webapi/fillField.mustache +1 -1
  53. package/docs/webapi/forceClick.mustache +1 -1
  54. package/docs/webapi/forceRightClick.mustache +1 -1
  55. package/docs/webapi/moveCursorTo.mustache +1 -1
  56. package/docs/webapi/openNewTab.mustache +1 -1
  57. package/docs/webapi/pressKey.mustache +1 -1
  58. package/docs/webapi/pressKeyDown.mustache +1 -1
  59. package/docs/webapi/pressKeyUp.mustache +1 -1
  60. package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
  61. package/docs/webapi/refreshPage.mustache +1 -1
  62. package/docs/webapi/resizeWindow.mustache +1 -1
  63. package/docs/webapi/rightClick.mustache +1 -1
  64. package/docs/webapi/saveElementScreenshot.mustache +2 -2
  65. package/docs/webapi/saveScreenshot.mustache +2 -2
  66. package/docs/webapi/say.mustache +1 -1
  67. package/docs/webapi/scrollIntoView.mustache +1 -1
  68. package/docs/webapi/scrollPageToBottom.mustache +1 -1
  69. package/docs/webapi/scrollPageToTop.mustache +1 -1
  70. package/docs/webapi/scrollTo.mustache +1 -1
  71. package/docs/webapi/see.mustache +1 -1
  72. package/docs/webapi/seeAttributesOnElements.mustache +1 -1
  73. package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
  74. package/docs/webapi/seeCookie.mustache +1 -1
  75. package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
  76. package/docs/webapi/seeCurrentUrlEquals.mustache +1 -1
  77. package/docs/webapi/seeElement.mustache +1 -1
  78. package/docs/webapi/seeElementInDOM.mustache +1 -1
  79. package/docs/webapi/seeInCurrentUrl.mustache +1 -1
  80. package/docs/webapi/seeInField.mustache +1 -1
  81. package/docs/webapi/seeInPopup.mustache +1 -1
  82. package/docs/webapi/seeInSource.mustache +1 -1
  83. package/docs/webapi/seeInTitle.mustache +1 -1
  84. package/docs/webapi/seeNumberOfElements.mustache +1 -1
  85. package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
  86. package/docs/webapi/seeTextEquals.mustache +1 -1
  87. package/docs/webapi/seeTitleEquals.mustache +1 -1
  88. package/docs/webapi/selectOption.mustache +1 -1
  89. package/docs/webapi/setCookie.mustache +1 -1
  90. package/docs/webapi/setGeoLocation.mustache +1 -1
  91. package/docs/webapi/switchTo.mustache +1 -1
  92. package/docs/webapi/switchToNextTab.mustache +1 -1
  93. package/docs/webapi/switchToPreviousTab.mustache +1 -1
  94. package/docs/webapi/type.mustache +1 -1
  95. package/docs/webapi/uncheckOption.mustache +1 -1
  96. package/docs/webapi/wait.mustache +1 -1
  97. package/docs/webapi/waitForClickable.mustache +1 -1
  98. package/docs/webapi/waitForDetached.mustache +1 -1
  99. package/docs/webapi/waitForElement.mustache +1 -1
  100. package/docs/webapi/waitForEnabled.mustache +1 -1
  101. package/docs/webapi/waitForFunction.mustache +1 -1
  102. package/docs/webapi/waitForInvisible.mustache +1 -1
  103. package/docs/webapi/waitForText.mustache +1 -1
  104. package/docs/webapi/waitForValue.mustache +1 -1
  105. package/docs/webapi/waitForVisible.mustache +1 -1
  106. package/docs/webapi/waitInUrl.mustache +1 -1
  107. package/docs/webapi/waitNumberOfVisibleElements.mustache +1 -1
  108. package/docs/webapi/waitToHide.mustache +1 -1
  109. package/docs/webapi/waitUrlEquals.mustache +1 -1
  110. package/lib/command/configMigrate.js +1 -1
  111. package/lib/command/dryRun.js +2 -1
  112. package/lib/command/generate.js +35 -18
  113. package/lib/command/run-rerun.js +38 -0
  114. package/lib/command/utils.js +13 -3
  115. package/lib/config.js +2 -2
  116. package/lib/data/context.js +7 -0
  117. package/lib/helper/Appium.js +6 -4
  118. package/lib/helper/FileSystem.js +1 -1
  119. package/lib/helper/Nightmare.js +1 -1
  120. package/lib/helper/Playwright.js +50 -38
  121. package/lib/helper/Protractor.js +1 -1
  122. package/lib/helper/REST.js +2 -2
  123. package/lib/helper/TestCafe.js +1 -1
  124. package/lib/helper/WebDriver.js +7 -7
  125. package/lib/plugin/wdio.js +11 -2
  126. package/lib/utils.js +3 -0
  127. package/package.json +2 -2
  128. package/typings/index.d.ts +46 -46
  129. package/typings/types.d.ts +497 -443
@@ -7,4 +7,4 @@ I.waitForInvisible('#popup');
7
7
 
8
8
  @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -10,4 +10,4 @@ I.waitForText('Thank you, form has been submitted', 5, '#modal');
10
10
  @param {string }text to wait for.
11
11
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
12
12
  @param {CodeceptJS.LocatorOrString} [context] (optional) element located by CSS|XPath|strict locator.
13
- [!] returns a _promise_ which is synchronized internally by recorder
13
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -7,4 +7,4 @@ I.waitForValue('//input', "GoodValue");
7
7
  @param {LocatorOrString} field input field.
8
8
  @param {string }value expected value.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -7,4 +7,4 @@ I.waitForVisible('#popup');
7
7
 
8
8
  @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -6,4 +6,4 @@ I.waitInUrl('/info', 2);
6
6
 
7
7
  @param {string} urlPart value to check.
8
8
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
9
- [!] returns a _promise_ which is synchronized internally by recorder
9
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -7,4 +7,4 @@ I.waitNumberOfVisibleElements('a', 3);
7
7
  @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
8
8
  @param {number} num number of elements.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -7,4 +7,4 @@ I.waitToHide('#popup');
7
7
 
8
8
  @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -7,4 +7,4 @@ I.waitUrlEquals('http://127.0.0.1:8000/info');
7
7
 
8
8
  @param {string} urlPart value to check.
9
9
  @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
10
- [!] returns a _promise_ which is synchronized internally by recorder
10
+ ⚠️ returns a _promise_ which is synchronized internally by recorder
@@ -50,7 +50,7 @@ module.exports = function (initPath) {
50
50
  },
51
51
  ]).then((result) => {
52
52
  if (result.configFile) {
53
- const jsonConfigFile = path.join(testsPath, 'codecept.json');
53
+ const jsonConfigFile = path.join(testsPath, 'codecept.js');
54
54
  const config = JSON.parse(fs.readFileSync(jsonConfigFile, 'utf8'));
55
55
  config.name = testsPath.split(path.sep).pop();
56
56
 
@@ -27,7 +27,7 @@ module.exports = async function (test, options) {
27
27
  codecept = new Codecept(config, options);
28
28
  codecept.init(testRoot);
29
29
 
30
- if (options.bootstrap) await codecept.runBootstrap();
30
+ if (options.bootstrap) await codecept.bootstrap();
31
31
 
32
32
  codecept.loadTests();
33
33
  store.dryRun = true;
@@ -71,6 +71,7 @@ function printTests(files) {
71
71
  output.print('');
72
72
  output.success(` Total: ${numOfSuites} suites | ${numOfTests} tests `);
73
73
  printFooter();
74
+ process.exit(0);
74
75
  }
75
76
 
76
77
  function printFooter() {
@@ -3,13 +3,16 @@ const fs = require('fs');
3
3
  const inquirer = require('inquirer');
4
4
  const mkdirp = require('mkdirp');
5
5
  const path = require('path');
6
-
7
- const { fileExists, ucfirst, lcfirst } = require('../utils');
6
+ const {
7
+ fileExists, ucfirst, lcfirst, beautify,
8
+ } = require('../utils');
8
9
  const output = require('../output');
9
10
  const {
10
- getConfig, getTestRoot, safeFileWrite,
11
+ getConfig, getTestRoot, safeFileWrite, readConfig,
11
12
  } = require('./utils');
12
13
 
14
+ let extension = 'js';
15
+
13
16
  const testTemplate = `Feature('{{feature}}');
14
17
 
15
18
  Scenario('test something', ({ {{actor}} }) => {
@@ -26,7 +29,7 @@ module.exports.test = function (genPath) {
26
29
  output.print('Creating a new test...');
27
30
  output.print('----------------------');
28
31
 
29
- const defaultExt = config.tests.match(/([^\*/]*?)$/)[1] || '_test.js';
32
+ const defaultExt = config.tests.match(/([^\*/]*?)$/)[1] || `_test.${extension}`;
30
33
 
31
34
  return inquirer.prompt([
32
35
  {
@@ -79,25 +82,34 @@ module.exports.pageObject = function (genPath, opts) {
79
82
  const kind = opts.T || 'page';
80
83
  if (!config) return;
81
84
 
85
+ let configFile = path.join(testsPath, `codecept.conf.${extension}`);
86
+
87
+ if (!fileExists(configFile)) {
88
+ extension = 'ts';
89
+ configFile = path.join(testsPath, `codecept.conf.${extension}`);
90
+ }
82
91
  output.print(`Creating a new ${kind} object`);
83
92
  output.print('--------------------------');
84
93
 
85
- return inquirer.prompt([{
86
- type: 'input',
87
- name: 'name',
88
- message: `Name of a ${kind} object`,
89
- validate: (val) => !!val,
90
- }, {
91
- type: 'input',
92
- name: 'filename',
93
- message: 'Where should it be stored',
94
- default: answers => `./${kind}s/${answers.name}.js`,
95
- }]).then((result) => {
94
+ return inquirer.prompt([
95
+ {
96
+ type: 'input',
97
+ name: 'name',
98
+ message: `Name of a ${kind} object`,
99
+ validate: (val) => !!val,
100
+ },
101
+ {
102
+ type: 'input',
103
+ name: 'filename',
104
+ message: 'Where should it be stored',
105
+ default: answers => `./${kind}s/${answers.name}.${extension}`,
106
+ }]).then((result) => {
96
107
  const pageObjectFile = path.join(testsPath, result.filename);
97
108
  const dir = path.dirname(pageObjectFile);
98
109
  if (!fileExists(dir)) fs.mkdirSync(dir);
99
110
 
100
111
  let actor = 'actor';
112
+
101
113
  if (config.include.I) {
102
114
  let actorPath = config.include.I;
103
115
  if (actorPath.charAt(0) === '.') { // relative path
@@ -107,16 +119,21 @@ module.exports.pageObject = function (genPath, opts) {
107
119
  }
108
120
  if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return;
109
121
  const name = lcfirst(result.name) + ucfirst(kind);
122
+ let data = readConfig(configFile);
110
123
  config.include[name] = result.filename;
124
+ data = data.replace(/include[\s\S][^\}]*/i, `include: ${JSON.stringify(config.include).slice(0, -1)}`);
125
+
126
+ fs.writeFileSync(configFile, beautify(data), 'utf-8');
111
127
 
112
128
  output.success(`${ucfirst(kind)} object for ${result.name} was created in ${pageObjectFile}`);
113
- output.print(`Update your config file (${colors.cyan('include')} section):
129
+ output.print(`Your config file (${colors.cyan('include')} section) has included the new created PO:
114
130
 
115
131
  include: {
132
+ ...
116
133
  ${name}: '${result.filename}',
117
134
  },`);
118
135
 
119
- output.print(`Use ${output.colors.bold(name)} as parameter in test scenarios to access this object`);
136
+ output.print(`Use ${output.colors.bold(colors.cyan(name))} as parameter in test scenarios to access this object`);
120
137
  });
121
138
  };
122
139
 
@@ -163,7 +180,7 @@ module.exports.helper = function (genPath) {
163
180
  type: 'input',
164
181
  name: 'filename',
165
182
  message: 'Where should it be stored',
166
- default: answers => `./${answers.name.toLowerCase()}_helper.js`,
183
+ default: answers => `./${answers.name.toLowerCase()}_helper.${extension}`,
167
184
  }]).then((result) => {
168
185
  const name = ucfirst(result.name);
169
186
  const helperFile = path.join(testsPath, result.filename);
@@ -0,0 +1,38 @@
1
+ const { getConfig, getTestRoot } = require('./utils');
2
+ const { printError, createOutputDir } = require('./utils');
3
+ const Config = require('../config');
4
+ const Codecept = require('../rerun');
5
+
6
+ module.exports = async function (test, options) {
7
+ // registering options globally to use in config
8
+ // Backward compatibility for --profile
9
+ process.profile = options.profile;
10
+ process.env.profile = options.profile;
11
+ const configFile = options.config;
12
+
13
+ let config = getConfig(configFile);
14
+ if (options.override) {
15
+ config = Config.append(JSON.parse(options.override));
16
+ }
17
+ const testRoot = getTestRoot(configFile);
18
+ createOutputDir(config, testRoot);
19
+
20
+ function processError(err) {
21
+ printError(err);
22
+ process.exit(1);
23
+ }
24
+ const codecept = new Codecept(config, options);
25
+
26
+ try {
27
+ codecept.init(testRoot);
28
+
29
+ await codecept.bootstrap();
30
+ codecept.loadTests(test);
31
+ await codecept.run();
32
+ } catch (err) {
33
+ printError(err);
34
+ process.exitCode = 1;
35
+ } finally {
36
+ await codecept.teardown();
37
+ }
38
+ };
@@ -18,6 +18,15 @@ module.exports.getConfig = function (configFile) {
18
18
  }
19
19
  };
20
20
 
21
+ module.exports.readConfig = function (configFile) {
22
+ try {
23
+ const data = fs.readFileSync(configFile, 'utf8');
24
+ return data;
25
+ } catch (err) {
26
+ output.error(err);
27
+ }
28
+ };
29
+
21
30
  function getTestRoot(currentPath) {
22
31
  if (!currentPath) currentPath = '.';
23
32
  if (!path.isAbsolute(currentPath)) currentPath = path.join(process.cwd(), currentPath);
@@ -33,11 +42,12 @@ function fail(msg) {
33
42
 
34
43
  module.exports.fail = fail;
35
44
 
36
- function updateConfig(testsPath, config, key) {
37
- const configFile = path.join(testsPath, 'codecept.conf.js');
45
+ function updateConfig(testsPath, config, key, extension = 'js') {
46
+ const configFile = path.join(testsPath, `codecept.conf.${extension}`);
38
47
  if (!fileExists(configFile)) {
39
48
  console.log();
40
- console.log(`${output.colors.bold.red('codecept.conf.js config can\'t be updated automatically')}`);
49
+ const msg = `codecept.conf.${extension} config can\'t be updated automatically`;
50
+ console.log(`${output.colors.bold.red(msg)}`);
41
51
  console.log('Please update it manually:');
42
52
  console.log();
43
53
  console.log(`${key}: ${config[key]}`);
package/lib/config.js CHANGED
@@ -41,7 +41,7 @@ let config = {};
41
41
  const configFileNames = [
42
42
  'codecept.config.js',
43
43
  'codecept.conf.js',
44
- 'codecept.json',
44
+ 'codecept.js',
45
45
  'codecept.config.ts',
46
46
  'codecept.conf.ts',
47
47
  ];
@@ -69,7 +69,7 @@ class Config {
69
69
  * If directory provided:
70
70
  * * try to load `codecept.config.js` from it
71
71
  * * try to load `codecept.conf.js` from it
72
- * * try to load `codecept.json` from it
72
+ * * try to load `codecept.js` from it
73
73
  * If none of above: fail.
74
74
  *
75
75
  * @param {string} configFile
@@ -1,6 +1,7 @@
1
1
  const { isGenerator } = require('../utils');
2
2
  const DataTable = require('./table');
3
3
  const DataScenarioConfig = require('./dataScenarioConfig');
4
+ const Secret = require('../secret');
4
5
 
5
6
  module.exports = function (context) {
6
7
  context.Data = function (dataTable) {
@@ -70,6 +71,12 @@ function replaceTitle(title, dataRow) {
70
71
  // it should be printed
71
72
  if (Object.prototype.toString.call(dataRow.data) === (Object()).toString()
72
73
  && dataRow.data.toString() !== (Object()).toString()) {
74
+ Object.entries(dataRow.data).forEach(entry => {
75
+ const [key, value] = entry;
76
+ if (value instanceof Secret) {
77
+ dataRow.data[key] = value.getMasked();
78
+ }
79
+ });
73
80
  return `${title} | ${dataRow.data}`;
74
81
  }
75
82
 
@@ -34,7 +34,7 @@ const webRoot = 'body';
34
34
  *
35
35
  * ## Helper configuration
36
36
  *
37
- * This helper should be configured in codecept.json or codecept.conf.js
37
+ * This helper should be configured in codecept.conf.ts or codecept.conf.js
38
38
  *
39
39
  * * `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
40
40
  * * `host`: (default: 'localhost') Appium host
@@ -788,9 +788,11 @@ class Appium extends Webdriver {
788
788
  * I.startActivity('io.selendroid.testapp', '.RegisterUserActivity');
789
789
  * ```
790
790
  *
791
- * @return {Promise<void>}
792
- *
793
791
  * Appium: support only Android
792
+ *
793
+ * @param {string} appPackage
794
+ * @param {string} appActivity
795
+ * @return {Promise<void>}
794
796
  */
795
797
  async startActivity(appPackage, appActivity) {
796
798
  onlyForApps.call(this, 'Android');
@@ -1458,7 +1460,7 @@ class Appium extends Webdriver {
1458
1460
  }
1459
1461
 
1460
1462
  /**
1461
- * Saves a screenshot to ouput folder (set in codecept.json or codecept.conf.js).
1463
+ * Saves a screenshot to ouput folder (set in codecept.conf.ts or codecept.conf.js).
1462
1464
  * Filename is relative to output folder.
1463
1465
  *
1464
1466
  * ```js
@@ -13,7 +13,7 @@ const { fileEquals } = require('../assert/equal');
13
13
  *
14
14
  * ```js
15
15
  * I.amInPath('test');
16
- * I.seeFile('codecept.json');
16
+ * I.seeFile('codecept.js');
17
17
  * I.seeInThisFile('FileSystem');
18
18
  * I.dontSeeInThisFile("WebDriver");
19
19
  * ```
@@ -35,7 +35,7 @@ let withinStatus = false;
35
35
  *
36
36
  * ## Configuration
37
37
  *
38
- * This helper should be configured in codecept.json or codecept.conf.js
38
+ * This helper should be configured in codecept.conf.ts or codecept.conf.js
39
39
  *
40
40
  * * `url` - base url of website to be tested
41
41
  * * `restart` (optional, default: true) - restart browser between tests.
@@ -62,13 +62,14 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
62
62
  * @prop {boolean} [disableScreenshots=false] - don't save screenshot on failure.
63
63
  * @prop {any} [emulate] - browser in device emulation mode.
64
64
  * @prop {boolean} [video=false] - enables video recording for failed tests; videos are saved into `output/videos` folder
65
+ * @prop {boolean} [keepVideoForPassedTests=false] - save videos for passed tests; videos are saved into `output/videos` folder
65
66
  * @prop {boolean} [trace=false] - record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
66
67
  * @prop {boolean} [fullPageScreenshots=false] - make full page screenshots on failure.
67
68
  * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
68
69
  * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to 'session'.
69
70
  * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to 'session'.
70
71
  * @prop {number} [waitForAction] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
71
- * @prop {number} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/main/docs/api.md#pagewaitfornavigationoptions).
72
+ * @prop {string} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://playwright.dev/docs/api/class-page#page-wait-for-navigation).
72
73
  * @prop {number} [pressKeyDelay=10] - Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
73
74
  * @prop {number} [getPageTimeout] - config option to set maximum navigation time in milliseconds.
74
75
  * @prop {number} [waitForTimeout] - default wait* timeout in ms. Default: 1000.
@@ -476,6 +477,8 @@ class Playwright extends Helper {
476
477
 
477
478
  // close other sessions
478
479
  try {
480
+ if (!this.browser.contexts) return this.browser;
481
+
479
482
  const contexts = await this.browser.contexts();
480
483
  const currentContext = contexts[0];
481
484
  if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
@@ -548,22 +551,22 @@ class Playwright extends Helper {
548
551
  }
549
552
 
550
553
  /**
551
- * Use Playwright API inside a test.
552
- *
553
- * First argument is a description of an action.
554
- * Second argument is async function that gets this helper as parameter.
555
- *
556
- * { [`page`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md), [`browserContext`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md) [`browser`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md) } objects from Playwright API are available.
557
- *
558
- * ```js
559
- * I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
560
- * await browserContext.setOffline(true);
561
- * });
562
- * ```
563
- *
564
- * @param {string} description used to show in logs.
565
- * @param {function} fn async function that executed with Playwright helper as argumen
566
- */
554
+ * Use Playwright API inside a test.
555
+ *
556
+ * First argument is a description of an action.
557
+ * Second argument is async function that gets this helper as parameter.
558
+ *
559
+ * { [`page`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md), [`browserContext`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md) [`browser`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md) } objects from Playwright API are available.
560
+ *
561
+ * ```js
562
+ * I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
563
+ * await browserContext.setOffline(true);
564
+ * });
565
+ * ```
566
+ *
567
+ * @param {string} description used to show in logs.
568
+ * @param {function} fn async function that executed with Playwright helper as argumen
569
+ */
567
570
  usePlaywrightTo(description, fn) {
568
571
  return this._useTo(...arguments);
569
572
  }
@@ -2029,7 +2032,11 @@ class Playwright extends Helper {
2029
2032
  }
2030
2033
 
2031
2034
  if (this.options.recordVideo && this.page && this.page.video()) {
2035
+ const videoPath = `${global.output_dir}/videos/${clearString(test.title)}.failed.webm`;
2032
2036
  test.artifacts.video = await this.page.video().path();
2037
+ fs.rename(test.artifacts.video, videoPath, (() => {
2038
+ test.artifacts.video = videoPath;
2039
+ }));
2033
2040
  }
2034
2041
 
2035
2042
  if (this.options.trace) {
@@ -2041,8 +2048,13 @@ class Playwright extends Helper {
2041
2048
 
2042
2049
  async _passed(test) {
2043
2050
  if (this.options.recordVideo && this.page && this.page.video()) {
2051
+ const videoPath = `${global.output_dir}/videos/${clearString(test.title)}.passed.webm`;
2052
+
2044
2053
  if (this.options.keepVideoForPassedTests) {
2045
2054
  test.artifacts.video = await this.page.video().path();
2055
+ fs.rename(test.artifacts.video, videoPath, (() => {
2056
+ test.artifacts.video = videoPath;
2057
+ }));
2046
2058
  } else {
2047
2059
  this.page.video().delete().catch(e => {});
2048
2060
  }
@@ -2456,32 +2468,32 @@ class Playwright extends Helper {
2456
2468
  }
2457
2469
 
2458
2470
  /**
2459
- * Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
2460
- *
2461
- * ```js
2462
- * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
2463
- * ```
2464
- * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
2465
- *
2466
- * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2467
- * @param {function} [handler] a function to process reques
2468
- */
2471
+ * Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
2472
+ *
2473
+ * ```js
2474
+ * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
2475
+ * ```
2476
+ * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
2477
+ *
2478
+ * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2479
+ * @param {function} [handler] a function to process reques
2480
+ */
2469
2481
  async mockRoute(url, handler) {
2470
2482
  return this.browserContext.route(...arguments);
2471
2483
  }
2472
2484
 
2473
2485
  /**
2474
- * Stops network mocking created by `mockRoute`.
2475
- *
2476
- * ```js
2477
- * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
2478
- * I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
2479
- * ```
2480
- * If no handler is passed, all mock requests for the rote are disabled.
2481
- *
2482
- * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2483
- * @param {function} [handler] a function to process reques
2484
- */
2486
+ * Stops network mocking created by `mockRoute`.
2487
+ *
2488
+ * ```js
2489
+ * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
2490
+ * I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
2491
+ * ```
2492
+ * If no handler is passed, all mock requests for the rote are disabled.
2493
+ *
2494
+ * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2495
+ * @param {function} [handler] a function to process reques
2496
+ */
2485
2497
  async stopMockingRoute(url, handler) {
2486
2498
  return this.browserContext.unroute(...arguments);
2487
2499
  }
@@ -36,7 +36,7 @@ let Runner;
36
36
  *
37
37
  * ### Configuration
38
38
  *
39
- * This helper should be configured in codecept.json or codecept.conf.js
39
+ * This helper should be configured in codecept.conf.ts or codecept.conf.js
40
40
  *
41
41
  * * `url` - base url of website to be tested
42
42
  * * `browser` - browser in which perform testing
@@ -9,7 +9,7 @@ const { beautify } = require('../utils');
9
9
  *
10
10
  * @typedef RESTConfig
11
11
  * @type {object}
12
- * @prop {string} endpoint - API base URL
12
+ * @prop {string} [endpoint] - API base URL
13
13
  * @prop {boolean} [prettyPrintJson=false] - pretty print json for response/request on console logs
14
14
  * @prop {number} [timeout=1000] - timeout for requests in milliseconds. 10000ms by default
15
15
  * @prop {object} [defaultHeaders] - a list of default headers
@@ -138,7 +138,7 @@ class REST extends Helper {
138
138
 
139
139
  if (request.data instanceof Secret) {
140
140
  _debugRequest.data = '*****';
141
- request.data = typeof request.data === 'object' ? { ...request.data.toString() } : request.data.toString();
141
+ request.data = (typeof request.data === 'object' && !(request.data instanceof Secret)) ? { ...request.data.toString() } : request.data.toString();
142
142
  }
143
143
 
144
144
  if ((typeof request.data) === 'string') {
@@ -42,7 +42,7 @@ const getHtmlSource = t => ClientFunction(() => document.getElementsByTagName('h
42
42
  *
43
43
  * ## Configuration
44
44
  *
45
- * This helper should be configured in codecept.json or codecept.conf.js
45
+ * This helper should be configured in codecept.conf.ts or codecept.conf.js
46
46
  *
47
47
  * * `url`: base url of website to be tested
48
48
  * * `show`: (optional, default: false) - show browser window.
@@ -44,11 +44,11 @@ let version;
44
44
  * @prop {string} browser browser in which to perform testing.
45
45
  * @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
46
46
  * @prop {string} [host=localhost] - WebDriver host to connect.
47
- * @prop {string} [port=4444] - WebDriver port to connect.
47
+ * @prop {number} [port=4444] - WebDriver port to connect.
48
48
  * @prop {string} [protocol=http] - protocol for WebDriver server.
49
49
  * @prop {string} [path=/wd/hub] - path to WebDriver server,
50
50
  * @prop {boolean} [restart=true] - restart browser between tests.
51
- * @prop {boolean} [smartWait=false] - **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
51
+ * @prop {boolean|number} [smartWait=false] - **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
52
52
  * @prop {boolean} [disableScreenshots=false] - don't save screenshots on failure.
53
53
  * @prop {boolean} [fullPageScreenshots=false] (optional - make full page screenshots on failure.
54
54
  * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
@@ -916,7 +916,7 @@ class WebDriver extends Helper {
916
916
  * {{ react }}
917
917
  */
918
918
  async click(locator, context = null) {
919
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
919
+ const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
920
920
  const locateFn = prepareLocateFn.call(this, context);
921
921
 
922
922
  const res = await findClickable.call(this, locator, locateFn);
@@ -1126,7 +1126,7 @@ class WebDriver extends Helper {
1126
1126
  * Appium: not tested
1127
1127
  */
1128
1128
  async checkOption(field, context = null) {
1129
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
1129
+ const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
1130
1130
  const locateFn = prepareLocateFn.call(this, context);
1131
1131
 
1132
1132
  const res = await findCheckable.call(this, field, locateFn);
@@ -1145,7 +1145,7 @@ class WebDriver extends Helper {
1145
1145
  * Appium: not tested
1146
1146
  */
1147
1147
  async uncheckOption(field, context = null) {
1148
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
1148
+ const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
1149
1149
  const locateFn = prepareLocateFn.call(this, context);
1150
1150
 
1151
1151
  const res = await findCheckable.call(this, field, locateFn);
@@ -1632,7 +1632,7 @@ class WebDriver extends Helper {
1632
1632
  assertElementExists(res);
1633
1633
  const elem = usingFirstElement(res);
1634
1634
  const elementId = getElementId(elem);
1635
- if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(offsetX, offsetY, elementId);
1635
+ if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId);
1636
1636
  const location = await elem.getLocation();
1637
1637
  assertElementExists(location, 'Failed to receive', 'location');
1638
1638
  /* eslint-disable prefer-arrow-callback */
@@ -1640,7 +1640,7 @@ class WebDriver extends Helper {
1640
1640
  /* eslint-enable */
1641
1641
  }
1642
1642
 
1643
- if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(locator, offsetX, offsetY);
1643
+ if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(locator, offsetX, offsetY);
1644
1644
 
1645
1645
  /* eslint-disable prefer-arrow-callback, comma-dangle */
1646
1646
  return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY);
@@ -85,6 +85,8 @@ let restartsSession;
85
85
  *
86
86
  */
87
87
  module.exports = (config) => {
88
+ // Keep initial configs to pass as options to wdio services
89
+ const wdioOptions = { ...config };
88
90
  const webDriver = container.helpers('WebDriver');
89
91
  if (webDriver) {
90
92
  config = Object.assign(webDriver.options, config);
@@ -108,7 +110,9 @@ module.exports = (config) => {
108
110
  if (version.indexOf('5') === 0) {
109
111
  launchers.push(new Launcher(config));
110
112
  } else {
111
- const options = { logPath: global.output_dir, installArgs: seleniumInstallArgs, args: seleniumArgs };
113
+ const options = {
114
+ logPath: global.output_dir, installArgs: seleniumInstallArgs, args: seleniumArgs, ...wdioOptions,
115
+ };
112
116
  launchers.push(new Launcher(options, [config.capabilities], config));
113
117
  }
114
118
  }
@@ -215,7 +219,12 @@ module.exports = (config) => {
215
219
  if (launcher.onPrepare) {
216
220
  event.dispatcher.on(event.all.before, () => {
217
221
  recorder.add(`launcher ${name} start`, async () => {
218
- await launcher.onPrepare(config, config.capabilities);
222
+ // browserstack-service expects capabilities as array
223
+ if (launcher.constructor.name === 'BrowserstackLauncherService') {
224
+ await launcher.onPrepare(config, [config.capabilities]);
225
+ } else {
226
+ await launcher.onPrepare(config, config.capabilities);
227
+ }
219
228
  output.debug(`Started ${name}`);
220
229
  });
221
230
  });
package/lib/utils.js CHANGED
@@ -132,6 +132,9 @@ module.exports.clearString = function (str) {
132
132
  if (!str) return '';
133
133
  /* Replace forbidden symbols in string
134
134
  */
135
+ if (str.endsWith('.')) {
136
+ str = str.slice(0, -1);
137
+ }
135
138
  return str
136
139
  .replace(/ /g, '_')
137
140
  .replace(/"/g, "'")