Skip to content

Commit 5ba8b10

Browse files
authored
Timeouts (#3113)
* updated changelog * implemented timeouts * fixed tests * removed retry to test
1 parent c6dfdf9 commit 5ba8b10

24 files changed

+356
-98
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 3.2.0
2+
13
## 3.1.3
24

35
🛩️ Features:

docs/advanced.md

Lines changed: 51 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Scenario('update user profile', ({ }) => {
100100
All tests with `@tag` could be executed with `--grep @tag` option.
101101
102102
```sh
103-
codeceptjs run --grep @slow
103+
npx codeceptjs run --grep @slow
104104
```
105105
106106
Use regex for more flexible filtering:
@@ -119,24 +119,30 @@ CodeceptJS provides a debug mode in which additional information is printed.
119119
It can be turned on with `--debug` flag.
120120
121121
```sh
122-
codeceptjs run --debug
122+
npx codeceptjs run --debug
123123
```
124124
125125
to receive even more information turn on `--verbose` flag:
126126
127127
```sh
128-
codeceptjs run --verbose
128+
npx codeceptjs run --verbose
129129
```
130130
131-
And don't forget that you can pause execution and enter **interactive console** mode by calling `pause()` inside your test.
131+
> You can pause execution and enter **interactive console** mode by calling `pause()` inside your test.
132132
133-
For advanced debugging use NodeJS debugger. In WebStorm IDE:
133+
To see a complete internal debug of CodeceptJS use `DEBUG` env variable:
134+
135+
```sh
136+
DEBUG=codeceptjs:* npx codeceptjs run
137+
```
138+
139+
For an interactive debugging use NodeJS debugger. In **WebStorm**:
134140
135141
```sh
136142
node $NODE_DEBUG_OPTION ./node_modules/.bin/codeceptjs run
137143
```
138144
139-
For Visual Studio Code, add the following configuration in launch.json:
145+
For **Visual Studio Code**, add the following configuration in launch.json:
140146
141147
```json
142148
{
@@ -180,27 +186,54 @@ You can use this options for build your own [plugins](https://codecept.io/hooks/
180186
});
181187
```
182188
183-
### Timeout
189+
### Timeout <Badge text="Updated in 3.2" type="warning"/>
190+
191+
Tests can get stuck due to various reasons such as network connection issues, crashed browser, etc.
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.
184193
185-
By default there is no timeout for tests, however you can change this value for a specific suite:
194+
> Previous timeout implementation was disabled as it had no effect when dealing with steps and promises.
195+
196+
### Steps Timeout
197+
198+
It is possible to limit a step execution to specified time with `I.limitTime` command.
199+
It will set timeout in seconds for the next executed step:
186200
187201
```js
188-
Feature('Stop me').timeout(5000); // set timeout to 5s
202+
// limit clicking to 5 seconds
203+
I.limitTime(5).click('Link')
189204
```
190205
191-
or for the test:
206+
It is possible to set a timeout for all steps implicitly (except waiters) using [stepTimeout plugin](/plugins/#steptimeout).
207+
208+
### Tests Timeout
209+
210+
Test timeout can be set in seconds via Scenario options:
192211
193212
```js
194-
// set timeout to 1s
195-
Scenario("Stop me faster",({ I }) => {
196-
// test goes here
197-
}).timeout(1000);
213+
// limit test to 20 seconds
214+
Scenario('slow test that should be stopped', { timeout: 20 }, ({ I }) => {
215+
// ...
216+
})
217+
```
218+
219+
This timeout can be set globally in `codecept.conf.js` in seconds:
220+
221+
```js
222+
exports.config = {
223+
224+
// each test must not run longer than 5 mins
225+
timeout: 300,
198226

199-
// alternative
200-
Scenario("Stop me faster", {timeout: 1000},({ I }) => {});
227+
}
228+
```
229+
230+
### Suites Timeout
201231
202-
// disable timeout for this scenario
203-
Scenario("Don't stop me", {timeout: 0},({ I }) => {});
232+
A timeout for a group of tests can be set on Feature level via options.
233+
234+
```js
235+
// limit all tests in this suite to 30 seconds
236+
Feature('flaky tests', { timeout: 30 })
204237
```
205238
206239
@@ -249,45 +282,3 @@ Please note that some config changes can't be applied on the fly. For instance,
249282
250283
Configuration changes will be reverted after a test or a suite.
251284
252-
253-
### Rerunning Flaky Tests Multiple Times <Badge text="Since 2.4" type="warning"/>
254-
255-
End to end tests can be flaky for various reasons. Even when we can't do anything to solve this problem it we can do next two things:
256-
257-
* Detect flaky tests in our suite
258-
* Fix flaky tests by rerunning them.
259-
260-
Both tasks can be achieved with [`run-rerun` command](/commands/#run-rerun) which runs tests multiple times until all tests are passed.
261-
262-
You should set min and max runs boundaries so when few tests fail in a row you can rerun them until they are succeeded.
263-
264-
```js
265-
// inside to codecept.conf.js
266-
exports.config = { // ...
267-
rerun: {
268-
// run 4 times until 1st success
269-
minSuccess: 1,
270-
maxReruns: 4,
271-
}
272-
}
273-
```
274-
275-
If you want to check all your tests for stability you can set high boundaries for minimal success:
276-
277-
```js
278-
// inside to codecept.conf.js
279-
exports.config = { // ...
280-
rerun: {
281-
// run all tests must pass exactly 5 times
282-
minSuccess: 5,
283-
maxReruns: 5,
284-
}
285-
}
286-
```
287-
288-
Now execute tests with `run-rerun` command:
289-
290-
```
291-
npx codeceptjs run-rerun
292-
```
293-

docs/plugins.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,73 @@ Scenario('scenario tite', () => {
626626
627627
- `config`
628628
629+
## retryTo
630+
631+
Adds global `retryTo` which retries steps a few times before failing.
632+
633+
Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
634+
635+
```js
636+
plugins: {
637+
retryTo: {
638+
enabled: true
639+
}
640+
}
641+
```
642+
643+
Use it in your tests:
644+
645+
```js
646+
// retry these steps 5 times before failing
647+
await retryTo((tryNum) => {
648+
I.click('Open');
649+
I.see('Opened')
650+
}, 5);
651+
```
652+
653+
Set polling interval as 3rd argument (200ms by default):
654+
655+
```js
656+
// retry these steps 5 times before failing
657+
await retryTo((tryNum) => {
658+
I.click('Open');
659+
I.see('Opened')
660+
}, 5, 100);
661+
```
662+
663+
Default polling interval can be changed in a config:
664+
665+
```js
666+
plugins: {
667+
retryTo: {
668+
enabled: true,
669+
pollingInterval: 500,
670+
}
671+
}
672+
```
673+
674+
Disables retryFailedStep plugin for steps inside a block;
675+
676+
Use this plugin if:
677+
678+
- you need repeat a set of actions in flaky tests
679+
- iframe was not rendered and you need to retry switching to it
680+
681+
#### Configuration
682+
683+
- `pollingInterval` - default interval between retries in ms. 200 by default.
684+
- `registerGlobal` - to register `retryTo` function globally, true by default
685+
686+
If `registerGlobal` is false you can use retryTo from the plugin:
687+
688+
```js
689+
const retryTo = codeceptjs.container.plugins('retryTo');
690+
```
691+
692+
### Parameters
693+
694+
- `config`
695+
629696
## screenshotOnFail
630697
631698
Creates screenshot on failure. Screenshot is saved into `output` directory.

examples/github_test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// / <reference path="./steps.d.ts" />
2-
Feature('GitHub');
2+
Feature('GitHub', { timeout: 6 });
33

44
Before(({ Smth }) => {
55
Smth.openGitHub();
@@ -12,7 +12,7 @@ Scenario('Visit Home Page @retry', async ({ I }) => {
1212
I.retry(2).see('IMAGES');
1313
});
1414

15-
Scenario('search @grop', ({ I }) => {
15+
Scenario('search @grop', { timeout: 6 }, ({ I }) => {
1616
I.amOnPage('https://github.com/search');
1717
const a = {
1818
b: {
@@ -32,20 +32,20 @@ Scenario('search @grop', ({ I }) => {
3232
urls: {},
3333
};
3434
I.fillField('Search GitHub', 'CodeceptJS');
35-
pause({ a, b });
35+
// pause({ a, b });
3636
I.pressKey('Enter');
37-
I.wait(1);
38-
pause();
37+
I.wait(3);
38+
// pause();
3939
I.see('Codeception/CodeceptJS', locate('.repo-list .repo-list-item').first());
4040
});
4141

42-
Scenario('signin', ({ I, loginPage }) => {
42+
Scenario('signin @sign', { timeout: 6 }, ({ I, loginPage }) => {
4343
I.say('it should not enter');
4444
loginPage.login('something@totest.com', '123456');
4545
I.see('Incorrect username or password.', '.flash-error');
4646
}).tag('normal').tag('important').tag('@slow');
4747

48-
Scenario('signin2', ({ I, Smth }) => {
48+
Scenario('signin2', { timeout: 1 }, ({ I, Smth }) => {
4949
Smth.openAndLogin();
5050
I.see('Incorrect username or password.', '.flash-error');
5151
});

examples/yahoo_test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Feature('Yahoo test');
22

3-
Scenario('Nightmare basic test', ({ I }) => {
3+
Scenario('Nightmare basic test', { timeout: 3 }, ({ I }) => {
44
I.amOnPage('http://yahoo.com');
55
I.fillField('p', 'github nightmare');
6-
I.click('Search Web');
6+
I.click('Search');
77
I.waitForElement('#main', 2);
88
I.seeElement('#main .searchCenterMiddle li a');
99
// I.seeElement("//a[contains(@href,'github.com/segmentio/nightmare')]");

lib/actor.js

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,18 @@ class Actor {
2626
});
2727
}
2828

29-
/**
30-
* @function
31-
* @return {number}
32-
* @inner
33-
*/
34-
popTimeout() {
35-
const nextStepTimeout = this.nextStepTimeout;
36-
this.nextStepTimeout = undefined;
37-
return nextStepTimeout || 0;
38-
}
39-
4029
/**
4130
* set the maximum execution time for the next step
4231
* @function
4332
* @param {number} timeout - step timeout in seconds
44-
* @param {boolean} force - whether to update timeout when it is already set for the step, default is true
4533
* @return {this}
4634
* @inner
4735
*/
48-
limitTime(timeout, force = true) {
49-
// give priority to timeout set from test code
50-
if (this.nextStepTimeout === undefined || force) {
51-
this.nextStepTimeout = timeout * 1000;
52-
}
36+
limitTime(timeout) {
37+
event.dispatcher.once(event.step.before, (step) => {
38+
output.log(`Timeout to ${step}: ${timeout}s`);
39+
step.totalTimeout = timeout * 1000;
40+
});
5341
return this;
5442
}
5543

@@ -115,7 +103,7 @@ module.exports = function (obj = {}) {
115103
step.actor = translation.I;
116104
}
117105
// add methods to promise chain
118-
return recordStep(step, Array.from(arguments), actor);
106+
return recordStep(step, Array.from(arguments));
119107
};
120108
}
121109
});
@@ -124,7 +112,7 @@ module.exports = function (obj = {}) {
124112
return actor;
125113
};
126114

127-
function recordStep(step, args, actor) {
115+
function recordStep(step, args) {
128116
step.status = 'queued';
129117
step.setArguments(args);
130118

@@ -141,7 +129,7 @@ function recordStep(step, args, actor) {
141129
step.startTime = Date.now();
142130
}
143131
return val = step.run(...args);
144-
}, false, undefined, actor.popTimeout());
132+
}, false, undefined, step.totalTimeout);
145133

146134
event.emit(event.step.after, step);
147135

lib/codecept.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class Codecept {
9595
runHook(require('./listener/steps'));
9696
runHook(require('./listener/config'));
9797
runHook(require('./listener/helpers'));
98+
runHook(require('./listener/timeout'));
9899
runHook(require('./listener/exit'));
99100

100101
// custom hooks

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const defaultConfig = {
1313
include: {},
1414
mocha: {},
1515
bootstrap: null,
16+
timeout: null,
1617
teardown: null,
1718
hooks: [],
1819
gherkin: {},

lib/interfaces/featureConfig.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class FeatureConfig {
2121
* @returns {this}
2222
*/
2323
timeout(timeout) {
24+
console.log(`Feature('${this.suite.title}').timeout(${timeout}) is deprecated!`);
25+
console.log(`Please use Feature('${this.suite.title}', { timeout: ${timeout / 1000} }) instead`);
26+
console.log('Timeout should be set in seconds');
2427
this.suite.timeout(timeout);
2528
return this;
2629
}

lib/interfaces/scenarioConfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class ScenarioConfig {
4545
* @returns {this}
4646
*/
4747
timeout(timeout) {
48+
console.log(`Scenario('${this.test.title}', () => {}).timeout(${timeout}) is deprecated!`);
49+
console.log(`Please use Scenario('${this.test.title}', { timeout: ${timeout / 1000} }, () => {}) instead`);
50+
console.log('Timeout should be set in seconds');
51+
4852
this.test.timeout(timeout);
4953
return this;
5054
}

0 commit comments

Comments
 (0)