fix(deps): update all major dependencies (major) #3563
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
| # .github/workflows/ci.yml | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| # Every pull request, not only those targeting main. A stacked pull request | |
| # is based on the branch below it, so filtering by base branch skipped CI | |
| # entirely for the stacks this repository's workflow produces. | |
| pull_request: | |
| jobs: | |
| lint-and-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| enable-cache: true | |
| - name: Install dependencies | |
| run: | | |
| uv sync | |
| uv sync --group dev | |
| - name: Run pre-commit hooks | |
| uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 | |
| - name: Optimize uv cache for CI | |
| run: uv cache prune --ci | |
| dependency-review: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Review dependency changes | |
| uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 | |
| with: | |
| fail-on-severity: high | |
| fail-on-scopes: runtime, development, unknown | |
| retry-on-snapshot-warnings: true | |
| retry-on-snapshot-warnings-timeout: 120 | |
| # The action reports snapshot warnings after its retry window but does not | |
| # fail on them. That can make a missing head snapshot look like an empty | |
| # dependency diff, so query the same comparison and reject any warning. | |
| - name: Require complete dependency snapshots | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| set -euo pipefail | |
| response="$RUNNER_TEMP/dependency-comparison" | |
| gh api --include \ | |
| "repos/${GITHUB_REPOSITORY}/dependency-graph/compare/${BASE_SHA}...${HEAD_SHA}?per_page=100" \ | |
| > "$response" | |
| python3 - "$response" <<'PY' | |
| import sys | |
| from pathlib import Path | |
| header = "x-github-dependency-graph-snapshot-warnings:" | |
| for line in Path(sys.argv[1]).read_text(encoding="utf-8").splitlines(): | |
| if not line: | |
| break | |
| if line.lower().startswith(header) and line[len(header) :].strip(): | |
| raise SystemExit("Dependency comparison returned snapshot warnings") | |
| PY | |
| windows-daemon: | |
| name: Windows daemon (Python ${{ matrix.python-version }}) | |
| runs-on: windows-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 3.12.4 exactly, because it is the declared floor and the one version | |
| # where private state creation on Windows became possible at all. The | |
| # others follow their newest patch, so nothing else here would notice a | |
| # daemon path reaching for something added after it. | |
| python-version: ["3.12.4", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| enable-cache: true | |
| python-version: ${{ matrix.python-version }} | |
| - run: uv sync --group dev | |
| - name: Run daemon protocol and containment tests | |
| run: >- | |
| uv run pytest | |
| tests/test_daemon_election.py::TestAtomicStartupCommit | |
| tests/test_daemon_election.py::TestPublishingLast | |
| tests/test_daemon_election.py::TestRealOwner::test_an_owner_is_started_and_answers | |
| tests/test_daemon_election.py::TestRealOwner::test_a_crashed_owner_is_replaced | |
| tests/test_daemon_election.py::TestRealOwner::test_many_clients_starting_at_once_elect_exactly_one_owner | |
| tests/test_daemon_election.py::TestRealOwner::test_launch_barrier_timeout_exits_without_electing | |
| tests/test_daemon_election.py::TestRealOwner::test_launch_barrier_failure_collects_started_frontends | |
| tests/test_bootstrap.py::TestInstallerSupervisorLaunch::test_real_cpython_wait_unregister_contract | |
| tests/test_daemon_descriptor.py::TestPreparedCommit | |
| tests/test_daemon_descriptor.py::TestStateLocation | |
| tests/test_daemon_lock.py | |
| tests/test_installer_supervisor.py | |
| tests/test_installer_worker.py | |
| tests/test_process_tree.py | |
| tests/test_private_state.py | |
| - run: uv cache prune --ci | |
| # Branch protection on `main` requires `lint-and-check` and `test` only, and | |
| # that list is a repository setting no workflow file can edit. So this job is | |
| # where a contract that has to block a merge gets attached: it fails unless | |
| # every job it names succeeded, which makes those jobs required without | |
| # renaming anything. `if: always()` so a failure upstream still reaches the | |
| # check below rather than skipping it, because a skipped required check is a | |
| # green one. | |
| test: | |
| needs: [dependency-review, windows-daemon, platform-behaviour] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| # The review job is intentionally skipped for pushes to main. On pull | |
| # requests it joins this existing required check instead of adding a new | |
| # branch-protection context. | |
| - name: Require dependency review on pull requests | |
| if: github.event_name == 'pull_request' | |
| run: test '${{ needs.dependency-review.result }}' = success | |
| - name: Require the Windows daemon tests | |
| run: test '${{ needs.windows-daemon.result }}' = success | |
| # Real browser containment on Windows and macOS runs nowhere else: the | |
| # POSIX environment marker and the Windows Job a Chromium joins are | |
| # facts only those platforms can confirm, and the lease and owner-only | |
| # storage are a different implementation on each. Reading the result of | |
| # the whole matrix, so one red leg fails this. | |
| - name: Require the platform behaviour tests | |
| run: test '${{ needs.platform-behaviour.result }}' = success | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| enable-cache: true | |
| - run: uv sync --group dev | |
| # The browser is keyed on the resolved patchright version, because that | |
| # is what decides which Chromium revision `install` fetches. Read from | |
| # the synced environment rather than from `pyproject.toml`, which names | |
| # a floor and not the version in use. | |
| - name: Resolve the patchright version | |
| id: patchright | |
| run: | | |
| version=$(uv run python -c 'from importlib.metadata import version; print(version("patchright"))') | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| - name: Restore the browser | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: ms-playwright-${{ runner.os }}-${{ steps.patchright.outputs.version }} | |
| # `--no-shell`, the same install the server performs: the headless shell | |
| # is a different product with no plugins and no `window.chrome`, and | |
| # nothing here launches it. `--with-deps` runs on a cache hit too, since | |
| # the cache holds the browser and not the apt packages it links against; | |
| # it also brings Xvfb, which is in playwright's `tools` dependency group. | |
| - name: Install the browser | |
| run: uv run patchright install --with-deps chromium --no-shell | |
| # A headed browser needs a display. The default mode does not on Linux, | |
| # where the hidden target is unsupported and Chromium's own headless mode | |
| # runs instead, but the identity gate launches headed as well and that is | |
| # the mode where the headless token has to be absent. | |
| - name: Start a display | |
| run: | | |
| Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/tmp/xvfb.log 2>&1 & | |
| for _ in $(seq 1 100); do | |
| if [ -S /tmp/.X11-unix/X99 ]; then exit 0; fi | |
| sleep 0.1 | |
| done | |
| echo "Xvfb never created /tmp/.X11-unix/X99:" >&2 | |
| cat /tmp/xvfb.log >&2 | |
| exit 1 | |
| # `--dist loadgroup` keeps every test that launches Chromium on one | |
| # worker. The identity cases share two cached launches; the DOM and auth | |
| # contracts launch per case, and the DOM timers use wall-clock deadlines | |
| # that concurrent browser startups made flaky. Tests without the group | |
| # mark are distributed exactly as before. | |
| - name: Run tests | |
| env: | |
| DISPLAY: ":99" | |
| run: uv run pytest --cov --cov-report=term-missing -n auto --dist loadgroup -v -s | |
| - run: uv cache prune --ci | |
| # Everything here coordinates or protects state through the operating system | |
| # rather than through Python, so each platform runs a different | |
| # implementation: the lease locks with flock or with LockFileEx, and | |
| # owner-only storage is a permission bit or an access control list. The | |
| # Ubuntu job above executes neither Windows path. Serial by choice: the lease | |
| # tests spawn their own competing processes, and xdist would only add | |
| # scheduling noise on top of that. | |
| platform-behaviour: | |
| name: Platform behaviour (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| # Long enough for a cold browser download and far short of the six hours | |
| # a job without one is allowed to sit. These tests spawn real processes | |
| # and wait on them, so a contract that stops holding turns into a wedged | |
| # job rather than a red one, and a wedged job reads as work in progress | |
| # for as long as anyone is willing to wait. | |
| timeout-minutes: 25 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-latest | |
| browsers: ~/Library/Caches/ms-playwright | |
| - os: windows-latest | |
| browsers: ~\AppData\Local\ms-playwright | |
| # Here for the browser rather than for the operating system. | |
| # Patchright ships Chrome for Testing everywhere except Linux arm64, | |
| # which gets Playwright's own Chromium build, and the release | |
| # publishes an arm64 image. Without this leg an identity regression | |
| # in that build reaches the container with both other jobs green. | |
| - os: ubuntu-24.04-arm | |
| browsers: ~/.cache/ms-playwright | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| enable-cache: true | |
| - run: uv sync --group dev | |
| # ``faulthandler_timeout`` rather than a plugin: it is pytest's own, and | |
| # what it buys is the stack of every thread at the moment a test stops | |
| # making progress. These tests wait on processes they started, so the | |
| # difference between a slow one and a stuck one is not visible from the | |
| # outside and the report is the only way to tell them apart. | |
| - name: Run platform behaviour tests | |
| run: >- | |
| uv run pytest -v -o faulthandler_timeout=120 | |
| tests/test_profile_lease.py | |
| tests/test_profile_lease_integration.py | |
| tests/test_private_state.py | |
| tests/test_daemon_lock.py | |
| tests/test_process_tree.py | |
| tests/test_greenlet_runtime.py | |
| # The identity gate runs on every leg, because every leg's browser is a | |
| # different one from the Ubuntu job's. macOS is the only platform with a | |
| # hidden target, which is the whole reason the default mode can run | |
| # without announcing itself as headless. Linux arm64 is the only platform | |
| # whose bundle is Playwright's own Chromium rather than Chrome for | |
| # Testing. Windows has its own binary, its own window manager and its own | |
| # DPI handling, and the gate measures a headed window against the screen | |
| # it stands on -- which is exactly where those differ. An earlier version | |
| # of this file left Windows out on the grounds that it falls back to | |
| # headless mode like Linux does; that is true of one case in that file | |
| # and says nothing about the rest of them. | |
| # | |
| # Every leg blocks a merge, through `test` rather than by name. Branch | |
| # protection requires `lint-and-check` and `test`, and `test` fails | |
| # unless this whole matrix succeeded, so the required list stays as it is | |
| # while a red leg here stops the merge. Before that it did not: this job | |
| # held the only real browser containment run on Windows and macOS and | |
| # nothing depended on it, so those contracts were advisory. | |
| # | |
| # `shell: bash` because one of the legs is Windows, where the default is | |
| # PowerShell and none of the syntax below means anything. | |
| - name: Resolve the patchright version | |
| id: patchright | |
| shell: bash | |
| run: | | |
| version=$(uv run python -c 'from importlib.metadata import version; print(version("patchright"))') | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| - name: Restore the browser | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ matrix.browsers }} | |
| key: ms-playwright-${{ matrix.os }}-${{ steps.patchright.outputs.version }} | |
| # `--with-deps` on Linux only. It is the platform where a missing | |
| # system library stops Chromium starting at all, and the runner image | |
| # makes no promise about them. macOS needs none, and on Windows the | |
| # switch installs a Media Pack through a script that wants privileges | |
| # this job has no reason to take. | |
| - name: Install the browser | |
| run: uv run patchright install ${{ runner.os == 'Linux' && '--with-deps' || '' }} chromium --no-shell | |
| # Same reason as the Ubuntu job: the headed half of the gate needs | |
| # somewhere to put a window, and the default half does not. | |
| - name: Start a display | |
| if: runner.os == 'Linux' | |
| run: | | |
| Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/tmp/xvfb.log 2>&1 & | |
| for _ in $(seq 1 100); do | |
| if [ -S /tmp/.X11-unix/X99 ]; then exit 0; fi | |
| sleep 0.1 | |
| done | |
| echo "Xvfb never created /tmp/.X11-unix/X99:" >&2 | |
| cat /tmp/xvfb.log >&2 | |
| exit 1 | |
| # Separate from the process tests above, which run before a browser | |
| # exists and would skip this. It belongs on every leg because the | |
| # attribution differs per platform: POSIX scans for an environment | |
| # marker the browser carries, while Windows has no such marker and | |
| # depends on Chromium joining the Job its Node driver was assigned to | |
| # -- a fact about Windows that only Windows can confirm. | |
| - name: Run the browser containment test | |
| run: uv run pytest tests/test_browser_containment.py | |
| - name: Run the identity gate | |
| env: | |
| # Only where one was started. macOS ignores it, but naming a display | |
| # that does not exist is the kind of thing someone later debugs. | |
| DISPLAY: ${{ runner.os == 'Linux' && ':99' || '' }} | |
| run: uv run pytest tests/test_browser_identity.py | |
| - run: uv cache prune --ci |