(
html_path: Path,
png_path: Path,
*,
timeout_ms: int,
clip_height: int | None = None,
)
| 524 | |
| 525 | |
| 526 | def _screenshot_page( |
| 527 | html_path: Path, |
| 528 | png_path: Path, |
| 529 | *, |
| 530 | timeout_ms: int, |
| 531 | clip_height: int | None = None, |
| 532 | ) -> None: |
| 533 | # Hard ceiling so a stalled npx (e.g. cold install) can't hang the run. |
| 534 | subprocess_timeout = max(timeout_ms / 1000 * 3, 60) |
| 535 | cmd = [ |
| 536 | "npx", |
| 537 | "playwright", |
| 538 | "screenshot", |
| 539 | "--browser", |
| 540 | "chromium", |
| 541 | "--timeout", |
| 542 | str(timeout_ms), |
| 543 | ] |
| 544 | if clip_height is None: |
| 545 | cmd += ["--full-page", "--viewport-size=1280,900"] |
| 546 | else: |
| 547 | # Dropping --full-page makes Playwright capture only the viewport, |
| 548 | # giving us a top-clipped screenshot that stays under Chromium's |
| 549 | # full-page capture limits. |
| 550 | cmd += [f"--viewport-size=1280,{clip_height}"] |
| 551 | cmd += [html_path.resolve().as_uri(), str(png_path)] |
| 552 | try: |
| 553 | result = subprocess.run( |
| 554 | cmd, |
| 555 | cwd=REPO_ROOT, |
| 556 | capture_output=True, |
| 557 | text=True, |
| 558 | check=False, |
| 559 | timeout=subprocess_timeout, |
| 560 | ) |
| 561 | except subprocess.TimeoutExpired as exc: |
| 562 | raise RuntimeError( |
| 563 | f"playwright screenshot timed out after {subprocess_timeout:.0f}s" |
| 564 | ) from exc |
| 565 | if result.returncode != 0: |
| 566 | detail = (result.stderr or result.stdout).strip() |
| 567 | raise RuntimeError( |
| 568 | "playwright screenshot failed; run `npx playwright install chromium` " |
| 569 | f"if browsers are missing. Details: {detail}" |
| 570 | ) |
| 571 | |
| 572 | |
| 573 | def _screenshot_with_clip_fallback( |
no outgoing calls
no test coverage detected