Files
agent-desktop/e2e/tests/test_memory.py
T

186 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
"""
QwenPaw Long-term Memory end-to-end tests.
UI-driven only. Pure API contract tests for /api/workspace/memory and
/api/workspace/running-config live in ``tests/integration/``.
Cases:
- MEM-003 P1 test_memory_card_ui_renders
- MEM-004 P1 test_workspace_memory_md_visible
- MEM-005 P2 test_memory_search_recall_seeded (xfail, requires_llm)
"""
from __future__ import annotations
import logging
import time
import pytest
from playwright.sync_api import expect
from pages.memory_page import MemoryPage
from utils.helpers import log_test_step, log_test_result
logger = logging.getLogger(__name__)
# ============================================================================
# MEM-003 P1 — Long-term Memory card renders on /agent-config
# ============================================================================
@pytest.mark.integration
@pytest.mark.p1
@pytest.mark.memory
class TestMemoryCardUI:
"""MEM-003: Tab is visible; switching to it shows the card body."""
@pytest.mark.test_id("MEM-003")
def test_memory_card_ui_renders(
self,
memory_page: MemoryPage,
request: pytest.FixtureRequest,
) -> None:
test_name = request.node.name
log_test_step("1. Open /agent-config")
memory_page.open_agent_config()
log_test_step("2. 'Long-term Memory' tab is visible")
expect(
memory_page.page.locator(memory_page.MEMORY_TAB).first
).to_be_visible(timeout=memory_page.timeout)
log_test_step("3. Click the tab and verify the dream_cron input")
memory_page.click_memory_tab()
# The dream_cron input is unique to this card and is the most
# stable "card body rendered" signal; the card title text
# collides with the Tab label and the className is design-system
# specific.
expect(
memory_page.page.locator(memory_page.DREAM_CRON_INPUT).first
).to_be_visible(timeout=memory_page.timeout)
log_test_result(test_name, True, 0)
logger.info(f"Test {test_name} passed")
# ============================================================================
# MEM-004 P1 — MEMORY.md is visible in the Workspace files panel
# ============================================================================
@pytest.mark.integration
@pytest.mark.p1
@pytest.mark.memory
class TestWorkspaceMemoryMd:
"""MEM-004: Workspace lists MEMORY.md in the file panel.
Seeding via the workspace files API is a setup-only API call;
the assertion itself is rendered in the Workspace UI panel.
"""
@pytest.mark.test_id("MEM-004")
def test_workspace_memory_md_visible(
self,
memory_page: MemoryPage,
api_context,
request: pytest.FixtureRequest,
) -> None:
test_name = request.node.name
log_test_step("1. Seed MEMORY.md via the workspace files API")
# Isolated test backends start empty — MEMORY.md does not exist
# by default. Seed one so the file panel has it to render.
seed_resp = api_context.put(
"/api/workspace/files/MEMORY.md",
data={"content": "# Memory\n\ne2e seed\n"},
headers=memory_page._agent_headers(),
)
assert seed_resp.ok, (
f"Seed MEMORY.md failed [{seed_resp.status}]: {seed_resp.text()}"
)
log_test_step("2. Open /workspace")
memory_page.open_workspace()
log_test_step("3. MEMORY.md row is visible")
# The file list renders each entry as a div with class
# *fileItemName* — text-based locator is enough.
expect(
memory_page.page.locator('text="MEMORY.md"').first
).to_be_visible(timeout=memory_page.timeout)
log_test_result(test_name, True, 0)
logger.info(f"Test {test_name} passed")
# ============================================================================
# MEM-005 P2 — Memory search recall (xfail when LLM unavailable)
# ============================================================================
@pytest.mark.integration
@pytest.mark.requires_llm
@pytest.mark.p2
@pytest.mark.memory
class TestMemorySearchRecall:
"""
MEM-005: With a seeded daily memory entry containing a unique
keyword, asking the agent through the chat UI should produce a
reply that mentions the keyword. Strongly LLM- and embedding-
dependent; declared xfail strict=False so passes do not silently
regress.
"""
@pytest.mark.test_id("MEM-005")
@pytest.mark.xfail(
reason=(
"Requires a configured LLM and may also need embedding "
"infrastructure; environments without them will not recall "
"the seeded keyword."
),
strict=False,
)
def test_memory_search_recall_seeded(
self,
memory_page: MemoryPage,
api_context,
request: pytest.FixtureRequest,
) -> None:
test_name = request.node.name
keyword = f"e2eKW{int(time.time())}"
log_test_step(f"1. Seed memory with keyword {keyword}")
memory_page.api_write_daily_memory(
api_context,
"2099-02-15.md",
f"User mentioned the secret token {keyword} on this day.",
)
log_test_step("2. Open chat and ask about the keyword")
memory_page.page.goto(
f"{memory_page.WORKSPACE_URL.replace('/workspace', '/chat')}",
wait_until="commit",
timeout=memory_page.timeout,
)
chat_input = memory_page.page.locator(
"textarea.qwenpaw-sender-input"
).first
expect(chat_input).to_be_visible(timeout=memory_page.timeout)
chat_input.fill(
f"What did I previously say about {keyword}? Quote it."
)
send_btn = memory_page.page.locator(
"button.qwenpaw-sender-actions-btn.qwenpaw-btn-primary"
).first
send_btn.click()
log_test_step("3. Wait for AI bubble that mentions the keyword")
expect(
memory_page.page.locator(
f'.qwenpaw-bubble.qwenpaw-bubble-start:has-text("{keyword}")'
).first
).to_be_visible(timeout=180000)
log_test_result(test_name, True, 0)
logger.info(f"Test {test_name} passed")