trainUs/tests/test_step28_init_retry.py
David 270ff24c60
Some checks failed
CI / Lint & Format (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Tests (${{ matrix.browser }}) (chromium) (push) Has been cancelled
CI / Tests (${{ matrix.browser }}) (firefox) (push) Has been cancelled
Fix some issue at first load and cache issue
2026-07-26 13:03:09 +02:00

150 lines
5.6 KiB
Python

"""
Step 28 — First-load network failure: clear error + in-app retry
On a first load nothing is cached yet, so the DB worker downloads sqlite3.wasm
over the network. These tests simulate that download failing and verify the
app shows a network-specific error with a working "Try again" button instead
of the misleading "browser not compatible" dead end.
The wasm-route tests are Chromium-only: Playwright cannot intercept requests
made inside a dedicated worker on Firefox, so the download failure cannot be
simulated there. The retry machinery itself is covered on both browsers by
test_worker_load_failure_retry, which breaks worker creation instead.
- test_wasm_fetch_failure_shows_network_error (chromium): blocking
sqlite3.wasm shows the db-error overlay tagged DB_LOAD_FAILED with a retry
button
- test_retry_recovers_once_network_is_back (chromium): unblocking the request
and clicking "Try again" initializes the app in place, without a page reload
- test_retry_fails_again_while_still_offline (chromium): retrying with the
network still down shows the error overlay again (retry stays available)
- test_worker_load_failure_retry (both browsers): a DB worker that fails to
load shows the error overlay with retry; fixing the worker and clicking
"Try again" brings the app up in place
"""
import pytest
from playwright.sync_api import Browser, Page, expect
WASM_ROUTE = "**/sqlite3.wasm"
# Wrap window.Worker so the DB worker initially points at a 404 URL (the
# worker fails to load, like on a broken network). window.__fixWorker()
# restores normal behaviour for the retry.
_BREAK_WORKER = """
(() => {
const RealWorker = window.Worker;
let broken = true;
window.__fixWorker = () => { broken = false; };
window.Worker = function (url, opts) {
let target = url;
if (broken) {
// Corrupt the pathname (not the raw string: Vite dev appends a query
// string, and appending there would leave the path resolvable)
const u = new URL(url, window.location.href);
u.pathname += '.broken-by-test';
target = u;
}
return new RealWorker(target, opts);
};
})();
"""
@pytest.fixture
def wasm_blocked_page(browser: Browser):
"""A page in its own context (service workers blocked so route interception
reliably sees the worker's wasm fetch) where sqlite3.wasm requests fail."""
context = browser.new_context(
viewport={"width": 1280, "height": 720},
service_workers="block",
)
page = context.new_page()
page.route(WASM_ROUTE, lambda route: route.abort())
yield page
context.close()
def _wait_for_error_overlay(page: Page):
overlay = page.locator('[data-testid="db-error"]')
expect(overlay).to_be_visible(timeout=20000)
return overlay
@pytest.mark.only_browser("chromium")
def test_wasm_fetch_failure_shows_network_error(wasm_blocked_page, app_url: str):
"""A failed sqlite3.wasm download shows the error overlay with the
network-specific message (DB_LOAD_FAILED) and a retry button."""
page = wasm_blocked_page
page.goto(app_url)
_wait_for_error_overlay(page)
err = page.evaluate("document.documentElement.getAttribute('data-db-error')")
assert err and err.startswith("DB_LOAD_FAILED"), (
f"Expected a DB_LOAD_FAILED tagged error, got: {err!r}"
)
expect(page.locator('[data-testid="db-retry"]')).to_be_visible()
@pytest.mark.only_browser("chromium")
def test_retry_recovers_once_network_is_back(wasm_blocked_page, app_url: str):
"""Unblocking the wasm request and clicking 'Try again' brings the app up
in place — no page reload involved."""
page = wasm_blocked_page
page.goto(app_url)
_wait_for_error_overlay(page)
# Marker survives only if no navigation/reload happens during the retry
page.evaluate("window.__retryMarker = true")
page.unroute(WASM_ROUTE)
page.locator('[data-testid="db-retry"]').click()
page.wait_for_function(
"document.documentElement.getAttribute('data-db-ready') === 'true'",
timeout=20000,
)
expect(page.locator('[data-testid="db-error"]')).not_to_be_visible()
assert page.evaluate("window.__retryMarker") is True, (
"The retry must recover in place, not through a page reload"
)
@pytest.mark.only_browser("chromium")
def test_retry_fails_again_while_still_offline(wasm_blocked_page, app_url: str):
"""Retrying while the network is still down fails again and keeps the
retry button available."""
page = wasm_blocked_page
page.goto(app_url)
_wait_for_error_overlay(page)
page.locator('[data-testid="db-retry"]').click()
# The overlay disappears (loading spinner) then comes back with the button
_wait_for_error_overlay(page)
expect(page.locator('[data-testid="db-retry"]')).to_be_visible()
def test_worker_load_failure_retry(page: Page, app_url: str):
"""Cross-browser: a DB worker that fails to load shows the error overlay
with a retry button, and retrying after the 'network' recovers initializes
the app in place."""
page.add_init_script(_BREAK_WORKER)
page.goto(app_url)
_wait_for_error_overlay(page)
expect(page.locator('[data-testid="db-retry"]')).to_be_visible()
page.evaluate("window.__retryMarker = true")
page.evaluate("window.__fixWorker()")
page.locator('[data-testid="db-retry"]').click()
page.wait_for_function(
"document.documentElement.getAttribute('data-db-ready') === 'true'",
timeout=20000,
)
expect(page.locator('[data-testid="db-error"]')).not_to_be_visible()
assert page.evaluate("window.__retryMarker") is True, (
"The retry must recover in place, not through a page reload"
)