Obscura
Extends CDPBrowser
Obscura drives Obscura, a minimal headless
browser exposed over the Chrome DevTools Protocol. From v0.2.0, default release builds ship a
real rendering engine (layout, paint, screenshots); -no-render variants and v0.1.x builds keep
the original single-V8-isolate, nothing-rendered mode. This helper does not hardcode which mode a
given binary is in — CDPBrowser._probeCapabilities detects layout/screenshot per binary at
runtime, so the same helper works against either.
This helper is a thin CDPBrowser subclass: it changes nothing about how locating or acting on
elements works, it only pins the config presets Obscura requires and manages the obscura serve
process lifecycle, the same way Playwright manages its own browser process.
Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a Playwright/WebDriver job for browser-compatibility coverage.
Compatibility
Section titled “Compatibility”| CodeceptJS | Recommended Obscura | Notes |
|---|---|---|
| 4.2.x | 0.2.2 | Version used by the CodeceptJS Obscura CI workflow |
| 4.2.x | 0.2.x | Supported; capabilities are detected at runtime |
| 4.2.x | 0.1.x / -no-render | DOM-only mode; no layout, visibility assertions, or screenshots |
- ATTACH —
endpointis set explicitly in the config. The helper only connects to it; it never spawns or kills anything, no matter whatbinaryPath/portare set to. - SELF-LAUNCH —
endpointis unset and a binary can be resolved, in order:binaryPathin the config, then theOBSCURA_PATHenvironment variable, thenobscuraonPATH. The helper spawnsobscura serve --port <port> --allow-private-network --allow-file-access(portfrom the config, or a free port picked automatically), waits for it to answer, connects, and kills it in_finishTest. - COURTESY-ATTACH —
endpointis unset and no binary can be resolved, but something already answershttp://127.0.0.1:9222/json/version(e.g.obscura servestarted by hand, or by CI before this process ever ran). The helper attaches to it and never kills it — it isn’t the helper’s process to kill. If neither a binary nor a running server on :9222 can be found, the helper throws a loud, actionable error.
Install
Section titled “Install”Download a release archive from Obscura releases. CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:
| platform | archive |
|---|---|
| Linux x64 | obscura-x86_64-linux.tar.gz |
| Linux ARM64 | obscura-aarch64-linux.tar.gz |
| macOS Intel | obscura-x86_64-macos.tar.gz |
| macOS Apple Silicon | obscura-aarch64-macos.tar.gz |
| Windows x64 | obscura-x86_64-windows.zip |
Extract the archive and put obscura (obscura.exe on Windows) on your PATH, or point
binaryPath/OBSCURA_PATH at it. The helper then launches and tears it down automatically.
For example, on Linux x64:
curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz--allow-private-network and --allow-file-access are always passed by this helper: the first
is required to reach apps running on localhost/private IPs, e.g. a dev server on
127.0.0.1:8000, the second to let attachFile upload local files. Obscura blocks both by
default.
Config presets
Section titled “Config presets”These are set automatically and only need overriding for unusual setups:
| option | value | why |
|---|---|---|
input | synthetic | coordinate-click navigation is unreliable over CDP on Obscura even on rendering builds (no frameNavigated event, stale page.url()); click always takes the forceClick path — on Obscura, click and forceClick are the same thing |
xpathPolyfill | auto | probed per binary/page: Obscura’s native document.evaluate still doesn’t support attribute selection or not(), so the polyfill is used until that lands |
capabilities.layout/capabilities.screenshot/capabilities.xpath are intentionally left
unset here — CDPBrowser._probeCapabilities detects them at runtime from the actual binary
('real'/true on v0.2.0+ default builds, 'none'/false on -no-render builds and v0.1.x).
Set them explicitly in your own config to skip probing or to force a mode.
Limitations
Section titled “Limitations”inputis alwayssynthetic, even on rendering builds — seeinputabove.- No frames or popups.
- On
-no-renderbuilds and v0.1.x: no screenshots, no visibility assertions (seeElement/dontSeeElementalways throw) — only DOM presence (seeElementInDOM/dontSeeElementInDOM) is meaningful without a layout engine. - On v0.2.0+ default (rendering) builds: layout, screenshots, and CSS work, but it’s a new, independently implemented rendering/CSS engine — expect edge cases and gaps versus a real browser.
- Single V8 isolate: heavy or long-running pages, or many pages in parallel against one
obscura serveprocess, compete for the same isolate.
Configuration
Section titled “Configuration”This helper should be configured in codecept.conf.js. It accepts everything CDPBrowser
accepts (see its config table), plus:
Type: object
Properties
Section titled “Properties”endpointstring? explicit CDP endpoint. Setting this switches the helper to ATTACH mode: it only connects, and never spawns or kills a process, no matter what else is configured. Leave it unset for SELF-MANAGED mode (see below).binaryPathstring? path to theobscuraexecutable, used in SELF-MANAGED mode (endpointunset). Checked beforeOBSCURA_PATHandPATH.portnumber? portobscura servelistens on, in SELF-MANAGED mode. When unset, a free port is picked automatically, which is what makesrun-workerscollision-free — every worker gets its own instance on its own port with zero config.serverStartTimeoutnumber? milliseconds to wait for a spawnedobscura serveto answer/json/versionbefore_connectgives up.
Example
Section titled “Example”// inside codecept.conf.js — SELF-LAUNCH mode (recommended): the helper finds/starts/stops// obscura serve on its own, on a free port. Ideal for run-workers: every worker gets its own// instance with no config.{ helpers: { Obscura: { url: 'http://localhost', } }}// ATTACH mode — connect to an Obscura instance you manage yourself (remote host, container, etc.){ helpers: { Obscura: { url: 'http://localhost', endpoint: 'http://127.0.0.1:9222', } }}Methods
Section titled “Methods”Parameters
Section titled “Parameters”configObscuraConfig
_connect
Section titled “_connect”In ATTACH mode, connects exactly as CDPBrowser._connect would. In SELF-MANAGED mode,
resolves and spawns obscura serve (or courtesy-attaches to an already-running one on
:9222) exactly once via _resolveSelfManaged, then connects.
A spawn failure (e.g. a bad binary) is delivered asynchronously by Node as an error
event; it is recorded on this.serverError and surfaced as a rejection from _waitForServer
instead of crashing the process as an uncaught exception.
_findFreePort
Section titled “_findFreePort”Picks a free TCP port on 127.0.0.1 by briefly listening on port 0 and reading back the OS-assigned
port. Used as the SELF-LAUNCH default when options.port isn’t explicitly set, so multiple
run-workers workers never collide on the same port.
Returns Promise<number> a free port.
_finishTest
Section titled “_finishTest”Closes the CDP connection (via CDPBrowser._finishTest), then kills the obscura serve
process spawned by _connect, if any (never runs in ATTACH or COURTESY-ATTACH mode, since
this.serverProcess is only ever set in SELF-LAUNCH mode). Runs in a finally so the process
is always reaped even if closing the CDP connection throws. Sends SIGTERM first and waits for
the process to exit; a process that ignores SIGTERM is escalated to SIGKILL after 5s. The
promise only resolves once the child has actually exited (confirmed via the exit event, not
merely once SIGKILL was sent — the kernel needs a moment to reap it), with a final safety-net
timeout so a stuck child can never keep the event loop alive even if that confirmation is
somehow lost.
_probeUp
Section titled “_probeUp”Probes a /json/version-style URL with a short timeout, used for the COURTESY-ATTACH check.
Parameters
Section titled “Parameters”urlstring
Returns Promise<boolean> true if the URL answered.
_resolveBinary
Section titled “_resolveBinary”Resolves the obscura binary to spawn, in priority order: options.binaryPath, then the
OBSCURA_PATH environment variable, then obscura on PATH. The PATH lookup walks the
directories itself instead of shelling out to which, which does not exist on Windows: on
Windows every PATHEXT suffix is tried, so an obscura.exe on PATH is found too.
Returns (string | null) an absolute or relative path to the binary, or null if none resolved.
_resolveSelfManaged
Section titled “_resolveSelfManaged”Resolves how to reach Obscura when no explicit endpoint was configured, trying, in order:
spawn a binary (binaryPath config, then OBSCURA_PATH env, then obscura on PATH),
courtesy-attach to http://127.0.0.1:9222 if something already answers there, or throw a
loud, actionable error. Sets this.options.endpoint as a side effect.
_waitForServer
Section titled “_waitForServer”Polls http://127.0.0.1:<port>/json/version until obscura serve responds, this.serverError
is set by the spawned process’ error event, or options.serverStartTimeout elapses. The
process typically comes up within tens of milliseconds — a 20ms retry interval (down from a
previous 200ms) keeps the wasted tail after the server is actually ready small, since this cost
is paid once per run and counts directly toward real-world startup latency.