Ir al contenido
angel.library

Testing en Astro — lógica, componentes y E2E

Astro mezcla código de build, servidor y navegador. La prueba correcta depende del límite que quieres verificar.

función importable → Vitest
contenido y frontmatter → schema + astro check/build
endpoint o Action → Request/Response + integración
componente de UI de framework → su Testing Library
página, script o isla hidratada → Playwright
adapter y SSR → build + preview/runtime de producción
CapaHerramientaEjemplos
Función puraVitesttransformaciones, schemas, ordenamiento
Endpoint/ActionVitest o integraciónstatus, autorización, validación
Página e interacciónPlaywrightrutas, formularios, copy, islas, accesibilidad
Contenidobuild/checkschemas y referencias

Un archivo .astro se transforma dentro del pipeline de Astro. En lugar de forzar su render en jsdom, extrae reglas puras, prueba componentes React/Vue/Svelte con su herramienta y conserva una prueba de página para composición, slots y HTML final.

vitest.config.ts
/// <reference types="vitest/config" />
import { getViteConfig } from "astro/config"
export default getViteConfig({
test: {
environment: "node",
include: ["src/**/*.test.ts"]
}
})

getViteConfig incorpora aliases y configuración de Vite del proyecto. Cambia environment solo en pruebas que realmente necesiten DOM.

playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
webServer: {
command: "pnpm build && pnpm preview",
url: "http://localhost:4321",
reuseExistingServer: !process.env.CI
},
use: { baseURL: "http://localhost:4321" }
})

preview sirve la salida real y descubre diferencias que el dev server oculta. Si el sitio utiliza un adapter, confirma que el comando reproduce su runtime; algunos adapters necesitan una prueba desplegada o un servidor específico.

tests/navigation.spec.ts
import { expect, test } from "@playwright/test"
test("abre una entrada desde búsqueda", async ({ page }) => {
await page.goto("/")
await page.getByRole("button", { name: /buscar/i }).click()
await page.getByPlaceholder(/buscar/i).fill("content collections")
await page.getByRole("option", { name: /content collections/i }).click()
await expect(
page.getByRole("heading", { name: /content collections/i })
).toBeVisible()
})
  • Navegación con y sin JavaScript si dependes de mejora progresiva (progressive enhancement).
  • View Transitions después de varias navegaciones, no solo carga inicial.
  • Teclado, foco y nombres accesibles.
  • Rutas on-demand contra el adapter real cuando el runtime importa.

Referencia oficial: Testing.

Las funciones que transforman contenido o datos no necesitan Astro ni un navegador: pruébalas con Vitest y casos de borde. Para endpoints, construye requests con headers, cookies y body reales, y comprueba autorización, status y formato de error. Para islas, prueba la interacción con navegador real porque el problema puede estar en la hidratación y no en el HTML generado.

import { describe, expect, it } from "vitest"
import { POST } from "../src/pages/api/subscriptions"
it("rechaza un cuerpo inválido", async () => {
const request = new Request("http://example.test/api/subscriptions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email: "no-es-correo" })
})
const response = await POST({ request } as never)
expect(response.status).toBe(400)
await expect(response.json()).resolves.toMatchObject({
code: "INVALID_EMAIL"
})
})

Extrae la lógica que no depende del contexto de Astro y pruébala sin casts. Conserva una prueba de integración para comprobar el adapter, cookies y runtime que usa producción.

Incluye pnpm check y pnpm build en CI para que el schema de la colección, enlaces internos y rutas dinámicas se validen como parte del producto. Un test de navegación debe visitar tanto una ruta generada estáticamente como una ruta servida por adapter cuando el proyecto se despliega con SSR.

Puedes probar transformaciones de contenido como funciones puras y dejar al build la integridad global:

it("ordena entradas por prioridad y título", () => {
expect(sortEntries(entries)).toEqual([highPriority, alphabetical])
})

No simules getCollection en todos los casos y concluyas que el schema o la ruta funcionan. El build real es el contrato para colecciones y getStaticPaths.

Prueba una carga directa, una navegación interna, volver atrás y desactivar JavaScript si la funcionalidad debe degradar de forma útil. Después de cada transición verifica foco, estado de búsqueda, listeners e islas que se montan una sola vez. Los bugs de navegación no siempre aparecen en la primera carga.

Comprueba que el HTML útil exista antes de hidratar y que client:idle, client:visible o client:media activen la interacción en la condición esperada. Un componente puede funcionar al cargar directamente y duplicar listeners después de navegar; repite el recorrido y verifica que cada acción produce un solo efecto.

Prueba validación y autorización en funciones importables. Después confirma con navegador:

  • estado pendiente y doble envío;
  • error relacionado con el campo;
  • cookie/sesión y protección CSRF cuando aplica;
  • redirect y mensaje después del éxito;
  • comportamiento sin JavaScript si existe fallback.

Una llamada directa al handler no ejecuta middleware, adapter ni proxy. Conserva una integración desplegada para esas fronteras.

ModoCaso que no debe faltar
estáticoruta generada, assets y 404
on-demandcookie, header, método y error del adapter
híbridonavegación entre página estática y dinámica
islaHTML inicial, hidratación y navegación repetida