feat: CDPBrowser helper family — Obscura and Kitesurf mini-browser support - #5681
Open
DavertMik wants to merge 21 commits into
Open
feat: CDPBrowser helper family — Obscura and Kitesurf mini-browser support#5681DavertMik wants to merge 21 commits into
DavertMik wants to merge 21 commits into
Conversation
…menavigated, read innerText via evaluate Prevents runner hang when CDP endpoint lacks Page.bringToFront (startup aborted before isRunning=true, teardown skipped, open socket kept node alive). Context also refreshes on framenavigated for endpoints that do not emit Page.loadEventFired on click navigations. Text extraction via evaluate avoids getProperty remote-object round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Wrap JSON.parse and listener invocations in try/catch to prevent uncaught exceptions from crashing the process or preventing other listeners from firing - Guard send() to reject immediately if connection not open, preventing timer leaks when called before connect() or after close() - Add test cases covering both fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…luate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution Global fetch is unavailable on Node 16, which package.json's engines field still supports. Every other HTTP-calling helper (REST.js, GraphQL.js, ApiDataFactory.js) already depends on axios, so _resolveEndpoint now uses it instead of relying on a Node 18+ global. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, fix screenshot test output dir Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Attach an error listener to the spawned obscura process so a bad binaryPath surfaces as a clear _connect rejection instead of an uncaught exception, and make _finishTest escalate SIGTERM to SIGKILL after 5s (confirming actual exit, not just that the signal was sent) so a SIGTERM-ignoring child can never keep the event loop alive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lans/006
Kitesurf's pre-existing example.com tests used amOnPage('/'), which broke
once the instance url could point at a SITE_URL tunnel instead. Switch
them to absolute URLs so they pass regardless of the configured base url.
plans/ is intentionally untracked in this repo; drop plans/006 from the
index (content unchanged on disk) to keep that convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kitesurf CI intentionally omitted until the promised open-source binary ships; the Kitesurf spec self-skips without CF credentials. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ons, docs corrections, scroll-into-view clicks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the CDPBrowser actions the canonical webapi.js shared spec exercises that were implementable in a few lines on top of the existing _run/_candidates/_evaluate machinery: wait, dontSeeInSource, dontSeeInTitle, seeCurrentUrlEquals/dontSeeCurrentUrlEquals, seeCurrentPathEquals/ dontSeeCurrentPathEquals, waitUrlEquals, waitCurrentPathEquals, seeInField/dontSeeInField, grabNumberOfVisibleElements/ seeNumberOfVisibleElements, grabPageScrollPosition/scrollPageToTop/ scrollPageToBottom/scrollTo, grabCssPropertyFrom(All), seeCssPropertiesOnElements, seeAttributesOnElements, focus/blur, type, resizeWindow, doubleClick/ rightClick/clickXY, waitForVisible/waitForInvisible/waitToHide/waitForDetached, seeCookie/dontSeeCookie/waitForCookie, grabHTMLFrom(All), executeAsyncScript, and saveElementScreenshot. The client script's fill/append/clear/values actions now handle contenteditable elements (checked via the contenteditable attribute, not just the isContentEditable property, since Obscura's minimal DOM doesn't compute it) alongside <input>/<textarea>/<select>. select gained native multi-select support and a fallback for ARIA role="combobox"/role="listbox" widgets (click the trigger if present, then click the matching [role="option"]). Also fixes three small pre-existing bugs surfaced while wiring the shared spec: - waitInUrl's timeout error message used the raw, unresolved urlPart instead of resolving it against options.url. - selectOption always did String(option), so an array value for a multi-select collapsed into one unmatchable comma-joined string. - see/dontSee ignored store.currentStep.opts.ignoreCase and didn't normalize whitespace, unlike every other browser helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… instead of parallel spec
CDPBrowser_chrome_test.js, CDPBrowser_obscura_test.js, and
CDPBrowser_kitesurf_test.js now wire into test/helper/webapi.js's tests(),
the same shared spec Puppeteer/Playwright/WebDriver run, instead of the
rejected parallel spec test/helper/cdpwebapi.js (deleted). Kitesurf's file
already had its own its covering the 3 public-page scenarios that used to
live in cdpwebapi.publicTests(), so nothing needed to be duplicated;
webApiTests.tests() is gated on the SITE_URL tunnel condition exactly as
cdpwebapi.tests() was.
webapi.js's isHelper() now walks the constructor's prototype chain instead
of a single constructor.name check, so isHelper('CDPBrowser') matches
CDPBrowser, Obscura, and Kitesurf, while isHelper('Obscura') still matches
only Obscura. Tests CDPBrowser can't run (or, narrower, that Obscura's lack
of a layout/rendering/focus engine can't run) are guarded with this.skip()
so they show as pending -- visible coverage -- instead of silently missing.
Rich text editor tests (ProseMirror, Quill, CKEditor 5, etc.) are skipped
for the whole CDP helper family per explicit direction: they need real
focus/selection/beforeinput semantics a raw-CDP helper intentionally does
not implement. No existing Playwright/WebDriver/Puppeteer guard was touched.
Final tallies: Chrome 185 passing/128 pending/0 failing, Obscura
108/193/0, Kitesurf 0/4/0 (skip-clean, no CF creds). The Puppeteer #see
regression slice (71/1/4) was verified byte-identical against the
unmodified webapi.js -- the 4 failures are pre-existing environment
issues (a hardcoded 'http://localhost' cookie domain vs this env's
127.0.0.1 SITE_URL, and codecept.io content drift), not caused by this
change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…semantics, regenerate docs - cdpBrowserClient.js: cssProps now reads only the requested properties (mirroring the attrsMap pattern) instead of serializing the entire ~1090-key/~35KB computed style object per element. CDPBrowser.js's grabCssPropertyFrom(All)/seeCssPropertiesOnElements now pass the camelCase-resolved property names through the payload. - _seeInField's boolean branch asserted field *existence* (values.length > 0) instead of checkbox *checked state*, so I.seeInField(locator, false) silently passed as long as the field existed, regardless of whether it was checked. Now asserts the checkbox's checked state via the existing 'checked' client action, mirroring Puppeteer's proceedSeeInField boolean semantics: true passes iff checked, false passes iff unchecked. No webapi.js test currently exercises seeInField/dontSeeInField with a boolean value, so this does not change either covering suite's tallies. - waitForCookie passed a literal 'placeholder' message to _poll and rewrote it in a catch block that also swallowed any real transport error from _poll/grabCookies. Now passes the real timeout message directly and the try/catch is gone. - Regenerated docs/helpers/CDPBrowser.md (npm run docs), which was still missing all ~40 methods added when CDPBrowser was wired to the shared webapi.js spec. Obscura.md/Kitesurf.md are unchanged (they only document methods declared directly on those classes). Chrome: 185 passing / 128 pending / 0 failing (unchanged). Obscura: 108 passing / 193 pending / 0 failing (unchanged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation/Description of the PR
A new class of lightweight, agent-era browsers (Obscura, Cloudflare Kitesurf) speaks CDP but breaks under Puppeteer/Playwright's private machinery (isolated worlds, injected query handlers, actionability checks). This PR adds a raw-CDP helper family that drives any CDP endpoint with one in-page round-trip per action — no element handles, no stale-context failures:
CDPBrowser— abstract base helper (~100 actions: navigation, semantic locators, assertions, grabbers, waits, cookies, screenshots, scrolls, CSS/attribute checks, keyboard/mouse basics). Transport is a minimalws-based CDP client; semantic by-text/by-label locators run through an injected in-page client plus an XPath polyfill (built from our existingxpathdependency withisHtml: true) for browsers with partial XPath engines. Capability flags (layout,xpath,screenshot) make unsupported actions fail with clear messages instead of protocol noise.Obscura— preset subclass for the Obscura mini-browser: synthetic input (clickdispatches in-pageel.click()), XPath polyfill on, screenshots/visibility assertions rejected with actionable errors, optionalbinaryPathto auto-spawnobscura serve(hardened: fail-fast on bad path, SIGTERM→SIGKILL escalation).Kitesurf— preset subclass for Cloudflare Browser Run (beta): session acquire/release via the CF API, Bearer-auth WebSocket, CDP input on real layout, screenshots. Tests self-skip withoutCF_ACCOUNT_ID/CF_API_TOKEN.test/helper/webapi.jssuite as Puppeteer/Playwright/WebDriver — no parallel spec.isHelper()now walks the prototype chain soisHelper('CDPBrowser')covers subclasses; unsupported tests are marked withthis.skip()so the pending list shows true coverage. Tallies: Chrome 185 passing / 128 pending, Obscura 108 passing / 193 pending, 0 failing. Skips are real limitations only (iframes, popups, file upload, drag&drop, rich-text editors, strict mode; plus layout/screenshot gates and 3 documented upstream DOM bugs on Obscura). Existing helpers are unaffected — the Puppeteer slice was verified byte-identical against unmodifiedwebapi.js.obscura.ymlworkflow — binary version- and SHA-256-pinned (verified on every run incl. cache hits),permissions: contents: read,persist-credentials: false. Kitesurf CI intentionally omitted until the promised open-source binary ships.docs/alternative-browsers.md(when these engines beat Playwright: CI smoke speed, DOM-not-pixels testing, and near-unlimited parallel scale with Kitesurf +run-workers— each worker auto-acquires its own cloud browser), plus generated helper reference pages.Puppeteerhelper fixes from the research phase: tolerate missingPage.bringToFront(previously hung the runner after printing OK), refresh context onframenavigated, readinnerTextviaevaluateinstead ofgetProperty.Based on the empirical research in plans/006 (session research doc, kept untracked). Reviewer note: the CI workflow downloads and executes the Obscura release binary — the SHA-256 pin freezes the artifact (trust-on-first-use from the audited v0.1.11 release); please eyeball that step when reviewing. Squash-merge recommended (one intermediate commit briefly tracked a local plans file).
Applicable helpers:
Applicable plugins:
Type of change
Checklist:
npm run docs)npm run lint)npm test) — unit suite 779 passed, 0 failed, 11 skipped🤖 Generated with Claude Code