codeceptjs 4.2.0-beta.2 → 4.2.0-beta.4

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/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  | 🌐 Web | Playwright | [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml) |
11
11
  | 🌐 Web | Puppeteer | [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) |
12
12
  | 🌐 Web | WebDriver | [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) |
13
+ | 🌐 Web | Obscura | [![Obscura Helper Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/obscura.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/obscura.yml) |
13
14
  | 📱 Mobile | Appium | [![Appium Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium_Android.yml) |
14
15
 
15
16
  # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)
@@ -42,6 +43,7 @@ CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently,
42
43
  - [**Playwright**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Playwright.md) - is a Node library to automate the Chromium, WebKit and Firefox browsers with a single API.
43
44
  - [**Puppeteer**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Puppeteer.md) - uses Google Chrome's Puppeteer for fast headless testing.
44
45
  - [**WebDriver**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) - uses [webdriverio](http://webdriver.io/) to run tests via WebDriver or Devtools protocol.
46
+ - [**Obscura**](https://codecept.io/helpers/Obscura) - drives the lightweight Obscura browser through Chrome DevTools Protocol. See [Alternative Browser Engines](https://codecept.io/alternative-browsers).
45
47
  - [**Appium**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Appium.md) - for **mobile testing** with Appium
46
48
  - [**Detox**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Detox.md) - This is a wrapper on top of Detox library, aimed to unify testing experience for CodeceptJS framework. Detox provides a grey box testing for mobile applications, playing especially well for React Native apps.
47
49
 
@@ -102,7 +104,7 @@ Later you can even automagically update Type Definitions to include your own cus
102
104
 
103
105
  Note:
104
106
 
105
- - CodeceptJS requires Node.js version `12+` or later.
107
+ - CodeceptJS 4.2 requires Node.js `22.12.0` or later.
106
108
 
107
109
  ## Usage
108
110
 
@@ -5,6 +5,10 @@ title: Alternative Browser Engines
5
5
 
6
6
  # Alternative Browser Engines
7
7
 
8
+ ::: warning Experimental
9
+ The `CDPBrowser`, `Obscura`, and `Kitesurf` helpers are experimental in CodeceptJS 4.2. Pin browser versions in CI and retain Playwright or WebDriver coverage for compatibility-critical tests.
10
+ :::
11
+
8
12
  Playwright and Puppeteer drive full Chromium — the most accurate way to test what users see.
9
13
  But a new class of lightweight, agent-era browsers has appeared, and CodeceptJS can drive them
10
14
  through the `CDPBrowser` helper family:
@@ -12,8 +16,7 @@ through the `CDPBrowser` helper family:
12
16
  - **[Obscura](https://github.com/h4ckf0r0day/obscura)** — an open-source Rust browser with a real
13
17
  V8 engine. From v0.2.0, the default release build also renders — real layout, computed styles,
14
18
  and screenshots — with `-no-render` builds still available for pure-speed, nothing-painted
15
- scraping mode. A single 70 MB binary, ~30 MB RAM per instance, page loads in tens of
16
- milliseconds.
19
+ scraping mode. Release archives are available for Linux, macOS, and Windows.
17
20
  - **[Kitesurf](https://blog.cloudflare.com/kitesurf/)** — Cloudflare's browser that runs in V8
18
21
  isolates on Cloudflare Workers, with a real layout and rendering pipeline. Cloud-only,
19
22
  free in beta, planned to be open-sourced.
@@ -24,10 +27,9 @@ on navigation races.
24
27
 
25
28
  ## When are they better than Playwright?
26
29
 
27
- **Smoke suites where seconds matter.** An Obscura scenario (navigate, fill a form, submit,
28
- assert) completes in 150–500 ms. There is no browser binary to download in CI — a 70 MB
29
- static binary starts instantly. If your PR gate runs 50 smoke scenarios, Obscura turns
30
- minutes into seconds.
30
+ **Smoke suites where startup and execution time matter.** Obscura is distributed as a standalone
31
+ binary and is designed for lightweight browser automation. Benchmark it against your own pages and
32
+ CI environment before choosing it for a PR gate.
31
33
 
32
34
  **Massive parallel scale.** Kitesurf sessions are Cloudflare Workers — they spawn in about a
33
35
  second, cost nothing while idle, and there is no practical ceiling on how many you run at once.
@@ -57,9 +59,8 @@ app's real JavaScript in real V8; it only skips painting. For API-adjacent flows
57
59
  **Constrained environments.** ARM CI runners, thin containers, air-gapped machines:
58
60
  a static binary with no system dependencies goes where Chromium will not.
59
61
 
60
- **Scraping-grade network realism.** Obscura's stealth mode presents a consistent Chrome TLS
61
- fingerprint — useful when your tests must pass through bot-protection layers that block
62
- headless Chromium.
62
+ **Optional stealth builds.** Obscura publishes separate `-stealth` archives. Treat their behaviour
63
+ as an Obscura capability rather than a browser-compatibility guarantee from CodeceptJS.
63
64
 
64
65
  ## When to stay with Playwright
65
66
 
@@ -77,6 +78,10 @@ headless Chromium.
77
78
 
78
79
  ## Configuration
79
80
 
81
+ CodeceptJS 4.2 is tested in CI with Obscura 0.2.2. Obscura 0.2.x is recommended; 0.1.x and
82
+ `-no-render` builds operate without layout, visibility assertions, or screenshots. See
83
+ [Installation](/installation#obscura-experimental) for platform-specific archive names.
84
+
80
85
  helpers: {
81
86
  Obscura: {
82
87
  url: 'http://localhost:3000',
@@ -144,7 +149,7 @@ process — there is nothing to start by hand in the common case:
144
149
  | Screenshots | yes | yes (v0.2.0+ default builds); no on `-no-render`/v0.1.x | yes |
145
150
  | Visibility assertions | yes | yes (v0.2.0+ default builds); no, DOM-presence only, on `-no-render`/v0.1.x | yes |
146
151
  | Screencast / video (`screencast` plugin) | yes — WebM via `page.screencast`, with caption burn-in | yes — APNG via CDP `Page.startScreencast`, assembled in-process (v0.2.0+ default builds; verified PNG frames on the live server); no caption burn-in | untested |
147
- | Startup cost | seconds + ~300 MB install | instant, 70 MB binary | ~1 s, zero local |
152
+ | Startup model | local browser process | standalone local binary | remote cloud session |
148
153
  | Parallel scale | machine-bound | machine-bound (light) | near-unlimited (cloud) |
149
154
  | Where it runs | local/grid | local | Cloudflare only |
150
155
  | License / cost | open source | Apache-2.0 | proprietary, free beta |
@@ -9,7 +9,7 @@ CodeceptJS runs in any CI that can install Node.js. This page covers the setup,
9
9
 
10
10
  ## Setup
11
11
 
12
- - **Node.js** — install it on the runner (`actions/setup-node`, the `node:20` image, `NodeTool@0` on Azure). Examples below use Node 20.
12
+ - **Node.js 22.12 or newer** — install it on the runner (`actions/setup-node`, the `node:22` image, `NodeTool@0` on Azure). Examples below use Node 22.
13
13
  - **Headless** — `codecept.conf.js` must contain `setHeadlessWhen(process.env.HEADLESS || process.env.CI)`. `codeceptjs init` adds it; since CI sets `CI=true`, the suite runs headless automatically.
14
14
 
15
15
  ```js
@@ -61,7 +61,7 @@ Use [`@testomatio/reporter`](https://github.com/testomatio/reporter). It ships p
61
61
 
62
62
  ## CI examples
63
63
 
64
- Each example uses Playwright by default; a WebDriver variant follows where it differs. WebdriverIO 9 downloads its own browser and driver, so the WebDriver variants run on a plain `node:20` image with no Selenium service and no browser-install step. For Playwright, a `node:20` base image plus `npx playwright install --with-deps` keeps these configs free of version pins.
64
+ Each example uses Playwright by default; a WebDriver variant follows where it differs. WebdriverIO 9 downloads its own browser and driver, so the WebDriver variants run on a plain `node:22` image with no Selenium service and no browser-install step. For Playwright, a `node:22` base image plus `npx playwright install --with-deps` keeps these configs free of version pins.
65
65
 
66
66
  ### GitHub Actions — Playwright
67
67
 
@@ -85,7 +85,7 @@ jobs:
85
85
  - uses: actions/checkout@v4
86
86
  - uses: actions/setup-node@v4
87
87
  with:
88
- node-version: 20
88
+ node-version: 22
89
89
  cache: npm
90
90
  - run: npm ci
91
91
  - run: npx playwright install --with-deps chromium
@@ -115,7 +115,7 @@ jobs:
115
115
  - uses: actions/checkout@v4
116
116
  - uses: actions/setup-node@v4
117
117
  with:
118
- node-version: 20
118
+ node-version: 22
119
119
  - run: npm ci
120
120
  - run: npx codeceptjs check
121
121
  - run: npx codeceptjs run-workers 2 --by pool
@@ -142,7 +142,7 @@ jobs:
142
142
  - uses: actions/checkout@v4
143
143
  - uses: actions/setup-node@v4
144
144
  with:
145
- node-version: 20
145
+ node-version: 22
146
146
  - run: npm ci
147
147
  - run: npx playwright install --with-deps chromium
148
148
  - run: npx codeceptjs check
@@ -173,7 +173,7 @@ jobs:
173
173
  - uses: actions/checkout@v4
174
174
  - uses: actions/setup-node@v4
175
175
  with:
176
- node-version: 20
176
+ node-version: 22
177
177
  - run: npm ci
178
178
  - run: npx playwright install --with-deps
179
179
  - run: npx codeceptjs check
@@ -194,7 +194,7 @@ stages: [test]
194
194
 
195
195
  playwright:
196
196
  stage: test
197
- image: node:20
197
+ image: node:22
198
198
  variables:
199
199
  FORCE_COLOR: "1"
200
200
  parallel: 4
@@ -211,7 +211,7 @@ playwright:
211
211
 
212
212
  webdriver:
213
213
  stage: test
214
- image: node:20 # WebdriverIO 9 downloads its own browser and driver
214
+ image: node:22 # WebdriverIO 9 downloads its own browser and driver
215
215
  script:
216
216
  - npm ci
217
217
  - npx codeceptjs check
@@ -228,7 +228,7 @@ webdriver:
228
228
  `bitbucket-pipelines.yml`:
229
229
 
230
230
  ```yaml
231
- image: node:20
231
+ image: node:22
232
232
 
233
233
  definitions:
234
234
  caches:
@@ -260,7 +260,7 @@ pipelines:
260
260
  For WebDriver, no Selenium service or browser image is needed — WebdriverIO 9 downloads its own browser and driver:
261
261
 
262
262
  ```yaml
263
- image: node:20
263
+ image: node:22
264
264
 
265
265
  pipelines:
266
266
  default:
@@ -280,7 +280,7 @@ pipelines:
280
280
  pipeline {
281
281
  agent {
282
282
  docker {
283
- image 'node:20'
283
+ image 'node:22'
284
284
  args '-u root'
285
285
  }
286
286
  }
@@ -311,7 +311,7 @@ pipeline {
311
311
  }
312
312
  ```
313
313
 
314
- For WebDriver, keep the same `node:20` agent — WebdriverIO 9 downloads its own browser and driver, so no Selenium container is needed:
314
+ For WebDriver, keep the same `node:22` agent — WebdriverIO 9 downloads its own browser and driver, so no Selenium container is needed:
315
315
 
316
316
  ```groovy
317
317
  stage('Test') {
@@ -332,7 +332,7 @@ version: 2.1
332
332
  jobs:
333
333
  test:
334
334
  docker:
335
- - image: cimg/node:20.18-browsers
335
+ - image: cimg/node:22.14-browsers
336
336
  parallelism: 4
337
337
  steps:
338
338
  - checkout
@@ -350,7 +350,7 @@ jobs:
350
350
  webdriver:
351
351
  docker:
352
352
  # WebdriverIO 9 downloads its own browser and driver
353
- - image: cimg/node:20.18
353
+ - image: cimg/node:22.14
354
354
  steps:
355
355
  - checkout
356
356
  - run: npm ci
@@ -22,15 +22,26 @@ This helper is a thin `CDPBrowser` subclass: it changes nothing about how locati
22
22
  elements works, it only pins the config presets Obscura requires and manages the `obscura serve`
23
23
  process lifecycle, the same way Playwright manages its own browser process.
24
24
 
25
+ > Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a
26
+ > Playwright/WebDriver job for browser-compatibility coverage.
27
+
28
+ ## Compatibility
29
+
30
+ | CodeceptJS | Recommended Obscura | Notes |
31
+ | ---------- | -------------------- | --------------------------------------------------------------- |
32
+ | 4.2.x | 0.2.2 | Version used by the CodeceptJS Obscura CI workflow |
33
+ | 4.2.x | 0.2.x | Supported; capabilities are detected at runtime |
34
+ | 4.2.x | 0.1.x / `-no-render` | DOM-only mode; no layout, visibility assertions, or screenshots |
35
+
25
36
  ## Modes
26
37
 
27
38
  * **ATTACH** — `endpoint` is set explicitly in the config. The helper only connects to it; it
28
39
  never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
29
40
  * **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
30
41
  the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
31
- spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
32
- free port picked automatically), waits for it to answer, connects, and kills it in
33
- `_finishTest`.
42
+ spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
43
+ the config, or a free port picked automatically), waits for it to answer, connects, and kills
44
+ it in `_finishTest`.
34
45
  * **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
35
46
  answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
36
47
  before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -39,16 +50,29 @@ process lifecycle, the same way Playwright manages its own browser process.
39
50
 
40
51
  ## Install
41
52
 
42
- Download a release binary and put it on your `PATH` (or point `binaryPath`/`OBSCURA_PATH` at
43
- it directly) and the helper launches and tears it down for you automatically:
53
+ Download a release archive from [Obscura releases][2].
54
+ CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:
55
+
56
+ | platform | archive |
57
+ | ------------------- | ------------------------------ |
58
+ | Linux x64 | `obscura-x86_64-linux.tar.gz` |
59
+ | Linux ARM64 | `obscura-aarch64-linux.tar.gz` |
60
+ | macOS Intel | `obscura-x86_64-macos.tar.gz` |
61
+ | macOS Apple Silicon | `obscura-aarch64-macos.tar.gz` |
62
+ | Windows x64 | `obscura-x86_64-windows.zip` |
63
+
64
+ Extract the archive and put `obscura` (`obscura.exe` on Windows) on your `PATH`, or point
65
+ `binaryPath`/`OBSCURA_PATH` at it. The helper then launches and tears it down automatically.
66
+ For example, on Linux x64:
44
67
 
45
68
  ```sh
46
- curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
69
+ curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
47
70
  ```
48
71
 
49
- `--allow-private-network` is always passed by this helper (it's required to reach apps running
50
- on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
51
- private-network requests by default).
72
+ `--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
73
+ is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
74
+ `127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
75
+ default.
52
76
 
53
77
  ## Config presets
54
78
 
@@ -67,7 +91,7 @@ Set them explicitly in your own config to skip probing or to force a mode.
67
91
  ## Limitations
68
92
 
69
93
  * `input` is always `synthetic`, even on rendering builds — see `input` above.
70
- * No frames, popups, or file uploads.
94
+ * No frames or popups.
71
95
  * On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
72
96
  (`seeElement`/`dontSeeElement` always throw) — only DOM presence
73
97
  (`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.
@@ -83,19 +107,19 @@ Set them explicitly in your own config to skip probing or to force a mode.
83
107
  This helper should be configured in codecept.conf.js. It accepts everything `CDPBrowser`
84
108
  accepts (see its config table), plus:
85
109
 
86
- Type: [object][6]
110
+ Type: [object][7]
87
111
 
88
112
  ### Properties
89
113
 
90
- * `endpoint` **[string][4]?** explicit CDP endpoint. Setting this switches the helper to ATTACH
114
+ * `endpoint` **[string][5]?** explicit CDP endpoint. Setting this switches the helper to ATTACH
91
115
  mode: it only connects, and never spawns or kills a process, no matter what else is configured.
92
116
  Leave it unset for SELF-MANAGED mode (see below).
93
- * `binaryPath` **[string][4]?** path to the `obscura` executable, used in SELF-MANAGED mode
117
+ * `binaryPath` **[string][5]?** path to the `obscura` executable, used in SELF-MANAGED mode
94
118
  (`endpoint` unset). Checked before `OBSCURA_PATH` and `PATH`.
95
- * `port` **[number][3]?** port `obscura serve` listens on, in SELF-MANAGED mode. When unset, a
119
+ * `port` **[number][4]?** port `obscura serve` listens on, in SELF-MANAGED mode. When unset, a
96
120
  free port is picked automatically, which is what makes `run-workers` collision-free — every
97
121
  worker gets its own instance on its own port with zero config.
98
- * `serverStartTimeout` **[number][3]?** milliseconds to wait for a spawned `obscura serve`
122
+ * `serverStartTimeout` **[number][4]?** milliseconds to wait for a spawned `obscura serve`
99
123
  to answer `/json/version` before `_connect` gives up.
100
124
 
101
125
 
@@ -149,7 +173,7 @@ Picks a free TCP port on 127.0.0.1 by briefly listening on port 0 and reading ba
149
173
  port. Used as the SELF-LAUNCH default when `options.port` isn't explicitly set, so multiple
150
174
  `run-workers` workers never collide on the same port.
151
175
 
152
- Returns **[Promise][2]<[number][3]>** a free port.
176
+ Returns **[Promise][3]<[number][4]>** a free port.
153
177
 
154
178
  ### _finishTest
155
179
 
@@ -169,9 +193,9 @@ Probes a `/json/version`-style URL with a short timeout, used for the COURTESY-A
169
193
 
170
194
  #### Parameters
171
195
 
172
- * `url` **[string][4]**&#x20;
196
+ * `url` **[string][5]**&#x20;
173
197
 
174
- Returns **[Promise][2]<[boolean][5]>** true if the URL answered.
198
+ Returns **[Promise][3]<[boolean][6]>** true if the URL answered.
175
199
 
176
200
  ### _resolveBinary
177
201
 
@@ -180,7 +204,7 @@ Resolves the `obscura` binary to spawn, in priority order: `options.binaryPath`,
180
204
  directories itself instead of shelling out to `which`, which does not exist on Windows: on
181
205
  Windows every `PATHEXT` suffix is tried, so an `obscura.exe` on `PATH` is found too.
182
206
 
183
- Returns **([string][4] | null)** an absolute or relative path to the binary, or null if none resolved.
207
+ Returns **([string][5] | null)** an absolute or relative path to the binary, or null if none resolved.
184
208
 
185
209
  ### _resolveSelfManaged
186
210
 
@@ -199,12 +223,14 @@ is paid once per run and counts directly toward real-world startup latency.
199
223
 
200
224
  [1]: https://github.com/h4ckf0r0day/obscura
201
225
 
202
- [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
226
+ [2]: https://github.com/h4ckf0r0day/obscura/releases
227
+
228
+ [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
203
229
 
204
- [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
230
+ [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
205
231
 
206
- [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
232
+ [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
207
233
 
208
- [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
234
+ [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
209
235
 
210
- [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
236
+ [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
@@ -78,7 +78,7 @@ Type: [object][6]
78
78
  * `ignoreHTTPSErrors` **[boolean][27]?** Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
79
79
  * `bypassCSP` **[boolean][27]?** bypass Content Security Policy or CSP
80
80
  * `highlightElement` **[boolean][27]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
81
- * `visibleLocator` **[boolean][27]?** append [`visible()`][49] to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`, which check the DOM regardless of visibility. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
81
+ * `visibleLocator` **[boolean][27]?** append [`visible()`][49] to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to steps that must reach hidden elements: `grab*` methods, `scrollTo`, `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
82
82
  * `recordHar` **[object][6]?** record HAR and will be saved to `output/har`. See more of [HAR options][3].
83
83
  * `testIdAttribute` **[string][9]?** locate elements based on the testIdAttribute. See more of [locate by test id][50].
84
84
  * `storageState` **([string][9] | [object][6])?** Playwright storage state (path to JSON file or object)
@@ -7,6 +7,12 @@ title: Installation
7
7
 
8
8
  For the quickest start (CodeceptJS + Playwright) follow the [Quickstart](/quickstart). This page covers every browser, mobile, and API helper and what each one needs installed.
9
9
 
10
+ CodeceptJS 4.2 requires **Node.js 22.12 or newer**. Check your installed version before continuing:
11
+
12
+ ```sh
13
+ node --version
14
+ ```
15
+
10
16
  ## Set up a project
11
17
 
12
18
  ```sh
@@ -64,6 +70,35 @@ WebdriverIO 9 downloads and starts the matching driver automatically — no Sele
64
70
 
65
71
  To run against a cloud grid (Sauce Labs, BrowserStack, LambdaTest, and so on) instead, set the grid's `host`, `port`, `user`, and `key` (or `protocol` / `path`) in the helper config. For running this on CI, see [Continuous Integration](/continuous-integration).
66
72
 
73
+ ## Obscura (experimental)
74
+
75
+ Obscura is a lightweight browser driven through Chrome DevTools Protocol. CodeceptJS 4.2 is tested with Obscura 0.2.2. Obscura 0.2.x is the recommended version; older 0.1.x and `-no-render` builds have no layout, visibility assertions, or screenshots.
76
+
77
+ Download the archive for your platform from the [Obscura releases](https://github.com/h4ckf0r0day/obscura/releases):
78
+
79
+ | Platform | Rendering archive |
80
+ | --- | --- |
81
+ | Linux x64 | `obscura-x86_64-linux.tar.gz` |
82
+ | Linux ARM64 | `obscura-aarch64-linux.tar.gz` |
83
+ | macOS Intel | `obscura-x86_64-macos.tar.gz` |
84
+ | macOS Apple Silicon | `obscura-aarch64-macos.tar.gz` |
85
+ | Windows x64 | `obscura-x86_64-windows.zip` |
86
+
87
+ Extract the archive and either put `obscura` (`obscura.exe` on Windows) on `PATH`, set `OBSCURA_PATH`, or configure `binaryPath`. The helper then starts and stops `obscura serve` automatically:
88
+
89
+ ```js
90
+ export const config = {
91
+ helpers: {
92
+ Obscura: {
93
+ url: 'http://localhost:3000',
94
+ // binaryPath: '/absolute/path/to/obscura', // optional
95
+ },
96
+ },
97
+ }
98
+ ```
99
+
100
+ See [Alternative Browser Engines](/alternative-browsers) for connection modes and limitations, and the [Obscura helper reference](/helpers/Obscura) for every option.
101
+
67
102
  ## Appium (mobile)
68
103
 
69
104
  Native iOS and Android testing. Appium speaks the WebDriver protocol, so CodeceptJS drives it through `webdriverio`:
package/docs/mcp.md CHANGED
@@ -441,7 +441,7 @@ Storage capture is **enabled** for `run_code`, `snapshot`, `run_step_by_step` fa
441
441
 
442
442
  ### Server doesn't start
443
443
 
444
- - Node 18+ recommended.
444
+ - Node 22.12+ is required by CodeceptJS 4.2.
445
445
  - Verify the path / `npx` resolution in your client config.
446
446
 
447
447
  ### Config not found
@@ -35,7 +35,7 @@ The rest of this guide documents every change the skill makes — read it if you
35
35
 
36
36
  ## 1. Update Node and Package
37
37
 
38
- CodeceptJS 4.x supports Node 16+, but Node 20 or newer is recommended.
38
+ CodeceptJS 4.2 requires Node 22.12 or newer. This matches the minimum supported version of its current runtime dependencies. Upgrade Node before installing CodeceptJS 4.2.
39
39
 
40
40
  ```bash
41
41
  npm install codeceptjs@4
@@ -766,4 +766,4 @@ You don't need these to upgrade, but they unlock new workflows:
766
766
  4. TypeScript users: run with `tsx` installed and confirm error stack traces point at `.ts` files.
767
767
  5. If you removed `autoLogin`: confirm sessions restore under the `auth` plugin.
768
768
  6. If you used `tryTo` / `retryTo` / `eachElement` plugins: grep your tests for the old globals and switch to subpath imports.
769
- 7. CI: bump the Node version to 20+ if you were on 18 or below.
769
+ 7. CI: bump the Node version to 22.12+.
package/docs/parallel.md CHANGED
@@ -100,7 +100,7 @@ jobs:
100
100
  - uses: actions/checkout@v4
101
101
  - uses: actions/setup-node@v4
102
102
  with:
103
- node-version: 20
103
+ node-version: 22
104
104
  - run: npm ci
105
105
  - run: npx codeceptjs run --shard ${{ matrix.shard }}
106
106
  ```
@@ -21,9 +21,16 @@ function parsePlaywrightBrowsers(output) {
21
21
  return versions.join(', ')
22
22
  }
23
23
 
24
+ // Bun has its own package runner and a Bun-only install has no `npx` on PATH at all, so the
25
+ // runner has to follow the runtime that is actually executing rather than what PATH happens to hold.
26
+ function getPackageRunner() {
27
+ if (process.versions.bun) return 'bunx'
28
+ return 'npx'
29
+ }
30
+
24
31
  async function getPlaywrightBrowsers() {
25
32
  try {
26
- const info = execSync('npx playwright install --dry-run').toString().trim()
33
+ const info = execSync(`${getPackageRunner()} playwright install --dry-run`).toString().trim()
27
34
  return parsePlaywrightBrowsers(info)
28
35
  } catch (err) {
29
36
  return 'Playwright not installed'
@@ -85,7 +92,7 @@ export default async function (path) {
85
92
  output.print('***************************************')
86
93
  }
87
94
 
88
- export { parsePlaywrightBrowsers, getRuntimeInfo }
95
+ export { parsePlaywrightBrowsers, getRuntimeInfo, getPackageRunner }
89
96
 
90
97
  export const getMachineInfo = async () => {
91
98
  const info = {
@@ -1631,6 +1631,9 @@ class CDPBrowser extends Helper {
1631
1631
  const value = Array.isArray(option) ? option.map(String) : String(option)
1632
1632
  const res = await this._run(this._candidates(select, 'field'), 'select', { value }, context)
1633
1633
  if (!res.found) throw new ElementNotFound(select, 'Selectable field')
1634
+ if (res.result === '__RADIOGROUP_MULTI__') {
1635
+ throw new Error(`selectOption: a radio group holds one value, but ${value.length} options were passed: ${value.join(', ')}`)
1636
+ }
1634
1637
  if (res.result === false) throw new Error(`Option "${Array.isArray(option) ? option.join(',') : option}" not found in ${new Locator(select).toString()}`)
1635
1638
  }
1636
1639
 
@@ -1810,7 +1813,6 @@ class CDPBrowser extends Helper {
1810
1813
  */
1811
1814
  async waitInUrl(urlPart, sec = null) {
1812
1815
  const timeout = sec || this.options.waitForTimeout
1813
- const expectedUrl = resolveUrl(urlPart, this.options.url)
1814
1816
  let lastUrl = ''
1815
1817
  try {
1816
1818
  return await this._poll(
@@ -1822,7 +1824,7 @@ class CDPBrowser extends Helper {
1822
1824
  'placeholder',
1823
1825
  )
1824
1826
  } catch (e) {
1825
- throw new Error(`expected url to include ${expectedUrl}, but found ${lastUrl}`)
1827
+ throw new Error(`expected url to include ${urlPart}, but found ${lastUrl}`)
1826
1828
  }
1827
1829
  }
1828
1830
 
@@ -39,15 +39,26 @@ const config = {}
39
39
  * elements works, it only pins the config presets Obscura requires and manages the `obscura serve`
40
40
  * process lifecycle, the same way Playwright manages its own browser process.
41
41
  *
42
+ * > Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a
43
+ * > Playwright/WebDriver job for browser-compatibility coverage.
44
+ *
45
+ * ## Compatibility
46
+ *
47
+ * | CodeceptJS | Recommended Obscura | Notes |
48
+ * | --- | --- | --- |
49
+ * | 4.2.x | 0.2.2 | Version used by the CodeceptJS Obscura CI workflow |
50
+ * | 4.2.x | 0.2.x | Supported; capabilities are detected at runtime |
51
+ * | 4.2.x | 0.1.x / `-no-render` | DOM-only mode; no layout, visibility assertions, or screenshots |
52
+ *
42
53
  * ## Modes
43
54
  *
44
55
  * - **ATTACH** — `endpoint` is set explicitly in the config. The helper only connects to it; it
45
56
  * never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
46
57
  * - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
47
58
  * the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
48
- * spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
49
- * free port picked automatically), waits for it to answer, connects, and kills it in
50
- * `_finishTest`.
59
+ * spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
60
+ * the config, or a free port picked automatically), waits for it to answer, connects, and kills
61
+ * it in `_finishTest`.
51
62
  * - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
52
63
  * answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
53
64
  * before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -56,16 +67,29 @@ const config = {}
56
67
  *
57
68
  * ## Install
58
69
  *
59
- * Download a release binary and put it on your `PATH` (or point `binaryPath`/`OBSCURA_PATH` at
60
- * it directly) and the helper launches and tears it down for you automatically:
70
+ * Download a release archive from [Obscura releases](https://github.com/h4ckf0r0day/obscura/releases).
71
+ * CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:
72
+ *
73
+ * | platform | archive |
74
+ * | --- | --- |
75
+ * | Linux x64 | `obscura-x86_64-linux.tar.gz` |
76
+ * | Linux ARM64 | `obscura-aarch64-linux.tar.gz` |
77
+ * | macOS Intel | `obscura-x86_64-macos.tar.gz` |
78
+ * | macOS Apple Silicon | `obscura-aarch64-macos.tar.gz` |
79
+ * | Windows x64 | `obscura-x86_64-windows.zip` |
80
+ *
81
+ * Extract the archive and put `obscura` (`obscura.exe` on Windows) on your `PATH`, or point
82
+ * `binaryPath`/`OBSCURA_PATH` at it. The helper then launches and tears it down automatically.
83
+ * For example, on Linux x64:
61
84
  *
62
85
  * ```sh
63
- * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
86
+ * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
64
87
  * ```
65
88
  *
66
- * `--allow-private-network` is always passed by this helper (it's required to reach apps running
67
- * on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
68
- * private-network requests by default).
89
+ * `--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
90
+ * is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
91
+ * `127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
92
+ * default.
69
93
  *
70
94
  * ## Config presets
71
95
  *
@@ -84,7 +108,7 @@ const config = {}
84
108
  * ## Limitations
85
109
  *
86
110
  * - `input` is always `synthetic`, even on rendering builds — see `input` above.
87
- * - No frames, popups, or file uploads.
111
+ * - No frames or popups.
88
112
  * - On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
89
113
  * (`seeElement`/`dontSeeElement` always throw) — only DOM presence
90
114
  * (`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.
@@ -179,7 +203,7 @@ class Obscura extends CDPBrowser {
179
203
  const port = this.options.port || (await this._findFreePort())
180
204
  this.options.port = port
181
205
  this.serverError = null
182
- this.serverProcess = spawn(binaryPath, ['serve', '--port', String(port), '--allow-private-network'], { stdio: 'ignore' })
206
+ this.serverProcess = spawn(binaryPath, ['serve', '--port', String(port), '--allow-private-network', '--allow-file-access'], { stdio: 'ignore' })
183
207
  this.serverProcess.on('error', err => {
184
208
  this.serverError = err
185
209
  })
@@ -50,7 +50,7 @@ let defaultSelectorEnginesInitialized = false
50
50
  const popupStore = new Popup()
51
51
  const consoleLogStore = new Console()
52
52
  const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']
53
- const domPresenceSteps = ['seeElementInDOM', 'dontSeeElementInDOM', 'seeNumberOfElements']
53
+ const visibilityAgnosticSteps = ['seeElementInDOM', 'dontSeeElementInDOM', 'seeNumberOfElements', 'scrollTo']
54
54
  const checkableRoles = ['checkbox', 'radio', 'switch']
55
55
 
56
56
  import { setRestartStrategy, restartsSession, restartsContext, restartsBrowser } from './extras/PlaywrightRestartOpts.js'
@@ -103,7 +103,7 @@ const pathSeparator = path.sep
103
103
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
104
104
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
105
105
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
106
- * @prop {boolean} [visibleLocator=false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`, which check the DOM regardless of visibility. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
106
+ * @prop {boolean} [visibleLocator=false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to steps that must reach hidden elements: `grab*` methods, `scrollTo`, `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
107
107
  * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
108
108
  * @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
109
109
  * @prop {string|object} [storageState] - Playwright storage state (path to JSON file or object)
@@ -559,7 +559,8 @@ class Playwright extends Helper {
559
559
  }
560
560
 
561
561
  _beforeStep(step) {
562
- store.visibleLocator = step.opts?.visibleLocator ?? (this.options.visibleLocator && !domPresenceSteps.includes(step.helperMethod))
562
+ const reachesHidden = step.helperMethod?.startsWith('grab') || visibilityAgnosticSteps.includes(step.helperMethod)
563
+ store.visibleLocator = step.opts?.visibleLocator ?? (this.options.visibleLocator && !reachesHidden)
563
564
  }
564
565
 
565
566
  async _before(test) {
@@ -583,7 +584,8 @@ class Playwright extends Helper {
583
584
  // Clear popup state to ensure clean state for each test
584
585
  popupStore.clear()
585
586
 
586
- recorder.retry({
587
+ // Configure retry for this test; will clean up after test completes
588
+ this._retryConfig = {
587
589
  retries: test?.opts?.conditionalRetries || 3,
588
590
  when: err => {
589
591
  if (!err || typeof err.message !== 'string') {
@@ -592,7 +594,8 @@ class Playwright extends Helper {
592
594
  // ignore context errors
593
595
  return err.message.includes('context')
594
596
  },
595
- })
597
+ }
598
+ recorder.retry(this._retryConfig)
596
599
 
597
600
  // Start browser if needed (initial start or browser restart strategy)
598
601
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser()
@@ -697,6 +700,12 @@ class Playwright extends Helper {
697
700
  }
698
701
 
699
702
  async _after() {
703
+ // Clean up our retry config to prevent accumulation
704
+ if (this._retryConfig) {
705
+ recorder.retries = recorder.retries.filter(r => r !== this._retryConfig)
706
+ this._retryConfig = null
707
+ }
708
+
700
709
  if (!this.isRunning) return
701
710
 
702
711
  // Clear popup state to prevent leakage between tests
@@ -1525,6 +1534,7 @@ class Playwright extends Helper {
1525
1534
  assertElementExists(el, locator)
1526
1535
  }
1527
1536
 
1537
+ await el.scrollIntoViewIfNeeded()
1528
1538
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
1529
1539
  const { x, y } = await clickablePoint(el)
1530
1540
  await this.page.mouse.move(x + offsetX, y + offsetY)
@@ -3665,8 +3675,7 @@ class Playwright extends Helper {
3665
3675
  }
3666
3676
 
3667
3677
  try {
3668
- // Always create frame locator from page to avoid nested frame paths
3669
- this.frame = await Promise.race([this.page.frameLocator(locator), new Promise((_, reject) => setTimeout(() => reject(new Error('Frame locator timeout')), 5000))])
3678
+ this.frame = this.frame ? this.frame.frameLocator(locator) : this.page.frameLocator(locator)
3670
3679
  } catch (e) {
3671
3680
  console.warn('Warning during frame locator creation:', e.message)
3672
3681
  throw new Error(`Frame ${JSON.stringify(locator)} could not be accessed`)
@@ -354,7 +354,8 @@ class Puppeteer extends Helper {
354
354
  async _before(test) {
355
355
  this.sessionPages = {}
356
356
  this.currentRunningTest = test
357
- recorder.retry({
357
+ // Configure retry for this test; will clean up after test completes
358
+ this._retryConfig = {
358
359
  retries: test?.opts?.conditionalRetries || 3,
359
360
  when: err => {
360
361
  if (!err || typeof err.message !== 'string') {
@@ -363,13 +364,20 @@ class Puppeteer extends Helper {
363
364
  // ignore context errors
364
365
  return err.message.includes('context')
365
366
  },
366
- })
367
+ }
368
+ recorder.retry(this._retryConfig)
367
369
  if (this.options.restart && !this.options.manualStart) return this._startBrowser()
368
370
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
369
371
  return this.browser
370
372
  }
371
373
 
372
374
  async _after() {
375
+ // Clean up our retry config to prevent accumulation
376
+ if (this._retryConfig) {
377
+ recorder.retries = recorder.retries.filter(r => r !== this._retryConfig)
378
+ this._retryConfig = null
379
+ }
380
+
373
381
  if (!this.isRunning) return
374
382
 
375
383
  // Clear popup state to prevent leakage between tests
@@ -845,6 +853,9 @@ class Puppeteer extends Helper {
845
853
  }
846
854
  }
847
855
 
856
+ if (!(await el.isIntersectingViewport({ threshold: 1 }))) {
857
+ await el.evaluate(el => el.scrollIntoView({ block: 'center', inline: 'center' }))
858
+ }
848
859
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
849
860
  const { x, y } = await getClickablePoint(el)
850
861
  await this.page.mouse.move(x + offsetX, y + offsetY)
@@ -387,6 +387,17 @@ export default function installCodeceptClient(xpathNeedsPolyfill) {
387
387
  return true
388
388
  }
389
389
 
390
+ if (resolveRole(el) === 'radiogroup') {
391
+ if (values.length > 1) return '__RADIOGROUP_MULTI__'
392
+ const radios = Array.from(el.querySelectorAll('[role="radio"]'))
393
+ const [wanted] = values
394
+ const named = (radio, matchFn) => roleTextCandidates(radio).some(matchFn)
395
+ const radio = radios.find(r => named(r, t => t === wanted)) || radios.find(r => named(r, t => t.indexOf(wanted) !== -1))
396
+ if (!radio) return false
397
+ radio.click()
398
+ return true
399
+ }
400
+
390
401
  // ARIA combobox/listbox widgets: click the trigger (if any) to reveal the
391
402
  // listbox, then click each matching [role="option"].
392
403
  let container = el
@@ -98,7 +98,6 @@ export default function (config) {
98
98
 
99
99
  const when = err => {
100
100
  if (!enableRetry) return
101
- if (store.debugMode) return false
102
101
  if (!store.autoRetries) return false
103
102
  if (err && err.isTerminal) return false
104
103
  if (err && err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))) return false
@@ -152,7 +151,9 @@ export default function (config) {
152
151
  test.opts.stepRetryPriority = stepRetryPriority
153
152
 
154
153
  debug('applying retries = %d for test %s', config.retries, test.title)
155
- recorder.retry(config)
154
+ if (!recorder.retries.find(r => r === config)) {
155
+ recorder.retry(config)
156
+ }
156
157
  })
157
158
 
158
159
  event.dispatcher.on(event.test.started, test => {
@@ -171,4 +172,8 @@ export default function (config) {
171
172
  test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries
172
173
  }
173
174
  })
175
+
176
+ event.dispatcher.on(event.test.after, () => {
177
+ recorder.retries = recorder.retries.filter(r => r !== config)
178
+ })
174
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.2.0-beta.2",
3
+ "version": "4.2.0-beta.4",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [
@@ -207,7 +207,7 @@
207
207
  }
208
208
  },
209
209
  "engines": {
210
- "node": ">=16.0",
210
+ "node": ">=22.12.0",
211
211
  "npm": ">=5.6.0"
212
212
  },
213
213
  "es6": true,
@@ -3423,15 +3423,26 @@ declare namespace CodeceptJS {
3423
3423
  * elements works, it only pins the config presets Obscura requires and manages the `obscura serve`
3424
3424
  * process lifecycle, the same way Playwright manages its own browser process.
3425
3425
  *
3426
+ * > Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a
3427
+ * > Playwright/WebDriver job for browser-compatibility coverage.
3428
+ *
3429
+ * ## Compatibility
3430
+ *
3431
+ * | CodeceptJS | Recommended Obscura | Notes |
3432
+ * | --- | --- | --- |
3433
+ * | 4.2.x | 0.2.2 | Version used by the CodeceptJS Obscura CI workflow |
3434
+ * | 4.2.x | 0.2.x | Supported; capabilities are detected at runtime |
3435
+ * | 4.2.x | 0.1.x / `-no-render` | DOM-only mode; no layout, visibility assertions, or screenshots |
3436
+ *
3426
3437
  * ## Modes
3427
3438
  *
3428
3439
  * - **ATTACH** — `endpoint` is set explicitly in the config. The helper only connects to it; it
3429
3440
  * never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
3430
3441
  * - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
3431
3442
  * the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
3432
- * spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
3433
- * free port picked automatically), waits for it to answer, connects, and kills it in
3434
- * `_finishTest`.
3443
+ * spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
3444
+ * the config, or a free port picked automatically), waits for it to answer, connects, and kills
3445
+ * it in `_finishTest`.
3435
3446
  * - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
3436
3447
  * answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
3437
3448
  * before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -3440,16 +3451,29 @@ declare namespace CodeceptJS {
3440
3451
  *
3441
3452
  * ## Install
3442
3453
  *
3443
- * Download a release binary and put it on your `PATH` (or point `binaryPath`/`OBSCURA_PATH` at
3444
- * it directly) and the helper launches and tears it down for you automatically:
3454
+ * Download a release archive from [Obscura releases](https://github.com/h4ckf0r0day/obscura/releases).
3455
+ * CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:
3456
+ *
3457
+ * | platform | archive |
3458
+ * | --- | --- |
3459
+ * | Linux x64 | `obscura-x86_64-linux.tar.gz` |
3460
+ * | Linux ARM64 | `obscura-aarch64-linux.tar.gz` |
3461
+ * | macOS Intel | `obscura-x86_64-macos.tar.gz` |
3462
+ * | macOS Apple Silicon | `obscura-aarch64-macos.tar.gz` |
3463
+ * | Windows x64 | `obscura-x86_64-windows.zip` |
3464
+ *
3465
+ * Extract the archive and put `obscura` (`obscura.exe` on Windows) on your `PATH`, or point
3466
+ * `binaryPath`/`OBSCURA_PATH` at it. The helper then launches and tears it down automatically.
3467
+ * For example, on Linux x64:
3445
3468
  *
3446
3469
  * ```sh
3447
- * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
3470
+ * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
3448
3471
  * ```
3449
3472
  *
3450
- * `--allow-private-network` is always passed by this helper (it's required to reach apps running
3451
- * on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
3452
- * private-network requests by default).
3473
+ * `--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
3474
+ * is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
3475
+ * `127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
3476
+ * default.
3453
3477
  *
3454
3478
  * ## Config presets
3455
3479
  *
@@ -3468,7 +3492,7 @@ declare namespace CodeceptJS {
3468
3492
  * ## Limitations
3469
3493
  *
3470
3494
  * - `input` is always `synthetic`, even on rendering builds — see `input` above.
3471
- * - No frames, popups, or file uploads.
3495
+ * - No frames or popups.
3472
3496
  * - On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
3473
3497
  * (`seeElement`/`dontSeeElement` always throw) — only DOM presence
3474
3498
  * (`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.
@@ -3607,7 +3631,7 @@ declare namespace CodeceptJS {
3607
3631
  * @property [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
3608
3632
  * @property [bypassCSP] - bypass Content Security Policy or CSP
3609
3633
  * @property [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
3610
- * @property [visibleLocator = false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`, which check the DOM regardless of visibility. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
3634
+ * @property [visibleLocator = false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to steps that must reach hidden elements: `grab*` methods, `scrollTo`, `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
3611
3635
  * @property [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
3612
3636
  * @property [testIdAttribute = data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
3613
3637
  * @property [storageState] - Playwright storage state (path to JSON file or object)
@@ -3456,15 +3456,26 @@ declare namespace CodeceptJS {
3456
3456
  * elements works, it only pins the config presets Obscura requires and manages the `obscura serve`
3457
3457
  * process lifecycle, the same way Playwright manages its own browser process.
3458
3458
  *
3459
+ * > Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a
3460
+ * > Playwright/WebDriver job for browser-compatibility coverage.
3461
+ *
3462
+ * ## Compatibility
3463
+ *
3464
+ * | CodeceptJS | Recommended Obscura | Notes |
3465
+ * | --- | --- | --- |
3466
+ * | 4.2.x | 0.2.2 | Version used by the CodeceptJS Obscura CI workflow |
3467
+ * | 4.2.x | 0.2.x | Supported; capabilities are detected at runtime |
3468
+ * | 4.2.x | 0.1.x / `-no-render` | DOM-only mode; no layout, visibility assertions, or screenshots |
3469
+ *
3459
3470
  * ## Modes
3460
3471
  *
3461
3472
  * - **ATTACH** — `endpoint` is set explicitly in the config. The helper only connects to it; it
3462
3473
  * never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
3463
3474
  * - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
3464
3475
  * the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
3465
- * spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
3466
- * free port picked automatically), waits for it to answer, connects, and kills it in
3467
- * `_finishTest`.
3476
+ * spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
3477
+ * the config, or a free port picked automatically), waits for it to answer, connects, and kills
3478
+ * it in `_finishTest`.
3468
3479
  * - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
3469
3480
  * answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
3470
3481
  * before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -3473,16 +3484,29 @@ declare namespace CodeceptJS {
3473
3484
  *
3474
3485
  * ## Install
3475
3486
  *
3476
- * Download a release binary and put it on your `PATH` (or point `binaryPath`/`OBSCURA_PATH` at
3477
- * it directly) and the helper launches and tears it down for you automatically:
3487
+ * Download a release archive from [Obscura releases](https://github.com/h4ckf0r0day/obscura/releases).
3488
+ * CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:
3489
+ *
3490
+ * | platform | archive |
3491
+ * | --- | --- |
3492
+ * | Linux x64 | `obscura-x86_64-linux.tar.gz` |
3493
+ * | Linux ARM64 | `obscura-aarch64-linux.tar.gz` |
3494
+ * | macOS Intel | `obscura-x86_64-macos.tar.gz` |
3495
+ * | macOS Apple Silicon | `obscura-aarch64-macos.tar.gz` |
3496
+ * | Windows x64 | `obscura-x86_64-windows.zip` |
3497
+ *
3498
+ * Extract the archive and put `obscura` (`obscura.exe` on Windows) on your `PATH`, or point
3499
+ * `binaryPath`/`OBSCURA_PATH` at it. The helper then launches and tears it down automatically.
3500
+ * For example, on Linux x64:
3478
3501
  *
3479
3502
  * ```sh
3480
- * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
3503
+ * curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
3481
3504
  * ```
3482
3505
  *
3483
- * `--allow-private-network` is always passed by this helper (it's required to reach apps running
3484
- * on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
3485
- * private-network requests by default).
3506
+ * `--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
3507
+ * is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
3508
+ * `127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
3509
+ * default.
3486
3510
  *
3487
3511
  * ## Config presets
3488
3512
  *
@@ -3501,7 +3525,7 @@ declare namespace CodeceptJS {
3501
3525
  * ## Limitations
3502
3526
  *
3503
3527
  * - `input` is always `synthetic`, even on rendering builds — see `input` above.
3504
- * - No frames, popups, or file uploads.
3528
+ * - No frames or popups.
3505
3529
  * - On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
3506
3530
  * (`seeElement`/`dontSeeElement` always throw) — only DOM presence
3507
3531
  * (`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.
@@ -3641,7 +3665,7 @@ declare namespace CodeceptJS {
3641
3665
  * @property [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
3642
3666
  * @property [bypassCSP] - bypass Content Security Policy or CSP
3643
3667
  * @property [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
3644
- * @property [visibleLocator = false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`, which check the DOM regardless of visibility. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
3668
+ * @property [visibleLocator = false] - append [`visible()`](https://playwright.dev/docs/api/class-locator#locator-visible) to locators, so only visible elements are matched. Requires Playwright 1.63 or newer. Switch it off for a single step with `stepOpts({ visibleLocator: false })`. Not applied to `dragAndDrop`, which passes selectors to Playwright directly, nor to steps that must reach hidden elements: `grab*` methods, `scrollTo`, `seeElementInDOM`, `dontSeeElementInDOM` and `seeNumberOfElements`. When enabled, a locator matching only hidden elements fails as "element not found" instead of timing out on actionability, `strict` mode ignores hidden duplicates, and elements hidden by CSS (like a custom checkbox built on a visually hidden `input`) are no longer found.
3645
3669
  * @property [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
3646
3670
  * @property [testIdAttribute = data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
3647
3671
  * @property [storageState] - Playwright storage state (path to JSON file or object)