codeceptjs 3.2.2 → 3.3.0-beta.3

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 (64) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/docs/advanced.md +2 -2
  3. package/docs/api.md +227 -188
  4. package/docs/build/ApiDataFactory.js +13 -6
  5. package/docs/build/Appium.js +98 -36
  6. package/docs/build/FileSystem.js +11 -1
  7. package/docs/build/GraphQL.js +11 -0
  8. package/docs/build/JSONResponse.js +297 -0
  9. package/docs/build/Nightmare.js +48 -48
  10. package/docs/build/Playwright.js +271 -151
  11. package/docs/build/Puppeteer.js +76 -67
  12. package/docs/build/REST.js +36 -0
  13. package/docs/build/TestCafe.js +44 -44
  14. package/docs/build/WebDriver.js +69 -69
  15. package/docs/changelog.md +7 -0
  16. package/docs/configuration.md +8 -8
  17. package/docs/custom-helpers.md +1 -1
  18. package/docs/data.md +9 -9
  19. package/docs/helpers/ApiDataFactory.md +7 -0
  20. package/docs/helpers/Appium.md +240 -198
  21. package/docs/helpers/FileSystem.md +11 -1
  22. package/docs/helpers/JSONResponse.md +230 -0
  23. package/docs/helpers/Playwright.md +283 -216
  24. package/docs/helpers/Puppeteer.md +9 -1
  25. package/docs/helpers/REST.md +30 -9
  26. package/docs/installation.md +3 -1
  27. package/docs/internal-api.md +265 -0
  28. package/docs/mobile.md +11 -11
  29. package/docs/nightmare.md +3 -3
  30. package/docs/playwright.md +73 -18
  31. package/docs/plugins.md +136 -36
  32. package/docs/puppeteer.md +28 -12
  33. package/docs/quickstart.md +2 -3
  34. package/docs/reports.md +44 -3
  35. package/docs/testcafe.md +1 -1
  36. package/docs/translation.md +2 -2
  37. package/docs/videos.md +2 -2
  38. package/docs/visual.md +2 -2
  39. package/docs/vue.md +1 -1
  40. package/docs/webdriver.md +92 -4
  41. package/lib/cli.js +25 -20
  42. package/lib/command/init.js +5 -15
  43. package/lib/command/workers/runTests.js +25 -7
  44. package/lib/config.js +17 -13
  45. package/lib/helper/ApiDataFactory.js +13 -6
  46. package/lib/helper/Appium.js +65 -3
  47. package/lib/helper/FileSystem.js +11 -1
  48. package/lib/helper/GraphQL.js +11 -0
  49. package/lib/helper/JSONResponse.js +297 -0
  50. package/lib/helper/Playwright.js +209 -89
  51. package/lib/helper/Puppeteer.js +12 -3
  52. package/lib/helper/REST.js +36 -0
  53. package/lib/helper/extras/Console.js +8 -0
  54. package/lib/helper/extras/PlaywrightRestartOpts.js +35 -0
  55. package/lib/interfaces/bdd.js +3 -1
  56. package/lib/plugin/allure.js +12 -0
  57. package/lib/plugin/autoLogin.js +1 -1
  58. package/lib/plugin/eachElement.js +127 -0
  59. package/lib/plugin/tryTo.js +6 -0
  60. package/lib/utils.js +20 -0
  61. package/package.json +25 -23
  62. package/translations/pt-BR.js +8 -0
  63. package/typings/index.d.ts +4 -0
  64. package/typings/types.d.ts +318 -109
package/docs/webdriver.md CHANGED
@@ -203,8 +203,6 @@ Scenario('login test', ({ I }) => {
203
203
  I.see('Welcome, John');
204
204
  });
205
205
  ```
206
- > ▶ Actions like `amOnPage`, `click`, `fillField` are not limited to WebDriver only. They work similarly for all available helpers. [Go to Basics guide to learn them](/basics#writing-tests).
207
-
208
206
 
209
207
  An empty test case can be created with `npx codeceptjs gt` command.
210
208
 
@@ -212,6 +210,43 @@ An empty test case can be created with `npx codeceptjs gt` command.
212
210
  npx codeceptjs gt
213
211
  ```
