codeceptjs 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 3.2.2
2
+
3
+ * [Playwright] Reverted removal of retry on context errors. Fixes #3130
4
+ * Timeout improvements by @nikocanvacom:
5
+ * Added priorites to timeouts
6
+ * Added `overrideStepLimits` to [stepTimeout plugin](https://codecept.io/plugins/#steptimeout) to override steps timeouts set by `limitTime`.
7
+ * Fixed step timeout not working due to override by NaN by test timeout #3126
8
+ * [Appium] Fixed logging error when `manualStart` is true. See #3140 by @nikocanvacom
9
+
10
+
1
11
  ## 3.2.1
2
12
 
3
13
  > ♻️ This release fixes hanging of tests by reducing timeouts for automatic retries on failures.
package/docs/advanced.md CHANGED
@@ -186,7 +186,7 @@ You can use this options for build your own [plugins](https://codecept.io/hooks/
186
186
  });
187
187
  ```
188
188
 
189
- ### Timeout <Badge text="Updated in 3.2" type="warning"/>
189
+ ## Timeout <Badge text="Updated in 3.2" type="warning"/>
190
190
 
191
191
  Tests can get stuck due to various reasons such as network connection issues, crashed browser, etc.
192
192
  This can make tests process hang. To prevent these situations timeouts can be used. Timeouts can be set explicitly for flaky parts of code, or implicitly in a config.
package/docs/basics.md CHANGED
@@ -41,7 +41,6 @@ Refer to following guides to more information on:
41
41
  * [▶ Playwright](/playwright)
42
42
  * [▶ WebDriver](/webdriver)
43
43
  * [▶ Puppeteer](/puppeteer)
44
- * [▶ Protractor](/angular)
45
44
  * [▶ Nightmare](/nightmare)
46
45
  * [▶ TestCafe](/testcafe)
47
46
 
@@ -317,6 +316,32 @@ var assert = require('assert');
317
316
  assert.equal(title, 'CodeceptJS');
318
317
  ```
319
318
 
319
+ It is important to understand the usage of **async** functions in CodeceptJS. While non-returning actions can be called without await, if an async function uses `grab*` action it must be called with `await`:
320
+
321
+ ```js
322
+ // a helper function
323
+ async function getAllUsers(I) {
324
+ const users = await I.grabTextFrom('.users');
325
+ return users.filter(u => u.includes('active'))
326
+ }
327
+
328
+ // a test
329
+ Scenario('try helper functions', async ({ I }) => {
330
+ // we call function with await because it includes `grab`
331
+ const users = await getAllUsers(I);
332
+ });
333
+ ```
334
+
335
+ If you miss `await` you get commands unsynchrhonized. And this will result to an error like this:
336
+
337
+ ```
338
+ (node:446390) UnhandledPromiseRejectionWarning: ...
339
+ at processTicksAndRejections (internal/process/task_queues.js:95:5)
340
+ (node:446390) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
341
+ ```
342
+
343
+ If you face that error please make sure that all async functions are called with `await`.
344
+
320
345
  ## Running Tests
321
346
 
