Skip to content

Commit a31d116

Browse files
committed
add retry documentation
1 parent dca57c2 commit a31d116

File tree

1 file changed

+370
-0
lines changed

1 file changed

+370
-0
lines changed

docs/retry.md

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
# Retry Mechanisms
2+
3+
CodeceptJS provides flexible retry mechanisms to handle flaky tests at different levels.
4+
5+
## Overview
6+
7+
CodeceptJS supports retries at **four levels** with a **priority system** to prevent conflicts:
8+
9+
| Priority | Level | Value | Description |
10+
|----------|-------|-------|-------------|
11+
| **Highest** | Manual Step (`I.retry()`) | 100 | Explicit retries in test code |
12+
| | Step Plugin (`retryFailedStep`) | 50 | Automatic step-level retries |
13+
| | Scenario Config | 30 | Retry entire scenarios |
14+
| | Feature Config | 20 | Retry all scenarios in feature |
15+
| **Lowest** | Hook Config | 10 | Retry failed hooks |
16+
17+
**Rule:** Higher priority retries cannot be overwritten by lower priority ones.
18+
19+
## Step-Level Retries
20+
21+
### Manual Retry: `I.retry()`
22+
23+
Retry specific steps in your tests:
24+
25+
```js
26+
// Retry up to 5 times
27+
I.retry().click('Submit')
28+
29+
// Custom options
30+
I.retry({
31+
retries: 3,
32+
minTimeout: 1000, // 1 second
33+
maxTimeout: 5000, // 5 seconds
34+
}).see('Welcome')
35+
36+
// Infinite retries
37+
I.retry(0).waitForElement('Dashboard')
38+
```
39+
40+
### Automatic Retry: `retryFailedStep` Plugin
41+
42+
Automatically retry all failed steps without modifying test code.
43+
44+
**Basic configuration:**
45+
46+
```js
47+
// codecept.conf.js
48+
plugins: {
49+
retryFailedStep: {
50+
enabled: true,
51+
retries: 3,
52+
}
53+
}
54+
```
55+
56+
**Advanced options:**
57+
58+
```js
59+
plugins: {
60+
retryFailedStep: {
61+
enabled: true,
62+
retries: 3,
63+
factor: 1.5, // exponential backoff factor
64+
minTimeout: 1000, // 1 second before first retry
65+
maxTimeout: 5000, // 5 seconds max between retries
66+
67+
// Steps to ignore (never retry these)
68+
ignoredSteps: [
69+
'scroll*', // ignore all scroll steps
70+
/Cookie/, // ignore by regexp
71+
],
72+
73+
// Defer to scenario retries to prevent excessive retries (default: true)
74+
deferToScenarioRetries: true,
75+
}
76+
}
77+
```
78+
79+
**Ignored steps by default:** `amOnPage`, `wait*`, `send*`, `execute*`, `run*`, `have*`
80+
81+
**Disable per test:**
82+
83+
```js
84+
Scenario('test', { disableRetryFailedStep: true }, () => {
85+
I.retry(5).click('Button') // Use manual retries instead
86+
})
87+
```
88+
89+
## Scenario-Level Retries
90+
91+
Configure retries for individual test scenarios.
92+
93+
```js
94+
// Simple: All scenarios retry 3 times
95+
{
96+
retry: 3
97+
}
98+
99+
// Advanced: By pattern
100+
{
101+
retry: [
102+
{
103+
Scenario: 2,
104+
grep: 'Login', // Only scenarios containing "Login"
105+
},
106+
{
107+
Scenario: 5,
108+
grep: 'API',
109+
},
110+
]
111+
}
112+
113+
// In-code
114+
Scenario('my test', { retries: 3 }, () => {
115+
I.amOnPage('/')
116+
I.click('Login')
117+
})
118+
```
119+
120+
## Feature-Level Retries
121+
122+
Retry all scenarios within a feature file:
123+
124+
```js
125+
{
126+
retry: [
127+
{
128+
Feature: 3,
129+
grep: 'Authentication', // Only features containing "Authentication"
130+
},
131+
]
132+
}
133+
```
134+
135+
## Hook-Level Retries
136+
137+
Configure retries for failed hooks:
138+
139+
```js
140+
{
141+
retry: [
142+
{
143+
BeforeSuite: 2, // Retry setup hook
144+
Before: 1, // Retry test setup
145+
After: 1, // Retry teardown
146+
},
147+
]
148+
}
149+
```
150+
151+
## Retry Coordination
152+
153+
### How Different Retries Work Together
154+
155+
When multiple retry mechanisms are configured, they work together based on priorities:
156+
157+
**Example 1: Step Plugin + Scenario Retries (default behavior)**
158+
159+
```js
160+
plugins: {
161+
retryFailedStep: {
162+
enabled: true,
163+
retries: 3,
164+
deferToScenarioRetries: true, // default
165+
}
166+
}
167+
168+
Scenario('API test', { retries: 2 }, () => {
169+
I.sendPostRequest('/api/users', { name: 'John' })
170+
})
171+
```
172+
173+
**Result:** Step retries are **disabled**. Only scenario retries run (2 times).
174+
**Total attempts:** 1 initial + 2 retries = **3 attempts**
175+
176+
**Example 2: Step Plugin without Defer**
177+
178+
```js
179+
plugins: {
180+
retryFailedStep: {
181+
enabled: true,
182+
retries: 3,
183+
deferToScenarioRetries: false,
184+
}
185+
}
186+
187+
Scenario('API test', { retries: 2 }, () => {
188+
I.sendPostRequest('/api/users', { name: 'John' })
189+
I.seeResponseCodeIs(200)
190+
})
191+
```
192+
193+
**Result:** Each step can retry 3 times, scenario can retry 2 times.
194+
**⚠️ Warning:** Can lead to excessive execution time
195+
196+
**Example 3: Manual Retry + Plugin**
197+
198+
```js
199+
plugins: {
200+
retryFailedStep: {
201+
enabled: true,
202+
retries: 3,
203+
}
204+
}
205+
206+
Scenario('test', () => {
207+
I.retry(5).click('Button') // Manual (priority 100)
208+
I.click('AnotherButton') // Plugin (priority 50)
209+
})
210+
```
211+
212+
**Result:**
213+
- First button: **5 retries** (manual takes precedence)
214+
- Second button: **3 retries** (plugin)
215+
216+
## Common Patterns
217+
218+
### External API Flakiness
219+
220+
```js
221+
{
222+
retry: [
223+
{
224+
Scenario: 3,
225+
grep: 'API',
226+
},
227+
],
228+
plugins: {
229+
retryFailedStep: {
230+
enabled: true,
231+
deferToScenarioRetries: true, // Let scenario retries handle it
232+
},
233+
},
234+
}
235+
```
236+
237+
### UI Element Intermittent Visibility
238+
239+
```js
240+
Scenario('form submission', () => {
241+
I.amOnPage('/form')
242+
I.fillField('email', 'test@example.com')
243+
244+
// This specific button is sometimes not immediately clickable
245+
I.retry(3).click('Submit')
246+
247+
I.see('Success')
248+
})
249+
```
250+
251+
### Flaky Feature Suite
252+
253+
```js
254+
{
255+
retry: [
256+
{
257+
Feature: 2,
258+
grep: 'ThirdPartyIntegration',
259+
},
260+
],
261+
}
262+
```
263+
264+
## Best Practices
265+
266+
1. **Use `deferToScenarioRetries: true`** (default) to avoid excessive retries
267+
2. **Prefer scenario retries** over step retries for general flakiness
268+
3. **Use manual `I.retry()`** for specific problematic steps
269+
4. **Avoid combining** step plugin with scenario retries unless necessary
270+
5. **Don't over-retry** - it can mask real bugs and slow down tests
271+
272+
## Troubleshooting
273+
274+
### Tests Taking Too Long
275+
276+
**Solutions:**
277+
- Enable `deferToScenarioRetries: true`
278+
- Reduce retry counts
279+
- Use more specific retry patterns (grep)
280+
- Fix the root cause instead of retrying
281+
282+
### Retries Not Working
283+
284+
**Check:**
285+
1. Verify configuration syntax
286+
2. Check if higher priority retry is overriding
287+
3. Ensure `disableRetryFailedStep: true` isn't set
288+
4. Run with `DEBUG_RETRY_PLUGIN=1`:
289+
290+
```bash
291+
DEBUG_RETRY_PLUGIN=1 npx codeceptjs run
292+
```
293+
294+
### Too Many Retries
295+
296+
**Solutions:**
297+
1. Set `deferToScenarioRetries: true`
298+
2. Add problematic steps to `ignoredSteps`
299+
3. Use scenario retries instead of step retries
300+
4. Add `when` condition to filter errors:
301+
302+
```js
303+
plugins: {
304+
retryFailedStep: {
305+
enabled: true,
306+
when: (err) => {
307+
// Only retry network errors
308+
return err.message.includes('ECONNREFUSED')
309+
},
310+
}
311+
}
312+
```
313+
314+
## Configuration Reference
315+
316+
### Global Retry Options
317+
318+
```js
319+
// Simple
320+
{
321+
retry: 3
322+
}
323+
324+
// Advanced
325+
{
326+
retry: [
327+
{
328+
Feature: 2,
329+
grep: 'Auth',
330+
},
331+
{
332+
Scenario: 5,
333+
grep: 'Payment',
334+
},
335+
{
336+
BeforeSuite: 3,
337+
},
338+
]
339+
}
340+
```
341+
342+
### retryFailedStep Plugin Options
343+
344+
| Option | Type | Default | Description |
345+
|--------|------|---------|-------------|
346+
| `retries` | number | `3` | Number of retries per step |
347+
| `factor` | number | `1.5` | Exponential backoff factor |
348+
| `minTimeout` | number | `1000` | Min milliseconds before first retry |
349+
| `maxTimeout` | number | `Infinity` | Max milliseconds between retries |
350+
| `randomize` | boolean | `false` | Randomize timeouts |
351+
| `ignoredSteps` | array | `[]` | Additional steps to ignore |
352+
| `deferToScenarioRetries` | boolean | `true` | Disable step retries when scenario retries exist |
353+
| `when` | function | - | Custom condition (receives error) |
354+
355+
### I.retry() Options
356+
357+
```js
358+
I.retry({
359+
retries: 3, // number of retries (0 = infinite)
360+
minTimeout: 1000, // milliseconds
361+
maxTimeout: 5000, // milliseconds
362+
factor: 1.5, // exponential backoff
363+
})
364+
```
365+
366+
## Related
367+
368+
- [Plugins](plugins.md) - Plugin system overview
369+
- [Configuration](configuration.md) - Full configuration reference
370+
- [Hooks](hooks.md) - Test hooks and lifecycle

0 commit comments

Comments
 (0)