File size: 4,801 Bytes
f87fe7f
 
 
 
2b5a90d
f87fe7f
2b5a90d
1f4c2be
f87fe7f
 
 
 
 
 
 
 
 
 
 
 
 
2b5a90d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f87fe7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b5a90d
1f4c2be
2b5a90d
1f4c2be
 
 
 
 
 
 
 
 
 
2b5a90d
 
 
 
 
f87fe7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b5a90d
 
 
 
 
f87fe7f
 
 
 
2b5a90d
f87fe7f
 
 
 
 
 
2b5a90d
f87fe7f
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""
Pytest configuration and shared fixtures for DemoPrep E2E tests.
"""
import os
import time
import pytest
import requests
from pathlib import Path
from dotenv import load_dotenv
from playwright.sync_api import Page, BrowserContext, Browser, expect

load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env'))

BASE_URL = "https://thoughtspot-dp-demoprep.hf.space"
TEST_USER = os.getenv("TEST_USER")
TEST_PASSWORD = os.getenv("TEST_PASSWORD")

if not TEST_USER or not TEST_PASSWORD:
    raise EnvironmentError("TEST_USER and TEST_PASSWORD must be set in .env")


def _wake_space(max_wait: int = 180) -> None:
    """
    Ping HF Spaces until it responds with HTTP 200.
    HF free-tier spaces go to sleep after inactivity and take 30-60s to wake.
    Raises if the space doesn't wake within max_wait seconds.
    """
    deadline = time.time() + max_wait
    last_status = None
    while time.time() < deadline:
        try:
            r = requests.get(BASE_URL, timeout=10, allow_redirects=True)
            last_status = r.status_code
            if r.status_code == 200:
                return
        except requests.RequestException:
            pass
        time.sleep(5)
    raise RuntimeError(
        f"HF Space did not wake within {max_wait}s (last HTTP status: {last_status})"
    )


def _do_login(page: Page) -> None:
    """Perform the login flow on a page."""
    page.goto(BASE_URL, timeout=90000)
    page.wait_for_selector('input[placeholder="Type here..."]', timeout=90000)
    page.fill('input[type=text]', TEST_USER)
    page.fill('input[type=password]', TEST_PASSWORD)
    page.click('button:has-text("Login")')
    page.wait_for_selector('.gradio-container', timeout=90000)
    page.wait_for_timeout(3000)  # allow Gradio JS to settle


def login(page: Page) -> None:
    """Log in to the app with test credentials (for tests that need a fresh login)."""
    _do_login(page)


@pytest.fixture(scope="session", autouse=True)
def wake_space(request):
    """Wake the HF Space before any tests run. Blocks until it's responsive."""
    e2e_files = {
        "e2e_chat.py",
        "e2e_settings.py",
        "e2e_smoke.py",
        "e2e_z_auth.py",
        "test_mcp_liveboard.py",
    }
    collected_files = {Path(str(item.fspath)).name for item in request.session.items}
    if not collected_files.intersection(e2e_files):
        return
    print("\n⏳ Waiting for HF Space to be ready...", flush=True)
    _wake_space(max_wait=180)
    print("✅ HF Space is up.", flush=True)


@pytest.fixture(scope="session")
def auth_context(browser: Browser, tmp_path_factory):
    """
    Session-scoped authenticated browser context.

    Logs in once, saves storage state, and yields a persistent context.
    All logged_in_page fixtures share this context, avoiding repeated expensive
    logins and connection setup against HF Spaces.
    """
    state_file = str(tmp_path_factory.mktemp("auth") / "state.json")

    # Log in on a temp page to capture auth state
    setup_ctx = browser.new_context(viewport={"width": 1280, "height": 900})
    setup_page = setup_ctx.new_page()
    _do_login(setup_page)
    setup_ctx.storage_state(path=state_file)
    setup_page.close()
    setup_ctx.close()

    # Create the long-lived context with saved auth state
    ctx = browser.new_context(
        storage_state=state_file,
        viewport={"width": 1280, "height": 900},
    )
    yield ctx
    ctx.close()


@pytest.fixture()
def logged_in_page(auth_context: BrowserContext):
    """
    Function-scoped: opens a fresh page in the shared authenticated context.
    Each test gets an isolated page (own URL, own Gradio session) without
    the overhead of a full login or new browser context.
    """
    page = auth_context.new_page()
    for attempt in range(2):
        try:
            page.goto(BASE_URL, timeout=120000)
            # Wait for tabs — the minimum signal that Gradio has initialized.
            # Individual tests wait for their own specific elements.
            page.wait_for_selector('button[role=tab]', timeout=120000)
            page.wait_for_timeout(3000)
            break
        except Exception:
            if attempt == 1:
                raise
            page.wait_for_timeout(10000)
    yield page
    page.close()


def get_chat_input(page: Page):
    """Return the main chat message input field."""
    return page.locator('input[placeholder*="Amazon.com"]')


# Alias for backwards compatibility
def get_chat_textarea(page: Page):
    return get_chat_input(page)


def wait_for_response(page: Page, timeout_ms: int = 90000) -> None:
    """Wait until the assistant has finished responding."""
    send_btn = page.locator('button:has-text("Send")')
    expect(send_btn).to_be_enabled(timeout=timeout_ms)
    page.wait_for_timeout(500)