Skip to content

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.

CodeceptJSRecommended ObscuraNotes
4.2.x0.2.2Version used by the CodeceptJS Obscura CI workflow
4.2.x0.2.xSupported; capabilities are detected at runtime
4.2.x0.1.x / -no-renderDOM-only mode; no layout, visibility assertions, or screenshots
  • ATTACHendpoint is set explicitly in the config. The helper only connects to it; it never spawns or kills anything, no matter what binaryPath/port are set to.
  • SELF-LAUNCHendpoint is unset and a binary can be resolved, in order: binaryPath in the config, then the OBSCURA_PATH environment variable, then obscura on PATH. The helper spawns obscura serve --port <port> --allow-private-network --allow-file-access (port from the config, or a free port picked automatically), waits for it to answer, connects, and kills it in _finishTest.
  • COURTESY-ATTACHendpoint is unset and no binary can be resolved, but something already answers http://127.0.0.1:9222/json/version (e.g. obscura serve started 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.

Download a release archive from Obscura releases. CodeceptJS 4.2 is tested with Obscura 0.2.2. Rendering archives are available for:

platformarchive
Linux x64obscura-x86_64-linux.tar.gz
Linux ARM64obscura-aarch64-linux.tar.gz
macOS Intelobscura-x86_64-macos.tar.gz
macOS Apple Siliconobscura-aarch64-macos.tar.gz
Windows x64obscura-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:

Terminal window
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.

These are set automatically and only need overriding for unusual setups:

optionvaluewhy
inputsyntheticcoordinate-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
xpathPolyfillautoprobed 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.

  • input is always synthetic, even on rendering builds — see input above.
  • No frames or popups.
  • On -no-render builds and v0.1.x: no screenshots, no visibility assertions (seeElement/dontSeeElement always 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 serve process, compete for the same isolate.

This helper should be configured in codecept.conf.js. It accepts everything CDPBrowser accepts (see its config table), plus:

Type: object

  • endpoint string? 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).
  • binaryPath string? path to the obscura executable, used in SELF-MANAGED mode (endpoint unset). Checked before OBSCURA_PATH and PATH.
  • port number? port obscura serve listens on, in SELF-MANAGED mode. When unset, a free port is picked automatically, which is what makes run-workers collision-free — every worker gets its own instance on its own port with zero config.
  • serverStartTimeout number? milliseconds to wait for a spawned obscura serve to answer /json/version before _connect gives up.
// 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',
}
}
}
  • config ObscuraConfig

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.

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.

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.

Probes a /json/version-style URL with a short timeout, used for the COURTESY-ATTACH check.

Returns Promise<boolean> true if the URL answered.

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.

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.

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.