From d21cd72f8d938b8069aa2ae4532db9ce05189c44 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Sun, 17 May 2026 19:22:17 +0200 Subject: [PATCH] test(files): cover server-detail hover-download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New test module (test_server_detail.py) — the server-detail page is NOT a files-overlay (files_overlay=False in the template), it just reuses _overlay_file_tree.html in read-only mode. Tests live separately to make the semantic split visible. The test navigates to /servers/, hovers the server.cfg row to defeat the CSS :hover gate on .files-row-actions (opacity:0/pointer-events:none → 1/auto), clicks the ⬇ download link, and asserts both the suggested filename and the byte content of the downloaded file. The :hover gate is load-bearing: without locator.hover() first, pointer-events:none blocks the click. A regression that ships actions always-visible would change the user-flow ergonomics and needs to update this test deliberately. Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md (Tier 3). Co-Authored-By: Claude Opus 4.7 (1M context) --- l4d2web/tests/e2e/test_server_detail.py | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 l4d2web/tests/e2e/test_server_detail.py diff --git a/l4d2web/tests/e2e/test_server_detail.py b/l4d2web/tests/e2e/test_server_detail.py new file mode 100644 index 0000000..50973c3 --- /dev/null +++ b/l4d2web/tests/e2e/test_server_detail.py @@ -0,0 +1,60 @@ +"""End-to-end Playwright tests for the server detail page. + +Distinct from test_files_overlay.py because the server-detail page +is NOT a files-overlay (`files_overlay=False` in the template). It +reuses the same `_overlay_file_tree.html` partial but renders rows +in read-only mode — no edit button on filenames, no delete action, +only the download anchor in the per-row action strip. + +Tier 3 from docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md. +""" +from __future__ import annotations + +import pytest +from playwright.sync_api import Page, expect + +from .conftest import login + +pytestmark = pytest.mark.e2e + + +def test_hover_download_initiates_file_download(page: Page, server_with_files) -> None: + """Navigate to /servers/, hover a file row to defeat the + `opacity: 0; pointer-events: none` CSS gate on `.files-row-actions`, + click the ⬇ download link, and assert the browser receives a file + download with the expected filename + bytes. + + Pins the contract that the read-only file tree on server-detail + pages can download files served from the runtime//merged/ + directory via the dedicated /servers//files/download + endpoint (separate from the files-overlay download path). + + The CSS hover gate is non-decorative: without `locator.hover()` + first, `pointer-events: none` makes the link unclickable. A + regression that ships row actions always-visible (or always-hidden) + would change the user-flow ergonomics; if the gate semantics ever + move, this test needs an update. + """ + base = server_with_files["base_url"] + server_id = server_with_files["server_id"] + merged_root = server_with_files["merged_root"] + expected_bytes = (merged_root / "server.cfg").read_bytes() + + login(page, base) + page.goto(f"{base}/servers/{server_id}") + + # On server detail, files_overlay=False in the template, so the + # row
  • has no data-target-path. Match by row class + visible + # filename text instead. + row = page.locator("li.file-tree-row-file", has_text="server.cfg") + expect(row).to_be_visible(timeout=5000) + row.hover() + + download_link = row.locator('a.files-row-action[title="Download"]') + with page.expect_download() as dl_info: + download_link.click() + download = dl_info.value + + assert download.suggested_filename == "server.cfg" + # Playwright auto-saves to a temp dir we can read back from. + assert download.path().read_bytes() == expected_bytes