Files
2026-07-13 13:12:00 +08:00

76 lines
2.8 KiB
Python

"""E2E: the copy action on a user's own message bubble.
Users can copy assistant responses; the same affordance now sits below their
own messages. This drives the full browser → SPA → server stack: send a
message, click the Copy control beneath the user bubble, and assert the text
landed on the clipboard and the icon flipped to its copied state.
Selectors:
- user bubble: ``data-testid="message-bubble"`` + ``data-role="user"``
- copy button: accessible name "Copy" (MessageAction sr-only label/tooltip)
- copied state: lucide check icon (``svg.lucide-check``) replaces the copy
icon (``svg.lucide-copy``) for ~2s after a successful write
- composer: placeholder "Ask the agent anything…"
"""
from __future__ import annotations
import uuid
from playwright.sync_api import Browser, Page, expect
_COMPOSER_PLACEHOLDER = "Ask the agent anything…"
_USER_BUBBLE = '[data-testid="message-bubble"][data-role="user"]'
def _send(page: Page, text: str) -> None:
"""Type ``text`` into the composer and click Send."""
composer = page.get_by_placeholder(_COMPOSER_PLACEHOLDER)
expect(composer).to_be_visible()
composer.fill(text)
page.get_by_role("button", name="Send", exact=True).click()
def test_user_message_copy_button_copies_text(
browser: Browser,
seeded_session: tuple[str, str],
) -> None:
"""Clicking Copy under a user bubble writes its text to the clipboard.
A failure means the copy affordance regressed: either the button is not
rendered below the user bubble, its click handler no longer writes to
``navigator.clipboard``, or the copied-state icon swap broke.
Clipboard read/write requires the ``clipboard-read``/``clipboard-write``
permissions, granted on a dedicated context here so the default
function-scoped ``page`` fixture stays untouched.
"""
base_url, session_id = seeded_session
marker = f"copy-me-{uuid.uuid4().hex[:8]}"
ctx = browser.new_context()
ctx.grant_permissions(["clipboard-read", "clipboard-write"])
try:
page = ctx.new_page()
page.goto(f"{base_url}/c/{session_id}")
_send(page, marker)
bubble = page.locator(_USER_BUBBLE).filter(has_text=marker)
expect(bubble).to_be_visible(timeout=15_000)
copy_button = bubble.get_by_role("button", name="Copy")
# Copy icon is present before the click; hover-reveal only affects
# opacity, not DOM presence, so the button is always in the tree.
expect(copy_button.locator("svg.lucide-copy")).to_have_count(1)
copy_button.click()
# The write is async; the icon swaps to a check on success.
expect(copy_button.locator("svg.lucide-check")).to_have_count(1, timeout=5_000)
clipboard_text = page.evaluate("() => navigator.clipboard.readText()")
assert clipboard_text == marker
finally:
ctx.close()