"""Phase 107 (task 02) E2E (Playwright): TRUE per-file git dates end to end for a URL-transport git source. Story: n/a — owner bug report 2026-09-16 (phase 106 follow-up): the live ``https://gitea…/homelab.git`` URL source displayed the repo's TIP-commit date for every document (``container_bifrost`` "created 8/16/2026", months off) because phase 106 D10 locked ``--depth 1`` shallow URL clones. Phase 107 D11 (task 01) removed the shallow clone and added the one-time ``git fetch --unshallow`` self-heal — this suite proves the FIX END TO END over the ``file://`` transport (the transport-true stand-in for the live https source: pre-fix it rendered the uniform tip date too). Run in isolation (DB must be up: ``podman compose up -d db``; git on PATH — a documented environment prerequisite, phase 28): uv run pytest tests/e2e/test_git_source_dates.py -v --no-cov The whole owner scenario through the REAL pipeline: a real ``file://`` git fixture with TWO commits of controlled ``GIT_COMMITTER_DATE``s (``old/old-note.md`` @ 2020-06-15, ``recent/recent-note.md`` @ the 2024-06-15 tip — MID-YEAR dates so the browser's locale/TZ rendering of the YEAR is stable in any timezone, the test_document_dates.py L455 lesson), a real in-app admin Sync (``clone_or_pull`` task-01 full-history path → real importer → real KB overview, mock LLM), and the assertions that the OLD file shows its OLD commit date and the NEW file shows the TIP date — in ``GET /api/docs`` / ``GET /api/docs/tree`` (deterministic ISO) and in the Sources tables + document viewer (locale-tolerant year regexes / the badge's full-ISO ``title``). Per-module app env (the E2E conftest pattern, module-scoped — copied verbatim in shape from ``test_sync_button.py``): this suite's app boots with ``BOR_GIT_SOURCES=file://`` and its own ``BOR_SOURCES_DIR`` (fresh dir — the first sync takes the NEW-CLONE path, the later per-test syncs the existing-checkout probe→pull path); the session app (no git sources) is never started in this isolated run, so no port clash. Test → contract mapping (four tests): 1. ``test_api_created_dates_are_true_per_file`` — THE REGRESSION PIN: after a real sync, ``GET /api/docs`` carries the OLD commit date (2020-06-15) for the old file and the TIP date (2024-06-15) for the new file — the two DIFFER (the phase-106 bug made both carry the tip date). ``GET /api/docs/tree``: the file nodes carry the same ISO dates verbatim; the folder ``updated_at`` values are the subtree MAXES (D9 — now over TRUE dates): ``old`` = 2020, ``recent`` = 2024, the source node = the 2024 max. 2. ``test_sources_tables_render_distinct_created_dates`` — the RAG view's FILE table ``Created`` column (phase 106 D8 — between ``Chunks`` and ``Indexed``) renders 2020 for the old file and 2024 for the new one (year regexes — ``toLocaleString`` is locale/TZ-dependent, the year is stable for mid-year dates; the full ISO rides the cell's ``title``, asserted verbatim); the FOLDER table ``Updated`` column (between ``Documents`` and ``Description``) renders 2020 / 2024 per folder, the source row the 2024 max. The two file cells' visible text differs. 3. ``test_viewer_created_badge_is_the_true_git_date`` — opening the old document (the phase-26 same-page modal) shows the ``.doc-created`` badge (phase 106, before the ``Indexed`` badge) with the RAW ISO on its ``title`` — ``2020-06-15T12:00:00+00:00`` (``metaBadge``'s title, deterministic under any locale/TZ — the house solution to the L455 rendering trap) and ``Created …`` in the visible text; Escape closes the modal cleanly (focus restored to the row link). 4. ``test_page_a11y_and_no_cdn_basics`` — the standard light pass (AGENTS.md rules 5/6, the test_git_sources_admin.py a11y test shape): landmarks on the Sources view, the ``Created`` / ``Updated`` ```` cells present in both tables (this phase adds no color — no new contrast surface), the 3px ``:focus-visible`` outline on a table row link (a real keyboard Tab walk), and same-origin assets only. Isolation: per-test TRUNCATE of the KB tables (the ``test_sync_button.py`` ``_truncate_kb`` shape, extended with ``sources_meta``) so each test's sync counts are its own; own ``BOR_SOURCES_DIR``; the suite touches no other suite's fixtures. """ from __future__ import annotations import os import re import subprocess import sys from collections.abc import Iterator from pathlib import Path from typing import Any import pytest from playwright.sync_api import Page, expect from sqlalchemy import text from app.db import SessionLocal from e2e.auth_helpers import login from e2e.conftest import ( ADMIN_PASSWORD, SESSION_SECRET, USE_REAL_LLM, _wait_http, ) REPO = Path(__file__).resolve().parents[2] # Phase 79 (task 04, full inventory): the conftest session app owns its # port in a combined run — this module app binds its own port instead # (a same-port second uvicorn dies on bind and would drive the wrong # server). Env-overridable. APP_PORT = int(os.environ.get("E2E_APP_PORT_GITDATES", "8130")) APP_URL = f"http://127.0.0.1:{APP_PORT}" # -------------------------------------------------------------------------- # Fixture constants (deterministic — see the module docstring) # -------------------------------------------------------------------------- #: The fixture repo's directory name — the source name the import #: records for it (the repo_name basename rule, phase 28). REPO_NAME = "git-dates" OLD_PATH = "old/old-note.md" TIP_PATH = "recent/recent-note.md" #: The controlled commit dates (MID-YEAR, 12:00Z — the local year of #: the instant is the committed year in ANY timezone, from UTC-12 to #: UTC+14, so the browser's locale/TZ rendering of the year is stable, #: the test_document_dates.py L455 lesson). OLD_COMMIT = "2020-06-15T12:00:00Z" TIP_COMMIT = "2024-06-15T12:00:00Z" #: The same instants as stored (``%cI`` → ``datetime.fromisoformat`` → #: ``normalize_doc_date`` → Postgres → ``isoformat()``): the exact ISO #: the API, the cell ``title``s, and the viewer badge ``title`` carry. OLD_ISO = "2020-06-15T12:00:00+00:00" TIP_ISO = "2024-06-15T12:00:00+00:00" OLD_TEXT = """\ # Old stable note An old, stable note that has not changed since it was first written. """ TIP_TEXT = """\ # Recent note A recent note added at the tip of the repository. """ #: "Synced HH:MM" — the local-time last-result label (sources.js's #: fmtSyncTime), any hour/minute. SYNCED_LABEL = re.compile(r"Synced \d{1,2}:\d{2}") #: Real git clone + embed against the mock LLM — generous budget #: (the sync can legitimately take a while, no client timeout). SYNC_TIMEOUT_MS = 60_000 # -------------------------------------------------------------------------- # The fixture repo (module-scoped — real git, two controlled commits) # -------------------------------------------------------------------------- def _git(cwd: Path, *args: str, env: dict[str, str] | None = None) -> None: """Run git in *cwd*; a non-zero exit fails the fixture loudly. *env* overrides (the ``GIT_AUTHOR_DATE`` / ``GIT_COMMITTER_DATE`` commit-date controls — the phase's whole point). """ full_env = dict(os.environ) if env: full_env.update(env) proc = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, env=full_env) if proc.returncode != 0: raise AssertionError(f"git {' '.join(args)} failed: {proc.stderr.strip()}") def _git_out(cwd: Path, *args: str) -> str: proc = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True) if proc.returncode != 0: raise AssertionError(f"git {' '.join(args)} failed: {proc.stderr.strip()}") return proc.stdout def _commit(cwd: Path, message: str, when: str) -> None: """A ``git add -A`` + commit with BOTH the author and the committer dates pinned to *when* (``file_commit_dates`` reads ``%cI`` — the committer date; the author date is pinned too so the fixture has one unambiguous date per commit).""" _git(cwd, "add", "-A") _git( cwd, "-c", "user.email=e@x", "-c", "user.name=t", "-c", "commit.gpgsign=false", # the fixture commits never sign "commit", "-qm", message, env={"GIT_AUTHOR_DATE": when, "GIT_COMMITTER_DATE": when}, ) @pytest.fixture(scope="module") def git_dates_repo(tmp_path_factory: pytest.TempPathFactory) -> Path: """A real two-commit git repo the sync must clone over ``file://`` (task 02 step 1): commit one @ 2020-06-15 adds ``old/old-note.md``; commit two (the tip) @ 2024-06-15 adds ``recent/recent-note.md``. Built under ``tmp_path_factory`` (``tmp_path`` is function-scoped while the module-scoped app fixture needs the repo for the module's lifetime) via real ``git`` subprocesses — the ``test_sync_button.py`` idiom. """ root = tmp_path_factory.mktemp("bor_git_dates") repo = root / REPO_NAME (repo / "old").mkdir(parents=True) (repo / "old" / "old-note.md").write_text(OLD_TEXT, encoding="utf-8") _git(repo, "init", "-q") _commit(repo, "old note", OLD_COMMIT) (repo / "recent").mkdir() (repo / "recent" / "recent-note.md").write_text(TIP_TEXT, encoding="utf-8") _commit(repo, "recent note", TIP_COMMIT) assert (repo / ".git").is_dir() assert _git_out(repo, "rev-list", "--count", "HEAD").strip() == "2" return repo # -------------------------------------------------------------------------- # The app under test (module-scoped env, the test_sync_button.py shape) # -------------------------------------------------------------------------- @pytest.fixture(scope="module") def app_server(mock_llm: int, git_dates_repo: Path) -> Iterator[str]: """The real app under test — per-module env: the sync's subject is a real ``file://`` URL-transport git source with its own checkout dir (the conftest session app boots without ``BOR_GIT_SOURCES`` and is never started in this isolated run).""" env = dict(os.environ) env.pop("DEBUGPY", None) env["BOR_ENVIRONMENT"] = "e2e" env["BOR_STATIC_DIR"] = str(REPO / "frontend") env["BOR_LLM_BASE_URL"] = ( "https://aipi.reeseapps.com/v1" if USE_REAL_LLM else f"http://127.0.0.1:{mock_llm}/v1" ) # Mock-calibrated threshold (conftest pattern) — no chat turn is # sent in this suite, but the app boots with the same env shape. env["BOR_RELEVANCE_THRESHOLD"] = "0.30" env.setdefault( "BOR_DATABASE_URL", "postgresql+psycopg://reese:reese@localhost:5432/brain_of_reese", ) # Phase 16: admin auth must be set or create_app() refuses to boot. env["BOR_ADMIN_PASSWORD"] = ADMIN_PASSWORD env["BOR_SESSION_SECRET"] = SESSION_SECRET # Phase 107: the subject — one real local repo over the URL # transport, checked out under its own fresh dir (first sync = # the task-01 NEW-CLONE full-history path; later per-test syncs = # the existing-checkout probe → pull path). env["BOR_GIT_SOURCES"] = f"file://{git_dates_repo}" env["BOR_SOURCES_DIR"] = str(git_dates_repo.parent / "checkouts") proc = subprocess.Popen( [sys.executable, "-m", "uvicorn", "app.main:app", "--host", "127.0.0.1", "--port", str(APP_PORT), "--log-level", "warning"], cwd=REPO, env=env, ) try: _wait_http(f"{APP_URL}/api/health") yield APP_URL finally: proc.terminate() try: proc.wait(timeout=10) except subprocess.TimeoutExpired: proc.kill() @pytest.fixture(scope="module") def app_url(app_server: str) -> str: return app_server def _truncate_kb() -> None: """Fresh KB per test (the E2E isolation pattern): each test's sync counts are its own. ``git_sources`` too: the shared Postgres may carry leftover registry rows from other suites, and effective_sources prefers the DB list over this app's ``BOR_GIT_SOURCES`` env fallback. ``sources_meta`` (the KB generation counter) resets with the KB — the bump is an upsert, so the sync re-creates the row.""" with SessionLocal() as db: db.execute(text( "TRUNCATE chunks, documents, query_log, kb_overview, " "git_sources, sources_meta" )) db.commit() @pytest.fixture(autouse=True) def _clean_kb(db_ready: None) -> Iterator[None]: _truncate_kb() yield # -------------------------------------------------------------------------- # Helpers # -------------------------------------------------------------------------- def _sync_in_app(page: Page, app_url: str) -> None: """Admin login → the admin Sources page → click "Sync sources" → the terminal success label + the fresh-import counts (the ``test_sync_button.py`` lifecycle wait, generous timeout — a real clone/pull + the real import + the KB overview, mock LLM).""" login(page, app_url) # lands on /sources.html (the button's home) btn = page.locator("#sync-btn") expect(btn).to_be_visible() # Fresh app state: "Sync sources" — or, when this app process has # ALREADY run a previous test's sync (the per-module server-side # status survives the per-test page), the boot re-attach adopted # that terminal state ("Synced HH:MM") — either way the button is # clickable (never stale). expect(page.locator("#sync-label")).to_have_text( re.compile(r"^Sync sources$|^Synced \d{1,2}:\d{2}$") ) btn.click() expect(btn).to_be_disabled() expect(page.locator("#sync-label")).to_have_text("Syncing…") expect(page.locator("#sync-label")).to_have_text(SYNCED_LABEL, timeout=SYNC_TIMEOUT_MS) expect(btn).to_be_enabled() # never stale — re-enabled at the terminal state # Both fixture docs are fresh after the per-test truncate. expect(page.locator("#sync-result")).to_have_text("2 added") def _source_node(tree_json: dict[str, Any]) -> dict[str, Any]: for s in tree_json["sources"]: if s["name"] == REPO_NAME: return s raise AssertionError(f"source {REPO_NAME!r} not in the tree") def _folder_node(node: dict[str, Any], path: str) -> dict[str, Any]: for child in node.get("children", []): if child.get("kind") == "folder" and child["path"] == path: return child raise AssertionError(f"folder {path!r} not under the node") def _find_file(node: dict[str, Any], path: str) -> dict[str, Any]: """The file node with *path* anywhere under *node* (recursing into the folder children).""" for child in node.get("children", []): if child.get("kind") == "file" and child["path"] == path: return child if child.get("kind") == "folder": try: return _find_file(child, path) except AssertionError: continue raise AssertionError(f"file {path!r} not under the node") def _drill_to_folder(page: Page, folder: str) -> None: """Top level → the source → *folder* (the two real clicks).""" page.locator("#folders-tbody .folder-link").first.wait_for(state="visible") page.click(f'#folders-tbody a.folder-link:text-is("{REPO_NAME}")') page.click(f'#folders-tbody a.folder-link:text-is("{folder}")') def _back_to_source_level(page: Page) -> None: """From a folder level, the breadcrumb's source segment (the "Knowledge base" segment goes all the way to the top).""" page.click(f'#kb-crumb a.kb-crumb-link:text-is("{REPO_NAME}")') expect(page.locator("#folders-tbody .folder-link")).to_have_count(2) # --------------------------------------------------------------------------- # 1. THE REGRESSION PIN: the API dates are the TRUE per-file commit # dates — 2020 for the old file, the tip date for the new file # --------------------------------------------------------------------------- def test_api_created_dates_are_true_per_file( page: Page, app_url: str, db_ready: None ) -> None: """After a real in-app sync of the ``file://`` fixture: the old file's ``created_at`` is its OLD commit date (2020-06-15) and the new file's is the TIP date (2024-06-15) — the two DIFFER (the phase-106 bug made every URL-source file carry the tip date, i.e. both ``2024-06-15``). The tree carries the same dates verbatim and the folder/source ``updated_at`` values are the subtree MAXES (D9). """ _sync_in_app(page, app_url) # GET /api/docs (the page context's request client — the login's # cookie jar rides along). r = page.request.get(f"{app_url}/api/docs") assert r.status == 200, r.text docs = {d["path"]: d for d in r.json()["documents"]} old_created = docs[OLD_PATH]["created_at"] tip_created = docs[TIP_PATH]["created_at"] assert old_created[:10] == "2020-06-15" assert tip_created[:10] == "2024-06-15" assert old_created != tip_created, ( "the two dates must DIFFER — the phase-106 bug made a URL " f"source carry the uniform tip date for both ({old_created!r}, {tip_created!r})" ) # The TRUE commit dates, verbatim (the %cI instants through D3 — # past dates pass normalize_doc_date unchanged). assert old_created == OLD_ISO assert tip_created == TIP_ISO # GET /api/docs/tree: the file nodes carry the same ISO dates # verbatim; the folder updated_at values are the subtree MAXES # (old folder = the 2020 date alone, recent folder = the 2024 # date alone, source = the whole subtree's 2024 max — D9, now over # TRUE dates). t = page.request.get(f"{app_url}/api/docs/tree") assert t.status == 200, t.text source = _source_node(t.json()) assert source["documents"] == 2 assert _find_file(source, OLD_PATH)["created_at"] == OLD_ISO assert _find_file(source, TIP_PATH)["created_at"] == TIP_ISO old_folder = _folder_node(source, "old") recent_folder = _folder_node(source, "recent") assert old_folder["documents"] == 1 assert old_folder["updated_at"] is not None assert old_folder["updated_at"][:10] == "2020-06-15" assert recent_folder["documents"] == 1 assert recent_folder["updated_at"] is not None assert recent_folder["updated_at"][:10] == "2024-06-15" assert source["updated_at"] is not None assert source["updated_at"][:10] == "2024-06-15" # max(2020, 2024) # --------------------------------------------------------------------------- # 2. The Sources tables render the DISTINCT created dates # --------------------------------------------------------------------------- def test_sources_tables_render_distinct_created_dates( page: Page, app_url: str, db_ready: None ) -> None: """The RAG view's FILE table ``Created`` column (phase 106 D8 — between ``Chunks`` and ``Indexed``) renders 2020 for the old file and 2024 for the new one, the FOLDER table ``Updated`` column (between ``Documents`` and ``Description``) renders 2020 / 2024 per folder, and the two file cells' visible text differs. Year regexes — ``toLocaleString`` is locale/TZ-dependent, the year is stable for the mid-year fixture dates; the full ISO rides the cells' ``title`` (asserted verbatim — the locale-stable idiom). textContent reads only — never set innerHTML.""" page.set_default_timeout(30_000) _sync_in_app(page, app_url) # Top level: one source row; its Updated cell carries the 2024 # subtree max. page.locator("#folders-tbody .folder-link").first.wait_for(state="visible") source_row = page.locator( f'#folders-tbody tr:has(a.folder-link:text-is("{REPO_NAME}"))' ) expect(source_row).to_have_count(1) expect(source_row.locator("td:nth-child(3)")).to_have_text(re.compile("2024")) # Drill into the source: the two folders, each Updated = its own # year (D9 through the UI). page.click(f'#folders-tbody a.folder-link:text-is("{REPO_NAME}")') expect(page.locator("#folders-tbody .folder-link")).to_have_count(2) old_row = page.locator('#folders-tbody tr:has(a.folder-link:text-is("old"))') recent_row = page.locator('#folders-tbody tr:has(a.folder-link:text-is("recent"))') expect(old_row).to_have_count(1) expect(recent_row).to_have_count(1) expect(old_row.locator("td:nth-child(3)")).to_have_text(re.compile("2020")) expect(recent_row.locator("td:nth-child(3)")).to_have_text(re.compile("2024")) # The FILE table: the old file's Created cell (the column between # Chunks and Indexed) carries the 2020 year in text + the FULL ISO # on the cell's title. page.click('#folders-tbody a.folder-link:text-is("old")') old_file_row = page.locator("#docs-tbody tr", has_text=OLD_PATH) expect(old_file_row).to_have_count(1) old_created_cell = old_file_row.locator("td:nth-child(5)") expect(old_created_cell).to_have_text(re.compile("2020"), timeout=15_000) assert old_created_cell.get_attribute("title", timeout=15_000) == OLD_ISO old_created_text = (old_created_cell.text_content() or "").strip() # Back to the source level, then the new file's Created cell: the # 2024 year + its full ISO. _back_to_source_level(page) page.click('#folders-tbody a.folder-link:text-is("recent")') recent_file_row = page.locator("#docs-tbody tr", has_text=TIP_PATH) expect(recent_file_row).to_have_count(1) recent_created_cell = recent_file_row.locator("td:nth-child(5)") expect(recent_created_cell).to_have_text(re.compile("2024"), timeout=15_000) assert recent_created_cell.get_attribute("title", timeout=15_000) == TIP_ISO recent_created_text = (recent_created_cell.text_content() or "").strip() # The two cells' visible text differs (the bug made them identical # — both the tip date). assert old_created_text and recent_created_text assert old_created_text != recent_created_text, ( f"the two Created cells must differ, both show {old_created_text!r}" ) # --------------------------------------------------------------------------- # 3. The viewer's Created badge carries the TRUE git date (raw ISO) # --------------------------------------------------------------------------- def test_viewer_created_badge_is_the_true_git_date( page: Page, app_url: str, db_ready: None ) -> None: """Opening the OLD document (the phase-26 same-page modal) shows the ``.doc-created`` badge (phase 106, before the ``Indexed`` badge) with the RAW ISO on its ``title`` — ``2020-06-15T12:00:00+00:00`` (``metaBadge``'s title, deterministic under any locale/TZ — the house solution to the L455 rendering trap) and ``Created …`` in the visible text; Escape closes the modal cleanly (focus restored to the row link).""" page.set_default_timeout(30_000) _sync_in_app(page, app_url) _drill_to_folder(page, "old") row = page.locator("#docs-tbody tr", has_text=OLD_PATH) expect(row).to_have_count(1) before_tabs = len(page.context.pages) row.locator("td:nth-child(2) a.doc-link").click() assert len(page.context.pages) == before_tabs, "row link must not open a new tab" expect(page.locator(".doc-modal")).to_be_visible() expect(page.locator("#doc-modal-title")).to_have_text("Old stable note") created = page.locator("#doc-modal-meta .doc-created") expect(created).to_have_count(1) # The badge text: "Created " (text + format pairing — # the date is carried by text, never color alone, B5). expect(created).to_have_text(re.compile(r"^Created \S")) # THE assertion: the TRUE old commit date, the full raw ISO on the # badge's title (hover precision, locale-independent). assert created.get_attribute("title", timeout=15_000) == OLD_ISO # Phase 106 D8 in the DOM: the Created badge PRECEDES the Indexed # badge (unchanged by this phase — it now simply carries the true # date). assert page.evaluate( """() => { const a = document.querySelector('#doc-modal-meta .doc-created'); const b = document.querySelector('#doc-modal-meta .doc-indexed'); return a !== null && b !== null && !!(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING); }""" ) # No regression: the source/format/chunks badges are all still there. meta = page.locator("#doc-modal-meta") expect(meta.locator(".doc-source-badge")).to_have_text(REPO_NAME) expect(meta.locator(".format-badge")).to_have_text("md") expect(meta.locator(".doc-chunks")).to_have_text(re.compile(r"^\d+ chunk")) # Close: Escape — the viewer reverts cleanly (the modal hides, # focus returns to the row link that opened it). page.keyboard.press("Escape") expect(page.locator(".doc-modal")).to_be_hidden() assert page.evaluate( "() => String(document.activeElement.className)" ) == "doc-link" # --------------------------------------------------------------------------- # 4. UI Structure Check (AGENTS.md rule 5) + no CDN (rule 6) — light pass # --------------------------------------------------------------------------- def test_page_a11y_and_no_cdn_basics( page: Page, app_url: str, db_ready: None ) -> None: """The standard light pass (the test_git_sources_admin.py a11y test shape): landmarks on the Sources view, the ``Created`` / ``Updated`` ```` cells present in both tables (this phase adds NO color — the phase-106 columns only, already ≥4.5:1 — so there is no new contrast surface to pin), the 3px ``:focus-visible`` outline on a table row link (a real keyboard Tab walk), and same-origin assets only (rule 6).""" page.set_default_timeout(30_000) _sync_in_app(page, app_url) # Standard app frame: landmarks + skip link (PLAN §7.2). expect(page.locator("header.app-header")).to_have_count(1) expect(page.locator('nav[aria-label="Primary"]')).to_have_count(1) expect(page.locator("main#main")).to_have_count(1) expect(page.locator("footer.app-footer")).to_have_count(1) expect(page.locator(".skip-link")).to_have_count(1) # The date columns' cells (phase 106 D8, verbatim — text_content, # not inner_text: the rendered s are CSS-uppercased). file_headers = [ th.text_content() for th in page.locator("#docs-table thead th").all() ] assert file_headers == ["Source", "Path", "Title", "Chunks", "Created", "Indexed"] folder_headers = [ th.text_content() for th in page.locator("#folders-table thead th").all() ] assert folder_headers == ["Folder", "Documents", "Updated", "Description"] # :focus-visible draws the 3px outline on a table row link — a real # keyboard Tab walk (the test_responsive_polish.py idiom): reset # focus to the top of the document, then Tab until the old file's # row link is focused (the page state is deterministic, so the # walk order is — the bound is generous, never the contract). _drill_to_folder(page, "old") expect(page.locator("#docs-tbody tr", has_text=OLD_PATH)).to_have_count(1) page.evaluate( "() => { if (document.activeElement instanceof HTMLElement)" " document.activeElement.blur(); }" ) reached = False for _ in range(150): page.keyboard.press("Tab") if page.evaluate("() => String(document.activeElement.className)") == "doc-link": reached = True break assert reached, "keyboard Tab never reached the table row link" outline = page.evaluate("() => getComputedStyle(document.activeElement).outlineWidth") assert outline == "3px", f"focus-visible outline missing on the row link: {outline!r}" # No CDN (rule 6): no https:// asset tags; every script/link ref is # same-origin or a data: URI. html = page.content() assert 'src="https://' not in html and 'href="https://' not in html refs = page.evaluate( """() => [...document.querySelectorAll("script[src], link[href]")] .map((el) => el.src || el.href)""" ) assert refs, "expected local asset references" for ref in refs: assert ref.startswith(app_url) or ref.startswith("data:"), ( f"non-local asset reference: {ref}" )