Motivación
- Detectar automáticamente posts que provoquen regresiones de accesibilidad (a11y), frontmatter no renderizado o scroll horizontal inesperado.
- Ejecutar una única visita por post y validar 3 comprobaciones para reducir tiempo de test.
- Añadir una env var para controlar el número máximo de posts por colección (útil para PRs vs nightly).
Criterios de aceptación
- Existe
tools/getAllPosts.ts que devuelve [{ slug, title, locale, path }] y respeta la env var MAX_POSTS_PER_COLLECTION.
- Hay una suite E2E
tests/e2e/content.spec.ts que por cada post hace:
- Auditoría a11y con
@axe-core/playwright.
- Verifica que el
h1 (o título) y metatags se rendericen según frontmatter.
- Verifica que no haya scroll horizontal (document.documentElement.scrollWidth <= window.innerWidth).
- En CI:
- PRs usan subset configurable (ej.
MAX_POSTS_PER_COLLECTION=20).
- Job nightly recorre todo el contenido (sin límite).
- Fallos generan artefactos (JSON/HTML) para debugging.
Checklist
Snippet: lectura de env var en tools/getAllPosts.ts
// tools/getAllPosts.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
export function getAllPosts(collectionDir: string) {
const files = fs.readdirSync(collectionDir).filter(f => f.endsWith('.md'))
const max = process.env.MAX_POSTS_PER_COLLECTION ? Number(process.env.MAX_POSTS_PER_COLLECTION) : undefined
const selected = typeof max === 'number' ? files.slice(0, max) : files
return selected.map(file => {
const content = fs.readFileSync(path.join(collectionDir, file), 'utf-8')
const { data } = matter(content)
const slug = file.replace(/\.md$/, '')
return { slug, title: data.title, locale: data.lang || 'es', path: file }
})
}
Snippet: uso en tests/e2e/content.spec.ts
import { test, expect } from '@playwright/test'
import { injectAxe, checkA11y } from '@axe-core/playwright'
import { getAllPosts } from '../../tools/getAllPosts'
const posts = getAllPosts('src/content/blog/es') // respetará MAX_POSTS_PER_COLLECTION
for (const p of posts) {
test(`content: ${p.slug} — a11y + render + no horizontal scroll`, async ({ page }) => {
await page.goto(`/es/posts/${p.slug}`)
await injectAxe(page)
await checkA11y(page)
await expect(page.locator('h1')).toHaveText(p.title)
const hasHorizontalScroll = await page.evaluate(() =>
document.documentElement.scrollWidth > window.innerWidth
)
expect(hasHorizontalScroll).toBe(false)
})
}
Motivación
Criterios de aceptación
tools/getAllPosts.tsque devuelve [{ slug, title, locale, path }] y respeta la env varMAX_POSTS_PER_COLLECTION.tests/e2e/content.spec.tsque por cada post hace:@axe-core/playwright.h1(o título) y metatags se rendericen según frontmatter.MAX_POSTS_PER_COLLECTION=20).Checklist
MAX_POSTS_PER_COLLECTION(default: unset => full run; en PRs setear20).tools/getAllPosts.tspara respetar la env var.@axe-core/playwright.tests/e2e/content.spec.ts(parametrizado por slugs).@content,@a11y,@slowsegún corresponda.test-results/e2e/content/cuando falle.Snippet: lectura de env var en
tools/getAllPosts.tsSnippet: uso en
tests/e2e/content.spec.ts