214
212
 
213
+
214
+ ### Actions
215
+
216
+ Tests consist with a scenario of user's action taken on a page. The most widely used ones are:
217
+
218
+ * `amOnPage` - to open a webpage (accepts relative or absolute url)
219
+ * `click` - to locate a button or link and click on it
220
+ * `fillField` - to enter a text inside a field
221
+ * `selectOption`, `checkOption` - to interact with a form
222
+ * `wait*` to wait for some parts of page to be fully rendered (important for testing SPA)
223
+ * `grab*` to get values from page sources
224
+ * `see`, `dontSee` - to check for a text on a page
225
+ * `seeElement`, `dontSeeElement` - to check for elements on a page
226
+
227
+ > ℹ All actions are listed in [WebDriver helper reference](https://codecept.io/helpers/WebDriver/).*
228
+
229
+ All actions which interact with elements **support CSS and XPath locators**. Actions like `click` or `fillField` by locate elements by their name or value on a page:
230
+
231
+ ```js
232
+ // search for link or button
233
+ I.click('Login');
234
+ // locate field by its label
235
+ I.fillField('Name', 'Miles');
236
+ // we can use input name
237
+ I.fillField('user[email]','miles@davis.com');
238
+ ```
239
+
240
+ You can also specify the exact locator type with strict locators:
241
+
242
+ ```js
243
+ I.click({css: 'button.red'});
244
+ I.fillField({name: 'user[email]'},'miles@davis.com');
245
+ I.seeElement({xpath: '//body/header'});
246
+ ```
247
+
248
+ ### Interactive Pause
249
+
215
250
  It's easy to start writing a test if you use [interactive pause](/basics#debug). Just open a web page and pause execution.
216
251
 
217
252
  ```js
@@ -250,7 +285,7 @@ An interactive shell output may look like this:
250
285
  ```
251
286
  After typing in successful commands you can copy them into a test.
252
287
 
253
- Here is a test checking basic [todo application](http://todomvc.com/).
288
+ Here is a test checking basic [todo application](https://todomvc.com/).
254
289
 
255
290
  ```js
256
291
  Feature('TodoMVC');
@@ -268,6 +303,59 @@ Scenario('create todo item', ({ I }) => {
268
303
 
269
304
  WebDriver helper supports standard [CSS/XPath and text locators](/locators) as well as non-trivial [React locators](/react) and [Shadow DOM](/shadow).
270
305
 
306
+ ### Grabbers
307
+
308
+ If you need to get element's value inside a test you can use `grab*` methods. They should be used with `await` operator inside `async` function:
309
+
310
+ ```js
311
+ const assert = require('assert');
312
+ Scenario('get value of current tasks', async ({ I }) => {
313
+ I.fillField('.todo', 'my first item');
314
+ I.pressKey('Enter')
315
+ I.fillField('.todo', 'my second item');
316
+ I.pressKey('Enter')
317
+ let numTodos = await I.grabTextFrom('.todo-count strong');
318
+ assert.equal(2, numTodos);
319
+ });
320
+ ```
321
+
322
+ ### Within
323
+
324
+ In case some actions should be taken inside one element (a container or modal window or iframe) you can use `within` block to narrow the scope.
325
+ Please take a note that you can't use within inside another within in Puppeteer helper:
326
+
327
+ ```js
328
+ await within('.todoapp', () => {
329
+ I.fillField('.todo', 'my new item');
330
+ I.pressKey('Enter')
331
+ I.see('1 item left', '.todo-count');
332
+ I.click('.todo-list input.toggle');
333
+ });
334
+ I.see('0 items left', '.todo-count');
335
+ ```
336
+
337
+ ### Each Element <Badge text="Since 3.3" type="warning"/>
338
+
339
+ Usually, CodeceptJS performs an action on the first matched element.
340
+ In case you want to do an action on each element found, use the special function `eachElement` which comes from [eachElement](https://codecept.io/plugins/#eachelement) plugin.
341
+
342
+ `eachElement` function matches all elements by locator and performs a callback on each of those element. A callback function receives element of webdriverio. `eachElement` may perform arbitrary actions on a page, so the first argument should by a description of the actions performed. This description will be used for logging purposes.
343
+
344
+ Usage example
345
+
346
+ ```js
347
+ await eachElement(
348
+ 'click all checkboxes',
349
+ 'input.custom-checkbox',
350
+ async (el, index) => {
351
+ await el.click();
352
+ });
353
+ );
354
+ ```
355
+
356
+ > ℹ Learn more about [eachElement plugin](/plugins/#eachelement)
357
+
358
+
271
359
  ## Waiting
272
360
 
273
361
  Web applications do not always respond instantly. That's why WebDriver protocol has methods to wait for changes on a page. CodeceptJS provides such commands prefixed with `wait*` so you could explicitly define what effects we wait for.
@@ -295,7 +383,7 @@ exports.config = {
295
383
 
296
384
  ## SmartWait
297
385
 
298
- It is possible to wait for elements pragmatically. If a test uses element which is not on a page yet, CodeceptJS will wait for few extra seconds before failing. This feature is based on [Implicit Wait](http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits) of Selenium. CodeceptJS enables implicit wait only when searching for a specific element and disables in all other cases. Thus, the performance of a test is not affected.
386
+ It is possible to wait for elements pragmatically. If a test uses element which is not on a page yet, CodeceptJS will wait for few extra seconds before failing. This feature is based on [Implicit Wait](https://www.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits) of Selenium. CodeceptJS enables implicit wait only when searching for a specific element and disables in all other cases. Thus, the performance of a test is not affected.
299
387
 
300
388
  SmartWait can be enabled by setting wait option in WebDriver config.
301
389
  Add `smartWait: 5000` to wait for additional 5s.
package/lib/cli.js CHANGED
@@ -7,6 +7,7 @@ const { MetaStep } = require('./step');
7
7
 
8
8
  const cursor = Base.cursor;
9
9
  let currentMetaStep = [];
10
+ let codeceptjsEventDispatchersRegistered = false;
10
11
 
11
12
  class Cli extends Base {
12
13
  constructor(runner, opts) {
@@ -77,29 +78,33 @@ class Cli extends Base {
77
78
  }
78
79
  });
79
80
 
80
- event.dispatcher.on(event.step.started, (step) => {
81
- let processingStep = step;
82
- const metaSteps = [];
83
- while (processingStep.metaStep) {
84
- metaSteps.unshift(processingStep.metaStep);
85
- processingStep = processingStep.metaStep;
86
- }
87
- const shift = metaSteps.length;
81
+ if (!codeceptjsEventDispatchersRegistered) {
82
+ codeceptjsEventDispatchersRegistered = true;
88
83
 
89
- for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
90
- if (currentMetaStep[i] !== metaSteps[i]) {
91
- output.stepShift = 3 + 2 * i;
92
- if (metaSteps[i]) output.step(metaSteps[i]);
84
+ event.dispatcher.on(event.step.started, (step) => {
85
+ let processingStep = step;
86
+ const metaSteps = [];
87
+ while (processingStep.metaStep) {
88
+ metaSteps.unshift(processingStep.metaStep);
89
+ processingStep = processingStep.metaStep;
93
90
  }
94
- }
95
- currentMetaStep = metaSteps;
96
- output.stepShift = 3 + 2 * shift;
97
- output.step(step);
98
- });
91
+ const shift = metaSteps.length;
99
92
 
100
- event.dispatcher.on(event.step.finished, () => {
101
- output.stepShift = 0;
102
- });
93
+ for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
94
+ if (currentMetaStep[i] !== metaSteps[i]) {
95
+ output.stepShift = 3 + 2 * i;
96
+ if (metaSteps[i]) output.step(metaSteps[i]);
97
+ }
98
+ }
99
+ currentMetaStep = metaSteps;
100
+ output.stepShift = 3 + 2 * shift;
101
+ output.step(step);
102
+ });
103
+
104
+ event.dispatcher.on(event.step.finished, () => {
105
+ output.stepShift = 0;
106
+ });
107
+ }
103
108
  }
104
109
 
105
110
  runner.on('suite end', suite => {
@@ -22,7 +22,7 @@ const defaultConfig = {
22
22
  mocha: {},
23
23
  };
24
24
 
25
- const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'TestCafe', 'Protractor', 'Nightmare', 'Appium'];
25
+ const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'REST', 'GraphQL', 'Appium', 'TestCafe', 'Nightmare'];
26
26
  const translations = Object.keys(require('../../translations'));
27
27
 
28
28
  const noTranslation = 'English (no localization)';
@@ -30,12 +30,15 @@ translations.unshift(noTranslation);
30
30
 
31
31
  let packages;
32
32
 
33
- const configHeader = `const { setHeadlessWhen } = require('@codeceptjs/configure');
33
+ const configHeader = `const { setHeadlessWhen, setCommonPlugins } = require('@codeceptjs/configure');
34
34
 
35
35
  // turn on headless mode when running with HEADLESS=true environment variable
36
36
  // export HEADLESS=true && npx codeceptjs run
37
37
  setHeadlessWhen(process.env.HEADLESS);
38
38
 
39
+ // enable all common plugins https://github.com/codeceptjs/configure#setcommonplugins
40
+ setCommonPlugins();
41
+
39
42
  `;
40
43
 
41
44
  const defaultActor = `// in this file you can append custom step methods to 'I' object
@@ -150,19 +153,6 @@ module.exports = function (initPath) {
150
153
  config.include.I = stepFile;
151
154
  print(`Steps file created at ${stepFile}`);
152
155
 
153
- config.plugins = {
154
- pauseOnFail: {},
155
- retryFailedStep: {
156
- enabled: true,
157
- },
158
- tryTo: {
159
- enabled: true,
160
- },
161
- screenshotOnFail: {
162
- enabled: true,
163
- },
164
- };
165
-
166
156
  let configSource = beautify(`exports.config = ${inspect(config, false, 4, false)}`);
167
157
 
168
158
  if (require.resolve('@codeceptjs/configure') && isLocal && !initPath) {
@@ -73,6 +73,27 @@ function filterTests() {
73
73
  }
74
74
 
75
75
  function initializeListeners() {
76
+ function simplifyError(error) {
77
+ if (error) {
78
+ const {
79
+ stack,
80
+ uncaught,
81
+ message,
82
+ actual,
83
+ expected,
84
+ } = error;
85
+
86
+ return {
87
+ stack,
88
+ uncaught,
89
+ message,
90
+ actual,
91
+ expected,
92
+ };
93
+ }
94
+
95
+ return null;
96
+ }
76
97
  function simplifyTest(test, err = null) {
77
98
  test = { ...test };
78
99
 
@@ -82,13 +103,10 @@ function initializeListeners() {
82
103
  }
83
104
 
84
105
  if (test.err) {
85
- err = {
86
- stack: test.err.stack,
87
- uncaught: test.err.uncaught,
88
- message: test.err.message,
89
- actual: test.err.actual,
90
- expected: test.err.expected,
91
- };
106
+ err = simplifyError(test.err);
107
+ test.status = 'failed';
108
+ } else if (err) {
109
+ err = simplifyError(err);
92
110
  test.status = 'failed';
93
111
  }
94
112
  const parent = {};
package/lib/config.js CHANGED
@@ -38,6 +38,14 @@ const defaultConfig = {
38
38
  let hooks = [];
39
39
  let config = {};
40
40
 
41
+ const configFileNames = [
42
+ 'codecept.config.js',
43
+ 'codecept.conf.js',
44
+ 'codecept.json',
45
+ 'codecept.config.ts',
46
+ 'codecept.conf.ts',
47
+ ];
48
+
41
49
  /**
42
50
  * Current configuration
43
51
  */
@@ -59,6 +67,7 @@ class Config {
59
67
  * If js file provided: require it and get .config key
60
68
  * If json file provided: load and parse JSON
61
69
  * If directory provided:
70
+ * * try to load `codecept.config.js` from it
62
71
  * * try to load `codecept.conf.js` from it
63
72
  * * try to load `codecept.json` from it
64
73
  * If none of above: fail.
@@ -78,23 +87,18 @@ class Config {
78
87
  return loadConfigFile(configFile);
79
88
  }
80
89
 
81
- // is path to directory
82
- const jsConfig = path.join(configFile, 'codecept.conf.js');
83
- if (isFile(jsConfig)) {
84
- return loadConfigFile(jsConfig);
85
- }
90
+ for (const name of configFileNames) {
91
+ // is path to directory
92
+ const jsConfig = path.join(configFile, name);
86
93
 
87
- const jsonConfig = path.join(configFile, 'codecept.json');
88
- if (isFile(jsonConfig)) {
89
- return loadConfigFile(jsonConfig);
94
+ if (isFile(jsConfig)) {
95
+ return loadConfigFile(jsConfig);
96
+ }
90
97
  }
91
98
 
92
- const tsConfig = path.join(configFile, 'codecept.conf.ts');
93
- if (isFile(tsConfig)) {
94
- return loadConfigFile(tsConfig);
95
- }
99
+ const configPaths = configFileNames.map(name => path.join(configFile, name)).join(' or ');
96
100
 
97
- throw new Error(`Can not load config from ${jsConfig} or ${jsonConfig} or ${tsConfig}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`);
101
+ throw new Error(`Can not load config from ${configPaths}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`);
98
102
  }
99
103
 
100
104
  /**
@@ -257,13 +257,16 @@ class ApiDataFactory extends Helper {
257
257
  * // create user with defined email
258
258
  * // and receive it when inside async function
259
259
  * const user = await I.have('user', { email: 'user@user.com'});
260
+ * // create a user with options that will not be included in the final request
261
+ * I.have('user', { }, { age: 33, height: 55 })
260
262
  * ```
261
263
  *
262
264
  * @param {*} factory factory to use
263
265
  * @param {*} params predefined parameters
266
+ * @param {*} options options for programmatically generate the attributes
264
267
  */
265
- have(factory, params) {
266
- const item = this._createItem(factory, params);
268
+ have(factory, params, options) {
269
+ const item = this._createItem(factory, params, options);
267
270
  this.debug(`Creating ${factory} ${JSON.stringify(item)}`);
268
271
  return this._requestCreate(factory, item);
269
272
  }
@@ -277,21 +280,25 @@ class ApiDataFactory extends Helper {
277
280
  *
278
281
  * // create 3 posts by one author
279
282
  * I.haveMultiple('post', 3, { author: 'davert' });
283
+ *
284
+ * // create 3 posts by one author with options
285
+ * I.haveMultiple('post', 3, { author: 'davert' }, { publish_date: '01.01.1997' });
280
286
  * ```
281
287
  *
282
288
  * @param {*} factory
283
289
  * @param {*} times
284
290
  * @param {*} params
291
+ * @param {*} options
285
292
  */
286
- haveMultiple(factory, times, params) {
293
+ haveMultiple(factory, times, params, options) {
287
294
  const promises = [];
288
295
  for (let i = 0; i < times; i++) {
289
- promises.push(this.have(factory, params));
296
+ promises.push(this.have(factory, params, options));
290
297
  }
291
298
  return Promise.all(promises);
292
299
  }
293
300
 
294
- _createItem(model, data) {
301
+ _createItem(model, data, options) {
295
302
  if (!this.factories[model]) {
296
303
  throw new Error(`Factory ${model} is not defined in config`);
297
304
  }
@@ -303,7 +310,7 @@ class ApiDataFactory extends Helper {
303
310
  modulePath = path.join(global.codecept_dir, modulePath);
304
311
  }
305
312
  const builder = require(modulePath);
306
- return builder.build(data);
313
+ return builder.build(data, options);
307
314
  } catch (err) {
308
315
  throw new Error(`Couldn't load factory file from ${modulePath}, check that
309
316