codeceptjs 4.0.0-rc.21 → 4.0.0-rc.23
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/docs/advanced.md +1 -1
- 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/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/actor.js +1 -1
- package/lib/command/workers/runTests.js +1 -5
- package/lib/heal.js +2 -2
- package/lib/helper/Playwright.js +10 -4
- package/lib/plugin/aiTrace.js +4 -3
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/retryFailedStep.js +4 -3
- package/lib/plugin/screencast.js +1 -1
- package/lib/plugin/screenshot.js +2 -2
- package/lib/plugin/stepTimeout.js +2 -1
- package/lib/step/base.js +7 -7
- package/lib/step/comment.js +2 -2
- package/lib/step/helper.js +4 -4
- package/lib/step/meta.js +3 -3
- package/lib/step/record.js +3 -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/actor.js
CHANGED
|
@@ -94,7 +94,7 @@ export default function (obj = {}, container) {
|
|
|
94
94
|
actor[action] = actor[actionAlias] = function () {
|
|
95
95
|
const step = new Step(helper, action)
|
|
96
96
|
if (translation.loaded) {
|
|
97
|
-
step.
|
|
97
|
+
step.title = actionAlias
|
|
98
98
|
step.actor = translation.I
|
|
99
99
|
}
|
|
100
100
|
// add methods to promise chain
|
|
@@ -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
|
@@ -49,12 +49,12 @@ class Heal {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
hasCorrespondingRecipes(step) {
|
|
52
|
-
return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.
|
|
52
|
+
return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.title)).length > 0
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
async getCodeSuggestions(context) {
|
|
56
56
|
const suggestions = []
|
|
57
|
-
const stepName = context.step?.
|
|
57
|
+
const stepName = context.step?.title
|
|
58
58
|
const recipes = matchRecipes(this.recipes, this.contextName)
|
|
59
59
|
.filter(r => !r.steps || !stepName || r.steps.includes(stepName))
|
|
60
60
|
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -2683,7 +2683,7 @@ class Playwright extends Helper {
|
|
|
2683
2683
|
if (arg && typeof arg.evaluate === 'function' && typeof arg.locator === 'function') {
|
|
2684
2684
|
return arg.evaluate(fn)
|
|
2685
2685
|
}
|
|
2686
|
-
if (this.context && this.context.
|
|
2686
|
+
if (this.context && typeof this.context.url !== 'function' && typeof this.context.innerText !== 'function') {
|
|
2687
2687
|
return this.context.locator(':root').evaluate(fn, arg)
|
|
2688
2688
|
}
|
|
2689
2689
|
return this.page.evaluate.apply(this.page, [fn, arg])
|
|
@@ -3420,7 +3420,7 @@ class Playwright extends Helper {
|
|
|
3420
3420
|
}
|
|
3421
3421
|
|
|
3422
3422
|
async _getContext() {
|
|
3423
|
-
if (
|
|
3423
|
+
if (this.context) {
|
|
3424
3424
|
return this.context
|
|
3425
3425
|
}
|
|
3426
3426
|
if (this.frame) {
|
|
@@ -4359,7 +4359,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
4359
4359
|
if (!context) {
|
|
4360
4360
|
const el = await this.context
|
|
4361
4361
|
|
|
4362
|
-
allText = el.
|
|
4362
|
+
allText = typeof el.url !== 'function' && typeof el.innerText === 'function'
|
|
4363
|
+
? [await el.innerText()]
|
|
4364
|
+
: [await el.locator('body').innerText()]
|
|
4363
4365
|
|
|
4364
4366
|
description = 'web application'
|
|
4365
4367
|
} else {
|
|
@@ -4642,12 +4644,16 @@ async function targetCreatedHandler(page) {
|
|
|
4642
4644
|
.catch(() => null)
|
|
4643
4645
|
.then(async () => {
|
|
4644
4646
|
if (this.context && this.context._type === 'Frame') {
|
|
4645
|
-
// we are inside iframe
|
|
4647
|
+
// we are inside iframe via Frame object — refresh handle
|
|
4646
4648
|
const frameEl = await this.context.frameElement()
|
|
4647
4649
|
this.context = await frameEl.contentFrame()
|
|
4648
4650
|
this.contextLocator = null
|
|
4649
4651
|
return
|
|
4650
4652
|
}
|
|
4653
|
+
if (this.context && this.context.constructor && this.context.constructor.name === 'FrameLocator') {
|
|
4654
|
+
// we are inside iframe via FrameLocator — keep it across load events
|
|
4655
|
+
return
|
|
4656
|
+
}
|
|
4651
4657
|
// if context element was in iframe - keep it
|
|
4652
4658
|
// if (await this.context.ownerFrame()) return;
|
|
4653
4659
|
this.context = page
|
package/lib/plugin/aiTrace.js
CHANGED
|
@@ -195,7 +195,7 @@ export default function (config = {}) {
|
|
|
195
195
|
} else {
|
|
196
196
|
if (stepNum === -1) return
|
|
197
197
|
if (isStepIgnored(step)) return
|
|
198
|
-
if (step.metaStep && step.metaStep.
|
|
198
|
+
if (step.metaStep && step.metaStep.title === 'BeforeSuite') return
|
|
199
199
|
|
|
200
200
|
const stepPrefix = generateStepPrefix(step, stepNum)
|
|
201
201
|
stepNum++
|
|
@@ -247,7 +247,7 @@ export default function (config = {}) {
|
|
|
247
247
|
async function persistStep(step) {
|
|
248
248
|
if (stepNum === -1) return
|
|
249
249
|
if (isStepIgnored(step)) return
|
|
250
|
-
if (step.metaStep && step.metaStep.
|
|
250
|
+
if (step.metaStep && step.metaStep.title === 'BeforeSuite') return
|
|
251
251
|
|
|
252
252
|
const stepKey = step.toString()
|
|
253
253
|
|
|
@@ -437,8 +437,9 @@ export default function (config = {}) {
|
|
|
437
437
|
|
|
438
438
|
function isStepIgnored(step) {
|
|
439
439
|
if (!config.ignoreSteps) return false
|
|
440
|
+
if (!step.title) return false
|
|
440
441
|
for (const pattern of config.ignoreSteps || []) {
|
|
441
|
-
if (step.
|
|
442
|
+
if (step.title.match(pattern)) return true
|
|
442
443
|
}
|
|
443
444
|
return false
|
|
444
445
|
}
|