Skip to content

Maryna/monorepo - #237

Merged
Refaerds merged 55 commits into
mainfrom
maryna/monorepo
Oct 12, 2026
Merged

Refaerds merged 55 commits into
mainfrom
maryna/monorepo

Conversation

@Refaerds

@Refaerds Refaerds commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR turns cloudflare/puppeteer into the monorepo for Cloudflare's Browser Run client libraries (BRAPI-404). It replaces the vendored Puppeteer fork with upstream submodules plus patches. It also brings @cloudflare/playwright and @cloudflare/playwright-mcp into this repository, so the three packages share one layout, one test harness, and one CI and release process.

Package Version Upstream base
@cloudflare/puppeteer 1.5.0-rc.0 puppeteer-core v24.40.0
@cloudflare/playwright 1.3.7-next Playwright v1.58.2
@cloudflare/playwright-mcp 0.0.6-next Playwright MCP v0.0.30 (parity with cloudflare/playwright-mcp@ee81e278)
@cloudflare/browser-test-runtime private Shared Worker test runtime, not published

What kind of change does this PR introduce? Build, repository structure, and CI change. It also includes the Puppeteer upgrade to v24.40.0, the Playwright port.

Repository layout

packages/
  puppeteer-cloudflare/      @cloudflare/puppeteer
  playwright-cloudflare/     @cloudflare/playwright
  playwright-mcp-cloudflare/ @cloudflare/playwright-mcp
  browser-test-runtime/      shared test runtime (private)
