Adds three things to make frontend PRs reviewable directly from Gitea web UI: 1. Storybook 8 for Vue 3 components - .storybook/main.ts + preview.ts - HealthDashboard.stories.ts (1 example story) - npm run storybook / npm run build-storybook 2. Playwright JSON reporter + screenshot on every test - playwright.config.ts: json reporter + screenshot: 'on' - health.spec.ts: explicit screenshot path 3. Auto-generated markdown docs with breadcrumbs - scripts/generate-test-docs.mjs: reads results.json -> generates docs/e2e/<test>.md per test - docs/README.md: top-level frontend docs index - docs/e2e/README.md: e2e index with link to each test - Each test markdown has breadcrumb [<- Back to index] - Screenshots embedded via relative path Companion to PR #25 (Nuxt scaffold). Out of scope (future PRs): - Visual regression testing - Storybook deployment / hosted preview - More e2e test coverage 🤖 Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
121 lines
3.5 KiB
JavaScript
121 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const frontendDir = path.resolve(__dirname, '..')
|
|
|
|
const resultsPath = path.join(frontendDir, 'test-results', 'results.json')
|
|
const docsDir = path.join(frontendDir, 'docs', 'e2e')
|
|
const screenshotsDir = path.join(frontendDir, 'tests', 'e2e', 'screenshots')
|
|
|
|
async function main() {
|
|
// Read results
|
|
const resultsText = await fs.readFile(resultsPath, 'utf8')
|
|
const results = JSON.parse(resultsText)
|
|
|
|
// Create output directories
|
|
await fs.mkdir(docsDir, { recursive: true })
|
|
|
|
// Extract tests from suites
|
|
const testDocs = []
|
|
for (const suite of results.suites || []) {
|
|
for (const spec of suite.specs || []) {
|
|
for (const test of spec.tests || []) {
|
|
for (const result of test.results || []) {
|
|
const testInfo = {
|
|
title: spec.title,
|
|
specFile: spec.file || suite.file,
|
|
status: result.status,
|
|
duration: result.duration,
|
|
startTime: result.startTime,
|
|
attachments: result.attachments || [],
|
|
}
|
|
testDocs.push(testInfo)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate individual test markdown files
|
|
for (const test of testDocs) {
|
|
const slug = slugify(test.title)
|
|
const mdPath = path.join(docsDir, `${slug}.md`)
|
|
|
|
// Use slug-based screenshot name (matches explicit screenshot in test)
|
|
let screenshotPath = `${slug}.png`
|
|
|
|
// Also try to find screenshot attachment and use its basename
|
|
if (test.attachments && test.attachments.length > 0) {
|
|
for (const attachment of test.attachments) {
|
|
if (attachment.contentType === 'image/png') {
|
|
const basename = path.basename(attachment.path)
|
|
// Prefer explicit screenshot name if it matches our pattern
|
|
if (basename !== 'test-finished-1.png' && basename !== 'test-finished-2.png') {
|
|
screenshotPath = basename
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const absoluteScreenshotPath = path.join(screenshotsDir, screenshotPath)
|
|
const relativeScreenshotPath = path.relative(docsDir, absoluteScreenshotPath)
|
|
|
|
const mdContent = `# ${test.title}
|
|
|
|
[<- Back to index](./README.md) | [Top](../README.md)
|
|
|
|
**File**: \`tests/e2e/${test.specFile}\`
|
|
**Status**: ${test.status.toUpperCase()}
|
|
**Duration**: ${test.duration}ms
|
|
|
|
## Screenshot
|
|
|
|

|
|
|
|
## Test Details
|
|
|
|
- Start Time: ${test.startTime || 'N/A'}
|
|
- Spec File: ${test.specFile}
|
|
`
|
|
|
|
await fs.writeFile(mdPath, mdContent)
|
|
console.log(`Generated: ${path.relative(frontendDir, mdPath)}`)
|
|
}
|
|
|
|
// Generate index README
|
|
const indexContent = `# E2E Test Reports
|
|
|
|
[<- Up to docs](../README.md)
|
|
|
|
| Test | Status | Duration |
|
|
|------|--------|----------|
|
|
${testDocs.map(t => `| [${escapeMd(t.title)}](./${slugify(t.title)}.md) | ${t.status.toUpperCase()} | ${t.duration}ms |`).join('\n')}
|
|
`
|
|
|
|
await fs.writeFile(path.join(docsDir, 'README.md'), indexContent)
|
|
console.log(`Generated: ${path.relative(frontendDir, path.join(docsDir, 'README.md'))}`)
|
|
|
|
console.log(`\nGenerated ${testDocs.length} test docs`)
|
|
}
|
|
|
|
function slugify(str) {
|
|
return str
|
|
.toLowerCase()
|
|
.replace(/[^\w\s-]/g, '')
|
|
.replace(/[\s_]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
}
|
|
|
|
function escapeMd(str) {
|
|
return str.replace(/[|\\\[\]\{\}]/g, '\\$&')
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Error:', err)
|
|
process.exit(1)
|
|
})
|