| from __future__ import annotations |
|
|
| import inspect |
| import os |
|
|
| from playwright.sync_api import expect, sync_playwright |
|
|
|
|
| BASE_URL = os.getenv("MATCHDAY_E2E_BASE_URL", "http://127.0.0.1:3100") |
| PROOF_TIMEOUT_MS = int(os.getenv("MATCHDAY_E2E_PROOF_TIMEOUT_MS", "30000")) |
| E2E_BROWSER = os.getenv("MATCHDAY_E2E_BROWSER", "chromium").strip().lower() |
| SUPPORTED_E2E_BROWSERS = {"chromium", "firefox", "webkit"} |
|
|
| if E2E_BROWSER not in SUPPORTED_E2E_BROWSERS: |
| raise RuntimeError( |
| "MATCHDAY_E2E_BROWSER must be one of chromium, firefox, or webkit" |
| ) |
|
|
|
|
| def launch_browser(playwright): |
| return getattr(playwright, E2E_BROWSER).launch(headless=True) |
|
|
|
|
| def test_manual_profile_memory_is_recalled_in_chat_across_browser_reload() -> None: |
| |
| |
| with sync_playwright() as playwright: |
| browser = launch_browser(playwright) |
| context = browser.new_context(viewport={"width": 1280, "height": 900}) |
| page = context.new_page() |
|
|
| page.goto(BASE_URL, wait_until="domcontentloaded") |
| add = page.get_by_role("button", name="Add preference") |
| expect(add).to_be_enabled(timeout=PROOF_TIMEOUT_MS) |
| add.click() |
| page.get_by_label("Preference type").select_option("home_airport") |
| page.get_by_label("Preference value").fill("YUL") |
| page.get_by_role("button", name="Save preference").click() |
| home = page.locator('[data-memory-key="home_airport"]') |
| expect(home.locator("strong")).to_have_text("YUL", timeout=PROOF_TIMEOUT_MS) |
|
|
| page.reload(wait_until="domcontentloaded") |
| expect(page.locator('[data-memory-key="home_airport"] strong')).to_have_text( |
| "YUL", |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
| page.get_by_label("Trip request").fill("what are my preferences?") |
| page.get_by_role("button", name="Plan this trip").click() |
|
|
| expect(page.get_by_text("Home airport: YUL", exact=False).first).to_be_visible( |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
| page.get_by_text("Full run log", exact=True).click() |
| expect( |
| page.get_by_text( |
| "Recalled 1 relevant confirmed preferences.", |
| exact=True, |
| ) |
| ).to_be_visible( |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
| expect(page.get_by_text("FAILED", exact=True)).to_have_count(0) |
|
|
| context.close() |
| browser.close() |
|
|
|
|
| def test_saved_home_airport_conflict_is_clarified_then_replans_in_place() -> None: |
| with sync_playwright() as playwright: |
| browser = launch_browser(playwright) |
| context = browser.new_context(viewport={"width": 1280, "height": 900}) |
| page = context.new_page() |
|
|
| page.goto(BASE_URL, wait_until="domcontentloaded") |
| page.get_by_role("button", name="Add preference").click() |
| page.get_by_label("Preference type").select_option("home_airport") |
| page.get_by_label("Preference value").fill("YUL") |
| page.get_by_role("button", name="Save preference").click() |
| expect(page.locator('[data-memory-key="home_airport"] strong')).to_have_text( |
| "YUL", |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
|
|
| composer = page.get_by_label("Trip request") |
| composer.fill("Plan the 2026 World Cup Final from YVR under C$2,200.") |
| page.get_by_role("button", name="Plan this trip").click() |
| expect( |
| page.locator('[data-proof-step="verified"][data-proof-state="done"]') |
| ).to_be_visible(timeout=PROOF_TIMEOUT_MS) |
| parent_run_id = page.evaluate( |
| "localStorage.getItem('matchday-v3-active-run')" |
| ) |
| assert parent_run_id |
|
|
| composer.fill("book me another package basedon my new preference") |
| page.get_by_role("button", name="Send follow-up").click() |
| page.wait_for_function( |
| "parent => localStorage.getItem('matchday-v3-active-run') !== parent", |
| arg=parent_run_id, |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
| approval = page.locator(".approval-panel") |
| expect(approval).to_be_visible(timeout=PROOF_TIMEOUT_MS) |
| expect(approval).to_contain_text("saved home airport is YUL") |
| expect(approval).to_contain_text("currently starts at YVR") |
|
|
| held = page.evaluate( |
| """async () => { |
| const id = localStorage.getItem('matchday-v3-active-run'); |
| return await fetch(`/api/backend/v1/runs/${id}`, {cache: 'no-store'}).then(r => r.json()); |
| }""" |
| ) |
| assert held["status"] == "awaiting_approval" |
| recalled = next( |
| event for event in held["events"] if event["type"] == "memory.recalled" |
| ) |
| assert "home_airport" in { |
| memory["key"] for memory in recalled["payload"]["memories"] |
| } |
|
|
| expect(approval).to_contain_text("Which origin should I use") |
| approval.get_by_label("Clarification answer").fill("YUL") |
| approval.get_by_role("button", name="Continue this run").click() |
| expect( |
| page.locator('[data-proof-step="verified"][data-proof-state="done"]') |
| ).to_be_visible(timeout=PROOF_TIMEOUT_MS) |
| expect(page.locator(".trip-hero")).to_contain_text( |
| "YUL", |
| timeout=PROOF_TIMEOUT_MS, |
| ) |
| completed = page.evaluate( |
| """async () => { |
| const id = localStorage.getItem('matchday-v3-active-run'); |
| return await fetch(`/api/backend/v1/runs/${id}`, {cache: 'no-store'}).then(r => r.json()); |
| }""" |
| ) |
| assert completed["id"] == held["id"] |
| assert completed["status"] == "completed" |
| assert completed["result"]["itinerary"]["intent"]["origin_airport"] == "YUL" |
|
|
| context.close() |
| browser.close() |
|
|
|
|
| def test_memory_journeys_do_not_bypass_the_configured_browser_launcher() -> None: |
| for journey in ( |
| test_manual_profile_memory_is_recalled_in_chat_across_browser_reload, |
| test_saved_home_airport_conflict_is_clarified_then_replans_in_place, |
| ): |
| source = inspect.getsource(journey) |
| assert "launch_browser(playwright)" in source |
| assert "playwright.chromium" not in source |
|
|