submodules/                  puppeteer, playwright, playwright-mcp, jest (pinned upstream)
patches/                     puppeteer.patch, playwright.patch, playwright-mcp.patch
examples/playwright-mcp-cloudflare/
scripts/                     patch apply/regenerate/check helpers
  • Cloudflare-specific changes to upstream code are now in patches/*.patch. npm run patch applies them. npm run diff regenerates them from the submodules. npm run check:submodules confirms that the submodules are clean.
  • Setup: git submodule update --init && npm ci && npm run build. See README.md and RELEASING.md.

What changes

Puppeteer (@cloudflare/puppeteer)

  • Now built from upstream puppeteer-core v24.40.0 plus patches/puppeteer.patch, instead of the vendored fork.
  • Includes Browser Run RPC bindings: launch, acquire, connectSession, outboundByHost, lab sessions, and keep_alive → keepAlive.
  • A failed WebSocket upgrade now throws Unable to connect to browser: code: <status>: message: <body> (as Playwright does) instead of a null dereference.

Playwright (@cloudflare/playwright)

  • Moved here from cloudflare/playwright. It includes feat(cloudflare): support outbound workers in Browser Run playwright#228 (the same RPC features as Puppeteer).
  • Fixes the Worker-safe function normalization (Workers do not allow new Function()). Async arrows and anonymous functions were classified as method shorthand, which produced SyntaxError: Unexpected token '=>'.
  • HTTP-error navigations now follow the normal commit and waitUntil lifecycle, and request.sizes() resolves for failed responses.

Playwright MCP (@cloudflare/playwright-mcp)

  • Added with parity to the current cloudflare/playwright-mcp fork: upstream v0.0.30 submodule, the fork's schema coercion patch, the fork's Worker wrapper and example, and ESM/CJS output.
  • Keeps @cloudflare/playwright ^0.0.11 on purpose. The fork's fs → @cloudflare/playwright/fs (memfs) aliases require it, and current Playwright no longer exports that path. Moving to the workspace Playwright is a separate change.
  • The version is 0.0.6-next, above the published 0.0.5.
  • Adds a deployed-Worker smoke test (test:remote). The test Worker sets no_websocket_standard_binary_type, because Playwright 0.0.11 expects ArrayBuffer WebSocket frames. From compatibility date 2026-03-17, Workers deliver them as Blob by default. The README has a "Compatibility date" section for users of the published package.
  • The upstream MCP suite no longer runs against local Node browsers. The fork does not run it either. An in-Worker MCP suite is a follow-up.

Shared Worker test harness

Each package runs its upstream test suite inside a deployed test Worker against Browser Run. The Node Playwright runner sends each test to that Worker.

  • @cloudflare/browser-test-runtime has two new entry points:
    • /proxy (Node): Browser Run session fixture, runTest, and proxy spec generation.
    • /worker-routes (Worker): test list with x-worker-version, /v1 forwarding to the binding, and unusable-session results.
    • Both are plain ES modules and do not import @playwright/test.
  • Reliability fixes:
    • CI waits until the deployed Worker version serves the test list. It reads the version from wrangler deploy and the CF_VERSION_METADATA binding.
    • A saved session is reused only while it is still open.
    • When a Browser Run session becomes unusable (404 or 410, unhealthy browser), the Worker returns a failed result. The retry then acquires a new session instead of reusing the dead one.
    • Session-management specs check their own session instead of comparing account-wide lists. They also allow for the 200-row cap on limits().activeSessions.
    • The harness ignores the zone's Bot Management cookie __cf_bm in cookie assertions.

CI and release (.github/workflows/cf_*)

  • One set of workflows per product: full, smoke (manual), prerelease (PR package preview), and publish (release). There is also cf_lockfile.yml.
  • Path filters are product-specific, so a change starts only the relevant suite.
  • Credentials and deployments:
    • Pushes, manual runs, and PRs from branches in this repository deploy the test Workers and run the credentialed suites.
    • Fork and Dependabot PRs run validation only (build, type checks, Wrangler dry runs).
    • Repository writers can dispatch full and smoke runs from any branch.
  • Full-suite runs for the same PR cancel each other. A job-level lock serializes deployments to the shared test Workers.
  • Publish workflows check the tag, version, and dist-tag policy:
    • Stable releases advance latest.
    • Prereleases advance latest and next.
    • Versions that are already published are rejected.
  • Workflow tokens are contents: read (CodeQL).
  • Dependencies: Wrangler 4.137.0, @cloudflare/workers-types v5, and Vite floors ^6.4.3 / ^7.3.6 (security fixes). The remaining npm audit findings come from agents@0.0.109, which MCP parity pins; that upgrade is deferred.

Does this PR introduce a breaking change?

  • Repository: yes, for contributors. The vendored Puppeteer sources, docs, and website are removed; this accounts for most of the ~200k deleted lines. Upstream code now comes from submodules plus patches.
  • Published packages: package names and entry points do not change. @cloudflare/puppeteer moves from 1.4.x to 1.5.0-rc.0 on puppeteer-core 24.40.0, which includes the upstream API changes since the previous base.

Did you add tests for your changes?

Yes:

  • RPC binding runtime and type tests in both clients.
  • Session-management and guardrail specs.
  • Packed-tarball consumer checks.
  • The MCP remote smoke test.

Verification:

  • PR CI is green at 9db934a8: Puppeteer and Playwright full suites against Browser Run, MCP deploy and smoke, CodeQL, lockfile, and package validation.
  • Full runs on a separate account at the same code: Puppeteer 575 passed / 0 failed; Playwright 2285 passed / 0 failed.

If relevant, did you update the documentation?

Yes: root README.md, RELEASING.md (release procedure per package), the package READMEs, and the MCP example.

Review guide

The diff has 2,000+ files, and most of it is the removal of the vendored Puppeteer tree (docs/, website/, packages/puppeteer-core/, test/). Suggested order:

  1. README.md, RELEASING.md, root package.json, scripts/
  2. patches/*.patch (the Cloudflare changes to upstream code)
  3. packages/*/src, index.d.ts, and package metadata
  4. packages/*/tests and packages/browser-test-runtime
  5. .github/workflows/cf_*.yml

Other information / follow-ups

  • Follow-ups (not in this PR):
    • In-Worker Playwright MCP test suite, using the shared harness.
    • agents SDK upgrade and moving MCP to the workspace Playwright.
  • Browser Run occasionally reports unhealthy browsers (410 state: unhealthy) and runs slowly. The harness now retries on a new session, but a few timing-sensitive upstream tests can still be flaky.

ruifigueira and others added 21 commits September 24, 2026 15:23
Move the Workers integration into its own package and store the required upstream Puppeteer changes as a patch.
Delete the checked-in upstream repository after moving it to the submodule. Reduce the root workspace to Cloudflare's Browser Run packages.
…error response handling in Chromium navigation
…d alias configuration comments for Workers-compatible memfs exports
…ion capability check and add test coverage for incomplete RPC bindings
…xy test title escaping, and validate Stagehand screenshot data URLs
The Cloudflare playwright-mcp fork does not run this suite (its upstream CI
workflow is disabled), and it exercises local Node browsers rather than the
Workers wrapper. Drop test:upstream and the Chromium/Firefox installs; an
in-Worker MCP suite against Browser Run will follow separately.
…ctions

The Workers-only check that replaces new Function treated `async (a) => a`
and `function (a) {}` as method shorthand and prefixed them with
`function `, producing a SyntaxError for almost every evaluate/click in
Worker builds. Classify arrows (including async ones with nested or
default parameters) and function/class expressions as already valid, and
prefix only real method shorthand.
keep_alive is an inactivity timeout, so a session with a connected browser
stays open past it, as the spec on main expected. Check it through the
per-session endpoint instead of the shared session list, and report the
active-session count and limit when a new session is missing from limits.
…ions

Check the test's own session through the per-session endpoint instead of
comparing account-wide session lists, restore connected-session keep_alive
semantics, and poll the limits endpoint with a diagnostic on failure.
Chromium replaces an empty 4xx navigation response with its own error page,
so navigations to missing files ended on chrome-error://chromewebdata/.
Return "File not found: <path>" like the upstream test server.
Poll the per-session endpoint briefly after acquiring, and wait keep_alive
plus a margin for a released session to close.
…st Worker

@cloudflare/playwright 0.0.11 reassembles binary CDP chunks from
ArrayBuffer frames. From compatibility date 2026-03-17 Workers delivers
binary frames as Blob (websocket_standard_binary_type), so no CDP
response was ever parsed and browser_navigate timed out in
connectOverCDP. Opt the test Worker out with
no_websocket_standard_binary_type and document the flag for users on
recent compatibility dates.
Proxy tests take their test list from the deployed Worker. A deploy takes
a few seconds to propagate, so CI could read the previous version's list
and then run IDs that the new version does not have (a renamed test
failed in 43ms with no error). The test Workers now report their version
in x-worker-version, the workflows export the ID printed by wrangler
deploy, and the generators poll until that version serves the list.
The session details endpoint returns 200 for ended sessions too. After a
browser crash, retries reconnected to the dead session and every later
test in the worker failed with 410. Reuse a saved session only when it
has no endTime or close reason.
Browser Run returns at most 200 active sessions under limits. On an
account with more active sessions a new session can be cut off, so
confirm it through the per-session endpoint when the list is full.
JSON.stringify already escapes the generated literals, so the extra
replace() inserted a literal backslash into titles (CodeQL
js/incomplete-sanitization, alerts 54/55). Match the Playwright
generator and update the one skip entry that relied on the escaped
title.
When Browser Run's browser for the shared test session dies (for example
410 "Browser not running (state: unhealthy)"), the Puppeteer test
Worker retried the connect for 10s and then threw, so CI only saw a
generic 1101 page, and the retry reconnected to the same dead session.

Only retry connects that can recover (409 or no HTTP status), report
connect and probe failures as a failed test with the real error, and
mark such results sessionUnusable. The Playwright test Worker marks
results whose errors show a 404/410 connect failure the same way. The
proxy then drops the saved session so the retry acquires a new one.
…runtime

Move the duplicated Node-side proxy harness (Browser Run session fixture,
runTest, proxy spec generation with the deployed-version wait, file helpers)
to @cloudflare/browser-test-runtime/proxy, and the Worker routing helpers
(test list with x-worker-version, /v1 forwarding, unusable-session results)
to @cloudflare/browser-test-runtime/worker-routes. Both are plain ES modules
with no dependencies; the proxy module does not import @playwright/test, so
each test project keeps its own version. Test server DOs, worker fixtures,
worker test generation, and skip lists stay package-specific.
…all URLs per package

Add repository and homepage fields for cloudflare/browser to all three
packages, so npm and pkg.pr.new resolve this repository after the rename.
Publish previews with --no-compact, because pkg.pr.new resolves compact URLs
through the npm latest version's repository field, which still names the old
repositories. Each preview workflow now posts its own pull request comment
with the install URL and updates only that comment on new pushes and reruns.
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

@cloudflare/playwright-mcp preview for commit 6f38dab

npm i https://pkg.pr.new/cloudflare/puppeteer/@cloudflare/playwright-mcp@6f38dabf657ce1fa2f09f9e7efbaf749701f947f

Published by this workflow run. New pushes and reruns update this comment.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

@cloudflare/playwright preview for commit 6f38dab

npm i https://pkg.pr.new/cloudflare/puppeteer/@cloudflare/playwright@6f38dabf657ce1fa2f09f9e7efbaf749701f947f

Published by this workflow run. New pushes and reruns update this comment.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

@cloudflare/puppeteer preview for commit 6f38dab

npm i https://pkg.pr.new/cloudflare/puppeteer/@cloudflare/puppeteer@6f38dabf657ce1fa2f09f9e7efbaf749701f947f

Published by this workflow run. New pushes and reruns update this comment.

The smoke test compared an exact byte percentage of the live example.com,
which changes when that page or the browser changes. Use the JS and CSS
coverage pages from the Puppeteer test assets, which the test Worker serves,
with the same exact ranges as the upstream coverage specs.
Restore the main-branch behavior in both clients: connect through
connectSession only for bindings that acquired the session with the RPC
acquire method, and through the binding's fetch otherwise. A typeof check
cannot detect the method, because every property of a Browser Run binding is
an RPC stub, so a plain launch(env.BROWSER) called connectSession and failed
where Browser Run does not serve it, for example in local development.
Declare launch, acquire and connectSession as methods, so that the BrowserRun
type from @cloudflare/workers-types, whose options differ (for example,
outboundByHost values typed as Fetcher), is assignable and launch(env.BROWSER)
compiles without a cast.

Drop readonly from the global Response.webSocket augmentation in Puppeteer to
match workers-types; projects that load both failed with 'All declarations of
webSocket must have identical modifiers' unless skipLibCheck was set.

Add type tests in both packages against the real BrowserRun type from
@cloudflare/workers-types under strict function types, so they follow the
installed workers-types version. Exclude test-d from the Playwright tests
tsconfig, which uses the generated worker configuration types; tsd checks it.
Refer to the global Browser Run types of the Workers runtime instead of
copies that drifted from the binding (session timestamps, connection and
outbound Worker types). Every Worker project loads them, from
@cloudflare/workers-types or from wrangler types.

- SessionGuardrails, AcquireResponse, ActiveSession, ClosedSession and
  LimitsResponse alias BrowserRunAcquireGuardrails, BrowserRunAcquireResult,
  BrowserRunSession and BrowserRunLimits.
- BrowserWorker picks launch, acquire and connectSession from BrowserRun and
  still accepts fetch-only bindings such as service bindings.
- outboundByHost takes the Fetcher values of BrowserRunAcquireOptions.
- Remove the unreleased BrowserRunOptions, BrowserRunConnection,
  BrowserRunConnectOptions, BrowserRunTarget and BrowserRunAcquireResult
  exports, which shadowed the global names.

Puppeteer builds with the DOM library, so a build-only declaration file
defines the global names from the module form of @cloudflare/workers-types.
It is not emitted, and the published declarations do not import the package.

The types need @cloudflare/workers-types 5.20260917.1 or later, or Wrangler
4.134.0 or later for wrangler types; the READMEs say so. Regenerate the test
Workers' types with wrangler types and add type tests for the aliases and
fetch-only bindings.
Use the repository name after the planned rename for issue links, images,
package links and the Deploy to Cloudflare buttons. The Stagehand and
TodoMVC buttons pointed at the standalone cloudflare/playwright repository,
which will be archived. The links work once cloudflare/puppeteer is renamed
to cloudflare/browser.
The injected clock follows real time from injection until install(). On an
open page, install() comes after at least two round trips to Browser Run. If
they take more than 100 ms, the clock advances by one 100 ms step first, and
performance.now() is 1101 instead of 1000.
@Refaerds
Refaerds marked this pull request as ready for review October 12, 2026 09:07
The repository will be renamed to cloudflare/browser-run, not
cloudflare/browser. Update the package repository and homepage fields, the
README links and the Deploy to Cloudflare buttons.
Match the planned repository name. The root package is private and is not
published.
@Refaerds
Refaerds merged commit 537b058 into main Oct 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants