magicciv/public/games/age-of-dwarves/guide/test-results/guide-age-of-dwarves/all-routes-route-coverage-intro-full-game-chromium/error-context.md
Natalie 0cee526b07 fix(@projects/@magic-civilization): 🐛 clean up test artifacts and error docs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-18 03:02:55 -07:00

9.3 KiB

Instructions

  • Following Playwright test failed.
  • Explain why, be concise, respect Playwright best practices.
  • Provide a snippet of code with the fix, if possible.

Test info

  • Name: all-routes.spec.ts >> route coverage >> intro: full game
  • Location: e2e/all-routes.spec.ts:135:5

Error details

TimeoutError: page.goto: Timeout 10000ms exceeded.
Call log:
  - navigating to "http://localhost:5802/intro/full-game?skip=welcome", waiting until "networkidle"

Test source

  38  |   { path: '/intro/expansions',                   timeoutMs: 10_000, label: 'intro: expansions' },
  39  |   { path: '/intro/tools',                        timeoutMs: 10_000, label: 'intro: tools' },
  40  |   { path: '/episodes/age-of-dwarves',            timeoutMs: 10_000, label: 'episode: age of dwarves' },
  41  |   { path: '/about/early-access',                 timeoutMs: 10_000, label: 'about: early access' },
  42  |   { path: '/about/early-access/progress',        timeoutMs: 10_000, label: 'about: early access progress' },
  43  |   { path: '/progress',                           timeoutMs: 10_000, label: 'progress report' },
  44  |   { path: '/about/crowdfund',                    timeoutMs: 10_000, label: 'about: crowdfund' },
  45  |   { path: '/about/team',                         timeoutMs: 10_000, label: 'about: team' },
  46  |   { path: '/encyclopedia',                       timeoutMs: 10_000, label: 'encyclopedia index' },
  47  |   { path: '/encyclopedia/terrain',               timeoutMs: 10_000, label: 'encyclopedia: terrain topic' },
  48  |   { path: '/map/terrain',                        timeoutMs: 10_000, label: 'map: terrain' },
  49  |   { path: '/map/resources',                      timeoutMs: 10_000, label: 'map: resources' },
  50  |   { path: '/map/map-types',                      timeoutMs: 10_000, label: 'map: map types' },
  51  |   { path: '/economy/resources',                  timeoutMs: 10_000, label: 'economy: resources' },
  52  |   { path: '/climate',                            timeoutMs: 15_000, label: 'climate: overview' },
  53  |   { path: '/climate/weather',                    timeoutMs: 15_000, label: 'climate: weather' },
  54  |   { path: '/climate/terrain',                    timeoutMs: 15_000, label: 'climate: terrain evolution' },
  55  |   { path: '/climate/survival',                   timeoutMs: 15_000, label: 'climate: survival' },
  56  |   { path: '/climate/ecosystem',                  timeoutMs: 15_000, label: 'climate: ecosystem' },
  57  |   { path: '/climate/ecosystem/biomes',           timeoutMs: 15_000, label: 'ecosystem: biomes' },
  58  |   { path: '/climate/ecosystem/flora',            timeoutMs: 15_000, label: 'ecosystem: flora' },
  59  |   { path: '/climate/ecosystem/fauna',            timeoutMs: 15_000, label: 'ecosystem: fauna' },
  60  |   { path: '/climate/ecosystem/connections',      timeoutMs: 15_000, label: 'ecosystem: connections' },
  61  |   { path: '/climate/ecosystem/food-web',         timeoutMs: 15_000, label: 'ecosystem: food web' },
  62  |   { path: '/climate/ecosystem/populations',      timeoutMs: 15_000, label: 'ecosystem: populations' },
  63  |   { path: '/climate/ecosystem/lairs',            timeoutMs: 15_000, label: 'ecosystem: lairs' },
  64  |   { path: '/climate/ecosystem/stratigraphy',     timeoutMs: 15_000, label: 'ecosystem: stratigraphy' },
  65  |   { path: '/climate/ecosystem/evolution',        timeoutMs: 15_000, label: 'ecosystem: evolution' },
  66  |   { path: '/empire/races',                       timeoutMs: 10_000, label: 'empire: races' },
  67  |   { path: '/empire/personality',                 timeoutMs: 10_000, label: 'empire: personality' },
  68  |   { path: '/empire/government',                  timeoutMs: 10_000, label: 'empire: government' },
  69  |   { path: '/empire/eras',                        timeoutMs: 10_000, label: 'empire: eras' },
  70  |   { path: '/empire/victory',                     timeoutMs: 10_000, label: 'empire: victory' },
  71  |   { path: '/research/tech-tree',                 timeoutMs: 10_000, label: 'research: tech tree' },
  72  |   { path: '/research/culture-tree',              timeoutMs: 10_000, label: 'research: culture tree' },
  73  |   { path: '/military/units',                     timeoutMs: 10_000, label: 'military: units' },
  74  |   { path: '/military/combat',                    timeoutMs: 10_000, label: 'military: combat' },
  75  |   { path: '/military/keywords',                  timeoutMs: 10_000, label: 'military: keywords' },
  76  |   { path: '/military/promotions',                timeoutMs: 10_000, label: 'military: promotions' },
  77  |   { path: '/military/items',                     timeoutMs: 10_000, label: 'military: items' },
  78  |   { path: '/buildings/buildings',                timeoutMs: 10_000, label: 'buildings: buildings' },
  79  |   { path: '/buildings/improvements',             timeoutMs: 10_000, label: 'buildings: improvements' },
  80  |   { path: '/buildings/wonders',                  timeoutMs: 10_000, label: 'buildings: wonders' },
  81  |   { path: '/buildings/communications',           timeoutMs: 10_000, label: 'buildings: communications' },
  82  |   { path: '/playing/lenses',                     timeoutMs: 10_000, label: 'playing: lenses' },
  83  |   { path: '/dev/sprites',                        timeoutMs: 10_000, label: 'dev: sprites' },
  84  | ] as const satisfies readonly RouteSpec[]
  85  | 
  86  | // Routes that trigger WASM compile + world generation. Keep the budget
  87  | // generous but bounded so a hung route fails loudly instead of stalling.
  88  | const SLOW_ROUTES = [
  89  |   { path: '/climate/simulation', timeoutMs: 150_000, label: 'climate: simulation' },
  90  |   { path: '/worlds/khazad-prime', timeoutMs: 150_000, label: 'worlds: khazad-prime' },
  91  | ] as const satisfies readonly RouteSpec[]
  92  | 
  93  | // Non-error console noise we deliberately ignore. React-dev-tools and a few
  94  | // dev-only warnings surface at `error` level but are not runtime failures.
  95  | const IGNORED_ERROR_PATTERNS: readonly RegExp[] = [
  96  |   /Download the React DevTools/,
  97  | ]
  98  | 
  99  | interface ConsoleErrorCapture {
  100 |   readonly pageErrors: string[]
  101 |   readonly consoleErrors: string[]
  102 | }
  103 | 
  104 | function attachErrorCapture(page: Page): ConsoleErrorCapture {
  105 |   const pageErrors: string[] = []
  106 |   const consoleErrors: string[] = []
  107 | 
  108 |   page.on('pageerror', (err: Error) => {
  109 |     pageErrors.push(`${err.name}: ${err.message}`)
  110 |   })
  111 | 
  112 |   page.on('console', (msg: ConsoleMessage) => {
  113 |     if (msg.type() !== 'error') return
  114 |     const text = msg.text()
  115 |     if (IGNORED_ERROR_PATTERNS.some((re) => re.test(text))) return
  116 |     consoleErrors.push(text)
  117 |   })
  118 | 
  119 |   return { pageErrors, consoleErrors }
  120 | }
  121 | 
  122 | function assertClean(spec: RouteSpec, cap: ConsoleErrorCapture): void {
  123 |   const label = `${spec.path} (${spec.label})`
  124 |   const msgs = [
  125 |     ...cap.pageErrors.map((e) => `[pageerror] ${e}`),
  126 |     ...cap.consoleErrors.map((e) => `[console.error] ${e}`),
  127 |   ]
  128 |   expect(msgs, `${label} — runtime errors:\n${msgs.join('\n')}`).toHaveLength(0)
  129 | }
  130 | 
  131 | test.describe('route coverage', () => {
  132 |   test.describe.configure({ mode: 'parallel' })
  133 | 
  134 |   for (const spec of FAST_ROUTES) {
  135 |     test(spec.label, async ({ page }) => {
  136 |       const cap = attachErrorCapture(page)
  137 |       const url = `${spec.path}?skip=welcome`
> 138 |       await page.goto(url, { waitUntil: 'networkidle', timeout: spec.timeoutMs })
      |                  ^ TimeoutError: page.goto: Timeout 10000ms exceeded.
  139 |       // Wait an extra tick for any deferred suspense boundary to resolve.
  140 |       await page.waitForTimeout(200)
  141 |       assertClean(spec, cap)
  142 |     })
  143 |   }
  144 | 
  145 |   for (const spec of SLOW_ROUTES) {
  146 |     test(spec.label, async ({ page }) => {
  147 |       const cap = attachErrorCapture(page)
  148 |       const url = `${spec.path}?skip=welcome`
  149 |       await page.goto(url, { waitUntil: 'domcontentloaded', timeout: spec.timeoutMs })
  150 |       // Slow routes: wait for the canvas OR the route's page-heading to appear.
  151 |       // Either proves the route mounted; we don't require full sim completion.
  152 |       await Promise.race([
  153 |         page.locator('canvas').first().waitFor({ state: 'visible', timeout: spec.timeoutMs }),
  154 |         page.locator('h1, h2').first().waitFor({ state: 'visible', timeout: spec.timeoutMs }),
  155 |       ])
  156 |       assertClean(spec, cap)
  157 |     })
  158 |   }
  159 | 
  160 |   test('unknown route redirects to home without error', async ({ page }) => {
  161 |     const cap = attachErrorCapture(page)
  162 |     await page.goto('/this-route-does-not-exist?skip=welcome', {
  163 |       waitUntil: 'networkidle',
  164 |       timeout: 10_000,
  165 |     })
  166 |     // Router's fallback <Navigate to="/"> should land us at the home URL.
  167 |     expect(new URL(page.url()).pathname, 'fallback redirect target').toBe('/')
  168 |     assertClean(
  169 |       { path: '/this-route-does-not-exist', timeoutMs: 0, label: 'fallback redirect' },
  170 |       cap,
  171 |     )
  172 |   })
  173 | })
  174 |