From 3b66f3fdd8e8c6a948628cccb443d2ffacf71b1e Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 8 Jun 2026 11:19:22 +0200 Subject: [PATCH 1/2] Build driver from source instead of downloading from CDN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop downloading the prebuilt Playwright driver bundle from cdn.playwright.dev. Instead, clone microsoft/playwright at the tag matching driver_version and build the per-platform bundles from source via upstream's utils/build/build-playwright-driver.sh. - scripts/build_driver.sh: portable bash script (shareable across the language forks) that clones, builds (npm ci && npm run build), runs the upstream driver build, and stages all 6 platform zips into driver/. - setup.py: ensure_driver_bundle() shells out to the script when a bundle is missing; stale extract-dir cleanup before extraction. - CI/release: add Node 24 (matching the bundled runtime major) wherever wheels are built — ci.yml, publish_docker.yml, test_docker.yml host, Azure NodeTool; meta.yaml conda host gains git + nodejs. - Docs: ROLLING.md, CONTRIBUTING.md, CLAUDE.md, playwright-roll SKILL.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/publish.yml | 4 + .claude/skills/playwright-roll/SKILL.md | 25 ++-- .github/workflows/ci.yml | 12 ++ .github/workflows/publish_docker.yml | 4 + .github/workflows/test_docker.yml | 8 ++ CLAUDE.md | 7 +- CONTRIBUTING.md | 6 + DRIVER_SHA | 1 + ROLLING.md | 4 +- scripts/build_driver.sh | 157 ++++++++++++++++++++++++ setup.py | 50 ++++---- 11 files changed, 243 insertions(+), 35 deletions(-) create mode 100644 DRIVER_SHA create mode 100755 scripts/build_driver.sh diff --git a/.azure-pipelines/publish.yml b/.azure-pipelines/publish.yml index 0b8c37402..2d5f9a0a3 100644 --- a/.azure-pipelines/publish.yml +++ b/.azure-pipelines/publish.yml @@ -40,6 +40,10 @@ extends: inputs: versionSpec: '3.9' displayName: 'Use Python' + - task: NodeTool@0 + inputs: + versionSpec: '24.x' + displayName: 'Use Node.js' - script: | python -m pip install --upgrade pip pip install -r local-requirements.txt diff --git a/.claude/skills/playwright-roll/SKILL.md b/.claude/skills/playwright-roll/SKILL.md index 9a6adbd03..c63669f2e 100644 --- a/.claude/skills/playwright-roll/SKILL.md +++ b/.claude/skills/playwright-roll/SKILL.md @@ -5,7 +5,7 @@ description: Roll Playwright Python to a new driver version. Walks the upstream # Rolling Playwright Python -The goal of a roll is to move `driver_version` in `setup.py` to a new release, port every public API change introduced upstream during that interval, and suppress the rest, so that `./scripts/update_api.sh` runs clean and the test suite still passes. +The goal of a roll is to move the driver pin in `DRIVER_SHA` to a new release, port every public API change introduced upstream during that interval, and suppress the rest, so that `./scripts/update_api.sh` runs clean and the test suite still passes. The previous human-facing summary lives in `../../../ROLLING.md`. This skill is the operational playbook — read it end to end before starting. @@ -15,7 +15,7 @@ The Python port is hand-written code in `playwright/_impl/`, plus a generator (` 1. introspects the Python `_impl` classes via `inspect`, 2. emits typed wrapper classes into `playwright/{async,sync}_api/_generated.py`, and -3. diffs the introspected surface against `playwright/driver/package/api.json` (downloaded inside the new driver wheel). +3. diffs the introspected surface against `playwright/driver/package/api.json` (built into the new driver from source). Anything in `api.json` that is missing or differently typed in `_impl/` causes generation to fail. Three resolutions: @@ -52,18 +52,27 @@ There is sometimes no `vX.Y.0` tag for the latest release (the bots cut release - If `python3-venv` is missing system-wide, use `uv venv env` instead, then `uv pip install --python env/bin/python --upgrade pip`. Don't try to `apt install` — sudo is denied in the harness. - Always activate the venv before any `pip`, `pytest`, `mypy`, or `pre-commit` invocation. -### 2. Bump the driver and download it +### 2. Bump the driver and build it from source ```sh -# Edit setup.py -driver_version = "" # e.g. "1.59.1" +# Edit DRIVER_SHA (repo root): replace with the microsoft/playwright commit SHA +# for the new release, e.g. the commit that v points at. +# 87bb9ddbd78f329df18c2b24847bc9409240cd07 +# Update the "# microsoft/playwright @ v" comment in scripts/build_driver.sh too. source env/bin/activate -python -m build --wheel # downloads the new driver from cdn.playwright.dev +python -m build --wheel # clones microsoft/playwright @ DRIVER_SHA and builds the driver from source playwright install chromium # NOT --with-deps; sudo is denied ``` -The wheel build prints `Fetching https://cdn.playwright.dev/builds/driver/playwright--linux.zip` and unpacks the driver under `playwright/driver/package/`. From this point, `playwright/driver/package/api.json` reflects the new release. +The wheel build clones `microsoft/playwright` at the commit in `DRIVER_SHA` +into `driver/playwright-src`, runs `npm ci && npm run build`, and runs upstream's +`utils/build/build-playwright-driver.sh` to produce the per-platform driver +bundles (`driver/playwright--*.zip`), then unpacks the driver under +`playwright/driver/package/`. From this point, +`playwright/driver/package/api.json` reflects the new release. This requires +**Node.js, npm, git and bash** on PATH; the first build is slow (full upstream +build + per-platform Node downloads). ### 3. Identify the commit range @@ -240,7 +249,7 @@ For each PORT, add one async test and a matching sync test. Conventions: ### 7. Update existing high-touch artifacts -- `setup.py`: already done in step 2. +- `DRIVER_SHA` (and the version comment in `scripts/build_driver.sh`): already done in step 2. - `README.md`: gets the chromium/firefox/webkit version table updated automatically by `scripts/update_versions.py` (called from `update_api.sh`). Don't edit by hand. - The "Backport changes" tracking issue on GitHub (filed by `microsoft-playwright-automation`) is the *intent* tracker, but it's frequently out of sync with what's actually been ported. Treat it as a starting point, not the source of truth — the `docs/src/api/` commit walk is authoritative. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9abb66777..2742caec3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' - name: Install dependencies & browsers run: | python -m pip install --upgrade pip @@ -96,6 +100,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' - name: Install dependencies & browsers run: | python -m pip install --upgrade pip @@ -143,6 +151,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' - name: Install dependencies & browsers run: | python -m pip install --upgrade pip diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 83fa5b9b9..710b70193 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -28,6 +28,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' - name: Set up Docker QEMU for arm64 docker builds uses: docker/setup-qemu-action@v4 with: diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index a25a382b5..56d3420ce 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -35,6 +35,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' - name: Install dependencies run: | python -m pip install --upgrade pip @@ -53,6 +57,10 @@ jobs: docker exec "${CONTAINER_ID}" pip install -r local-requirements.txt docker exec "${CONTAINER_ID}" pip install -r requirements.txt docker exec "${CONTAINER_ID}" pip install -e . + # build.sh (above) already built the driver bundles into driver/ on the + # host. The repo is bind-mounted into the container, so this in-container + # wheel build reuses those bundles (setup.py skips the source build when + # the zip already exists) and therefore needs no Node.js in the image. docker exec "${CONTAINER_ID}" python -m build --wheel docker exec "${CONTAINER_ID}" xvfb-run pytest tests/sync/ docker exec "${CONTAINER_ID}" xvfb-run pytest tests/async/ diff --git a/CLAUDE.md b/CLAUDE.md index 1cbaf3e1a..af56130b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,19 +13,20 @@ Python bindings for [Playwright](https://playwright.dev). The Python client talk - `scripts/generate_api.py`, `scripts/generate_async_api.py`, `scripts/generate_sync_api.py`, `scripts/documentation_provider.py` — codegen and validation. They diff the Python implementation against the driver's `playwright/driver/package/api.json` and abort if either side is out of sync. - `scripts/expected_api_mismatch.txt` — explicit allowlist of "documented in JS, not in Python" or "named differently in Python" gaps. Lines that no longer apply must be removed. - `tests/async/`, `tests/sync/` — pytest suites. Most new tests are added to the async file with a sync mirror. -- `setup.py` — `driver_version = "X.Y.Z"` is the source of truth for which driver build is downloaded from `cdn.playwright.dev`. +- `DRIVER_SHA` — the single source of truth for which Playwright commit the driver is built from (one line, the 40-char `microsoft/playwright` commit SHA). Read by `setup.py`, `scripts/build_driver.sh`, and CI. The wheel build clones `microsoft/playwright` at this commit and builds the driver from source (via `scripts/build_driver.sh` + upstream's `utils/build/build-playwright-driver.sh`). The SHA is baked into the staged bundle filenames (`driver/playwright--.zip`), so it doubles as the build cache key. +- `scripts/build_driver.sh` — clones and builds the upstream driver bundles into `driver/`. A portable bash script (shareable with the other language forks) that needs Node.js, npm, git and bash; invoked from `setup.py`'s `bdist_wheel`. Reads the pin from `DRIVER_SHA`; takes no arguments. - `ROLLING.md`, `CONTRIBUTING.md` — human-facing setup and roll docs. ## Setup -`CONTRIBUTING.md` has the full sequence. The short version: +`CONTRIBUTING.md` has the full sequence. The short version (needs Node.js, npm, git and bash for the driver build): ```sh python3 -m venv env && source env/bin/activate pip install --upgrade pip pip install -r local-requirements.txt pip install -e . -python -m build --wheel # downloads the driver listed in setup.py +python -m build --wheel # clones microsoft/playwright @ DRIVER_SHA and builds the driver from source pre-commit install ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b59e281c8..ed7165fe0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,12 @@ pip install -r local-requirements.txt Build and install drivers: +The driver is built from upstream `microsoft/playwright` source, so building a +wheel requires **Node.js (with npm), git and bash** on your PATH. The commit to +build from is pinned in the `DRIVER_SHA` file. The first +`python -m build --wheel` clones that commit and runs its full +build, which is slow. + ```sh pip install -e . python -m build --wheel diff --git a/DRIVER_SHA b/DRIVER_SHA new file mode 100644 index 000000000..cce0793a8 --- /dev/null +++ b/DRIVER_SHA @@ -0,0 +1 @@ +87bb9ddbd78f329df18c2b24847bc9409240cd07 diff --git a/ROLLING.md b/ROLLING.md index 811d7fcb3..601c6c5b1 100644 --- a/ROLLING.md +++ b/ROLLING.md @@ -11,8 +11,8 @@ pip install -r local-requirements.txt pre-commit install pip install -e . ``` -* change driver version in `setup.py` -* download new driver: `python -m build --wheel` +* change the driver pin in `DRIVER_SHA` (the `microsoft/playwright` commit SHA to build from) +* build the new driver from source: `python -m build --wheel` (clones `microsoft/playwright` at that commit and builds it; requires Node.js, npm, git and bash) * generate API: `./scripts/update_api.sh` * commit changes & send PR * wait for bots to pass & merge the PR diff --git a/scripts/build_driver.sh b/scripts/build_driver.sh new file mode 100755 index 000000000..4ebc65354 --- /dev/null +++ b/scripts/build_driver.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build the Playwright driver bundles from upstream source. +# +# This script checks out microsoft/playwright at the commit pinned in the +# DRIVER_SHA file (repo root) and runs upstream's +# utils/build/build-playwright-driver.sh. That script cross-builds the +# per-platform bundles, which this script stages into driver/ as +# playwright--.zip for setup.py to embed into the platform wheels. +# +# The pin is an immutable commit SHA (tags can be moved upstream) and lives in +# the neutral DRIVER_SHA file so setup.py and CI can read it without parsing +# this script. The SHA is baked into the staged bundle filenames, so the +# filename doubles as the build cache key: a roll changes DRIVER_SHA, which +# changes the filenames and invalidates the cache. +# +# A single host builds all platform bundles at once: the upstream script +# downloads the matching Node.js binary for each target, so the host platform +# does not constrain which bundles can be produced. +# +# This is intentionally a shell script (rather than language-specific code) so +# the same build step can be shared across the Playwright language forks. +# +# Usage: scripts/build_driver.sh (reads the pin from DRIVER_SHA; no arguments) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DRIVER_DIR="$REPO_ROOT/driver" +SOURCE_DIR="$DRIVER_DIR/playwright-src" +PLAYWRIGHT_REPO="https://github.com/microsoft/playwright" + +# The driver pin: an immutable commit in microsoft/playwright. +# microsoft/playwright @ v1.60.0 +EXPECTED_SHA="$(tr -d '[:space:]' < "$REPO_ROOT/DRIVER_SHA")" +if [[ -z "$EXPECTED_SHA" ]]; then + echo "DRIVER_SHA is empty or missing at $REPO_ROOT/DRIVER_SHA" >&2 + exit 2 +fi + +# Bundle suffixes produced by utils/build/build-playwright-driver.sh. Keep in +# sync with the "zip_name" values in setup.py. +SUFFIXES=(mac mac-arm64 linux linux-arm64 win32_x64 win32_arm64) + +bundles_present() { + local suffix + for suffix in "${SUFFIXES[@]}"; do + [[ -f "$DRIVER_DIR/playwright-$EXPECTED_SHA-$suffix.zip" ]] || return 1 + done + return 0 +} + +require_tools() { + local missing=() + local tool + for tool in git node npm bash; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing+=("$tool") + fi + done + if [[ ${#missing[@]} -gt 0 ]]; then + echo "Building the Playwright driver from source requires the following tools," >&2 + echo "which were not found on PATH: ${missing[*]}." >&2 + echo "Install Node.js (with npm), git and bash, then retry. On Windows, run the" >&2 + echo "build from a bash shell (e.g. Git Bash)." >&2 + exit 1 + fi +} + +checked_out_sha() { + if [[ -d "$SOURCE_DIR/.git" ]]; then + git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || true + fi +} + +clone_source() { + # Reuse an existing checkout's git object store across rolls: only initialize + # a fresh repo when one isn't already present, then fetch and check out the + # pinned commit. This avoids re-cloning the repo (and re-running npm ci from + # scratch) every time the pin changes. + if [[ ! -d "$SOURCE_DIR/.git" ]]; then + rm -rf "$SOURCE_DIR" + mkdir -p "$SOURCE_DIR" + git -C "$SOURCE_DIR" init -q + git -C "$SOURCE_DIR" remote add origin "$PLAYWRIGHT_REPO" + fi + if [[ "$(checked_out_sha)" != "$EXPECTED_SHA" ]]; then + echo "Fetching $PLAYWRIGHT_REPO at $EXPECTED_SHA" + # Shallow-fetch a single commit. GitHub allows fetching an arbitrary commit + # by SHA, so a full clone is unnecessary. + git -C "$SOURCE_DIR" fetch --depth 1 origin "$EXPECTED_SHA" + git -C "$SOURCE_DIR" checkout -q --detach FETCH_HEAD + fi + # Make sure we landed on exactly the pinned commit. + if [[ "$(checked_out_sha)" != "$EXPECTED_SHA" ]]; then + echo "Checked out commit '$(checked_out_sha)' but '$EXPECTED_SHA' was requested." >&2 + exit 1 + fi +} + +build_source() { + echo "Installing Playwright dependencies (npm ci)" + (cd "$SOURCE_DIR" && npm ci) + # Drop build outputs left over from a previously checked-out commit when the + # checkout is reused across rolls (lib/ dirs are gitignored, so switching + # commits doesn't clear them). + echo "Cleaning previous build outputs (npm run clean)" + (cd "$SOURCE_DIR" && npm run clean) + echo "Building Playwright (npm run build)" + (cd "$SOURCE_DIR" && npm run build) + echo "Building driver bundles" + (cd "$SOURCE_DIR" && bash utils/build/build-playwright-driver.sh) +} + +copy_bundles() { + local output_dir="$SOURCE_DIR/utils/build/output" + # The output dir also holds build intermediates (downloaded Node.js binaries, + # tgz archives, extracted package dirs), so copy only the bundles. Upstream + # names them playwright--.zip; restage each one with the pin + # SHA in the name so the filename doubles as the build cache key. + local suffix matches + for suffix in "${SUFFIXES[@]}"; do + matches=("$output_dir"/playwright-*-"$suffix".zip) + if [[ ! -f "${matches[0]}" ]]; then + echo "Expected driver bundle for '$suffix' was not produced in $output_dir" >&2 + exit 1 + fi + cp "${matches[0]}" "$DRIVER_DIR/playwright-$EXPECTED_SHA-$suffix.zip" + done +} + +# Fast path: the bundles for this exact pin are already staged, so there is +# nothing to (re)build. This keeps repeat invocations cheap and lets consumers +# that only downloaded the prebuilt bundles skip the build entirely (no Node). +if bundles_present; then + echo "Driver bundles for $EXPECTED_SHA already present in $DRIVER_DIR; skipping build." + exit 0 +fi + +require_tools +clone_source +build_source +copy_bundles diff --git a/setup.py b/setup.py index 89e1cda0a..7a355d705 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,11 @@ from pathlib import Path from typing import Dict -driver_version = "1.60.0" +# The driver is built from microsoft/playwright at the commit pinned in the +# DRIVER_SHA file (the single source of truth, also read by scripts/build_driver.sh +# and CI). The SHA is baked into the staged bundle filenames, so it doubles as +# the build cache key: a roll changes DRIVER_SHA, which changes the filenames. +driver_sha = (Path(__file__).parent / "DRIVER_SHA").read_text().strip() base_wheel_bundles = [ { @@ -98,24 +102,18 @@ def extractall(zip: zipfile.ZipFile, path: str) -> None: os.chmod(extracted_path, attr) -def download_driver(zip_name: str) -> None: - zip_file = f"playwright-{driver_version}-{zip_name}.zip" - destination_path = "driver/" + zip_file +def ensure_driver_bundle(zip_name: str) -> None: + destination_path = f"driver/playwright-{driver_sha}-{zip_name}.zip" if os.path.exists(destination_path): return - url = "https://cdn.playwright.dev/builds/driver/" - if ( - "-alpha" in driver_version - or "-beta" in driver_version - or "-next" in driver_version - ): - url = url + "next/" - url = url + zip_file - temp_destination_path = destination_path + ".tmp" - print(f"Fetching {url}") - # Don't replace this with urllib - Python won't have certificates to do SSL on all platforms. - subprocess.check_call(["curl", url, "-o", temp_destination_path]) - os.rename(temp_destination_path, destination_path) + # Build the driver bundles from source (microsoft/playwright @ DRIVER_SHA). + # One invocation produces every platform's bundle, so later calls early-return. + build_script = os.path.join(os.path.dirname(__file__), "scripts", "build_driver.sh") + subprocess.check_call(["bash", build_script]) + if not os.path.exists(destination_path): + raise RuntimeError( + f"Driver bundle {destination_path} was not produced by the source build." + ) class PlaywrightBDistWheelCommand(BDistWheelCommand): @@ -152,10 +150,16 @@ def _build_wheel( assert self.dist_dir base_wheel_location: str = glob.glob(os.path.join(self.dist_dir, "*.whl"))[0] without_platform = base_wheel_location[:-7] - download_driver(wheel_bundle["zip_name"]) - zip_file = f"driver/playwright-{driver_version}-{wheel_bundle['zip_name']}.zip" + ensure_driver_bundle(wheel_bundle["zip_name"]) + # Although the build produces every platform's bundle, only this wheel's + # target platform driver is extracted and packed below, so the wheel + # stays single-platform. + zip_file = f"driver/playwright-{driver_sha}-{wheel_bundle['zip_name']}.zip" + extract_dir = f"driver/{wheel_bundle['zip_name']}" + if os.path.exists(extract_dir): + shutil.rmtree(extract_dir) with zipfile.ZipFile(zip_file, "r") as zip: - extractall(zip, f"driver/{wheel_bundle['zip_name']}") + extractall(zip, extract_dir) wheel_location = without_platform + wheel_bundle["wheel"] shutil.copy(base_wheel_location, wheel_location) with zipfile.ZipFile( @@ -197,8 +201,10 @@ def _download_and_extract_local_driver( ) assert len(zip_names_for_current_system) == 1 zip_name = zip_names_for_current_system.pop() - download_driver(zip_name) - zip_file = f"driver/playwright-{driver_version}-{zip_name}.zip" + ensure_driver_bundle(zip_name) + zip_file = f"driver/playwright-{driver_sha}-{zip_name}.zip" + if os.path.exists("playwright/driver"): + shutil.rmtree("playwright/driver") with zipfile.ZipFile(zip_file, "r") as zip: extractall(zip, "playwright/driver") From 3035233aa61742f9b2ced4af3b28368f78fdd903 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 8 Jun 2026 12:47:16 +0200 Subject: [PATCH 2/2] CI: build driver once on Linux, share bundles as an artifact The per-job source build can't run on Windows (Git Bash lacks zip/unzip and the heavy upstream monorepo build is not exercised there), and rebuilding the fully cross-platform driver in every wheel job is redundant and slow. Add a single 'build-driver' job that builds all six platform bundles on Linux and uploads them as a workflow artifact. Every wheel-building job (Lint, Build, Stable, Conda) now downloads that artifact into driver/ so setup.py's ensure_driver_bundle early-returns and embeds the prebuilt zip -- no Node, bash or zip/unzip needed on Windows/macOS. A verify step asserts all six bundles are present before building. Docker and Azure publish are unchanged: they run on Linux and build the driver once on their own host, which already works. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2742caec3..e486f6983 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,31 @@ concurrency: cancel-in-progress: true jobs: + build-driver: + name: Build driver + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + - name: Build driver bundles from source + run: bash scripts/build_driver.sh + - name: Upload driver bundles + uses: actions/upload-artifact@v4 + with: + name: driver-bundles + path: driver/playwright-*.zip + if-no-files-found: error + # The bundles are already-compressed zips; skip re-compression. + compression-level: 0 + retention-days: 1 + infra: name: Lint + needs: build-driver runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -26,10 +49,11 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Download driver bundles + uses: actions/download-artifact@v4 with: - node-version: '24' + name: driver-bundles + path: driver/ - name: Install dependencies & browsers run: | python -m pip install --upgrade pip @@ -47,6 +71,7 @@ jobs: build: name: Build + needs: build-driver timeout-minutes: 45 strategy: fail-fast: false @@ -100,10 +125,11 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Download driver bundles + uses: actions/download-artifact@v4 with: - node-version: '24' + name: driver-bundles + path: driver/ - name: Install dependencies & browsers run: | python -m pip install --upgrade pip @@ -133,6 +159,7 @@ jobs: test-stable: name: Stable + needs: build-driver timeout-minutes: 45 strategy: fail-fast: false @@ -151,10 +178,11 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.10" - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Download driver bundles + uses: actions/download-artifact@v4 with: - node-version: '24' + name: driver-bundles + path: driver/ - name: Install dependencies & browsers run: | python -m pip install --upgrade pip