Skip to content

Commit 8a08eb8

Browse files
authored
ci: pin Chromium to an exact Chrome for Testing build (#217)
Skyvern-AI/rustwright-cloud#196 --------- Co-authored-by: suchintan <3853670+suchintan@users.noreply.github.com>
1 parent ba4bc96 commit 8a08eb8

5 files changed

Lines changed: 256 additions & 14 deletions

File tree

Dockerfile

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@ RUN --mount=type=cache,target=/root/.cache/pip \
6868
--mount=type=cache,target=/workspace/target \
6969
python -m pip install --no-build-isolation -e ".[dev]"
7070

71+
COPY .github/chromium-version ./.github/chromium-version
72+
7173
RUN --mount=type=cache,target=/var/cache/rustwright-browsers \
7274
python -m rustwright.cli install-deps chromium \
7375
&& mkdir -p "$RUSTWRIGHT_BROWSERS_PATH" \
7476
&& case "$(uname -m)" in \
7577
aarch64|arm64) echo "Using Playwright's Linux arm64 Chromium for Rustwright runtime in this image." ;; \
76-
*) RUSTWRIGHT_BROWSERS_PATH=/var/cache/rustwright-browsers python -m rustwright.cli install chromium \
78+
*) chromium_version="$(tr -d '[:space:]' < .github/chromium-version)" \
79+
&& RUSTWRIGHT_BROWSERS_PATH=/var/cache/rustwright-browsers \
80+
python -m rustwright.cli install chromium --version "$chromium_version" \
7781
&& cp -a /var/cache/rustwright-browsers/. "$RUSTWRIGHT_BROWSERS_PATH"/ ;; \
7882
esac
7983

@@ -102,10 +106,22 @@ RUN for root in "$RUSTWRIGHT_BROWSERS_PATH" "$PLAYWRIGHT_BROWSERS_PATH"; do \
102106

103107
RUN case "$(uname -m)" in \
104108
aarch64|arm64) browser="$(find "$PLAYWRIGHT_BROWSERS_PATH" -path '*/chrome-linux/chrome' -type f | head -n 1)" ;; \
105-
*) browser="$(find "$RUSTWRIGHT_BROWSERS_PATH" -path '*/chrome-linux64/chrome' -type f | head -n 1)" ;; \
109+
*) chromium_version="$(tr -d '[:space:]' < .github/chromium-version)" \
110+
&& browser="$RUSTWRIGHT_BROWSERS_PATH/chromium-$chromium_version/chrome-linux64/chrome" ;; \
106111
esac \
107112
&& test -n "$browser" \
108-
&& ln -sf "$browser" "$RUSTWRIGHT_CHROMIUM"
113+
&& test -x "$browser" \
114+
&& ln -sf "$browser" "$RUSTWRIGHT_CHROMIUM" \
115+
&& echo "Chromium executable: $RUSTWRIGHT_CHROMIUM" \
116+
&& resolved_version="$("$RUSTWRIGHT_CHROMIUM" --version)" \
117+
&& echo "Chromium version: $resolved_version" \
118+
&& case "$(uname -m)" in \
119+
aarch64|arm64) ;; \
120+
*) case "$resolved_version" in \
121+
*"$chromium_version"*) ;; \
122+
*) echo "Pinned Chromium mismatch: expected $chromium_version, got $resolved_version" >&2; exit 1 ;; \
123+
esac ;; \
124+
esac
109125

110126
RUN --mount=type=cache,target=/root/.npm \
111127
if [ "$INSTALL_PUPPETEER" = "1" ]; then \

benchmarks/automation_cases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5133,7 +5133,7 @@ def mouse_wheel_dispatches_single_trusted_event_like_playwright(page):
51335133

51345134
page.mouse.move(50, 50)
51355135
page.mouse.wheel(0, 40)
5136-
page.wait_for_timeout(100)
5136+
page.wait_for_function("() => window.events.length >= 2", timeout=5_000)
51375137

51385138
assert page.evaluate("window.events") == [
51395139
{

mcp/src/actor.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6707,9 +6707,14 @@ mod tests {
67076707
});
67086708
}
67096709
const detached = document.querySelector('#detach');
6710-
new IntersectionObserver(entries => {
6711-
if (entries.some(entry => entry.isIntersecting)) detached.remove();
6712-
}).observe(detached);
6710+
// Deliberately rely on the engine calling this property: synchronous removal makes
6711+
// its next snapshot see isConnected === false; async observer delivery races dispatch
6712+
// and the winner has changed between Chromium builds.
6713+
const nativeScrollIntoView = detached.scrollIntoView;
6714+
detached.scrollIntoView = function(options) {
6715+
nativeScrollIntoView.call(this, options);
6716+
this.remove();
6717+
};
67136718
fetch('/capture?events=actionability-ready');
67146719
</script>"#
67156720
.to_owned()
@@ -7578,7 +7583,7 @@ mod tests {
75787583
&& event["hit"] == Value::Bool(true))
75797584
);
75807585

