test: add playwright smoke suite

Add playwright.config.ts with webServer configured to run build and
preview on port 4173. Add e2e/smoke.spec.ts with 4 smoke tests:
rel=me anchor presence, all identity links in HTML, canvas and HUD
visibility, and reduced motion fallback rendering.

Restructure App.tsx to render Scroller outside #demo div to fix
selector ambiguity in e2e tests. Add test.exclude to vite.config.ts
to prevent vitest from running playwright tests.
This commit is contained in:
L'électron rare
2026-07-09 12:15:16 +02:00
parent 7ca96d049a
commit aa81b39600
4 changed files with 64 additions and 18 deletions
+30
View File
@@ -0,0 +1,30 @@
import { expect, test } from '@playwright/test'
import { LINKS } from '../src/content/links'
test('raw HTML contains the rel=me anchor (no JS needed)', async ({ request }) => {
const res = await request.get('/')
const html = await res.text()
expect(html).toMatch(
/<a[^>]*href="https:\/\/mastodon\.saillant\.cc\/@clement"[^>]*rel="me"/,
)
})
test('raw HTML contains every identity link', async ({ request }) => {
const html = await (await request.get('/')).text()
for (const l of LINKS) expect(html).toContain(`href="${l.href}"`)
})
test('the demo boots: canvas mounts and hud shows', async ({ page }) => {
await page.goto('/')
await expect(page.locator('#demo canvas')).toBeVisible()
await expect(page.locator('#tracker')).toBeVisible()
})
test('reduced motion serves the readable fallback', async ({ browser }) => {
const ctx = await browser.newContext({ reducedMotion: 'reduce' })
const page = await ctx.newPage()
await page.goto('/')
await expect(page.locator('#fallback h1')).toBeVisible()
await expect(page.locator('#demo canvas')).toHaveCount(0)
await ctx.close()
})
+11
View File
@@ -0,0 +1,11 @@
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: 'e2e',
use: { baseURL: 'http://localhost:4173' },
webServer: {
command: 'npm run build && npm run preview',
port: 4173,
reuseExistingServer: !process.env.CI,
},
})
+3 -1
View File
@@ -15,6 +15,7 @@ export default function App() {
useRideInput()
const player = useMemo(() => createModPlayer(), [])
return (
<>
<div id="demo">
<Canvas
dpr={0.6}
@@ -31,8 +32,9 @@ export default function App() {
<Sky />
<Stops />
</Canvas>
<Scroller />
<TrackerPanel player={player} />
</div>
<Scroller />
</>
)
}
+3
View File
@@ -47,4 +47,7 @@ export default defineConfig({
// served straight from node_modules, where the relative lookup works.
exclude: ['chiptune3'],
},
test: {
exclude: ['node_modules', 'dist', 'e2e'],
},
})