left4me/l4d2web/tests/e2e/test_server_detail.py
mwiegand 2d28d9f800
test(e2e): tab switching + expand-to-modal on server detail
Append two new e2e tests that cover the inspection strip:
- test_tabs_switch_between_log_console_files: verifies data-active-tab
  attribute mirrors tab clicks and the correct tabpanel is shown/hidden
- test_expand_opens_matching_modal: verifies the ⛶ button opens the
  <dialog> matching the active tab name

Also fix the pre-existing test_hover_download_initiates_file_download
for the new tab-based layout: click the Files tab first (rows were
inside a hidden tabpanel), and scope the row locator to the tab pane
to avoid a strict-mode violation now that the modal also contains a
duplicate file tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:32:19 +02:00

110 lines
4.5 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}")
# The file tree lives inside the Files tab pane (hidden by default).
# Switch to it first so the row is visible and hover-clickable.
strip = page.locator("[data-tab-strip]")
strip.locator('[role="tab"][data-tab="files"]').click()
# On server detail, files_overlay=False in the template, so the
# row <li> has no data-target-path. Scope to the tab pane (not the
# expand modal which also contains a copy of the file tree) and match
# by row class + visible filename text.
files_pane = strip.locator('[role="tabpanel"][data-tab="files"]')
row = files_pane.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
def test_tabs_switch_between_log_console_files(page: Page, server_with_files) -> None:
"""The inspection strip on /servers/<id> defaults to the Log tab and
switches tab + tabpane visibility on click. Active-tab state is
mirrored on the strip via data-active-tab.
"""
base = server_with_files["base_url"]
sid = server_with_files["server_id"]
login(page, base)
page.goto(f"{base}/servers/{sid}")
strip = page.locator("[data-tab-strip]")
expect(strip).to_be_visible()
expect(strip).to_have_attribute("data-active-tab", "log")
# Click Console
strip.locator('[role="tab"][data-tab="console"]').click()
expect(strip).to_have_attribute("data-active-tab", "console")
expect(strip.locator('[role="tabpanel"][data-tab="console"]')).to_be_visible()
expect(strip.locator('[role="tabpanel"][data-tab="log"]')).to_be_hidden()
# Click Files
strip.locator('[role="tab"][data-tab="files"]').click()
expect(strip).to_have_attribute("data-active-tab", "files")
expect(strip.locator('[role="tabpanel"][data-tab="files"]')).to_be_visible()
def test_expand_opens_matching_modal(page: Page, server_with_files) -> None:
"""Clicking ⛶ on the inspection strip opens the <dialog> whose id
matches the active tab name (data-active-tab="files" → #files-modal).
"""
base = server_with_files["base_url"]
sid = server_with_files["server_id"]
login(page, base)
page.goto(f"{base}/servers/{sid}")
strip = page.locator("[data-tab-strip]")
strip.locator('[role="tab"][data-tab="files"]').click()
strip.locator(".strip-expand").click()
dialog = page.locator("dialog#files-modal")
expect(dialog).to_have_attribute("open", "")