7581-
page.click("#partially-offscreen", ActionOptions::timeout(1_000.0))
7586+
page.click("#partially-offscreen", ActionOptions::timeout(3_000.0))
75827587
.expect("click partially-offscreen target at its hit-tested viewport point");
75837588
let partially_offscreen = page
75847589
.evaluate(
@@ -7605,7 +7610,7 @@ mod tests {
76057610
);
76067611

76077612
assert_actionability(
7608-
page.click("#detach", ActionOptions::timeout(500.0))
7613+
page.click("#detach", ActionOptions::timeout(3_000.0))
76097614
.expect_err("detached target must not click"),
76107615
ActionabilityError::Detached,
76117616
);
@@ -7702,7 +7707,7 @@ mod tests {
77027707
);
77037708
assert_eq!(evidence["buttonDown"], Value::Bool(false));
77047709

7705-
page.click("#following", ActionOptions::timeout(1_000.0))
7710+
page.click("#following", ActionOptions::timeout(3_000.0))
77067711
.expect("following click must work after late cancellation");
77077712
assert_eq!(
77087713
page.evaluate(

python/rustwright/cli.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
from pathlib import Path
1111
import platform
12+
import re
1213
import shutil
1314
import stat
1415
import subprocess
@@ -78,6 +79,9 @@
7879
"--quality",
7980
}
8081
CHROME_FOR_TESTING_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json"
82+
CHROME_FOR_TESTING_DOWNLOAD_BASE_URL = "https://storage.googleapis.com/chrome-for-testing-public"
83+
CHROMIUM_VERSION_ENV = "RUSTWRIGHT_CHROMIUM_VERSION"
84+
CHROMIUM_VERSION_PATTERN = re.compile(r"\d+\.\d+\.\d+\.\d+")
8185
LINUX_TOOLS_DEPS = [
8286
"xvfb",
8387
"fonts-noto-color-emoji",
@@ -149,6 +153,10 @@ def _install_parser(program: str = "playwright") -> argparse.ArgumentParser:
149153
parser.add_argument("--with-deps", action="store_true", help="Accepted for Playwright CLI compatibility.")
150154
parser.add_argument("--dry-run", action="store_true", help="Print the resolved browser path without launching a download.")
151155
parser.add_argument("--force", action="store_true", help="Accepted for Playwright CLI compatibility.")
156+
parser.add_argument(
157+
"--version",
158+
help=f"Install an exact Chrome for Testing version (or set {CHROMIUM_VERSION_ENV}).",
159+
)
152160
parser.add_argument("--only-shell", action="store_true", help="Accepted for Playwright CLI compatibility.")
153161
parser.add_argument("--no-shell", action="store_true", help="Accepted for Playwright CLI compatibility.")
154162
return parser
@@ -392,8 +400,37 @@ def _ensure_browser_executable(executable: Path) -> None:
392400
raise RuntimeError(f"Could not mark Chromium executable at {executable} as executable: {exc}") from exc
393401

394402

395-
def _chrome_for_testing_download(platform_name: str | None = None) -> dict[str, str]:
403+
def _validate_chromium_version(version: str) -> str:
404+
normalized = version.strip()
405+
if not CHROMIUM_VERSION_PATTERN.fullmatch(normalized):
406+
raise ValueError(
407+
f"Invalid Chromium version {version!r}; expected a full Chrome for Testing version like 123.0.6312.86"
408+
)
409+
return normalized
410+
411+
412+
def _requested_chromium_version(cli_version: str | None) -> str | None:
413+
if cli_version is not None:
414+
return _validate_chromium_version(cli_version)
415+
requested = os.environ.get(CHROMIUM_VERSION_ENV)
416+
if requested is None or not requested.strip():
417+
return None
418+
return _validate_chromium_version(requested)
419+
420+
421+
def _chrome_for_testing_download(
422+
platform_name: str | None = None,
423+
*,
424+
version: str | None = None,
425+
) -> dict[str, str]:
396426
platform_name = platform_name or _chrome_for_testing_platform()
427+
if version is not None:
428+
version = _validate_chromium_version(version)
429+
return {
430+
"version": version,
431+
"url": f"{CHROME_FOR_TESTING_DOWNLOAD_BASE_URL}/{version}/{platform_name}/chrome-{platform_name}.zip",
432+
"platform": platform_name,
433+
}
397434
with url_request.urlopen(CHROME_FOR_TESTING_URL, timeout=30) as response:
398435
payload = json.loads(response.read().decode("utf-8"))
399436
stable = payload.get("channels", {}).get("Stable", {})
@@ -405,8 +442,17 @@ def _chrome_for_testing_download(platform_name: str | None = None) -> dict[str,
405442
raise RuntimeError(f"Could not find a Chrome for Testing download for {platform_name}")
406443

407444

408-
def _download_chromium(*, force: bool = False, dry_run: bool = False) -> dict[str, object]:
409-
download = _chrome_for_testing_download()
445+
def _download_chromium(
446+
*,
447+
force: bool = False,
448+
dry_run: bool = False,
449+
version: str | None = None,
450+
) -> dict[str, object]:
451+
download = (
452+
_chrome_for_testing_download(version=version)
453+
if version is not None
454+
else _chrome_for_testing_download()
455+
)
410456
platform_name = download["platform"]
411457
install_dir = _browser_cache_dir() / f"chromium-{download['version']}"
412458
executable = install_dir / _chrome_for_testing_executable(platform_name)
@@ -866,6 +912,40 @@ def install(argv: Sequence[str], *, program: str = "playwright") -> int:
866912
if not ok:
867913
print(message, file=sys.stderr)
868914
return 1
915+
try:
916+
pinned_version = _requested_chromium_version(args.version)
917+
except ValueError as exc:
918+
print(exc, file=sys.stderr)
919+
return 1
920+
if pinned_version is not None:
921+
if any(browser != "chromium" for browser in browsers):
922+
print(
923+
"An exact Chromium version can only be used with the chromium install target.",
924+
file=sys.stderr,
925+
)
926+
return 1
927+
try:
928+
result = _download_chromium(
929+
force=bool(args.force),
930+
dry_run=bool(args.dry_run),
931+
version=pinned_version,
932+
)
933+
except Exception as exc:
934+
print(
935+
f"Could not install pinned Chromium {pinned_version}. Rustwright will not fall back to a system "
936+
f"browser or a different Chrome for Testing build. Details: {exc}",
937+
file=sys.stderr,
938+
)
939+
return 1
940+
if args.with_deps:
941+
print("Rustwright does not install OS packages; ensure Chromium runtime dependencies are present.")
942+
if args.dry_run:
943+
print(f"Rustwright would download pinned Chromium {pinned_version} from: {result['url']}")
944+
print(result["executable"])
945+
else:
946+
verb = "installed" if result["downloaded"] else "found"
947+
print(f"Rustwright {verb} pinned Chromium {pinned_version} executable: {result['executable']}")
948+
return 0
869949
if any(browser in BRANDED_INSTALL_BROWSERS for browser in browsers):
870950
status = 0
871951
for browser in browsers:

tests/test_rustwright_sync_api.py

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9969,7 +9969,7 @@ def test_locator_click_dispatches_trusted_mouse_sequence_by_default(page, monkey
99699969
"""
99709970
)
99719971

9972-
page.locator("#go").click(timeout=1_000)
9972+
page.locator("#go").click(timeout=3_000)
99739973

99749974
events = page.evaluate("window.events")
99759975
assert [event["type"] for event in events] == [
@@ -25309,6 +25309,147 @@ def read(self):
2530925309
assert os.access(executable, os.X_OK)
2531025310

2531125311

25312+
def test_cli_exact_chromium_version_uses_direct_download_url():
25313+
from rustwright import cli
25314+
25315+
download = cli._chrome_for_testing_download("linux64", version="123.0.6312.86")
25316+
25317+
assert download == {
25318+
"version": "123.0.6312.86",
25319+
"url": (
25320+
"https://storage.googleapis.com/chrome-for-testing-public/"
25321+
"123.0.6312.86/linux64/chrome-linux64.zip"
25322+
),
25323+
"platform": "linux64",
25324+
}
25325+
25326+
25327+
def test_cli_install_pinned_chromium_bypasses_system_browser(monkeypatch, capsys):
25328+
from rustwright import cli
25329+
25330+
calls = []
25331+
monkeypatch.setenv("RUSTWRIGHT_CHROMIUM_VERSION", "124.0.6367.91")
25332+
monkeypatch.setattr(
25333+
cli,
25334+
"_chromium_executable_path",
25335+
lambda: pytest.fail("pinned install must not resolve a system browser"),
25336+
)
25337+
monkeypatch.setattr(
25338+
cli,
25339+
"_download_chromium",
25340+
lambda **kwargs: calls.append(kwargs)
25341+
or {
25342+
"executable": "/cache/chromium-124.0.6367.91/chrome-linux64/chrome",
25343+
"downloaded": True,
25344+
"url": "https://example.test/124.0.6367.91/chrome-linux64.zip",
25345+
},
25346+
)
25347+
25348+
assert cli.install(["chromium"]) == 0
25349+
25350+
assert calls == [{"force": False, "dry_run": False, "version": "124.0.6367.91"}]
25351+
output = capsys.readouterr().out
25352+
assert "installed pinned Chromium 124.0.6367.91 executable" in output
25353+
25354+
25355+
def test_cli_install_version_flag_overrides_environment(monkeypatch, capsys):
25356+
from rustwright import cli
25357+
25358+
calls = []
25359+
monkeypatch.setenv("RUSTWRIGHT_CHROMIUM_VERSION", "124.0.6367.91")
25360+
monkeypatch.setattr(
25361+
cli,
25362+
"_chromium_executable_path",
25363+
lambda: pytest.fail("pinned install must not resolve a system browser"),
25364+
)
25365+
monkeypatch.setattr(
25366+
cli,
25367+
"_download_chromium",
25368+
lambda **kwargs: calls.append(kwargs)
25369+
or {
25370+
"executable": "/cache/chromium-125.0.6422.60/chrome-linux64/chrome",
25371+
"downloaded": False,
25372+
"url": "https://example.test/125.0.6422.60/chrome-linux64.zip",
25373+
},
25374+
)
25375+
25376+
assert cli.install(["chromium", "--version", "125.0.6422.60", "--dry-run"]) == 0
25377+
25378+
assert calls == [{"force": False, "dry_run": True, "version": "125.0.6422.60"}]
25379+
output = capsys.readouterr().out
25380+
assert "would download pinned Chromium 125.0.6422.60" in output
25381+
assert output.rstrip().endswith("/cache/chromium-125.0.6422.60/chrome-linux64/chrome")
25382+
25383+
25384+
def test_cli_install_pinned_chromium_fails_without_fallback(monkeypatch, capsys):
25385+
from rustwright import cli
25386+
25387+
monkeypatch.setenv("RUSTWRIGHT_CHROMIUM_VERSION", "126.0.6478.55")
25388+
monkeypatch.setattr(
25389+
cli,
25390+
"_chromium_executable_path",
25391+
lambda: pytest.fail("pinned install must not resolve a system browser"),
25392+
)
25393+
monkeypatch.setattr(
25394+
cli,
25395+
"_download_chromium",
25396+
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("HTTP 404")),
25397+
)
25398+
25399+
assert cli.install(["chromium"]) == 1
25400+
25401+
error = capsys.readouterr().err
25402+
assert "Could not install pinned Chromium 126.0.6478.55" in error
25403+
assert "will not fall back to a system browser or a different Chrome for Testing build" in error
25404+
assert "HTTP 404" in error
25405+
25406+
25407+
def test_cli_install_rejects_invalid_pinned_chromium_version(monkeypatch, capsys):
25408+
from rustwright import cli
25409+
25410+
monkeypatch.setenv("RUSTWRIGHT_CHROMIUM_VERSION", "stable")
25411+
monkeypatch.setattr(
25412+
cli,
25413+
"_download_chromium",
25414+
lambda **kwargs: pytest.fail("invalid pinned version must fail before download resolution"),
25415+
)
25416+
25417+
assert cli.install(["chromium"]) == 1
25418+
25419+
assert "expected a full Chrome for Testing version" in capsys.readouterr().err
25420+
25421+
25422+
def test_cli_install_rejects_explicit_empty_chromium_version(monkeypatch, capsys):
25423+
from rustwright import cli
25424+
25425+
monkeypatch.delenv("RUSTWRIGHT_CHROMIUM_VERSION", raising=False)
25426+
monkeypatch.setattr(
25427+
cli,
25428+
"_download_chromium",
25429+
lambda **kwargs: pytest.fail("invalid pinned version must fail before download resolution"),
25430+
)
25431+
25432+
assert cli.install(["chromium", "--version", ""]) == 1
25433+
25434+
assert "expected a full Chrome for Testing version" in capsys.readouterr().err
25435+
25436+
25437+
def test_cli_install_empty_version_environment_keeps_unpinned_behavior(monkeypatch, capsys):
25438+
from rustwright import cli
25439+
25440+
monkeypatch.setenv("RUSTWRIGHT_CHROMIUM_VERSION", "")
25441+
monkeypatch.setattr(cli, "_chromium_executable_path", lambda: "/usr/bin/chromium")
25442+
monkeypatch.setattr(
25443+
cli,
25444+
"_download_chromium",
25445+
lambda **kwargs: pytest.fail("empty pin must preserve system-browser resolution"),
25446+
)
25447+
25448+
assert cli.install(["chromium"]) == 0
25449+
25450+
assert "Rustwright found Chromium executable: /usr/bin/chromium" in capsys.readouterr().out
25451+
25452+
2531225453
def test_cli_chrome_for_testing_platform_rejects_linux_arm64(monkeypatch):
2531325454
from rustwright import cli
2531425455

0 commit comments

Comments
 (0)