Sprint 2 of autonomous trainer day 2026-05-05. Mistral-implemented through ICM workspace ship-info-aggregator (bootstrapped backend + BDD before hitting price limit at stage 02), Claude-completed for frontend + Playwright + verifier + PR. Backend: - GET /api/info aggregator returning version, commit_short, build_date, uptime_seconds, cache_enabled, healthz_status (single round trip) - Optional cache via existing cache service (X-Cache: HIT/MISS) - BDD scenario @critical covers happy path + version regex; cache scenario kept under @skip @bdd-deferred until BDD harness gains a cache-enabled mode Frontend: - AppFooterView (dumb) + AppFooter (smart wrapper, useFetch) following the HealthDashboard / HealthDashboardView pattern - layouts/default.vue auto-applied via NuxtLayout in app.vue - humaniseUptime helper in utils/ - Playwright tests use route.fulfill mocking (decoupled from dev-proxy infra), assert visible AND content (PR #32 lesson) Docs: - documentation/API.md /api/info entry with schema and rationale - ADR-0026 documents composite endpoint vs separate calls choice Verifier verdict (skill-driven, audit at stage 04): APPROVE_WITH_NITS. Nits: handleInfo is 51 lines (could split into builder + emitter); X-Cache: DISABLED could improve ops clarity. Out-of-scope follow-up: existing tests/e2e/health.spec.ts happy path hits the same dev-proxy infra issue as my footer happy path before mocking. Same fix (server: false + route.fulfill) would apply.
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
// Both specs mock /api/info so they decouple from the dev-proxy plumbing.
|
|
// The integration with the real backend is covered by the BDD scenario in
|
|
// features/info/info.feature (server-side, no frontend proxy in the loop).
|
|
|
|
test('home page footer shows version, commit and uptime', async ({ page }) => {
|
|
await page.route('**/api/info', (route) => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
version: '1.4.0',
|
|
commit_short: '4a3f1bb',
|
|
build_date: '2026-05-05T00:00:00Z',
|
|
uptime_seconds: 8042,
|
|
cache_enabled: true,
|
|
healthz_status: 'healthy',
|
|
}),
|
|
})
|
|
})
|
|
await page.goto('/')
|
|
|
|
// Footer is mounted globally via layouts/default.vue
|
|
await expect(page.getByTestId('app-footer')).toBeVisible()
|
|
|
|
// The PR #32 lesson: assert content, not just visibility.
|
|
// Without the regex check the test would PASS even if the footer rendered the
|
|
// pending placeholder ("v?") indefinitely.
|
|
await expect(page.getByTestId('app-footer-info')).toBeVisible()
|
|
const versionLocator = page.getByTestId('app-footer-version')
|
|
await expect(versionLocator).toBeVisible()
|
|
await expect(versionLocator).toHaveText(/^v\d+\.\d+\.\d+$/)
|
|
|
|
// Commit and uptime should be present and non-empty.
|
|
await expect(page.getByTestId('app-footer-commit')).not.toBeEmpty()
|
|
await expect(page.getByTestId('app-footer-uptime')).not.toBeEmpty()
|
|
|
|
await page.screenshot({
|
|
path: 'tests/e2e/screenshots/app-footer-shows-version-commit-uptime.png',
|
|
fullPage: true,
|
|
})
|
|
})
|
|
|
|
// Regression spec: documents the expected error UX so we don't ship a silent failure.
|
|
// Routes /api/info to a 502 mock so the test is reproducible regardless of backend.
|
|
test('home page footer surfaces info endpoint errors gracefully', async ({ page }) => {
|
|
await page.route('**/api/info', (route) => {
|
|
route.fulfill({
|
|
status: 502,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'simulated_backend_down' }),
|
|
})
|
|
})
|
|
await page.goto('/')
|
|
|
|
// Footer must NOT crash the page
|
|
await expect(page.getByTestId('app-footer')).toBeVisible()
|
|
await expect(page.getByTestId('app-footer-error')).toBeVisible()
|
|
// The error placeholder should NOT contain a real version pattern
|
|
await expect(page.getByTestId('app-footer-info')).not.toBeVisible()
|
|
|
|
await page.screenshot({
|
|
path: 'tests/e2e/screenshots/app-footer-surfaces-info-endpoint-errors-gracefully.png',
|
|
fullPage: true,
|
|
})
|
|
})
|