322
347
  To launch tests use the `run` command, and to execute tests in [multiple browsers](/advanced/#multiple-browsers-execution) or [multiple threads](/advanced/#parallel-execution) use the `run-multiple` command.
package/docs/bdd.md CHANGED
@@ -5,7 +5,7 @@ title: Behavior Driven Development
5
5
 
6
6
  # Behavior Driven Development
7
7
 
8
- Behavior Driven Development (BDD) is a popular software development methodology. BDD is considered an extension of TDD, and is greatly inspired by [Agile](http://agilemanifesto.org/) practices. The primary reason to choose BDD as your development process is to break down communication barriers between business and technical teams. BDD encourages the use of automated testing to verify all documented features of a project from the very beginning. This is why it is common to talk about BDD in the context of test frameworks (like CodeceptJS). The BDD approach, however, is about much more than testing - it is a common language for all team members to use during the development process.
8
+ Behavior Driven Development (BDD) is a popular software development methodology. BDD is considered an extension of TDD, and is greatly inspired by [Agile](https://agilemanifesto.org/) practices. The primary reason to choose BDD as your development process is to break down communication barriers between business and technical teams. BDD encourages the use of automated testing to verify all documented features of a project from the very beginning. This is why it is common to talk about BDD in the context of test frameworks (like CodeceptJS). The BDD approach, however, is about much more than testing - it is a common language for all team members to use during the development process.
9
9
 
10
10
  ## What is Behavior Driven Development
11
11
 
@@ -55,7 +55,7 @@ I should see that total number of products I want to buy is 2
55
55
  And my order amount is $1600
56
56
  ```
57
57
 
58
- As we can see this simple story highlights core concepts that are called *contracts*. We should fulfill those contracts to model software correctly. But how we can verify that those contracts are being satisfied? [Cucumber](http://cucumber.io) introduced a special language for such stories called **Gherkin**. Same story transformed to Gherkin will look like this:
58
+ As we can see this simple story highlights core concepts that are called *contracts*. We should fulfill those contracts to model software correctly. But how we can verify that those contracts are being satisfied? [Cucumber](https://cucumber.io) introduced a special language for such stories called **Gherkin**. Same story transformed to Gherkin will look like this:
59
59
 
60
60
  ```gherkin
61
61
  Feature: checkout process
@@ -355,6 +355,17 @@ class Playwright extends Helper {
355
355
  }
356
356
 
357
357
  async _before() {
358
+ recorder.retry({
359
+ retries: 5,
360
+ when: err => {
361
+ if (!err || typeof (err.message) !== 'string') {
362
+ return false;
363
+ }
364
+ // ignore context errors
365
+ return err.message.includes('context');
366
+ },
367
+ });
368
+
358
369
  if (this.options.restart && !this.options.manualStart) await this._startBrowser();
359
370
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
360
371
 
@@ -582,7 +582,7 @@ class WebDriver extends Helper {
582
582
  this.context = this.root;
583
583
  if (this.options.restart && !this.options.manualStart) return this._startBrowser();
584
584
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
585
- this.$$ = this.browser.$$.bind(this.browser);
585
+ if (this.browser) this.$$ = this.browser.$$.bind(this.browser);
586
586
  return this.browser;
587
587
  }
588
588
 
package/docs/changelog.md CHANGED
@@ -7,6 +7,16 @@ layout: Section
7
7
 
8
8
  # Releases
9
9
 
10
+ ## 3.2.2
11
+
12
+ * **[Playwright]** Reverted removal of retry on context errors. Fixes [#3130](https://github.com/codeceptjs/CodeceptJS/issues/3130)
13
+ * Timeout improvements by **[nikocanvacom](https://github.com/nikocanvacom)**:
14
+ * Added priorites to timeouts
15
+ * Added `overrideStepLimits` to [stepTimeout plugin](https://codecept.io/plugins/#steptimeout) to override steps timeouts set by `limitTime`.
16
+ * Fixed step timeout not working due to override by NaN by test timeout [#3126](https://github.com/codeceptjs/CodeceptJS/issues/3126)
17
+ * **[Appium]** Fixed logging error when `manualStart` is true. See [#3140](https://github.com/codeceptjs/CodeceptJS/issues/3140) by **[nikocanvacom](https://github.com/nikocanvacom)**
18
+
19
+
10
20
  ## 3.2.1
11
21
 
12
22
  > ♻️ This release fixes hanging of tests by reducing timeouts for automatic retries on failures.
@@ -191,6 +191,8 @@ module.exports = new AttachFile();
191
191
  module.exports.AttachFile = AttachFile;
192
192
  ```
193
193
 
194
+ > ⚠ While building complex page objects it is important to keep all `async` functions to be called with `await`. While CodeceptJS allows to run commands synchronously if async function has `I.grab*` or any custom function that returns a promise it must be called with `await`. If you see `UnhandledPromiseRejectionWarning` it might be caused by async page object function that was called without `await`.
195
+
194
196
  ## Page Fragments
195
197
 
196
198
  Similarly, CodeceptJS allows you to generate **PageFragments** and any other abstractions
package/docs/plugins.md CHANGED
@@ -898,7 +898,7 @@ Run tests with plugin enabled:
898
898
  #### Configuration:
899
899
 
900
900
  - `timeout` - global step timeout, default 150 seconds
901
- - `force` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false
901
+ - `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false
902
902
  - `noTimeoutSteps` - an array of steps with no timeout. Default:
903
903
 
904
904
  - `amOnPage`
@@ -915,7 +915,7 @@ Run tests with plugin enabled:
915
915
  plugins: {
916
916
  stepTimeout: {
917
917
  enabled: true,
918
- force: true,
918
+ overrideStepLimits: true,
919
919
  noTimeoutSteps: [
920
920
  'scroll*', // ignore all scroll steps
921
921
  /Cookie/, // ignore all steps with a Cookie in it (by regexp)
package/lib/actor.js CHANGED
@@ -38,7 +38,7 @@ class Actor {
38
38
 
39
39
  event.dispatcher.prependOnceListener(event.step.before, (step) => {
40
40
  output.log(`Timeout to ${step}: ${timeout}s`);
41
- step.totalTimeout = timeout * 1000;
41
+ step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
42
42
  });
43
43
 
44
44
  return this;
@@ -132,7 +132,7 @@ function recordStep(step, args) {
132
132
  step.startTime = Date.now();
133
133
  }
134
134
  return val = step.run(...args);
135
- }, false, undefined, step.totalTimeout);
135
+ }, false, undefined, step.getTimeout());
136
136
 
137
137
  event.emit(event.step.after, step);
138
138
 
@@ -355,6 +355,17 @@ class Playwright extends Helper {
355
355
  }
356
356
 
357
357
  async _before() {
358
+ recorder.retry({
359
+ retries: 5,
360
+ when: err => {
361
+ if (!err || typeof (err.message) !== 'string') {
362
+ return false;
363
+ }
364
+ // ignore context errors
365
+ return err.message.includes('context');
366
+ },
367
+ });
368
+
358
369
  if (this.options.restart && !this.options.manualStart) await this._startBrowser();
359
370
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
360
371
 
@@ -582,7 +582,7 @@ class WebDriver extends Helper {
582
582
  this.context = this.root;
583
583
  if (this.options.restart && !this.options.manualStart) return this._startBrowser();
584
584
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
585
- this.$$ = this.browser.$$.bind(this.browser);
585
+ if (this.browser) this.$$ = this.browser.$$.bind(this.browser);
586
586
  return this.browser;
587
587
  }
588
588
 
@@ -3,6 +3,7 @@ const output = require('../output');
3
3
  const recorder = require('../recorder');
4
4
  const Config = require('../config');
5
5
  const { timeouts } = require('../store');
6
+ const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER;
6
7
 
7
8
  module.exports = function () {
8
9
  let timeout;
@@ -49,14 +50,14 @@ module.exports = function () {
49
50
  if (typeof timeout !== 'number') return;
50
51
 
51
52
  if (timeout < 0) {
52
- step.totalTimeout = 0.01;
53
+ step.setTimeout(0.01, TIMEOUT_ORDER.testOrSuite);
53
54
  } else {
54
- step.totalTimeout = timeout;
55
+ step.setTimeout(timeout, TIMEOUT_ORDER.testOrSuite);
55
56
  }
56
57
  });
57
58
 
58
59
  event.dispatcher.on(event.step.finished, (step) => {
59
- timeout -= step.duration;
60
+ if (typeof timeout === 'number' && !Number.isNaN(timeout)) timeout -= step.duration;
60
61
 
61
62
  if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
62
63
  if (currentTest && currentTest.callback) {
@@ -1,8 +1,9 @@
1
1
  const event = require('../event');
2
+ const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER;
2
3
 
3
4
  const defaultConfig = {
4
5
  timeout: 150,
5
- force: false,
6
+ overrideStepLimits: false,
6
7
  noTimeoutSteps: [
7
8
  'amOnPage',
8
9
  'wait*',
@@ -33,7 +34,7 @@ const defaultConfig = {
33
34
  * #### Configuration:
34
35
  *
35
36
  * * `timeout` - global step timeout, default 150 seconds
36
- * * `force` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false
37
+ * * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false
37
38
  * * `noTimeoutSteps` - an array of steps with no timeout. Default:
38
39
  * * `amOnPage`
39
40
  * * `wait*`
@@ -49,7 +50,7 @@ const defaultConfig = {
49
50
  * plugins: {
50
51
  * stepTimeout: {
51
52
  * enabled: true,
52
- * force: true,
53
+ * overrideStepLimits: true,
53
54
  * noTimeoutSteps: [
54
55
  * 'scroll*', // ignore all scroll steps
55
56
  * /Cookie/, // ignore all steps with a Cookie in it (by regexp)
@@ -85,6 +86,6 @@ module.exports = (config) => {
85
86
  }
86
87
  }
87
88
  stepTimeout = stepTimeout === undefined ? config.timeout : stepTimeout;
88
- step.totalTimeout = stepTimeout * 1000;
89
+ step.setTimeout(stepTimeout * 1000, config.overrideStepLimits ? TIMEOUT_ORDER.stepTimeoutHard : TIMEOUT_ORDER.stepTimeoutSoft);
89
90
  });
90
91
  };
package/lib/recorder.js CHANGED
@@ -178,7 +178,8 @@ module.exports = {
178
178
  if (process.env.DEBUG) debug(`${currentQueue()}Queued | ${taskName}`);
179
179
 
180
180
  return promise = Promise.resolve(promise).then((res) => {
181
- const retryOpts = this.retries.slice(-1).pop();
181
+ // prefer options for non-conditional retries
182
+ const retryOpts = this.retries.sort((r1, r2) => r1.when && !r2.when).slice(-1).pop();
182
183
  // no retries or unnamed tasks
183
184
  if (!retryOpts || !taskName || !retry) {
184
185
  const [promise, timer] = getTimeoutPromise(timeout, taskName);
package/lib/step.js CHANGED
@@ -13,6 +13,27 @@ const STACK_LINE = 4;
13
13
  * @param {string} name
14
14
  */
15
15
  class Step {
16
+ static get TIMEOUT_ORDER() {
17
+ return {
18
+ /**
19
+ * timeouts set with order below zero only override timeouts of higher order if their value is smaller
20
+ */
21
+ testOrSuite: -5,
22
+ /**
23
+ * 0-9 - designated for override of timeouts set from code, 5 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=true
24
+ */
25
+ stepTimeoutHard: 5,
26
+ /**
27
+ * 10-19 - designated for timeouts set from code, 15 is order of I.setTimeout(t) operation
28
+ */
29
+ codeLimitTime: 15,
30
+ /**
31
+ * 20-29 - designated for timeout settings which could be overriden in tests code, 25 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=false
32
+ */
33
+ stepTimeoutSoft: 25,
34
+ };
35
+ }
36
+
16
37
  constructor(helper, name) {
17
38
  /** @member {string} */
18
39
  this.actor = 'I'; // I = actor
@@ -38,8 +59,40 @@ class Step {
38
59
  this.metaStep = undefined;
39
60
  /** @member {string} */
40
61
  this.stack = '';
41
- /** @member {number} */
42
- this.totalTimeout = undefined;
62
+
63
+ const timeouts = new Map();
64
+ /**
65
+ * @method
66
+ * @returns {number|undefined}
67
+ */
68
+ this.getTimeout = function () {
69
+ let totalTimeout;
70
+ // iterate over all timeouts starting from highest values of order
71
+ new Map([...timeouts.entries()].sort().reverse()).forEach((timeout, order) => {
72
+ if (timeout !== undefined && (
73
+ // when orders >= 0 - timeout value overrides those set with higher order elements
74
+ order >= 0
75
+
76
+ // when `order < 0 && totalTimeout === undefined` - timeout is used when nothing is set by elements with higher order
77
+ || totalTimeout === undefined
78
+
79
+ // when `order < 0` - timeout overrides higher values of timeout or 'no timeout' (totalTimeout === 0) set by elements with higher order
80
+ || timeout > 0 && (timeout < totalTimeout || totalTimeout === 0)
81
+ )) {
82
+ totalTimeout = timeout;
83
+ }
84
+ });
85
+ return totalTimeout;
86
+ };
87
+ /**
88
+ * @method
89
+ * @param {number} timeout - timeout in milliseconds or 0 if no timeout
90
+ * @param {number} order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
91
+ * When order below 0 value of timeout only override if new value is lower
92
+ */
93
+ this.setTimeout = function (timeout, order) {
94
+ timeouts.set(order, timeout);
95
+ };
43
96
 
44
97
  this.setTrace();
45
98
  }
@@ -231,6 +284,8 @@ class MetaStep extends Step {
231
284
  }
232
285
  }
233
286
 
287
+ Step.TIMEOUTS = {};
288
+
234
289
  /** @type {Class<MetaStep>} */
235
290
  Step.MetaStep = MetaStep;
236
291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -10311,7 +10311,13 @@ declare namespace CodeceptJS {
10311
10311
  args: any[];
10312
10312
  metaStep: MetaStep;
10313
10313
  stack: string;
10314
- totalTimeout: number;
10314
+ getTimeout(): number | undefined;
10315
+ /**
10316
+ * @param timeout - timeout in milliseconds or 0 if no timeout
10317
+ * @param order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
10318
+ * When order below 0 value of timeout only override if new value is lower
10319
+ */
10320
+ setTimeout(timeout: number, order: number): void;
10315
10321
  setTrace(): void;
10316
10322
  setArguments(args: any[]): void;
10317
10323
  run(...args: any[]): any;
@@ -10889,3 +10895,23 @@ declare namespace CodeceptJS {
10889
10895
  }
10890
10896
  }
10891
10897
 
10898
+ /**
10899
+ * timeouts set with order below zero only override timeouts of higher order if their value is smaller
10900
+ */
10901
+ declare var testOrSuite: any;
10902
+
10903
+ /**
10904
+ * 0-9 - designated for override of timeouts set from code, 5 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=true
10905
+ */
10906
+ declare var stepTimeoutHard: any;
10907
+
10908
+ /**
10909
+ * 10-19 - designated for timeouts set from code, 15 is order of I.setTimeout(t) operation
10910
+ */
10911
+ declare var codeLimitTime: any;
10912
+
10913
+ /**
10914
+ * 20-29 - designated for timeout settings which could be overriden in tests code, 25 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=false
10915
+ */
10916
+ declare var stepTimeoutSoft: any;
10917
+