From 75204378c01b28d72d66fb5bfec3af9fccc5e34a Mon Sep 17 00:00:00 2001 From: DavertMik Date: Sat, 3 Oct 2026 00:08:40 +0300 Subject: [PATCH 1/8] feat(helpers): add Lightpanda helper, share server lifecycle in CDPBrowser Lightpanda is a thin CDPBrowser subclass that self-launches `lightpanda serve` with telemetry disabled. The spawn/free-port/wait/kill lifecycle moves from Obscura into CDPBrowser so both helpers share it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/lightpanda.yml | 73 + Bunoshfile.js | 4 +- README.md | 2 + docs/alternative-browsers.md | 59 +- docs/helpers/Kitesurf.md | 72 +- docs/helpers/Lightpanda.md | 2339 +++++++++++++++++++++ docs/helpers/Obscura.md | 68 +- docs/installation.md | 32 + lib/helper/CDPBrowser.js | 229 +- lib/helper/Lightpanda.js | 159 ++ lib/helper/Obscura.js | 214 +- package.json | 1 + test/acceptance/codecept.Lightpanda.js | 32 + test/acceptance/config_test.js | 14 +- test/acceptance/forms_test.js | 10 +- test/acceptance/within_test.js | 2 +- test/helper/CDPBrowser_lightpanda_test.js | 164 ++ test/helper/webapi.js | 14 +- typings/fixDefFiles.js | 1 + 19 files changed, 3217 insertions(+), 272 deletions(-) create mode 100644 .github/workflows/lightpanda.yml create mode 100644 docs/helpers/Lightpanda.md create mode 100644 lib/helper/Lightpanda.js create mode 100644 test/acceptance/codecept.Lightpanda.js create mode 100644 test/helper/CDPBrowser_lightpanda_test.js diff --git a/.github/workflows/lightpanda.yml b/.github/workflows/lightpanda.yml new file mode 100644 index 000000000..f4181f774 --- /dev/null +++ b/.github/workflows/lightpanda.yml @@ -0,0 +1,73 @@ +name: Lightpanda Helper Tests + +on: + push: + branches: + - 4.x + pull_request: + branches: + - '**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + CI: true + FORCE_COLOR: 1 + LIGHTPANDA_VERSION: 1.0.0 + LIGHTPANDA_SHA256: aa5a4b8ed53d1e38b3c73f5b2647d0a84a82e6744557f45f9a9c85858aa031c3 + LIGHTPANDA_DISABLE_TELEMETRY: true + +jobs: + build: + runs-on: ubuntu-22.04 + timeout-minutes: 15 + + strategy: + matrix: + node-version: [22.x] + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + - name: npm install + run: npm i --force + - name: Cache Lightpanda binary + uses: actions/cache@v4 + with: + path: ~/lightpanda-bin/lightpanda + key: lightpanda-${{ env.LIGHTPANDA_VERSION }}-x86_64-linux + - name: Download Lightpanda + run: | + mkdir -p ~/lightpanda-bin + if [ ! -f ~/lightpanda-bin/lightpanda ]; then + curl -sfL --retry 3 -o ~/lightpanda-bin/lightpanda https://github.com/lightpanda-io/browser/releases/download/${LIGHTPANDA_VERSION}/lightpanda-x86_64-linux + fi + echo "${LIGHTPANDA_SHA256} $HOME/lightpanda-bin/lightpanda" | sha256sum -c - + chmod +x ~/lightpanda-bin/lightpanda + ~/lightpanda-bin/lightpanda version + - name: start test server + run: | + php -S 127.0.0.1:8000 -t test/data/app & + sleep 1 + curl -sf http://127.0.0.1:8000/info > /dev/null + - name: run lightpanda helper tests + run: | + export LIGHTPANDA_PATH="$HOME/lightpanda-bin/lightpanda" + npm run test:unit:webbapi:lightpanda + - name: run lightpanda acceptance tests + run: | + export LIGHTPANDA_PATH="$HOME/lightpanda-bin/lightpanda" + ./bin/codecept.js run -c test/acceptance/codecept.Lightpanda.js --grep @Lightpanda --debug diff --git a/Bunoshfile.js b/Bunoshfile.js index 12a8d5682..f3cf66ff2 100644 --- a/Bunoshfile.js +++ b/Bunoshfile.js @@ -399,6 +399,7 @@ const inheritedHelperDocs = { exclude: [/Title/, /Popup/, /Cookie/, /Url/, /^press/, /^refreshPage/, /^resizeWindow/, /Script$/, /cursor/, /Css/, /Tab$/, /^wait/], }, Obscura: { parent: 'CDPBrowser' }, + Lightpanda: { parent: 'CDPBrowser' }, Kitesurf: { parent: 'CDPBrowser', excludeConfig: ['endpoint', 'headers'] }, } @@ -425,11 +426,12 @@ async function docsHelperMarkdown(name, { parent, exclude = [], excludeConfig = const documentation = await import('documentation') const buildOptions = { shallow: true, sortOrder: ['alpha'] } const doc = await documentation.build([`docs/build/${name}.js`], buildOptions) + doc.sort((a, b) => (b.kind === 'class') - (a.kind === 'class')) let members = doc[0].members.instance if (parent) { const parentDoc = await documentation.build([`docs/build/${parent}.js`], buildOptions) - for (const method of parentDoc[0].members.instance) { + for (const method of parentDoc.find(c => c.kind === 'class').members.instance) { if (exclude.some(f => method.name.match(f))) continue if (members.some(m => m.name === method.name)) continue members.push(method) diff --git a/README.md b/README.md index 22dde6ccc..663e6ae2b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ | 🌐 Web | Puppeteer | [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) | | 🌐 Web | WebDriver | [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) | | 🌐 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) | +| 🌐 Web | Lightpanda | [![Lightpanda Helper Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/lightpanda.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/lightpanda.yml) | | 📱 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) | # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua) @@ -44,6 +45,7 @@ CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently, - [**Puppeteer**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Puppeteer.md) - uses Google Chrome's Puppeteer for fast headless testing. - [**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. - [**Obscura**](https://codecept.io/helpers/Obscura) - drives the lightweight Obscura browser through Chrome DevTools Protocol. See [Alternative Browser Engines](https://codecept.io/alternative-browsers). +- [**Lightpanda**](https://codecept.io/helpers/Lightpanda) - drives the Lightpanda headless browser through Chrome DevTools Protocol. See [Alternative Browser Engines](https://codecept.io/alternative-browsers). - [**Appium**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Appium.md) - for **mobile testing** with Appium - [**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. diff --git a/docs/alternative-browsers.md b/docs/alternative-browsers.md index f3ea7dd8e..93d3f23e3 100644 --- a/docs/alternative-browsers.md +++ b/docs/alternative-browsers.md @@ -6,7 +6,7 @@ title: Alternative Browser Engines # Alternative Browser Engines ::: warning Experimental -The `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. +The `Obscura`, `Lightpanda` and `Kitesurf` helpers are experimental in CodeceptJS 4.2. Pin browser versions in CI and retain Playwright or WebDriver coverage for compatibility-critical tests. ::: Playwright and Puppeteer drive full Chromium — the most accurate way to test what users see. @@ -17,11 +17,15 @@ through dedicated helpers: V8 engine. From v0.2.0, the default release build also renders — real layout, computed styles, and screenshots — with `-no-render` builds still available for pure-speed, nothing-painted scraping mode. Release archives are available for Linux, macOS, and Windows. +- **[Lightpanda](https://lightpanda.io)** — an open-source browser written from scratch in Zig for + automation, with a real V8 engine and broad Web API coverage. It never paints: no screenshots, + but it does compute enough style and layout for visibility checks. Binaries are available for + Linux and macOS. - **[Kitesurf](https://blog.cloudflare.com/kitesurf/)** — Cloudflare's browser that runs in V8 isolates on Cloudflare Workers, with a real layout and rendering pipeline. Cloud-only, free in beta, planned to be open-sourced. -Both speak Chrome DevTools Protocol. CodeceptJS drives them with raw CDP — one round-trip per +All of them speak Chrome DevTools Protocol. CodeceptJS drives them with raw CDP — one round-trip per action, no stale element handles — which is why suites on these browsers run fast and never hang on navigation races. @@ -56,6 +60,10 @@ redirect happens, cookie is set — do not need a GPU raster pipeline. Obscura e app's real JavaScript in real V8; it only skips painting. For API-adjacent flows (login → dashboard data appears), that is exactly the right amount of browser. +Lightpanda takes the same trade further: it has no rendering pipeline at all, which is what makes +it start in milliseconds and use a fraction of Chromium's memory. Pick it when the suite never +needs a screenshot; pick Obscura when it does. + **Constrained environments.** ARM CI runners, thin containers, air-gapped machines: a static binary with no system dependencies goes where Chromium will not. @@ -64,7 +72,9 @@ as an Obscura capability rather than a browser-compatibility guarantee from Code ## When to stay with Playwright -- Anything visual: visual regression, PDF (neither helper exposes PDF output). Obscura's v0.2.0+ +- Anything visual: visual regression, PDF (none of these helpers exposes PDF output). Lightpanda + has no screenshots at all, no scrolling, and only computes the CSS properties visibility checks + need. Obscura's v0.2.0+ rendering/CSS engine is new and independently implemented — expect edge cases and gaps versus a real browser, especially around inherited properties and less common computed-style values. - Visibility semantics on `-no-render` Obscura builds (and v0.1.x): every element reports as @@ -88,6 +98,12 @@ CodeceptJS 4.2 is tested in CI with Obscura 0.2.2. Obscura 0.2.x is recommended; }, } + helpers: { + Lightpanda: { + url: 'http://localhost:3000', + }, + } + helpers: { Kitesurf: { url: 'https://staging.myapp.com', @@ -131,18 +147,29 @@ process — there is nothing to start by hand in the common case: that "start Obscura by hand and just run the tests" keeps working without any config, while self-launch is still the default for everyone else. +### Lightpanda's connection modes + +Lightpanda works the same way, with `lightpanda` in place of `obscura`: the binary is resolved +from `binaryPath`, then `LIGHTPANDA_PATH`, then `lightpanda` on `PATH`, and `lightpanda serve` is +started on a free port and stopped when the run ends. Setting `endpoint` attaches to an instance +you manage yourself, for example the `lightpanda/browser` Docker image. CodeceptJS is tested in CI +with Lightpanda 1.0.0; see [Installation](/installation#lightpanda-experimental). + +The helper always starts Lightpanda with `LIGHTPANDA_DISABLE_TELEMETRY=true`. When you start the +server yourself, set that variable yourself. + ## Capability matrix -| | Playwright | Obscura | Kitesurf | -|---|---|---|---| -| Real JS execution (V8) | yes | yes | yes | -| Layout / getBoundingClientRect | yes | yes (v0.2.0+ default builds); synthetic on `-no-render`/v0.1.x | yes | -| Screenshots | yes | yes (v0.2.0+ default builds); no on `-no-render`/v0.1.x | yes | -| Visibility assertions | yes | yes (v0.2.0+ default builds); no, DOM-presence only, on `-no-render`/v0.1.x | yes | -| 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 | -| Startup model | local browser process | standalone local binary | remote cloud session | -| Parallel scale | machine-bound | machine-bound (light) | near-unlimited (cloud) | -| Where it runs | local/grid | local | Cloudflare only | -| License / cost | open source | Apache-2.0 | proprietary, free beta | - -See helper reference pages: [Obscura](/helpers/Obscura), [Kitesurf](/helpers/Kitesurf). +| | Playwright | Obscura | Lightpanda | Kitesurf | +|---|---|---|---|---| +| Real JS execution (V8) | yes | yes | yes | yes | +| Layout / getBoundingClientRect | yes | yes (v0.2.0+ default builds); synthetic on `-no-render`/v0.1.x | computed boxes, nothing painted | yes | +| Screenshots | yes | yes (v0.2.0+ default builds); no on `-no-render`/v0.1.x | no | yes | +| Visibility assertions | yes | yes (v0.2.0+ default builds); no, DOM-presence only, on `-no-render`/v0.1.x | yes | yes | +| 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 | no | untested | +| Startup model | local browser process | standalone local binary | standalone local binary | remote cloud session | +| Parallel scale | machine-bound | machine-bound (light) | machine-bound (light) | near-unlimited (cloud) | +| Where it runs | local/grid | local | local (Linux, macOS) | Cloudflare only | +| License / cost | open source | Apache-2.0 | AGPL-3.0 | proprietary, free beta | + +See helper reference pages: [Obscura](/helpers/Obscura), [Lightpanda](/helpers/Lightpanda), [Kitesurf](/helpers/Kitesurf). diff --git a/docs/helpers/Kitesurf.md b/docs/helpers/Kitesurf.md index 2981d5465..d779b990d 100644 --- a/docs/helpers/Kitesurf.md +++ b/docs/helpers/Kitesurf.md @@ -5,6 +5,7 @@ sidebar: auto title: Kitesurf --- + ## Kitesurf @@ -1742,7 +1743,9 @@ Returns **[Promise][4]** ### _connect -Resolves the CDP endpoint and opens the underlying `CDPConnection`, storing it on `this.cdp`. +In SELF-MANAGED mode, resolves and spawns the browser server (or courtesy-attaches to an +already-running one on :9222) exactly once via `_resolveSelfManaged`. Then resolves the CDP +endpoint and opens the underlying `CDPConnection`, storing it on `this.cdp`. ### _ensureClient @@ -1790,6 +1793,14 @@ Evaluates a JavaScript expression in the page attached to the current session vi Returns **[Promise][4]** the evaluated value, or `undefined` if the expression has no result. +### _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 when `options.port` isn't explicitly set, so multiple `run-workers` +workers never collide on the same port. + +Returns **[Promise][4]<[number][7]>** a free port. + ### _grabCurrentPath Resolves the current page URL to a `pathname`, ignoring the origin, query string, and hash. @@ -1824,6 +1835,26 @@ retrying once, mirroring the `'__NO_CLIENT__'` handling right next to it. * `needsXPath` **[boolean][11]** +### _manageServer + +Opts a subclass into managing its own browser server process. Called from a subclass +constructor with the raw user config and a server descriptor: + +* `name` — binary name looked up on `PATH`, also used in messages +* `envVar` — environment variable that may point at the binary +* `args(port)` — arguments the binary is spawned with to serve CDP on `port` +* `env` — extra environment variables for the spawned process +* `releases` — URL shown when no binary can be resolved + +An explicit `endpoint` in the user config always wins: the helper stays in ATTACH mode and +never spawns or kills anything. Otherwise the helper becomes SELF-MANAGED and the default +`endpoint` is cleared until `_resolveSelfManaged` sets the real one. + +#### Parameters + +* `config` **[object][5]** the config object the helper was constructed with. +* `server` **[object][5]** the server descriptor described above. + ### _needsVisibleTextFallback Determines whether `see`/`dontSee`/`waitForText` should read whole-page text through the @@ -1931,6 +1962,25 @@ The `xpath`/`innerText` probes determine *whether* their respective fallback is do not install anything — injection stays deferred to `_ensureClient`'s reactive install and `_textSource`'s own read, matching `amOnPage` no longer eagerly installing the client. +### _probeUp + +Probes a `/json/version`-style URL with a short timeout, used for the courtesy-attach check. + +#### Parameters + +* `url` **[string][3]** + +Returns **[Promise][4]<[boolean][11]>** true if the URL answered. + +### _resolveBinary + +Resolves the binary to spawn, in priority order: `options.binaryPath`, then the server +descriptor's environment variable, then its name 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 a `.exe` on `PATH` is found too. + +Returns **([string][3] | null)** an absolute or relative path to the binary, or null if none resolved. + ### _resolveEndpoint Acquires a Kitesurf browser session from the Cloudflare Browser Run API and resolves it to @@ -1939,6 +1989,18 @@ which resolves a fixed local endpoint instead of provisioning a cloud session pe Returns **[Promise][4]<[string][3]>** a `wss://` debugger URL ready to be passed to `CDPConnection`. +### _resolveSelfManaged + +Resolves how to reach a SELF-MANAGED browser, trying, in order: spawn a binary +(`binaryPath` config, then the descriptor's `envVar`, then its `name` on `PATH`) on +`options.port` or a free port, courtesy-attach to `http://127.0.0.1:9222` if something already +answers there (never killed by this helper), or throw a loud, actionable error. Sets +`this.options.endpoint` as a side effect. + +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. + ### _run Delegates a find-and-act call to `window.__codecept.run(candidates, action, payload)`. This is @@ -2155,6 +2217,14 @@ navigation, competing for the same CDP connection with real work. Returns **[Promise][4]** +### _waitForServer + +Polls `http://127.0.0.1:/json/version` until the spawned server responds, +`this.serverError` is set by the process' `error` event, or `options.serverStartTimeout` +elapses. The process typically comes up within tens of milliseconds — a 20ms retry interval +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. + ### _withinBegin Starts a `within` block, scoping every subsequent `_run` call (and therefore every element diff --git a/docs/helpers/Lightpanda.md b/docs/helpers/Lightpanda.md new file mode 100644 index 000000000..4d2bcaf01 --- /dev/null +++ b/docs/helpers/Lightpanda.md @@ -0,0 +1,2339 @@ +--- +permalink: /helpers/Lightpanda +editLink: false +sidebar: auto +title: Lightpanda +--- + + + + +## Lightpanda + +**Extends CDPBrowser** + +Lightpanda drives [Lightpanda][1], a headless browser written from scratch +for automation, exposed over the Chrome DevTools Protocol. It executes real JavaScript in V8 and +implements the DOM and Web APIs, but has no graphical rendering engine: nothing is ever painted, +so there are no screenshots. + +This helper is a thin `CDPBrowser` subclass: it changes nothing about how locating or acting on +elements works, it only pins the config presets Lightpanda requires and manages the +`lightpanda serve` process lifecycle, the same way Playwright manages its own browser process. + +> Lightpanda support is experimental. Pin the browser version in CI and keep a +> Playwright/WebDriver job for browser-compatibility coverage. + +## Compatibility + +| CodeceptJS | Recommended Lightpanda | Notes | +| ---------- | ---------------------- | ----------------------------------------------------- | +| 4.2.x | 1.0.0 | Version used by the CodeceptJS Lightpanda CI workflow | + +## Modes + +* **ATTACH** — `endpoint` 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-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in + the config, then the `LIGHTPANDA_PATH` environment variable, then `lightpanda` on `PATH`. The + helper spawns `lightpanda serve --host 127.0.0.1 --port ` (`port` from the config, or a + free port picked automatically), waits for it to answer, connects, and kills it in + `_finishTest`. +* **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already + answers `http://127.0.0.1:9222/json/version` (e.g. `lightpanda serve` started by hand, or the + `lightpanda/browser` Docker image). The helper attaches to it and never kills it. If neither a + binary nor a running server on :9222 can be found, the helper throws a loud, actionable error. + +## Install + +Download a binary from [Lightpanda releases][2]. +CodeceptJS is tested with Lightpanda 1.0.0. Binaries are available for: + +| platform | binary | +| ------------------- | -------------------------- | +| Linux x64 | `lightpanda-x86_64-linux` | +| Linux ARM64 | `lightpanda-aarch64-linux` | +| macOS Intel | `lightpanda-x86_64-macos` | +| macOS Apple Silicon | `lightpanda-aarch64-macos` | + +There is no native Windows binary; use WSL2. Linux binaries require glibc. + +Put `lightpanda` on your `PATH`, or point `binaryPath`/`LIGHTPANDA_PATH` at it. The helper then +launches and tears it down automatically. For example, on Linux x64: + +```sh +curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/1.0.0/lightpanda-x86_64-linux +chmod +x lightpanda +``` + +Lightpanda sends usage telemetry by default. This helper always spawns it with +`LIGHTPANDA_DISABLE_TELEMETRY=true`; set that variable yourself when you start the server by hand. + +Lightpanda is licensed under AGPL-3.0. It runs as a separate process that CodeceptJS talks to +over a WebSocket, so it does not change the license of CodeceptJS or of your tests. + +## Config presets + +These are set automatically and only need overriding for unusual setups: + +| option | value | why | +| ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `input` | `synthetic` | nothing is painted, so coordinates are not hit-tested; `click` always takes the `forceClick` path | +| `xpathPolyfill` | `auto` | probed per binary/page | +| `capabilities.layout` | `real` | Lightpanda computes visibility-related styles and element boxes without painting, which is enough for `seeElement` and the `waitForVisible` family; it is pinned because the runtime layout probe cannot run on Lightpanda's `about:blank` | +| `capabilities.screenshot` | `false` | there is no rendering engine | + +## Limitations + +* No screenshots and no screencast: `saveScreenshot` throws. +* No frames or popups. +* No scrolling: scroll positions always stay at 0. +* Computed styles cover what visibility checks need (`display`, `visibility`, `opacity`, + `pointer-events`); `grabCssPropertyFrom` and `seeCssPropertiesOnElements` are unreliable for + other properties. +* No coordinate-based input (`clickXY`). +* No clipboard access. +* Some rich text editors (TinyMCE, Trix, Monaco) never finish initialising. + + + +## Configuration + +This helper should be configured in codecept.conf.js + +Type: [object][5] + +### Properties + +* `endpoint` **[string][3]?** 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][3]?** path to the `lightpanda` executable, used in SELF-MANAGED mode + (`endpoint` unset). Checked before `LIGHTPANDA_PATH` and `PATH`. +* `port` **[number][7]?** port `lightpanda 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][7]?** milliseconds to wait for a spawned `lightpanda serve` + to answer `/json/version` before `_connect` gives up. +* `url` **[string][3]?** base url of website to be tested. +* `headers` **[object][5]?** headers sent with the endpoint resolution request and the WebSocket handshake. Useful for authenticated remote browser providers. +* `input` **[string][3]?** how synthetic user actions (click, fill, etc.) are dispatched by helpers built on top of this class. `auto` picks `cdp` when a real layout engine is detected and `synthetic` otherwise; can be pinned to `cdp` or `synthetic`. +* `xpathPolyfill` **([string][3] | [boolean][11])?** whether to inject the bundled XPath polyfill before installing the in-page client. `auto` probes the page and only injects when `document.evaluate` is unavailable or broken; `true`/`false` force the behavior. +* `capabilities` **[object][5]?** pre-seed detected browser capabilities (`layout`, `xpath`, `screenshot`, `innerText`) to skip runtime probing. Values set here are never overwritten by `_probeCapabilities`/`_ensureClient`. +* `waitForTimeout` **[number][7]?** default wait* timeout in seconds, used by helpers built on top of this class. +* `waitForAction` **[number][7]?** only takes effect when set explicitly: a literal fixed pacing sleep (in milliseconds) after click, type, or other interactions, mirroring other browser helpers. Left unset, actions settle in an event-aware way instead — near-instant when nothing navigates, waiting for the navigation to actually finish (not a guessed fixed delay) when one does. +* `pollInterval` **[number][7]?** interval in milliseconds between retries while polling for a condition (e.g. page ready state, `waitFor*`). Distinct from `waitForAction`. +* `getPageTimeout` **[number][7]?** maximum time in seconds to wait for a page to finish loading after navigation or reload; also used as the CDP command timeout (in ms, x1000). +* `waitForNavigation` **[string][3]?** when to consider a navigation finished: `load`, `domcontentloaded`, or `networkidle`. Mirrors the Puppeteer helper's option name. `networkidle` waits for the CDP `networkIdle` lifecycle event, which on a busy page can lag `load` by a second or more — only opt in if the extra wait is actually needed. + + + +## Example + +```js +// inside codecept.conf.js — SELF-LAUNCH mode (recommended): the helper finds/starts/stops +// lightpanda serve on its own, on a free port. Ideal for run-workers: every worker gets its +// own instance with no config. +{ + helpers: { + Lightpanda: { + url: 'http://localhost', + } + } +} +``` + +```js +// ATTACH mode — connect to a Lightpanda instance you manage yourself (remote host, container, etc.) +{ + helpers: { + Lightpanda: { + url: 'http://localhost', + endpoint: 'http://127.0.0.1:9222', + } + } +} +``` + +## Methods + +### Parameters + +* `config` **LightpandaConfig** + +### amOnPage + +Opens a web page in the current session. + +```js +I.amOnPage('/'); // opens main page of website +I.amOnPage('https://github.com'); // opens github +I.amOnPage('/login'); // opens a login page +``` + +Navigates via `Page.navigate`, then waits (up to `options.getPageTimeout` seconds) for the page +to finish loading, preferring the push-based `Page.lifecycleEvent` signal (per +`options.waitForNavigation`) over polling `document.readyState`. Capabilities are (re-)probed +(a no-op after the first page, since they're cached for the helper's lifetime). + +The in-page client is deliberately *not* eagerly (re-)installed here — navigation discards any +previously injected script, but installing it is deferred to the first actual action after +this call, via `_runSelected`'s sentinel-and-retry. This keeps `amOnPage` itself down to the +navigate command plus the push-based wait: no `_evaluate` call is issued on this hot path, +which matters most right when the page's own JavaScript may still be busy (measured directly: +an `_evaluate` sent in that window can queue behind it for hundreds of ms to multiple seconds +on a JS-heavy real-world page, regardless of how small the evaluated expression is). + +#### Parameters + +* `url` **[string][3]** url path or global url. + +Returns **[Promise][4]** + +### appendField + +Appends text to a input field or textarea. +Field is located by name, label, CSS or XPath + +```js +I.appendField('#myTextField', 'appended'); +// typing secret +I.appendField('password', secret('123456')); +``` + +#### Parameters + +* `field` **([string][3] | [object][5])** located by label|name|CSS|XPath|strict locator +* `value` **[string][3]** text value to append. +* `context` **([string][3]? | [object][5])** (optional, `null` by default) element to search in CSS|XPath|Strict locator. + +Returns **[Promise][4]** + +### attachFile + +Attaches a file to a file input field, or drops it onto a drag-and-drop dropzone element, +resolved by label|name|CSS|XPath|strict locator. `pathToFile` is resolved relative to +`codecept_dir` (matching Puppeteer/WebDriver). Since `CDPBrowser` never brings element handles +back to Node, the resolved element is marked with a throwaway `data-codecept-upload` attribute +in-page (respecting `context`/`within`/elementIndex exactly like every other action). A real +`` is then addressed by that attribute through the CDP `DOM` domain, which +`CDPBrowser` otherwise never uses, to call `DOM.setFileInputFiles`; any other element (a +drag-and-drop dropzone) instead gets a synthetic `dragenter`/`dragover`/`drop` sequence with a +`DataTransfer` built from the file's contents, entirely in-page. The marker is removed again in +a `finally`. + +```js +I.attachFile('Avatar', 'data/avatar.jpg'); +I.attachFile('#file', 'data/avatar.jpg'); +I.attachFile('#dropzone', 'data/avatar.jpg'); +``` + +#### Parameters + +* `field` **([string][3] | [object][5])** located by label|name|CSS|XPath|strict locator. +* `pathToFile` **[string][3]** path to file, relative to `codecept_dir`. +* `context` **([string][3]? | [object][5])** (optional, `null` by default) element to search in CSS|XPath|Strict locator. + +Returns **[Promise][4]** + +### blur + +Removes focus from a given element. + +```js +I.blur('#name'); +``` + +#### Parameters + +* `locator` **([string][3] | [object][5])** element located by CSS|XPath|strict locator. + +Returns **[Promise][4]** + +### checkOption + +Selects a checkbox or radio button. +Element is located by label or name or CSS or XPath. + +```js +I.checkOption('#agree'); +I.checkOption('I Agree to Terms and Conditions'); +I.checkOption('agree', '//form'); +``` + +#### Parameters + +* `field` **([string][3] | [object][5])** checkbox located by label | name | CSS | XPath | strict locator. +* `context` **([string][3]? | [object][5])** (optional, `null` by default) element located by CSS | XPath | strict locator. + +Returns **[Promise][4]** + +### clearClipboard + +Clears the system clipboard. + +```js +I.clearClipboard(); +I.seeClipboardEquals(''); +``` + +Returns **[Promise][4]** + +### clearCookie + +Clears a cookie by name, +if none provided clears all cookies. + +```js +I.clearCookie(); +I.clearCookie('test'); +``` + +#### Parameters + +* `name` **([string][3] | null)** (optional, `null` by default) cookie name + +Returns **[Promise][4]** + +### clearField + +Clears a `