Files
YoungCan-Wang d87e7fe7e9 fix(ci): stop redownloading the playwright browser on every run (#447)
* fix(ci): stop redownloading the playwright browser on every run

The cache key hashed uv.lock, but the browser revision is chosen by the
installed playwright version, and pip resolved that version from
pyproject.toml. Playwright sat in a PEP 735 [dependency-groups] table that
pip cannot read, so `pip install -e ".[dev]" playwright` pulled an unpinned
latest release whose revision was never in the restored cache.

The prefix fallback then hid the miss instead of correcting it: uv.lock's
hash never changed, so the exact key always hit, actions/cache skipped the
post-step save, and restore-keys handed back a directory missing the current
revision. The download repeated on every run, taking a median of 20 minutes.

Pin playwright in [project.optional-dependencies] where pip can read it,
hash that file for the cache key, and drop the fallback. Keep the PEP 735
group as an alias so `uv run` and pip resolve the same set. Split the install
so system libraries land unconditionally and only the browser download is
gated on the cache hit.

* test: skip browser tests when chromium is missing, not just playwright

The five gated files only checked whether the playwright package imports.
That was enough while playwright lived in a group pip ignores, so the import
itself was absent wherever the browser was. Now that it ships in [dev], the
package installs in the test matrix while the browser does not, and 116 tests
would hard-fail on a missing executable instead of skipping.

Probe the chromium binary too, in one shared helper so all five files report
the same reason.

* fix(scripts): let the coverage gate import shared test helpers

The gate loads tests/test_viewer_contracts.py through
spec_from_file_location, which does not insert the repo root that pytest
normally provides. A `from tests.conftest import ...` in that file therefore
raised ModuleNotFoundError and killed the whole gate before it measured
anything, even though two other test modules already import helpers that way.

Put the repo root on sys.path before executing the module.

* fix(test): run fake clients on the interpreter running the tests

The fake client scripts carry `#!/usr/bin/env python3`, which resolves to
whatever python3 is first on PATH. On macOS that is the system 3.9 from the
Xcode toolchain, not the 3.13 venv running the suite, so any fake client
importing a project dependency died on an unrelated interpreter:
test_codex_zstd_request_body could not find backports.zstd, and
test_kimi_code_client_reverse_proxy could not find tomllib.

Rewrite the shebang to sys.executable in one helper and route all ten write
sites through it.

* fix(tests): skip browser tests when chromium is absent, not just playwright

Moving playwright into the dev extra so pip can read its pin made the import
succeed on runners that never download a browser. Guards that only caught
ImportError -- and pytest.importorskip -- stopped skipping and started failing:
the test matrix installs the package but not chromium, and reported
"Executable doesn't exist at .../chromium_headless_shell-1208".

Route every file that launches a browser through the shared
playwright_skip_reason(), which probes the executable path rather than the
import. With PLAYWRIGHT_BROWSERS_PATH pointed at an empty directory the suite
goes from 3 failures to 1004 passed, 130 skipped.

* test(ci): fail when a browser test file skips the chromium guard

The previous fix touched six files found by hand. Discover them by call
instead: matching launch/launch_persistent_context on a browser type finds
every such file regardless of how it imports or aliases playwright, so a new
one cannot quietly reintroduce an import-only guard.

A second test asserts the discovery is not vacuous -- an empty match would
make the first one pass while checking nothing.

* fix(tests): run fake clients through a shell trampoline

Embedding sys.executable in the shebang breaks when the checkout lives
under a directory containing a space: the kernel does not split that line
on quotes, so the path truncates at the space and every fake client
becomes unexecutable. Re-exec through /bin/sh, which does parse quotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 20:55:19 +08:00

120 lines
3.4 KiB
TOML

[project]
name = "claude-tap"
dynamic = ["version"]
description = "Trace AI CLI API requests via local reverse and forward proxies. Inspect system prompts, messages, tools, and token usage."
requires-python = ">=3.11"
license = "MIT"
authors = [{ name = "liaohch3" }]
readme = "README.md"
keywords = ["claude", "claude-code", "codex", "grok", "kimi", "cursor", "qoder", "trace", "proxy", "api", "llm", "context-engineering"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Debuggers",
"Topic :: Software Development :: Testing",
]
dependencies = [
"aiohttp==3.14.1",
"backports-zstd>=1.0; python_version < '3.14'",
"cryptography>=42.0",
]
[project.urls]
Homepage = "https://github.com/liaohch3/claude-tap"
Repository = "https://github.com/liaohch3/claude-tap"
Issues = "https://github.com/liaohch3/claude-tap/issues"
[project.scripts]
claude-tap = "claude_tap.cli:main_entry"
[build-system]
requires = ["setuptools>=68.0", "setuptools-scm[toml]>=8.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["claude_tap", "claude_tap.viewer_assets"]
[tool.setuptools_scm]
version_scheme = "guess-next-dev"
local_scheme = "no-local-version"
[tool.setuptools.package-data]
claude_tap = ["dashboard.html", "viewer.html", "viewer_i18n.json", "model_prices.json", "py.typed"]
"claude_tap.viewer_assets" = ["*.css", "*.js"]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23",
"pytest-timeout>=2.3",
"pexpect>=4.9",
"coverage>=7.6",
"matplotlib==3.11.1",
"ruff>=0.11",
# Pinned exactly: CI caches ~/.cache/ms-playwright keyed on this file's hash,
# and each playwright release expects its own browser revision. A floating
# spec would install a version whose revision is absent from the cache,
# forcing a fresh browser download on every run.
"playwright==1.58.0",
]
[tool.ruff]
target-version = "py311"
line-length = 120
[tool.ruff.lint]
select = ["E", "F", "W", "I"]
ignore = ["E501"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = [
"-v",
"--tb=short",
"--strict-markers",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
"asyncio: marks tests as async (run by pytest-asyncio)",
"real_e2e: marks tests as real E2E tests requiring claude CLI (use --run-real-e2e)",
]
timeout = 60
[tool.coverage.run]
source = ["claude_tap"]
branch = true
omit = ["tests/*"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
show_missing = true
[tool.claude_tap.coverage]
python_total_min = 65.0
python_diff_min = 80.0
viewer_js_function_min = 50.0
viewer_js_diff_min = 80.0
viewer_css_selector_min = 65.0
viewer_css_diff_min = 80.0
[dependency-groups]
# Kept as a thin alias so `uv run` (which auto-activates this group) and
# `pip install -e ".[dev]"` install the same set. Declare dev tooling in
# [project.optional-dependencies] only — pip cannot read PEP 735 groups.
dev = [
"claude-tap[dev]",
]