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/<id>, 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) <noreply@anthropic.com>
60 lines
2.4 KiB
Python
60 lines
2.4 KiB
Python
"""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/<id>, 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/<id>/merged/
|
|
directory via the dedicated /servers/<id>/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 <li> 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
|