""" 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" )