codeceptjs 4.0.0-rc.20 → 4.0.0-rc.22
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/bin/mcp-server.js +7 -5
- package/docs/advanced.md +1 -1
- package/docs/agents.md +32 -10
- package/docs/architecture.md +219 -0
- package/docs/configuration.md +82 -127
- package/docs/continuous-integration.md +113 -151
- package/docs/custom-helpers.md +1 -1
- package/docs/environment-variables.md +131 -0
- package/docs/hooks.md +76 -277
- package/docs/installation.md +95 -40
- package/docs/parallel.md +98 -496
- package/docs/plugins.md +43 -0
- package/docs/reports.md +102 -401
- package/docs/retry.md +44 -37
- package/docs/typescript.md +54 -269
- package/lib/codecept.js +1 -1
- package/lib/command/run-workers.js +0 -14
- package/lib/command/run.js +2 -16
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +1 -5
- package/lib/heal.js +2 -0
- package/lib/helper/Playwright.js +15 -4
- package/lib/plugin/aiTrace.js +16 -13
- package/lib/plugin/analyze.js +5 -4
- package/lib/plugin/heal.js +3 -2
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/pageInfo.js +5 -7
- package/lib/plugin/retryFailedStep.js +4 -3
- package/lib/plugin/screencast.js +6 -4
- package/lib/workers.js +11 -3
- package/package.json +1 -1
- package/docs/internal-api.md +0 -265
package/docs/retry.md
CHANGED
|
@@ -7,6 +7,17 @@ title: Retry Mechanisms
|
|
|
7
7
|
|
|
8
8
|
CodeceptJS provides flexible retry mechanisms to handle flaky tests. Use retries when dealing with unstable environments, network delays, or timing issues — not to mask bugs in your code.
|
|
9
9
|
|
|
10
|
+
## Retry Levels
|
|
11
|
+
|
|
12
|
+
* [helper retries](#helper-retries) — happen low level on browser interaction (resolves rendering of elements)
|
|
13
|
+
* [failed step retries](#failed-step-retries) — performed by CodeceptJS on step fail
|
|
14
|
+
* [manual step retries](#manual-step-retries) — on known flaky steps
|
|
15
|
+
* [multiple steps retry](#multiple-steps-retry) — retry a group of steps together as a single operation
|
|
16
|
+
* [self-healing steps](#self-healing-steps) — AI-powered recovery that continues tests without changing test code
|
|
17
|
+
* [scenario retry](#scenario-retry) — retry entire test scenarios on failure
|
|
18
|
+
* [feature retry](#feature-retry) — retry all scenarios within a feature
|
|
19
|
+
* [hook retries](#hook-retries) — retry `Before`/`After` hooks on failure
|
|
20
|
+
|
|
10
21
|
## Helper Retries
|
|
11
22
|
|
|
12
23
|
Browser automation helpers (Playwright, Puppeteer, WebDriver) have **built-in retry mechanisms** for element interactions. When you call `I.click('Button')`, Playwright automatically waits for the element to exist, be visible, stable, and enabled — retrying for up to 5 seconds.
|
|
@@ -24,38 +35,7 @@ helpers: {
|
|
|
24
35
|
|
|
25
36
|
**Learn more:** [Playwright Helper](/helpers/Playwright), [Timeouts](/timeouts)
|
|
26
37
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
When helper retries aren't enough, CodeceptJS adds retry layers on top.
|
|
30
|
-
|
|
31
|
-
### 1. Manual Step Retry
|
|
32
|
-
|
|
33
|
-
Retry a specific step known to be flaky:
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
import step from 'codeceptjs/steps'
|
|
37
|
-
|
|
38
|
-
Scenario('checkout', ({ I }) => {
|
|
39
|
-
I.amOnPage('/cart')
|
|
40
|
-
I.click('Proceed to Checkout', step.retry(5)) // retry up to 5 times
|
|
41
|
-
I.see('Payment')
|
|
42
|
-
})
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Configure timing with exponential backoff:
|
|
46
|
-
|
|
47
|
-
```js
|
|
48
|
-
I.click('Submit', step.retry({
|
|
49
|
-
retries: 3,
|
|
50
|
-
minTimeout: 1000, // wait 1 second before first retry
|
|
51
|
-
maxTimeout: 5000, // max 5 seconds between retries
|
|
52
|
-
factor: 1.5 // exponential backoff multiplier
|
|
53
|
-
}))
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Pass `0` for infinite retries.
|
|
57
|
-
|
|
58
|
-
### 2. Automatic Step Retry
|
|
38
|
+
## Failed Step Retries
|
|
59
39
|
|
|
60
40
|
Automatically retry all failed steps without modifying test code:
|
|
61
41
|
|
|
@@ -99,7 +79,34 @@ Full plugin options:
|
|
|
99
79
|
| `deferToScenarioRetries` | `true` | Disable step retries when scenario retries exist |
|
|
100
80
|
| `when` | `() => true` | Function receiving error; return `true` to retry |
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
## Manual Step Retries
|
|
83
|
+
|
|
84
|
+
Retry a specific step known to be flaky:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
import step from 'codeceptjs/steps'
|
|
88
|
+
|
|
89
|
+
Scenario('checkout', ({ I }) => {
|
|
90
|
+
I.amOnPage('/cart')
|
|
91
|
+
I.click('Proceed to Checkout', step.retry(5)) // retry up to 5 times
|
|
92
|
+
I.see('Payment')
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Configure timing with exponential backoff:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
I.click('Submit', step.retry({
|
|
100
|
+
retries: 3,
|
|
101
|
+
minTimeout: 1000, // wait 1 second before first retry
|
|
102
|
+
maxTimeout: 5000, // max 5 seconds between retries
|
|
103
|
+
factor: 1.5 // exponential backoff multiplier
|
|
104
|
+
}))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Pass `0` for infinite retries.
|
|
108
|
+
|
|
109
|
+
## Multiple Steps Retry
|
|
103
110
|
|
|
104
111
|
Retry a group of steps together as a single operation:
|
|
105
112
|
|
|
@@ -116,7 +123,7 @@ If any step inside fails, the entire block retries. Use this for sequences that
|
|
|
116
123
|
|
|
117
124
|
**Learn more:** [Effects](/effects#retryto)
|
|
118
125
|
|
|
119
|
-
|
|
126
|
+
## Self-Healing Steps
|
|
120
127
|
|
|
121
128
|
When a step fails, a healing recipe runs recovery actions and continues the test — without touching test code. With AI healing enabled:
|
|
122
129
|
|
|
@@ -143,7 +150,7 @@ You can also write custom recipes for non-UI failures — network errors, data g
|
|
|
143
150
|
|
|
144
151
|
**Learn more:** [Self-Healing Tests](/heal), [AI Configuration](/ai)
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
## Scenario Retry
|
|
147
154
|
|
|
148
155
|
Retry an entire test when it fails:
|
|
149
156
|
|
|
@@ -165,7 +172,7 @@ export const config = {
|
|
|
165
172
|
}
|
|
166
173
|
```
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
## Feature Retry
|
|
169
176
|
|
|
170
177
|
Retry all scenarios within a feature:
|
|
171
178
|
|
|
@@ -186,7 +193,7 @@ export const config = {
|
|
|
186
193
|
}
|
|
187
194
|
```
|
|
188
195
|
|
|
189
|
-
|
|
196
|
+
## Hook Retries
|
|
190
197
|
|
|
191
198
|
Retry `Before`/`After` hooks when they fail:
|
|
192
199
|
|
package/docs/typescript.md
CHANGED
|
@@ -5,370 +5,155 @@ title: TypeScript
|
|
|
5
5
|
|
|
6
6
|
# TypeScript
|
|
7
7
|
|
|
8
|
-
CodeceptJS
|
|
8
|
+
CodeceptJS ships [type declarations](https://github.com/codeceptjs/CodeceptJS/tree/master/typings), so you can write tests, page objects, and custom helpers in TypeScript and get autocomplete and type checking in your editor.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Getting started
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- [Autocomplete (with IntelliSense)](https://code.visualstudio.com/docs/editor/intellisense) - a tool that streamlines your work by suggesting when you typing what function or property which exists in a class, what arguments can be passed to that method, what it returns, etc.
|
|
14
|
-
Example:
|
|
15
|
-
|
|
16
|
-

|
|
17
|
-
|
|
18
|
-
- To show additional information for a step in a test. Example:
|
|
19
|
-
|
|
20
|
-

|
|
21
|
-
|
|
22
|
-
- Checks types - thanks to TypeScript support in CodeceptJS now allow to tests your tests. TypeScript can prevent some errors:
|
|
23
|
-
- invalid type of variables passed to function;
|
|
24
|
-
- calls no-exist method from PageObject or `I` object;
|
|
25
|
-
- incorrectly used CodeceptJS features;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Getting Started <Badge text="Since 3.3.5" type="warning"/>
|
|
29
|
-
|
|
30
|
-
CodeceptJS can initialize tests as a TypeScript project.
|
|
31
|
-
When starting a new project with a standard installation via
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
npx codeceptjs init
|
|
35
|
-
```
|
|
36
|
-
Then select TypeScript as the first question:
|
|
12
|
+
`npx codeceptjs init` scaffolds a TypeScript project when you answer **Yes** to:
|
|
37
13
|
|
|
38
14
|
```
|
|
39
15
|
? Do you plan to write tests in TypeScript? Yes
|
|
40
16
|
```
|
|
41
17
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
If a config file is set in TypeScript format (`codecept.conf.ts`) package `ts-node` will be used to run tests.
|
|
45
|
-
|
|
46
|
-
## TypeScript Tests in ESM (CodeceptJS 4.x) <Badge text="Since 4.0.0" type="tip"/>
|
|
47
|
-
|
|
48
|
-
CodeceptJS 4.x uses ES Modules (ESM) which requires a different approach for TypeScript test files. While TypeScript **config files** (`codecept.conf.ts`) are automatically transpiled, TypeScript **test files** need a loader.
|
|
49
|
-
|
|
50
|
-
### Using tsx (Recommended)
|
|
18
|
+
It writes `codecept.conf.ts` and `*_test.ts` files. The **config file** and helpers are transpiled automatically. **Test files** need a loader — CodeceptJS 4.x is ESM, and Mocha loads test files through CommonJS hooks, so use [`tsx`](https://tsx.is) (fast, esbuild-based, no `tsconfig.json` required):
|
|
51
19
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
**Installation:**
|
|
55
|
-
```bash
|
|
56
|
-
npm install --save-dev tsx
|
|
20
|
+
```sh
|
|
21
|
+
npm i tsx --save-dev
|
|
57
22
|
```
|
|
58
23
|
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
24
|
+
```ts
|
|
61
25
|
// codecept.conf.ts
|
|
62
26
|
export const config = {
|
|
63
27
|
tests: './**/*_test.ts',
|
|
64
|
-
require: ['tsx/cjs'],
|
|
28
|
+
require: ['tsx/cjs'], // loads the *_test.ts files
|
|
65
29
|
helpers: {
|
|
66
|
-
Playwright: {
|
|
67
|
-
url: 'http://localhost',
|
|
68
|
-
browser: 'chromium'
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
That's it! Now you can write tests in TypeScript with full language support:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
// login_test.ts
|
|
78
|
-
Feature('Login')
|
|
79
|
-
|
|
80
|
-
Scenario('successful login', ({ I }) => {
|
|
81
|
-
I.amOnPage('/login')
|
|
82
|
-
I.fillField('email', 'user@example.com')
|
|
83
|
-
I.fillField('password', 'password123')
|
|
84
|
-
I.click('Login')
|
|
85
|
-
I.see('Welcome')
|
|
86
|
-
})
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Why tsx?**
|
|
90
|
-
- ⚡ **Fast:** Built on esbuild, much faster than ts-node
|
|
91
|
-
- 🎯 **Zero config:** Works without tsconfig.json
|
|
92
|
-
- 🚀 **Works with Mocha:** Uses CommonJS hooks that Mocha understands
|
|
93
|
-
- ✅ **Complete:** Handles all TypeScript features (enums, decorators, etc.)
|
|
94
|
-
|
|
95
|
-
### Using ts-node/esm (Not Recommended)
|
|
96
|
-
|
|
97
|
-
> ⚠️ **Note:** `ts-node/esm` has significant limitations with module resolution and doesn't work well with modern ESM TypeScript projects. **We strongly recommend using `tsx` instead.** The information below is provided for reference only.
|
|
98
|
-
|
|
99
|
-
`ts-node/esm` has several issues:
|
|
100
|
-
- Doesn't support `"type": "module"` in package.json
|
|
101
|
-
- Doesn't resolve extensionless imports or `.js` imports to `.ts` files
|
|
102
|
-
- Requires explicit `.ts` extensions in imports, which isn't standard TypeScript practice
|
|
103
|
-
- Less reliable than `tsx` for ESM scenarios
|
|
104
|
-
|
|
105
|
-
**If you still want to use ts-node/esm:**
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npm install --save-dev ts-node
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
// codecept.conf.ts
|
|
113
|
-
export const config = {
|
|
114
|
-
tests: './**/*_test.ts',
|
|
115
|
-
require: ['ts-node/esm'],
|
|
116
|
-
helpers: { /* ... */ }
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
```json
|
|
121
|
-
// tsconfig.json
|
|
122
|
-
{
|
|
123
|
-
"compilerOptions": {
|
|
124
|
-
"module": "ESNext",
|
|
125
|
-
"target": "ES2022",
|
|
126
|
-
"moduleResolution": "node",
|
|
127
|
-
"esModuleInterop": true
|
|
30
|
+
Playwright: { url: 'http://localhost', browser: 'chromium' },
|
|
128
31
|
},
|
|
129
|
-
"ts-node": {
|
|
130
|
-
"esm": true
|
|
131
|
-
}
|
|
132
32
|
}
|
|
133
33
|
```
|
|
134
34
|
|
|
135
|
-
|
|
136
|
-
- ❌ Cannot use `"type": "module"` in package.json
|
|
137
|
-
- ❌ Import statements must match the actual file (no automatic resolution)
|
|
138
|
-
- ❌ Module resolution doesn't work like standard TypeScript/Node.js ESM
|
|
35
|
+
Run the tests with `npx codeceptjs run`.
|
|
139
36
|
|
|
140
|
-
|
|
37
|
+
> Adding TypeScript to an existing project: set `"type": "module"` in `package.json`, rename the config to `codecept.conf.ts` with `export const config = {}`, install `tsx`, and add `require: ['tsx/cjs']`.
|
|
141
38
|
|
|
142
|
-
|
|
39
|
+
## Writing tests
|
|
143
40
|
|
|
144
|
-
|
|
41
|
+
Test files use the full TypeScript syntax — imports, enums, interfaces, types:
|
|
145
42
|
|
|
146
|
-
```
|
|
147
|
-
//
|
|
148
|
-
export
|
|
149
|
-
|
|
150
|
-
STAGING = 'staging',
|
|
151
|
-
PRODUCTION = 'production'
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export interface User {
|
|
155
|
-
email: string
|
|
156
|
-
password: string
|
|
157
|
-
}
|
|
43
|
+
```ts
|
|
44
|
+
// fixtures.ts
|
|
45
|
+
export interface User { email: string; password: string }
|
|
46
|
+
export const admin: User = { email: 'admin@example.com', password: 's3cret' }
|
|
158
47
|
|
|
159
48
|
// login_test.ts
|
|
160
|
-
import {
|
|
161
|
-
|
|
162
|
-
const testUser: User = {
|
|
163
|
-
email: 'test@example.com',
|
|
164
|
-
password: 'password123'
|
|
165
|
-
}
|
|
49
|
+
import { admin } from './fixtures'
|
|
166
50
|
|
|
167
51
|
Feature('Login')
|
|
168
52
|
|
|
169
|
-
Scenario(
|
|
53
|
+
Scenario('admin signs in', ({ I }) => {
|
|
170
54
|
I.amOnPage('/login')
|
|
171
|
-
I.fillField('email',
|
|
172
|
-
I.fillField('password',
|
|
55
|
+
I.fillField('email', admin.email)
|
|
56
|
+
I.fillField('password', admin.password)
|
|
173
57
|
I.click('Login')
|
|
174
58
|
I.see('Welcome')
|
|
175
59
|
})
|
|
176
60
|
```
|
|
177
61
|
|
|
178
|
-
|
|
62
|
+
> **Cannot find module** or **Unexpected token** while running tests means the loader isn't wired up — check that `tsx` is installed and `require: ['tsx/cjs']` is in the config.
|
|
179
63
|
|
|
180
|
-
|
|
64
|
+
## Promise-based typings
|
|
181
65
|
|
|
182
|
-
|
|
183
|
-
1. You have `tsx` or `ts-node` installed: `npm install --save-dev tsx`
|
|
184
|
-
2. Your config includes the loader in `require` array: `require: ['tsx/cjs']`
|
|
185
|
-
3. The loader is specified before test files are loaded
|
|
66
|
+
CodeceptJS tests read synchronously even though every `I.*` call returns a promise:
|
|
186
67
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
// This will cause an error in ESM mode:
|
|
193
|
-
import loginPage from "./pages/Login"
|
|
194
|
-
|
|
195
|
-
// Use .js extension instead:
|
|
196
|
-
import loginPage from "./pages/Login.js"
|
|
68
|
+
```ts
|
|
69
|
+
I.amOnPage('/')
|
|
70
|
+
I.click('Login')
|
|
71
|
+
I.see('Hello')
|
|
197
72
|
```
|
|
198
73
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Alternatively, use `tsx/cjs` which doesn't require explicit extensions.
|
|
202
|
-
|
|
203
|
-
**TypeScript config files vs test files**
|
|
204
|
-
|
|
205
|
-
Note the difference:
|
|
206
|
-
- **Config files** (`codecept.conf.ts`, helpers): Automatically transpiled by CodeceptJS
|
|
207
|
-
- **Test files** (`*_test.ts`): Need a loader specified in `config.require`
|
|
208
|
-
|
|
209
|
-
### Migration from CodeceptJS 3.x
|
|
210
|
-
|
|
211
|
-
If you're upgrading from CodeceptJS 3.x (CommonJS) to 4.x (ESM):
|
|
212
|
-
|
|
213
|
-
**Old setup (3.x):**
|
|
214
|
-
```javascript
|
|
215
|
-
// codecept.conf.js
|
|
216
|
-
module.exports = {
|
|
217
|
-
tests: './*_test.ts',
|
|
218
|
-
require: ['ts-node/register'], // CommonJS loader
|
|
219
|
-
helpers: {}
|
|
220
|
-
}
|
|
221
|
-
```
|
|
74
|
+
The default typings declare these methods as returning `void`, so a linter won't demand `await` on every line. To follow TypeScript conventions and `await` each command instead — some teams find explicit flow control improves stability — enable promise-based typings in `codecept.conf.ts`:
|
|
222
75
|
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
// codecept.conf.ts
|
|
76
|
+
```ts
|
|
226
77
|
export const config = {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
helpers: {}
|
|
78
|
+
fullPromiseBased: true,
|
|
79
|
+
// ...
|
|
230
80
|
}
|
|
231
81
|
```
|
|
232
82
|
|
|
233
|
-
|
|
234
|
-
1. Install tsx: `npm install --save-dev tsx`
|
|
235
|
-
2. Update package.json: `"type": "module"`
|
|
236
|
-
3. Rename config to `codecept.conf.ts` and use `export const config = {}`
|
|
237
|
-
4. Change `require: ['ts-node/register']` to `require: ['tsx/cjs']`
|
|
238
|
-
5. Run tests: `npx codeceptjs run`
|
|
239
|
-
|
|
240
|
-
## Promise-Based Typings
|
|
241
|
-
|
|
242
|
-
By default, CodeceptJS tests are written in synchronous mode. This is a regular CodeceptJS test:
|
|
83
|
+
Rebuild the type definitions:
|
|
243
84
|
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
I.click('Login')
|
|
247
|
-
I.see('Hello!')
|
|
85
|
+
```sh
|
|
86
|
+
npx codeceptjs def
|
|
248
87
|
```
|
|
249
88
|
|
|
250
|
-
|
|
251
|
-
All methods of `I` object actually return promise and TypeScript linter requires to use `await` operator for those promises.
|
|
252
|
-
To trick TypeScript and allow writing tests in CodeceptJS manner we create typings where `void` is returned instead of promises. This way linter won't complain on async code without await, as no promise is returned.
|
|
89
|
+
Now the typings return promises:
|
|
253
90
|
|
|
254
|
-
|
|
255
|
-
This is why you might want to **enable promise-based typings**.
|
|
256
|
-
|
|
257
|
-
A previous test should be rewritten with `await`s:
|
|
258
|
-
|
|
259
|
-
```js
|
|
91
|
+
```ts
|
|
260
92
|
await I.amOnPage('/')
|
|
261
93
|
await I.click('Login')
|
|
262
|
-
await I.see('Hello
|
|
94
|
+
await I.see('Hello')
|
|
263
95
|
```
|
|
264
96
|
|
|
265
|
-
|
|
97
|
+
## Types for page objects and custom helpers
|
|
266
98
|
|
|
267
|
-
|
|
268
|
-
Otherwise they will still return promises but it won't be relfected in type definitions.
|
|
99
|
+
`npx codeceptjs def` regenerates `steps.d.ts` from your config — run it after adding a page object or a custom helper so autocomplete picks them up.
|
|
269
100
|
|
|
270
|
-
|
|
101
|
+
For a custom helper:
|
|
271
102
|
|
|
272
103
|
```ts
|
|
273
|
-
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
and rebuild type definitions with
|
|
277
|
-
|
|
278
|
-
```
|
|
279
|
-
npx codeceptjs def
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
## Types for custom helper or page object
|
|
283
|
-
|
|
284
|
-
If you want to get types for your [custom helper](https://codecept.io/helpers/#configuration), you can add their automatically with CodeceptJS command `npx codeceptjs def`.
|
|
285
|
-
|
|
286
|
-
For example, if you add the new step `printMessage` for your custom helper like this:
|
|
287
|
-
```js
|
|
288
|
-
// customHelper.ts
|
|
104
|
+
// CustomHelper.ts
|
|
289
105
|
export class CustomHelper extends Helper {
|
|
290
106
|
printMessage(msg: string) {
|
|
291
107
|
console.log(msg)
|
|
292
108
|
}
|
|
293
109
|
}
|
|
294
|
-
|
|
295
110
|
```
|
|
296
111
|
|
|
297
|
-
|
|
298
|
-
And then run the command `npx codeceptjs def`.
|
|
112
|
+
Register it in `codecept.conf.ts` ([helper configuration](/helpers#configuration)), run `npx codeceptjs def`, and `steps.d.ts` becomes:
|
|
299
113
|
|
|
300
|
-
As result our `steps.d.ts` file will be updated like this:
|
|
301
114
|
```ts
|
|
302
115
|
/// <reference types='codeceptjs' />
|
|
303
|
-
type CustomHelper = import('./CustomHelper')
|
|
116
|
+
type CustomHelper = import('./CustomHelper')
|
|
304
117
|
|
|
305
118
|
declare namespace CodeceptJS {
|
|
306
119
|
interface SupportObject { I: I }
|
|
307
|
-
interface Methods extends
|
|
120
|
+
interface Methods extends Playwright, CustomHelper {}
|
|
308
121
|
interface I extends WithTranslation<Methods> {}
|
|
309
|
-
namespace Translation {
|
|
310
|
-
interface Actions {}
|
|
311
|
-
}
|
|
312
122
|
}
|
|
313
123
|
```
|
|
314
124
|
|
|
315
|
-
|
|
125
|
+
Page objects appear the same way — `def` adds a `type` for each and lists them in `SupportObject`:
|
|
316
126
|
|
|
317
|
-
Generation types for PageObject looks like for a custom helper, but `steps.d.ts` will look like:
|
|
318
127
|
```ts
|
|
319
|
-
|
|
320
|
-
type
|
|
321
|
-
type homePage = typeof import('./homePage');
|
|
322
|
-
type CustomHelper = import('./CustomHelper');
|
|
128
|
+
type loginPage = typeof import('./loginPage')
|
|
129
|
+
type homePage = typeof import('./homePage')
|
|
323
130
|
|
|
324
131
|
declare namespace CodeceptJS {
|
|
325
132
|
interface SupportObject { I: I, loginPage: loginPage, homePage: homePage }
|
|
326
|
-
|
|
327
|
-
interface I extends WithTranslation<Methods> {}
|
|
328
|
-
namespace Translation {
|
|
329
|
-
interface Actions {}
|
|
330
|
-
}
|
|
133
|
+
// ...
|
|
331
134
|
}
|
|
332
135
|
```
|
|
333
136
|
|
|
334
|
-
## Types for custom
|
|
335
|
-
|
|
336
|
-
You can define [custom strict locators](https://codecept.io/locators/#custom-strict-locators) that can be used in all methods taking a locator (parameter type `LocatorOrString`).
|
|
337
|
-
|
|
338
|
-
Example: A custom strict locator with a `data` property, which can be used like this:
|
|
339
|
-
|
|
340
|
-
```ts
|
|
341
|
-
I.click({ data: 'user-login' });
|
|
342
|
-
```
|
|
137
|
+
## Types for custom locators
|
|
343
138
|
|
|
344
|
-
|
|
139
|
+
If you use [custom locators](/locators#custom-locators) — for example `I.click({ data: 'user-login' })` — declare their shape in the `CustomLocators` interface in `steps.d.ts` so they're accepted wherever a locator is expected:
|
|
345
140
|
|
|
346
141
|
```ts
|
|
347
142
|
/// <reference types='codeceptjs' />
|
|
348
|
-
...
|
|
349
143
|
|
|
350
144
|
declare namespace CodeceptJS {
|
|
351
|
-
...
|
|
352
|
-
|
|
353
145
|
interface CustomLocators {
|
|
354
|
-
data: { data: string }
|
|
146
|
+
data: { data: string }
|
|
355
147
|
}
|
|
356
148
|
}
|
|
357
149
|
```
|
|
358
150
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
You can also define more complicated custom locators with multiple (also optional) properties:
|
|
151
|
+
Only the property *types* matter, not the keys. Locators with several (optional) properties work too:
|
|
362
152
|
|
|
363
153
|
```ts
|
|
364
|
-
/// <reference types='codeceptjs' />
|
|
365
|
-
...
|
|
366
|
-
|
|
367
154
|
declare namespace CodeceptJS {
|
|
368
|
-
...
|
|
369
|
-
|
|
370
155
|
interface CustomLocators {
|
|
371
|
-
data: { data: string
|
|
156
|
+
data: { data: string; value?: number; flag?: boolean }
|
|
372
157
|
}
|
|
373
158
|
}
|
|
374
159
|
```
|
package/lib/codecept.js
CHANGED
|
@@ -316,7 +316,7 @@ class Codecept {
|
|
|
316
316
|
|
|
317
317
|
try {
|
|
318
318
|
event.emit(event.all.before, this)
|
|
319
|
-
mocha.run(async (failures) => await done(failures))
|
|
319
|
+
mocha.runner = mocha.run(async (failures) => await done(failures))
|
|
320
320
|
} catch (e) {
|
|
321
321
|
output.error(e.stack)
|
|
322
322
|
reject(e)
|
|
@@ -87,19 +87,5 @@ export default async function (workerCount, selectedRuns, options) {
|
|
|
87
87
|
process.exitCode = 1
|
|
88
88
|
} finally {
|
|
89
89
|
await workers.teardownAll()
|
|
90
|
-
|
|
91
|
-
// Force exit if event loop doesn't clear naturally
|
|
92
|
-
// This is needed because worker threads may leave handles open
|
|
93
|
-
// even after proper cleanup, preventing natural process termination
|
|
94
|
-
if (!options.noExit) {
|
|
95
|
-
// Use beforeExit to ensure we run after all other exit handlers
|
|
96
|
-
// have set the correct exit code
|
|
97
|
-
process.once('beforeExit', (code) => {
|
|
98
|
-
// Give cleanup a moment to complete, then force exit with the correct code
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
process.exit(code || process.exitCode || 0)
|
|
101
|
-
}, 100)
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
90
|
}
|
|
105
91
|
}
|
package/lib/command/run.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { getConfig, printError, getTestRoot, createOutputDir } from './utils.js'
|
|
1
|
+
import { getConfig, printError, getTestRoot, createOutputDir, autoExit } from './utils.js'
|
|
2
2
|
import Config from '../config.js'
|
|
3
3
|
import store from '../store.js'
|
|
4
4
|
import Codecept from '../codecept.js'
|
|
5
|
-
import container from '../container.js'
|
|
6
5
|
|
|
7
6
|
export default async function (test, options) {
|
|
8
7
|
// registering options globally to use in config
|
|
@@ -43,19 +42,6 @@ export default async function (test, options) {
|
|
|
43
42
|
process.exitCode = 1
|
|
44
43
|
} finally {
|
|
45
44
|
await codecept.teardown()
|
|
46
|
-
|
|
47
|
-
// Schedule a delayed exit to prevent process hanging due to browser helper event loops
|
|
48
|
-
// Only needed for Playwright/Puppeteer which keep the event loop alive
|
|
49
|
-
// Wait 1 second to allow final cleanup and output to complete
|
|
50
|
-
if (!process.env.CODECEPT_DISABLE_AUTO_EXIT) {
|
|
51
|
-
const helpers = container.helpers()
|
|
52
|
-
const hasBrowserHelper = helpers && (helpers.Playwright || helpers.Puppeteer || helpers.WebDriver)
|
|
53
|
-
|
|
54
|
-
if (hasBrowserHelper) {
|
|
55
|
-
setTimeout(() => {
|
|
56
|
-
process.exit(process.exitCode || 0)
|
|
57
|
-
}, 1000).unref()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
45
|
+
await autoExit()
|
|
60
46
|
}
|
|
61
47
|
}
|
package/lib/command/utils.js
CHANGED
|
@@ -107,6 +107,20 @@ export const createOutputDir = (config, testRoot) => {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
export async function autoExit() {
|
|
111
|
+
const timeout = parseInt(process.env.CODECEPT_AUTO_EXIT_TIMEOUT, 10)
|
|
112
|
+
if (timeout === 0) return
|
|
113
|
+
const exitTimeout = timeout || 2000
|
|
114
|
+
|
|
115
|
+
const { default: container } = await import('../container.js')
|
|
116
|
+
const helpers = container.helpers()
|
|
117
|
+
if (!helpers || !Object.values(helpers).some(h => typeof h._cleanup === 'function')) return
|
|
118
|
+
|
|
119
|
+
const { default: recorder } = await import('../recorder.js')
|
|
120
|
+
await Promise.race([recorder.promise(), new Promise(resolve => setTimeout(resolve, exitTimeout))])
|
|
121
|
+
process.exit(process.exitCode || 0)
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
export const findConfigFile = testsPath => {
|
|
111
125
|
const extensions = ['js', 'ts']
|
|
112
126
|
for (const ext of extensions) {
|
|
@@ -54,11 +54,7 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
54
54
|
process.stderr.write(`${reason.stack}\n`)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
if (msg.includes('expected') || msg.includes('AssertionError')) {
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
process.exit(1)
|
|
57
|
+
// Do not exit — killing the worker silently drops every remaining test from the report.
|
|
62
58
|
})
|
|
63
59
|
|
|
64
60
|
// hide worker output
|
package/lib/heal.js
CHANGED
|
@@ -54,7 +54,9 @@ class Heal {
|
|
|
54
54
|
|
|
55
55
|
async getCodeSuggestions(context) {
|
|
56
56
|
const suggestions = []
|
|
57
|
+
const stepName = context.step?.name
|
|
57
58
|
const recipes = matchRecipes(this.recipes, this.contextName)
|
|
59
|
+
.filter(r => !r.steps || !stepName || r.steps.includes(stepName))
|
|
58
60
|
|
|
59
61
|
debug('Recipes', recipes)
|
|
60
62
|
|