test(e2e): console transcript pinned to bottom on tab + submit

Adds server_with_console_history fixture (30 seeded CommandHistory rows)
and two Playwright tests that verify the inline Console transcript is
scrolled to its bottom when the Console tab is activated and after a
command is submitted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-21 09:29:05 +02:00
parent 06a358943e
commit 0307416b92
No known key found for this signature in database
2 changed files with 77 additions and 0 deletions

View file

@ -269,3 +269,27 @@ def server_with_files(tmp_path, monkeypatch):
}
finally:
shutdown()
@pytest.fixture(scope="function")
def server_with_console_history(server_with_files):
"""server_with_files + 30 seeded CommandHistory rows for that server,
so the inline Console transcript exceeds its visible height and the
autoscroll behaviour is observable."""
from datetime import UTC, datetime, timedelta
from l4d2web.models import CommandHistory
sid = server_with_files["server_id"]
uid = server_with_files["user_id"]
with session_scope() as session:
for i in range(30):
session.add(CommandHistory(
user_id=uid,
server_id=sid,
command=f"seed_{i:02d}",
reply=f"reply {i}",
is_error=False,
created_at=datetime.now(UTC) - timedelta(minutes=35 - i),
))
return server_with_files

View file

@ -108,3 +108,56 @@ def test_expand_opens_matching_modal(page: Page, server_with_files) -> None:
dialog = page.locator("dialog#files-modal")
expect(dialog).to_have_attribute("open", "")
def test_console_tab_pinned_to_bottom_on_activation(page: Page, server_with_console_history) -> None:
"""Clicking the Console tab leaves the transcript scrolled to its
bottom the newest seeded command must be visible, not the oldest."""
base = server_with_console_history["base_url"]
sid = server_with_console_history["server_id"]
login(page, base)
page.goto(f"{base}/servers/{sid}")
strip = page.locator("[data-tab-strip]")
strip.locator('[role="tab"][data-tab="console"]').click()
transcript = page.locator(f"#console-transcript-inline-{sid}")
expect(transcript).to_be_visible()
bottom_distance = transcript.evaluate(
"(el) => el.scrollHeight - el.scrollTop - el.clientHeight"
)
assert abs(bottom_distance) < 2, f"transcript not pinned to bottom: {bottom_distance}px"
line_count = transcript.locator(".console-line").count()
assert line_count == 20, f"inline expected 20 lines, got {line_count}"
def test_console_pane_pinned_after_command_submit(page: Page, server_with_console_history) -> None:
"""After submitting a command, the transcript scrolls so the new line
is visible at the bottom.
The dev server has no live RCON, but the POST still records a
CommandHistory row and HTMX appends a console-line to the transcript;
that's enough to exercise the autoscroll path.
"""
base = server_with_console_history["base_url"]
sid = server_with_console_history["server_id"]
login(page, base)
page.goto(f"{base}/servers/{sid}")
strip = page.locator("[data-tab-strip]")
strip.locator('[role="tab"][data-tab="console"]').click()
transcript = page.locator(f"#console-transcript-inline-{sid}")
pane = page.locator('[role="tabpanel"][data-tab="console"]')
cmd_input = pane.locator('input[name="command"]')
cmd_input.fill("verify_submit")
cmd_input.press("Enter")
expect(transcript.locator(".console-line", has_text="verify_submit")).to_be_visible()
bottom_distance = transcript.evaluate(
"(el) => el.scrollHeight - el.scrollTop - el.clientHeight"
)
assert abs(bottom_distance) < 2, f"transcript not pinned after submit: {bottom_distance}px"