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/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
- ## CodeceptJS Retry Levels
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
- ### 3. Multiple Steps Retry (retryTo)
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
- ### 4. Self-Healing Steps
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
- ### 5. Scenario Retry
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
- ### 6. Feature Retry
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
- ### 7. Hook Retries
196
+ ## Hook Retries
190
197
 
191
198
  Retry `Before`/`After` hooks when they fail:
192
199
 
@@ -5,370 +5,155 @@ title: TypeScript
5
5
 
6
6
  # TypeScript
7
7
 
8
- CodeceptJS supports [type declaration](https://github.com/codeceptjs/CodeceptJS/tree/master/typings) for [TypeScript](https://www.typescriptlang.org/). It means that you can write your tests in TS. Also, all of your custom steps can be written in TS
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
- # Why TypeScript?
10
+ ## Getting started
11
11
 
12
- With the TypeScript writing CodeceptJS tests becomes much easier. If you configure TS properly in your project as well as your IDE, you will get the following features:
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
- ![Auto Complete](/img/Auto_comlete.gif)
17
-
18
- - To show additional information for a step in a test. Example:
19
-
20
- ![Quick Info](/img/Quick_info.gif)
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
- Then a config file and new tests will be created in TypeScript format.
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
- [tsx](https://tsx.is) is a modern, fast TypeScript loader built on esbuild. It's the recommended way to run TypeScript tests in CodeceptJS 4.x.
53
-
54
- **Installation:**
55
- ```bash
56
- npm install --save-dev tsx
20
+ ```sh
21
+ npm i tsx --save-dev
57
22
  ```
58
23
 
59
- **Configuration:**
60
- ```typescript
24
+ ```ts
61
25
  // codecept.conf.ts
62
26
  export const config = {
63
27
  tests: './**/*_test.ts',
64
- require: ['tsx/cjs'], // Enable TypeScript loader for test files
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
- **Critical Limitations:**
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
- **Recommendation:** Use `tsx/cjs` instead for a better experience.
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
- ### Full TypeScript Features in Tests
39
+ ## Writing tests
143
40
 
144
- With tsx or ts-node/esm, you can use complete TypeScript syntax including imports, enums, interfaces, and types:
41
+ Test files use the full TypeScript syntax imports, enums, interfaces, types:
145
42
 
146
- ```typescript
147
- // types.ts
148
- export enum Environment {
149
- TEST = 'test',
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 { Environment, User } from './types'
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(`Login on ${Environment.TEST}`, ({ I }) => {
53
+ Scenario('admin signs in', ({ I }) => {
170
54
  I.amOnPage('/login')
171
- I.fillField('email', testUser.email)
172
- I.fillField('password', testUser.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
- ### Troubleshooting TypeScript Tests
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
- **Error: "Cannot find module" or "Unexpected token"**
64
+ ## Promise-based typings
181
65
 
182
- This means the TypeScript loader isn't configured. Make sure:
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
- **Error: Module not found when importing from `.ts` files**
188
-
189
- When using `ts-node/esm` with ESM, you need to use `.js` extensions in imports:
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
- TypeScript will resolve the `.js` import to your `.ts` file during compilation. This is the standard behavior for ESM + TypeScript.
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
- **New setup (4.x):**
224
- ```typescript
225
- // codecept.conf.ts
76
+ ```ts
226
77
  export const config = {
227
- tests: './*_test.ts',
228
- require: ['tsx/cjs'], // TypeScript loader
229
- helpers: {}
78
+ fullPromiseBased: true,
79
+ // ...
230
80
  }
231
81
  ```
232
82
 
233
- **Migration steps:**
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
- ```js
245
- I.amOnPage('/')
246
- I.click('Login')
247
- I.see('Hello!')
85
+ ```sh
86
+ npx codeceptjs def
248
87
  ```
249
88
 
250
- Even thought we don't see any `await`, those commands are executed synchronously, one by one.
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
- Our philosophy here is: use `await` only when it is actually needed, don't add visual mess to your code prefixing each line with `await`. However, you might want to get a better control of your tests and follow TypeScript conventions.
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
- Using `await` explicitly provides a beter control of execution flow. Some CodeceptJS users report that they increased stability of tests by adopting `await` for all CodeceptJS commands in their codebase.
97
+ ## Types for page objects and custom helpers
266
98
 
267
- If you select to use promise-based typings, type definitions will be generated so all actions to return a promise.
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
- To introduce promise-based typings into a current project edit `codecept.conf.ts`:
101
+ For a custom helper:
271
102
 
272
103
  ```ts
273
- fullPromiseBased: true;
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
- Then you need to add this helper to your `codecept.conf.js` like in this [docs](https://codecept.io/helpers/#configuration).
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 Puppeteer, CustomHelper {}
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
- And now you can use autocomplete on your test.
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
- /// <reference types='codeceptjs' />
320
- type loginPage = typeof import('./loginPage');
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
- interface Methods extends Puppeteer, CustomHelper {}
327
- interface I extends WithTranslation<Methods> {}
328
- namespace Translation {
329
- interface Actions {}
330
- }
133
+ // ...
331
134
  }
332
135
  ```
333
136
 
334
- ## Types for custom strict locators
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
- In order to use the custom locator in TypeScript code, its type shape needs to be registered in the interface `CustomLocators` in your `steps.d.ts` file:
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
- The property keys used in the `CustomLocators` interface do not matter (only the *types* of the interface properties are used). For simplicity it is recommended to use the name that is also used in your custom locator itself.
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, value?: number, flag?: boolean };
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.name = actionAlias
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
- // Don't exit on test-related rejections
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.name)).length > 0
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?.name
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
 
@@ -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.constructor.name === 'FrameLocator') {
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 ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) {
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.constructor.name !== 'Locator' ? [await el.locator('body').innerText()] : [await el.innerText()]
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
@@ -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.name === 'BeforeSuite') return
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.name === 'BeforeSuite') return
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.name.match(pattern)) return true
442
+ if (step.title.match(pattern)) return true
442
443
  }
443
444
  return false
444
445
  }