feat(dashboard): surface compression-vs-cache net impact in Prefix Cache panel (#913)
## Summary
Closes #911, and delivers the dashboard half of the question in #855
("how do I understand the cache impact — can we add it to the
dashboard?").
`GET /stats` already exposes `prefix_cache.compression_vs_cache`
(`tokens_saved_by_compression`, `tokens_lost_to_cache_bust`,
`cache_bust_count`, `net_tokens`) and `prefix_cache.prefix_freeze`
(`busts_avoided`, `tokens_preserved`, `compression_foregone_tokens`,
`net_benefit_tokens`) — built in `headroom/proxy/cost.py` — but the
dashboard never rendered them.
This adds a **Compression vs Cache** section to the existing Prefix
Cache Impact panel:
- **Saved by Compression** — tokens removed before send
- **Lost to Cache Busts** — tokens lost, with observed bust count
- **Net** — color-coded emerald when positive, red when negative, with a
matching "Net positive / Net negative" headline pill
- **Prefix Freeze Net** — net benefit of freeze decisions (preserved
minus compression foregone), with busts avoided
The section is gated on data presence (hidden until any underlying
counter is non-zero), styled to match the existing TTL bucket cards,
works in light and dark mode, and carries `data-testid` hooks.
Frontend-only: no backend changes; the stats endpoint already serves
every field.
## Testing
New `tests/test_dashboard_cache_net_playwright.py` (mirrors the TTL
playwright test): pins the rendered values, the negative-net red
styling, and the hidden-when-empty behavior. 3/3 pass locally under
chromium.
Note for reviewers: the harness matches stubbed routes on URL **path**,
because the dashboard now fetches `/stats?cached=1` — full-URL
`endswith("/stats")` checks miss it and the request escapes to the real
network. The pre-existing `test_dashboard_cache_ttl_playwright.py` has
this exact bitrot (plus stale text assertions) and currently fails when
playwright is actually installed — playwright isn't installed in CI so
it silently skips. Left out of scope here; can file separately.
`ruff check` and `ruff format --check` clean.
Refs #855.
Co-authored-by: integration-check <integration@local>
2026-06-13 06:43:49 +02:00
|
|
|
"""Behavior-driven Playwright validation for the Compression vs Cache panel.
|
|
|
|
|
|
|
|
|
|
The /stats endpoint has long exposed ``prefix_cache.compression_vs_cache``
|
|
|
|
|
and ``prefix_cache.prefix_freeze`` (built in ``headroom/proxy/cost.py``)
|
|
|
|
|
but the dashboard never rendered them. These tests pin the new section:
|
|
|
|
|
net tokens saved by compression against cached-prefix tokens its mutations
|
|
|
|
|
invalidated, plus the prefix-freeze net benefit.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from urllib.parse import urlsplit
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from headroom.dashboard import get_dashboard_html
|
fix(dashboard): serve tailwind/htmx/alpine locally instead of from CDNs (#2734)
## Description
The dashboard loaded all three of its front-end dependencies from
third-party CDNs at page load:
```html
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js" defer></script>
```
Microsoft Edge's Tracking Prevention classifies `unpkg.com` as a tracker
and blocks it by default on Windows; locked-down corporate proxies block
both hosts. On those machines none of the three scripts executed — no
Tailwind CSS, no htmx polling, no Alpine bindings, plus an uncaught
`ReferenceError: tailwind is not defined` from the inline
`tailwind.config` assignment at `dashboard.html:21`. The dashboard
rendered blank. Reported from a Windows user's console:
```text
Tracking Prevention blocked access to storage for https://unpkg.com/htmx.org@1.9.10.
Tracking Prevention blocked access to storage for https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js.
```
This vendors the three files and serves them from the proxy, so the
dashboard has no external network dependency at all.
Note for anyone triaging the same report: the `cdn.tailwindcss.com
should not be used in production` line in that console output is **not**
related. It is an unconditional `console.warn` in the Tailwind Play CDN
build (no hostname guard), so it fires on every load, localhost
included, and it still fires now that the bundle is self-hosted.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Vendored
`headroom/dashboard/static/{tailwind.min.js,htmx.min.js,alpine.min.js}`
— Tailwind Play CDN 3.4.17, htmx 1.9.10, Alpine 3.13.3, byte-for-byte as
published.
- `headroom/dashboard/__init__.py`: added `STATIC_DIR`.
- `headroom/proxy/server.py`: mounted `/dashboard/static`, registered
**before** `register_provider_routes`' catch-all so the asset requests
are not tunneled to the wrapped upstream provider (same ordering
constraint as the `/favicon.ico` route, GH #1787). `check_dir=False` so
a missing assets directory 404s the dashboard JS rather than aborting
proxy startup.
- `headroom/dashboard/templates/{dashboard,settings}.html`: script `src`
→ `/dashboard/static/…`.
- `NOTICE`: MIT / 0BSD attribution for the three vendored bundles.
- `tests/test_dashboard_static_assets.py`: new.
No packaging change needed — `[tool.maturin]` includes everything under
`headroom/`, so the wheel picks the assets up. Wheel grows ~498 KB (407
KB of that is the Tailwind Play bundle).
## Testing
- [x] Unit tests pass (`pytest`) — targeted, see note under *Not tested*
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ python -m pytest tests/test_dashboard_static_assets.py tests/test_proxy_settings_endpoints.py -q
tests/test_dashboard_static_assets.py ...... [ 21%]
tests/test_proxy_settings_endpoints.py ...................... [100%]
============================== 28 passed in 4.47s ==============================
$ ruff check .
All checks passed!
$ ruff format --check headroom/proxy/server.py headroom/dashboard/__init__.py tests/test_dashboard_static_assets.py
3 files already formatted
$ mypy headroom
Success: no issues found in 509 source files
```
## Real Behavior Proof
- **Environment:** macOS 15 (Darwin 25.4.0), Python 3.12.6, headless
Chromium via Playwright, proxy served in-process with
`create_app(ProxyConfig(optimize=False, cache_enabled=False,
log_full_messages=True))` on `:8787`.
- **Exact command / steps:** loaded `/dashboard` and
`/dashboard/settings` with `wait_until="networkidle"`, then asserted the
globals exist, that Tailwind actually generated CSS (computed style of a
`px-3` element), and recorded every non-localhost request plus all
`pageerror`/`console.error` events.
- **Observed result:**
```text
/dashboard | alpine: True | tailwind css: True | external: none | errors: none
/dashboard/settings | alpine: True | tailwind css: True | external: none | errors: none
/dashboard 200 text/html; charset=utf-8 191549
/dashboard/static/tailwind.min.js 200 text/javascript; charset=utf-8 407279
/dashboard/static/htmx.min.js 200 text/javascript; charset=utf-8 47755
/dashboard/static/alpine.min.js 200 text/javascript; charset=utf-8 43441
feed-toggle visible: True
alpine loaded: True htmx: True tailwind: True
tailwind applied (px-3 padding): 12px
external hosts: none
console errors: none
```
Zero external requests on either page, so the Edge/firewall failure mode
is structurally gone rather than worked around.
- **Not tested:**
- No Windows machine available — the fix is verified as "makes zero
external requests", which is the property the Windows failure depended
on, but it has not been confirmed against Edge with Tracking Prevention
on. Worth a check by someone on Windows before release.
- Full `pytest` suite not run (targeted runs only); CI covers it.
- `tests/test_dashboard/test_live_feed.py` still has 2 failures, both
pre-existing and unrelated: those tests need a manually started proxy on
`:8787` with `--log-messages`, and `test_live_feed_button_exists`
asserts `is_visible()` with no wait for the `/stats` poll that flips
`log_full_messages`. The other 2 in that file pass against this change,
which is itself end-to-end evidence that Alpine and htmx work from the
vendored bundles.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md`
## Additional Notes
- No issue number: reported directly rather than filed, so `Closes #` is
omitted. Closed #22 ("Dashboard is not working") and closed #533
(Windows cp949 `get_dashboard_html()`) are different failures.
- **Docs checklist item is N/A** — nothing user-facing changes; the
dashboard URL and behaviour are identical.
- Deliberately **not** switching to a real Tailwind CLI build. It would
cut 407 KB to ~20 KB and silence the production warning, but it puts
Node in the release path and silently leaves any class added to the
2,713-line template unstyled with no CI guard. The Play bundle behaves
exactly as it does today, just served locally. Worth revisiting if wheel
size becomes a problem (note the PyPI project-size ceiling).
- Upgrades are now manual: bumping these three means re-downloading the
files. Pinned versions are recorded in `NOTICE`.
2026-08-03 06:07:27 -07:00
|
|
|
from tests.test_dashboard_cache_ttl_playwright import (
|
|
|
|
|
_fulfill_static_asset,
|
|
|
|
|
_sample_history,
|
|
|
|
|
_sample_stats,
|
|
|
|
|
)
|
feat(dashboard): surface compression-vs-cache net impact in Prefix Cache panel (#913)
## Summary
Closes #911, and delivers the dashboard half of the question in #855
("how do I understand the cache impact — can we add it to the
dashboard?").
`GET /stats` already exposes `prefix_cache.compression_vs_cache`
(`tokens_saved_by_compression`, `tokens_lost_to_cache_bust`,
`cache_bust_count`, `net_tokens`) and `prefix_cache.prefix_freeze`
(`busts_avoided`, `tokens_preserved`, `compression_foregone_tokens`,
`net_benefit_tokens`) — built in `headroom/proxy/cost.py` — but the
dashboard never rendered them.
This adds a **Compression vs Cache** section to the existing Prefix
Cache Impact panel:
- **Saved by Compression** — tokens removed before send
- **Lost to Cache Busts** — tokens lost, with observed bust count
- **Net** — color-coded emerald when positive, red when negative, with a
matching "Net positive / Net negative" headline pill
- **Prefix Freeze Net** — net benefit of freeze decisions (preserved
minus compression foregone), with busts avoided
The section is gated on data presence (hidden until any underlying
counter is non-zero), styled to match the existing TTL bucket cards,
works in light and dark mode, and carries `data-testid` hooks.
Frontend-only: no backend changes; the stats endpoint already serves
every field.
## Testing
New `tests/test_dashboard_cache_net_playwright.py` (mirrors the TTL
playwright test): pins the rendered values, the negative-net red
styling, and the hidden-when-empty behavior. 3/3 pass locally under
chromium.
Note for reviewers: the harness matches stubbed routes on URL **path**,
because the dashboard now fetches `/stats?cached=1` — full-URL
`endswith("/stats")` checks miss it and the request escapes to the real
network. The pre-existing `test_dashboard_cache_ttl_playwright.py` has
this exact bitrot (plus stale text assertions) and currently fails when
playwright is actually installed — playwright isn't installed in CI so
it silently skips. Left out of scope here; can file separately.
`ruff check` and `ruff format --check` clean.
Refs #855.
Co-authored-by: integration-check <integration@local>
2026-06-13 06:43:49 +02:00
|
|
|
|
|
|
|
|
playwright = pytest.importorskip("playwright.sync_api")
|
|
|
|
|
Page = playwright.Page
|
|
|
|
|
expect = playwright.expect
|
|
|
|
|
sync_playwright = playwright.sync_playwright
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _stats_with_compression_vs_cache() -> dict:
|
|
|
|
|
stats = copy.deepcopy(_sample_stats())
|
|
|
|
|
stats["prefix_cache"]["compression_vs_cache"] = {
|
|
|
|
|
"tokens_saved_by_compression": 143_000,
|
|
|
|
|
"tokens_lost_to_cache_bust": 36_000,
|
|
|
|
|
"cache_bust_count": 4,
|
|
|
|
|
"net_tokens": 107_000,
|
|
|
|
|
}
|
|
|
|
|
stats["prefix_cache"]["prefix_freeze"] = {
|
|
|
|
|
"busts_avoided": 6,
|
|
|
|
|
"tokens_preserved": 88_000,
|
|
|
|
|
"compression_foregone_tokens": 21_000,
|
|
|
|
|
"net_benefit_tokens": 67_000,
|
|
|
|
|
}
|
|
|
|
|
return stats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _install_dashboard_routes(page: Page, stats: dict) -> None:
|
|
|
|
|
history = _sample_history()
|
|
|
|
|
health = {"status": "healthy", "version": "0.3.0"}
|
|
|
|
|
dashboard_html = get_dashboard_html()
|
|
|
|
|
|
|
|
|
|
def handler(route) -> None: # type: ignore[no-untyped-def]
|
|
|
|
|
# Match on the URL path only: the dashboard fetches /stats?cached=1,
|
|
|
|
|
# so suffix checks against the full URL miss it and the request
|
|
|
|
|
# escapes the harness to the real network.
|
|
|
|
|
path = urlsplit(route.request.url).path
|
|
|
|
|
if path in ("/dashboard", "/"):
|
|
|
|
|
route.fulfill(status=200, content_type="text/html", body=dashboard_html)
|
|
|
|
|
return
|
fix(dashboard): serve tailwind/htmx/alpine locally instead of from CDNs (#2734)
## Description
The dashboard loaded all three of its front-end dependencies from
third-party CDNs at page load:
```html
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js" defer></script>
```
Microsoft Edge's Tracking Prevention classifies `unpkg.com` as a tracker
and blocks it by default on Windows; locked-down corporate proxies block
both hosts. On those machines none of the three scripts executed — no
Tailwind CSS, no htmx polling, no Alpine bindings, plus an uncaught
`ReferenceError: tailwind is not defined` from the inline
`tailwind.config` assignment at `dashboard.html:21`. The dashboard
rendered blank. Reported from a Windows user's console:
```text
Tracking Prevention blocked access to storage for https://unpkg.com/htmx.org@1.9.10.
Tracking Prevention blocked access to storage for https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js.
```
This vendors the three files and serves them from the proxy, so the
dashboard has no external network dependency at all.
Note for anyone triaging the same report: the `cdn.tailwindcss.com
should not be used in production` line in that console output is **not**
related. It is an unconditional `console.warn` in the Tailwind Play CDN
build (no hostname guard), so it fires on every load, localhost
included, and it still fires now that the bundle is self-hosted.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Vendored
`headroom/dashboard/static/{tailwind.min.js,htmx.min.js,alpine.min.js}`
— Tailwind Play CDN 3.4.17, htmx 1.9.10, Alpine 3.13.3, byte-for-byte as
published.
- `headroom/dashboard/__init__.py`: added `STATIC_DIR`.
- `headroom/proxy/server.py`: mounted `/dashboard/static`, registered
**before** `register_provider_routes`' catch-all so the asset requests
are not tunneled to the wrapped upstream provider (same ordering
constraint as the `/favicon.ico` route, GH #1787). `check_dir=False` so
a missing assets directory 404s the dashboard JS rather than aborting
proxy startup.
- `headroom/dashboard/templates/{dashboard,settings}.html`: script `src`
→ `/dashboard/static/…`.
- `NOTICE`: MIT / 0BSD attribution for the three vendored bundles.
- `tests/test_dashboard_static_assets.py`: new.
No packaging change needed — `[tool.maturin]` includes everything under
`headroom/`, so the wheel picks the assets up. Wheel grows ~498 KB (407
KB of that is the Tailwind Play bundle).
## Testing
- [x] Unit tests pass (`pytest`) — targeted, see note under *Not tested*
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ python -m pytest tests/test_dashboard_static_assets.py tests/test_proxy_settings_endpoints.py -q
tests/test_dashboard_static_assets.py ...... [ 21%]
tests/test_proxy_settings_endpoints.py ...................... [100%]
============================== 28 passed in 4.47s ==============================
$ ruff check .
All checks passed!
$ ruff format --check headroom/proxy/server.py headroom/dashboard/__init__.py tests/test_dashboard_static_assets.py
3 files already formatted
$ mypy headroom
Success: no issues found in 509 source files
```
## Real Behavior Proof
- **Environment:** macOS 15 (Darwin 25.4.0), Python 3.12.6, headless
Chromium via Playwright, proxy served in-process with
`create_app(ProxyConfig(optimize=False, cache_enabled=False,
log_full_messages=True))` on `:8787`.
- **Exact command / steps:** loaded `/dashboard` and
`/dashboard/settings` with `wait_until="networkidle"`, then asserted the
globals exist, that Tailwind actually generated CSS (computed style of a
`px-3` element), and recorded every non-localhost request plus all
`pageerror`/`console.error` events.
- **Observed result:**
```text
/dashboard | alpine: True | tailwind css: True | external: none | errors: none
/dashboard/settings | alpine: True | tailwind css: True | external: none | errors: none
/dashboard 200 text/html; charset=utf-8 191549
/dashboard/static/tailwind.min.js 200 text/javascript; charset=utf-8 407279
/dashboard/static/htmx.min.js 200 text/javascript; charset=utf-8 47755
/dashboard/static/alpine.min.js 200 text/javascript; charset=utf-8 43441
feed-toggle visible: True
alpine loaded: True htmx: True tailwind: True
tailwind applied (px-3 padding): 12px
external hosts: none
console errors: none
```
Zero external requests on either page, so the Edge/firewall failure mode
is structurally gone rather than worked around.
- **Not tested:**
- No Windows machine available — the fix is verified as "makes zero
external requests", which is the property the Windows failure depended
on, but it has not been confirmed against Edge with Tracking Prevention
on. Worth a check by someone on Windows before release.
- Full `pytest` suite not run (targeted runs only); CI covers it.
- `tests/test_dashboard/test_live_feed.py` still has 2 failures, both
pre-existing and unrelated: those tests need a manually started proxy on
`:8787` with `--log-messages`, and `test_live_feed_button_exists`
asserts `is_visible()` with no wait for the `/stats` poll that flips
`log_full_messages`. The other 2 in that file pass against this change,
which is itself end-to-end evidence that Alpine and htmx work from the
vendored bundles.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md`
## Additional Notes
- No issue number: reported directly rather than filed, so `Closes #` is
omitted. Closed #22 ("Dashboard is not working") and closed #533
(Windows cp949 `get_dashboard_html()`) are different failures.
- **Docs checklist item is N/A** — nothing user-facing changes; the
dashboard URL and behaviour are identical.
- Deliberately **not** switching to a real Tailwind CLI build. It would
cut 407 KB to ~20 KB and silence the production warning, but it puts
Node in the release path and silently leaves any class added to the
2,713-line template unstyled with no CI guard. The Play bundle behaves
exactly as it does today, just served locally. Worth revisiting if wheel
size becomes a problem (note the PyPI project-size ceiling).
- Upgrades are now manual: bumping these three means re-downloading the
files. Pinned versions are recorded in `NOTICE`.
2026-08-03 06:07:27 -07:00
|
|
|
if _fulfill_static_asset(route, path):
|
|
|
|
|
return
|
feat(dashboard): surface compression-vs-cache net impact in Prefix Cache panel (#913)
## Summary
Closes #911, and delivers the dashboard half of the question in #855
("how do I understand the cache impact — can we add it to the
dashboard?").
`GET /stats` already exposes `prefix_cache.compression_vs_cache`
(`tokens_saved_by_compression`, `tokens_lost_to_cache_bust`,
`cache_bust_count`, `net_tokens`) and `prefix_cache.prefix_freeze`
(`busts_avoided`, `tokens_preserved`, `compression_foregone_tokens`,
`net_benefit_tokens`) — built in `headroom/proxy/cost.py` — but the
dashboard never rendered them.
This adds a **Compression vs Cache** section to the existing Prefix
Cache Impact panel:
- **Saved by Compression** — tokens removed before send
- **Lost to Cache Busts** — tokens lost, with observed bust count
- **Net** — color-coded emerald when positive, red when negative, with a
matching "Net positive / Net negative" headline pill
- **Prefix Freeze Net** — net benefit of freeze decisions (preserved
minus compression foregone), with busts avoided
The section is gated on data presence (hidden until any underlying
counter is non-zero), styled to match the existing TTL bucket cards,
works in light and dark mode, and carries `data-testid` hooks.
Frontend-only: no backend changes; the stats endpoint already serves
every field.
## Testing
New `tests/test_dashboard_cache_net_playwright.py` (mirrors the TTL
playwright test): pins the rendered values, the negative-net red
styling, and the hidden-when-empty behavior. 3/3 pass locally under
chromium.
Note for reviewers: the harness matches stubbed routes on URL **path**,
because the dashboard now fetches `/stats?cached=1` — full-URL
`endswith("/stats")` checks miss it and the request escapes to the real
network. The pre-existing `test_dashboard_cache_ttl_playwright.py` has
this exact bitrot (plus stale text assertions) and currently fails when
playwright is actually installed — playwright isn't installed in CI so
it silently skips. Left out of scope here; can file separately.
`ruff check` and `ruff format --check` clean.
Refs #855.
Co-authored-by: integration-check <integration@local>
2026-06-13 06:43:49 +02:00
|
|
|
if "/stats-history" in path:
|
|
|
|
|
route.fulfill(status=200, content_type="application/json", body=json.dumps(history))
|
|
|
|
|
return
|
|
|
|
|
if path.endswith("/stats"):
|
|
|
|
|
route.fulfill(status=200, content_type="application/json", body=json.dumps(stats))
|
|
|
|
|
return
|
|
|
|
|
if path.endswith("/health"):
|
|
|
|
|
route.fulfill(status=200, content_type="application/json", body=json.dumps(health))
|
|
|
|
|
return
|
|
|
|
|
route.continue_()
|
|
|
|
|
|
|
|
|
|
page.route("**/*", handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _open_dashboard(page: Page, stats: dict) -> None:
|
|
|
|
|
_install_dashboard_routes(page, stats)
|
|
|
|
|
page.goto("http://headroom.local/dashboard")
|
|
|
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_dashboard_renders_compression_vs_cache_net_metrics() -> None:
|
|
|
|
|
artifact_dir = os.environ.get("HEADROOM_PLAYWRIGHT_ARTIFACT_DIR")
|
|
|
|
|
|
|
|
|
|
with sync_playwright() as p:
|
|
|
|
|
browser = p.chromium.launch()
|
|
|
|
|
page = browser.new_page(viewport={"width": 1440, "height": 1600})
|
|
|
|
|
_open_dashboard(page, _stats_with_compression_vs_cache())
|
|
|
|
|
|
|
|
|
|
expect(page.get_by_text("Compression vs Cache", exact=True)).to_be_visible()
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-headline")).to_have_text("Net positive")
|
|
|
|
|
expect(page.get_by_test_id("cvc-saved-value")).to_have_text("143.0k")
|
|
|
|
|
expect(page.get_by_test_id("cvc-bust-value")).to_have_text("36.0k")
|
|
|
|
|
expect(page.get_by_text("4 busts observed")).to_be_visible()
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-value")).to_have_text("+107.0k")
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-value")).to_have_class(
|
|
|
|
|
"mt-2 text-3xl font-light text-emerald-400"
|
|
|
|
|
)
|
|
|
|
|
expect(page.get_by_test_id("freeze-net-value")).to_have_text("+67.0k")
|
|
|
|
|
expect(page.get_by_text("6 busts avoided, 21.0k foregone")).to_be_visible()
|
|
|
|
|
|
|
|
|
|
if artifact_dir:
|
|
|
|
|
Path(artifact_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
page.screenshot(
|
|
|
|
|
path=str(Path(artifact_dir) / "dashboard-compression-vs-cache.png"),
|
|
|
|
|
full_page=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
browser.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_dashboard_marks_negative_compression_vs_cache_net_in_red() -> None:
|
|
|
|
|
stats = _stats_with_compression_vs_cache()
|
|
|
|
|
stats["prefix_cache"]["compression_vs_cache"] = {
|
|
|
|
|
"tokens_saved_by_compression": 12_000,
|
|
|
|
|
"tokens_lost_to_cache_bust": 48_000,
|
|
|
|
|
"cache_bust_count": 9,
|
|
|
|
|
"net_tokens": -36_000,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
with sync_playwright() as p:
|
|
|
|
|
browser = p.chromium.launch()
|
|
|
|
|
page = browser.new_page(viewport={"width": 1440, "height": 1600})
|
|
|
|
|
_open_dashboard(page, stats)
|
|
|
|
|
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-headline")).to_have_text("Net negative")
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-value")).to_have_text("-36.0k")
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-value")).to_have_class(
|
|
|
|
|
"mt-2 text-3xl font-light text-red-400"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
browser.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_dashboard_hides_compression_vs_cache_section_without_data() -> None:
|
|
|
|
|
stats = copy.deepcopy(_sample_stats())
|
|
|
|
|
stats["prefix_cache"]["compression_vs_cache"] = {
|
|
|
|
|
"tokens_saved_by_compression": 0,
|
|
|
|
|
"tokens_lost_to_cache_bust": 0,
|
|
|
|
|
"cache_bust_count": 0,
|
|
|
|
|
"net_tokens": 0,
|
|
|
|
|
}
|
|
|
|
|
stats["prefix_cache"]["prefix_freeze"] = {
|
|
|
|
|
"busts_avoided": 0,
|
|
|
|
|
"tokens_preserved": 0,
|
|
|
|
|
"compression_foregone_tokens": 0,
|
|
|
|
|
"net_benefit_tokens": 0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
with sync_playwright() as p:
|
|
|
|
|
browser = p.chromium.launch()
|
|
|
|
|
page = browser.new_page(viewport={"width": 1440, "height": 1600})
|
|
|
|
|
_open_dashboard(page, stats)
|
|
|
|
|
|
|
|
|
|
expect(page.get_by_test_id("cvc-net-headline")).to_have_count(0)
|
|
|
|
|
|
|
|
|
|
browser.close()
|