|
1 | 1 | import { test, expect } from '@playwright/test'; |
| 2 | +import { waitForReactMount } from './helpers'; |
2 | 3 |
|
3 | 4 | /** |
4 | | - * Smoke test to verify the console app loads correctly. |
5 | | - * This is a foundational E2E test that validates the basic app shell. |
| 5 | + * Smoke tests for the console production build. |
| 6 | + * |
| 7 | + * These tests run against `vite preview` (the same artefact deployed to Vercel) |
| 8 | + * and are designed to catch **blank-page** regressions caused by: |
| 9 | + * - Broken imports or missing modules in the production bundle |
| 10 | + * - Missing polyfills (e.g. `process`, `crypto`) |
| 11 | + * - Uncaught JavaScript exceptions during bootstrap |
| 12 | + * - Failed network requests for critical assets (JS/CSS bundles) |
| 13 | + * - React failing to mount into #root |
6 | 14 | */ |
7 | | -test.describe('Console App', () => { |
8 | | - test('should load the home page', async ({ page }) => { |
| 15 | + |
| 16 | +test.describe('Console App – Smoke', () => { |
| 17 | + test('should load the page without JavaScript errors', async ({ page }) => { |
| 18 | + const errors: string[] = []; |
| 19 | + page.on('pageerror', (err) => errors.push(err.message)); |
| 20 | + |
9 | 21 | await page.goto('/'); |
10 | | - // Wait for the app to render |
11 | | - await page.waitForLoadState('networkidle'); |
12 | | - // The page should have rendered something (not blank) |
13 | | - const body = page.locator('body'); |
14 | | - await expect(body).not.toBeEmpty(); |
| 22 | + await waitForReactMount(page); |
| 23 | + |
| 24 | + // The page must not have thrown any uncaught exceptions |
| 25 | + expect(errors, 'Uncaught JS errors during page load').toEqual([]); |
| 26 | + }); |
| 27 | + |
| 28 | + test('should render React content inside #root', async ({ page }) => { |
| 29 | + await page.goto('/'); |
| 30 | + await waitForReactMount(page); |
| 31 | + |
| 32 | + // #root must exist and have child elements (React mounted successfully) |
| 33 | + const root = page.locator('#root'); |
| 34 | + await expect(root).toBeAttached(); |
| 35 | + const childCount = await root.evaluate((el) => el.children.length); |
| 36 | + expect(childCount, '#root has no children – blank page detected').toBeGreaterThan(0); |
| 37 | + }); |
| 38 | + |
| 39 | + test('should not show a blank page (meaningful text rendered)', async ({ page }) => { |
| 40 | + await page.goto('/'); |
| 41 | + await waitForReactMount(page); |
| 42 | + |
| 43 | + // The visible page text must not be empty |
| 44 | + const bodyText = await page.locator('body').innerText(); |
| 45 | + expect(bodyText.trim().length, 'Page body has no visible text').toBeGreaterThan(0); |
15 | 46 | }); |
16 | 47 |
|
17 | | - test('should display the navigation sidebar', async ({ page }) => { |
| 48 | + test('should load all JavaScript bundles without 404s', async ({ page }) => { |
| 49 | + const failedAssets: string[] = []; |
| 50 | + |
| 51 | + page.on('response', (response) => { |
| 52 | + const url = response.url(); |
| 53 | + if ( |
| 54 | + (url.endsWith('.js') || url.endsWith('.css')) && |
| 55 | + response.status() >= 400 |
| 56 | + ) { |
| 57 | + failedAssets.push(`${response.status()} ${url}`); |
| 58 | + } |
| 59 | + }); |
| 60 | + |
18 | 61 | await page.goto('/'); |
19 | 62 | await page.waitForLoadState('networkidle'); |
20 | | - // The app shell should contain a navigation area |
21 | | - const nav = page.locator('nav').first(); |
22 | | - await expect(nav).toBeVisible(); |
| 63 | + |
| 64 | + expect(failedAssets, 'Critical assets returned HTTP errors').toEqual([]); |
23 | 65 | }); |
24 | 66 |
|
25 | 67 | test('should have correct page title', async ({ page }) => { |
26 | 68 | await page.goto('/'); |
27 | | - await expect(page).toHaveTitle(/.+/); |
| 69 | + await expect(page).toHaveTitle(/ObjectStack|ObjectUI|Console/i); |
| 70 | + }); |
| 71 | + |
| 72 | + test('should show the app shell or loading screen', async ({ page }) => { |
| 73 | + await page.goto('/'); |
| 74 | + |
| 75 | + // Either the app shell (nav / sidebar) or the loading screen should appear |
| 76 | + // within a reasonable time. Both are acceptable initial states. |
| 77 | + const appShell = page.locator('nav').first(); |
| 78 | + const loadingScreen = page.getByText(/Initializing|Loading|Connecting/i).first(); |
| 79 | + |
| 80 | + await expect( |
| 81 | + appShell.or(loadingScreen), |
| 82 | + ).toBeVisible({ timeout: 30_000 }); |
28 | 83 | }); |
29 | 84 | }); |
0 commit comments