Skip to content

Commit f571b93

Browse files
authored
fix(install): invoke playwright via python -m for uv tool installs (#525)
1 parent 744c3d5 commit f571b93

3 files changed

Lines changed: 134 additions & 2 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: Install Flow Smoke
2+
3+
# Reproduces the documented default install flow from README.md:
4+
#
5+
# uv tool install posit-vip
6+
# vip install
7+
#
8+
# `uv tool install` puts vip in an isolated venv and only exposes vip's own
9+
# entry point on PATH -- playwright's console script is NOT on PATH. Every other
10+
# install test runs `uv run vip install` from inside the project venv, where the
11+
# whole venv bin/ is on PATH, so this topology went untested and a bare
12+
# `playwright` lookup in `vip install` broke real users. This smoke installs vip
13+
# the way users do (from the local checkout, so it tests this branch) and runs
14+
# the bare `vip install`.
15+
16+
on:
17+
push:
18+
branches: [main]
19+
paths:
20+
- 'src/vip/install/**'
21+
- 'src/vip/cli.py'
22+
- 'pyproject.toml'
23+
- 'uv.lock'
24+
- '.github/workflows/install-flow-smoke.yml'
25+
pull_request:
26+
paths:
27+
- 'src/vip/install/**'
28+
- 'src/vip/cli.py'
29+
- 'pyproject.toml'
30+
- 'uv.lock'
31+
- '.github/workflows/install-flow-smoke.yml'
32+
workflow_dispatch:
33+
34+
permissions:
35+
contents: read
36+
37+
# In a container GitHub defaults `run:` steps to sh (dash), not bash, so any
38+
# bashism silently changes behavior (and `set -o pipefail` errors outright).
39+
# Force bash everywhere for consistent behavior across both jobs; GitHub's bash
40+
# default also runs with `-eo pipefail`.
41+
defaults:
42+
run:
43+
shell: bash
44+
45+
jobs:
46+
ubuntu:
47+
name: ubuntu-24.04 (uv tool, root)
48+
runs-on: ubuntu-latest
49+
# Bare ubuntu:24.04 as root faithfully replays the reported scenario: a full
50+
# `vip install` that apt-installs the Chromium system libs itself and then
51+
# runs the Playwright step. It is also the only CI job that exercises vip's
52+
# own `apt install` path (src/vip/install/runner.py) as root.
53+
container:
54+
image: ubuntu:24.04
55+
steps:
56+
# git for actions/checkout; ca-certificates for setup-uv's HTTPS download.
57+
- name: Install base tools
58+
run: |
59+
apt-get update
60+
apt-get install -y --no-install-recommends git ca-certificates
61+
62+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
63+
with:
64+
persist-credentials: false
65+
66+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
67+
with:
68+
version: "0.11.28"
69+
enable-cache: true
70+
71+
- name: Install vip as a uv tool (isolated venv, only vip on PATH)
72+
run: |
73+
uv tool install .
74+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
75+
76+
- name: vip install (default flow, as root)
77+
run: vip install
78+
79+
- name: Assert Chromium was cached
80+
run: |
81+
ls -d "$HOME/.cache/ms-playwright/chromium-"*
82+
echo "Chromium cache present."
83+
84+
macos:
85+
name: macos-latest (uv tool)
86+
runs-on: macos-latest
87+
steps:
88+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
89+
with:
90+
persist-credentials: false
91+
92+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
93+
with:
94+
version: "0.11.28"
95+
enable-cache: true
96+
97+
- name: Install vip as a uv tool (isolated venv, only vip on PATH)
98+
run: |
99+
uv tool install .
100+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
101+
102+
- name: vip install (default flow)
103+
run: vip install
104+
105+
- name: Assert Chromium was cached
106+
run: |
107+
ls -d "$HOME/Library/Caches/ms-playwright/chromium-"*
108+
echo "Chromium cache present."

selftests/install/test_playwright.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ def factory(args, **_):
125125

126126

127127
def test_install_chromium_invokes_subprocess(monkeypatch):
128+
"""Playwright must be invoked via ``python -m playwright`` using the current
129+
interpreter, not a bare ``playwright`` executable on PATH.
130+
131+
When vip is installed with ``uv tool install posit-vip`` it lives in an
132+
isolated venv and only vip's own entry point (``vip``) is exposed on PATH;
133+
playwright's console script never lands in ``~/.local/bin``. A bare
134+
``playwright`` lookup then fails with ``[Errno 2] No such file or
135+
directory``. Going through ``sys.executable -m playwright`` uses the same
136+
interpreter vip is running under, where playwright is importable.
137+
"""
128138
calls = []
129139

130140
def fake_popen(args, **kwargs):
@@ -133,7 +143,15 @@ def fake_popen(args, **kwargs):
133143

134144
monkeypatch.setattr(pw.subprocess, "Popen", fake_popen)
135145
pw.install_chromium()
136-
assert calls == [("playwright", "install", "chromium")]
146+
assert len(calls) == 1
147+
command = calls[0]
148+
# Shape guard: the install path must never shell out to a bare tool name on
149+
# PATH (which breaks under `uv tool install`); it must invoke the module with
150+
# the current interpreter. Asserted as a shape, not just the exact tuple, so
151+
# a newly-added bare-executable subprocess call is also caught.
152+
assert command[0] == pw.sys.executable
153+
assert command[1] == "-m"
154+
assert command == (pw.sys.executable, "-m", "playwright", "install", "chromium")
137155

138156

139157
def test_install_chromium_raises_on_failure(monkeypatch):

src/vip/install/playwright.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,14 @@ def install_chromium() -> None:
113113
vip-attributed summary line.
114114
"""
115115
try:
116+
# Invoke via `python -m playwright` using the interpreter vip is running
117+
# under, not a bare `playwright` executable on PATH. When vip is installed
118+
# with `uv tool install posit-vip` it lives in an isolated venv and only
119+
# vip's own entry point is exposed on PATH -- playwright's console script
120+
# is not -- so a bare `playwright` lookup fails with ENOENT. Playwright is
121+
# a dependency in that same venv, so `sys.executable -m playwright` finds it.
116122
proc = subprocess.Popen(
117-
["playwright", "install", "chromium"],
123+
[sys.executable, "-m", "playwright", "install", "chromium"],
118124
stdout=subprocess.PIPE,
119125
stderr=subprocess.PIPE,
120126
text=True,

0 commit comments

Comments
 (0)