fix(e2e): cloud auth via cookie, local port, Playwright WebSocket gotcha
E2E harness fixes to get all three modes (demo/cloud/local) passing: - conftest.py: use ctx.add_cookies() for cloud auth instead of ctx.route() or set_extra_http_headers(). Playwright's route() only intercepts HTTP; set_extra_http_headers() explicitly excludes WebSocket handshakes. Streamlit reads st.context.headers from the WebSocket upgrade, so cookies are the only vehicle that reaches it without a reverse proxy. - cloud_session.py: fall back to Cookie header when X-CF-Session is absent — supports direct access (E2E tests, dev without Caddy). In production Caddy sets X-CF-Session; in tests the cf_session cookie is set on the browser context and arrives in the Cookie header. - modes/cloud.py: add /peregrine base URL path (STREAMLIT_SERVER_BASE_URL_PATH=peregrine) - modes/local.py: correct port from 8502 → 8501 and add /peregrine path All three modes now pass smoke + interaction tests clean.
This commit is contained in:
parent
5d14542142
commit
cb8afa6539
4 changed files with 19 additions and 8 deletions
|
|
@ -151,7 +151,12 @@ def resolve_session(app: str = "peregrine") -> None:
|
||||||
if st.session_state.get("user_id"):
|
if st.session_state.get("user_id"):
|
||||||
return
|
return
|
||||||
|
|
||||||
cookie_header = st.context.headers.get("x-cf-session", "")
|
# Primary: Caddy injects X-CF-Session header in production.
|
||||||
|
# Fallback: direct access (E2E tests, dev without Caddy) reads the cookie header.
|
||||||
|
cookie_header = (
|
||||||
|
st.context.headers.get("x-cf-session", "")
|
||||||
|
or st.context.headers.get("cookie", "")
|
||||||
|
)
|
||||||
session_jwt = _extract_session_token(cookie_header)
|
session_jwt = _extract_session_token(cookie_header)
|
||||||
if not session_jwt:
|
if not session_jwt:
|
||||||
_render_auth_wall("Please sign in to access Peregrine.")
|
_render_auth_wall("Please sign in to access Peregrine.")
|
||||||
|
|
|
||||||
|
|
@ -93,11 +93,17 @@ def mode_contexts(active_modes, playwright) -> dict[str, BrowserContext]:
|
||||||
for mode in active_modes:
|
for mode in active_modes:
|
||||||
ctx = browser.new_context(viewport={"width": 1280, "height": 900})
|
ctx = browser.new_context(viewport={"width": 1280, "height": 900})
|
||||||
if mode.name == "cloud":
|
if mode.name == "cloud":
|
||||||
def _inject_jwt(route, request):
|
# Cookies are sent on WebSocket upgrade requests; set_extra_http_headers
|
||||||
|
# and ctx.route() are both HTTP-only and miss st.context.headers.
|
||||||
|
# cloud_session.py falls back to the Cookie header when X-CF-Session
|
||||||
|
# is absent (direct access without Caddy).
|
||||||
jwt = _get_jwt()
|
jwt = _get_jwt()
|
||||||
headers = {**request.headers, "x-cf-session": f"cf_session={jwt}"}
|
ctx.add_cookies([{
|
||||||
route.continue_(headers=headers)
|
"name": "cf_session",
|
||||||
ctx.route(f"{mode.base_url}/**", _inject_jwt)
|
"value": jwt,
|
||||||
|
"domain": "localhost",
|
||||||
|
"path": "/",
|
||||||
|
}])
|
||||||
else:
|
else:
|
||||||
mode.auth_setup(ctx)
|
mode.auth_setup(ctx)
|
||||||
contexts[mode.name] = ctx
|
contexts[mode.name] = ctx
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ def _cloud_auth_setup(context: Any) -> None:
|
||||||
|
|
||||||
CLOUD = ModeConfig(
|
CLOUD = ModeConfig(
|
||||||
name="cloud",
|
name="cloud",
|
||||||
base_url="http://localhost:8505",
|
base_url="http://localhost:8505/peregrine",
|
||||||
auth_setup=_cloud_auth_setup,
|
auth_setup=_cloud_auth_setup,
|
||||||
expected_failures=[],
|
expected_failures=[],
|
||||||
results_dir=Path("tests/e2e/results/cloud"),
|
results_dir=Path("tests/e2e/results/cloud"),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ _BASE_SETTINGS_TABS = [
|
||||||
|
|
||||||
LOCAL = ModeConfig(
|
LOCAL = ModeConfig(
|
||||||
name="local",
|
name="local",
|
||||||
base_url="http://localhost:8502",
|
base_url="http://localhost:8501/peregrine",
|
||||||
auth_setup=lambda ctx: None,
|
auth_setup=lambda ctx: None,
|
||||||
expected_failures=[],
|
expected_failures=[],
|
||||||
results_dir=Path("tests/e2e/results/local"),
|
results_dir=Path("tests/e2e/results/local"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue