Simulación de navegaciones de página y formularios con MSW 3
Simula navegaciones y formularios nativos con MSW 3 y sus manejadores http. Consulta las respuestas HTML, el uso de formData y los cambios clave de la migración.
Mock Service Worker (MSW) 3.0.0 se publicó el 28 de septiembre de 2026 e incorporó soporte para interceptar navegaciones de página y envíos de formularios (#2721). Esto significa que un clic en un enlace o el envío de un formulario HTML convencional ya pueden recibir una respuesta simulada con los mismos handlers http que ya escribes.
Hasta esta versión, el envío nativo de un formulario en un entorno con Storybook o Playwright escapaba por completo a MSW. O bien necesitabas un backend real en ejecución solo para ese flujo, o bien te saltabas la prueba. En una discusión de 2023, el mantenedor explicó que MSW omitía intencionadamente las peticiones de navegación para evitar que, por error, se sirviera un mock en lugar de una página HTML real.
Este artículo explica qué permite probar este cambio, cuáles son los breaking changes de la v3 con los que te toparás primero y qué patrones de handlers usar para páginas y envíos de formularios. La versión de parche 3.0.3 llegó el 10 de octubre de 2026. Para instalar MSW y crear tus primeros handlers, consulta cómo simular servidores de API con Mock Service Worker.
Puntos clave
- MSW 3.0.0 (28 de septiembre de 2026) añade la interceptación de navegaciones de página y envíos de formularios, pero no hay documentada ninguna API específica para ello, así que se utilizan los handlers estándar
http.getyhttp.post. - Un formulario HTML convencional envía sus campos codificados como URL (URL-encoded), no como JSON, por lo que debes leer el cuerpo con
request.formData()en lugar derequest.json(). - MSW 3 es exclusivamente ESM y requiere Node.js 22 o posterior y TypeScript 5.9 o posterior.
onUnhandledRequestpasa a llamarseonUnhandledFrame, yrequest.headers.get('cookie')ahora devuelvenulldentro de los handlers.
¿Qué permite la simulación de navegaciones y formularios en MSW 3?
Con MSW 3 puedes probar un formulario renderizado en el servidor o un enlace a otra página sin ejecutar un backend. El handler devuelve el documento HTML de la página siguiente y el navegador lo renderiza igual que haría con una respuesta real del servidor.
Antes de la versión 3, MSW solo cubría las peticiones que tu JavaScript realizaba con fetch o XHR. Eso dejaba fuera un formulario de registro con mejora progresiva, un formulario de búsqueda que recarga la página y un enlace de «ver detalles» a una página renderizada en el servidor. Ahora puedes describir cada uno de esos casos como un handler y hacer aserciones sobre lo que el usuario termina viendo.
El flujo de error es el que más se beneficia. Las grabaciones de sesión (session replays) de flujos con formularios suelen mostrar a usuarios que acaban en una página en blanco o en una página de error sin formato tras pulsar enviar. Un POST simulado que devuelve HTML con errores de validación o un 500 te permite reproducir esa pantalla en una prueba antes del lanzamiento.
Actualización a MSW 3: los breaking changes que te afectarán primero
La mayoría de las suites fallan primero por los requisitos mínimos del entorno de ejecución y por una única opción renombrada. Estos son los cambios de las notas de la versión v3.0.0 que debes revisar antes de examinar las nuevas funcionalidades:
- MSW ahora es exclusivamente ESM (#2765).
- La versión mínima de Node.js admitida es la v22 (#2729).
- TypeScript 5.9 es ahora la versión mínima.
graphqlahora se exporta comomsw/graphql(#2691), y GraphQL pasa a ser una peer dependency opcional en sentido estricto. Además, los handlers de GraphQL ahora se definen sobre ungraphql.link(), porquegraphql.query()ygraphql.mutation()se han eliminado del namespace.- La opción
onUnhandledRequestse ha renombrado aonUnhandledFrame. worker.stop()devuelve una Promise (#2724). Solo necesitas esperarla conawaiten escenarios críticos en los que aún haya peticiones en curso.msw/nativese elimina en favor de@msw/react-native. Un issue de GitHub señala que@msw/react-nativetodavía no está publicado en npm y que su código fuente sigue orientado a MSW 2, por lo que los proyectos de React Native no tienen una vía de actualización funcional hasta que se publique.
Las notas de la versión y la guía de migración de 2.x a 3.x cubren todo lo demás. Los fragmentos de código de este artículo usan el nuevo entrypoint msw/http. Según el anuncio de MSW 3.0, las exportaciones desde la raíz de msw se mantienen por compatibilidad con versiones anteriores, pero la próxima versión mayor eliminará todo aquello que pueda importarse desde un entrypoint específico.
¿Cómo se simula una navegación de página con http.get?
Para simular una navegación de página, escribe un handler http.get para la ruta de la página y devuelve un Response con un cuerpo HTML y un content type text/html. Las notas de la versión v3 anuncian la interceptación de navegaciones y formularios, pero ni ellas ni la guía de migración documentan una API o un paso de configuración específicos. Por eso, los ejemplos siguientes utilizan únicamente los handlers estándar.
Cada request handler de MSW combina un predicado, que filtra las peticiones entrantes, con un response resolver, que gestiona las que lo superan. La documentación sobre interceptación de peticiones indica que los predicados relativos, como /signup, se resuelven con respecto a location.href. Cuando un resolver devuelve una instancia de Response, esa instancia se convierte en la respuesta simulada, tal como describe la documentación sobre gestión de peticiones. Por eso aquí basta con un simple new Response, sin necesidad de ningún helper específico para HTML.
import { http } from 'msw/http'
export const html = (body: string, status = 200) =>
new Response(`<!doctype html><html><body>${body}</body></html>`, {
status,
headers: { 'Content-Type': 'text/html' },
})
export const pageHandlers = [
http.get('/signup', () =>
html(`<form method="post" action="/signup">
<input name="email" type="email">
<button>Sign up</button>
</form>`),
),
]
Los formularios de búsqueda que usan method="get" se comportan de otra manera. Un formulario enviado con method="get" coloca sus campos en la query string. Los predicados de MSW no deben contener parámetros de consulta, así que haz la coincidencia solo con la ruta y lee los campos desde new URL(request.url).searchParams:
http.get('/search', ({ request }) => {
const q = new URL(request.url).searchParams.get('q') ?? ''
const safe = q.replace(/&/g, '&').replace(/</g, '<')
return html(`<h1>Results for ${safe}</h1>`)
})
¿Cómo se simula el envío de un formulario con http.post?
El envío nativo de un formulario se gestiona con un handler http.post para la URL del atributo action del formulario. El resolver recibe request como un Request estándar de la Fetch API, junto con params y cookies, y el Response que devuelva se convierte en la página que el navegador muestra a continuación.
La única diferencia con respecto al patrón de MSW 2 que ya conoces es el formato del cuerpo. Cuando tu propio JavaScript envía un formulario, normalmente envía JSON:
import { http, HttpResponse } from 'msw/http'
http.post<never, { email: string }>('/api/signup', async ({ request }) => {
const body = await request.clone().json()
return HttpResponse.json({ ok: true, email: body.email }, { status: 201 })
})
Un formulario HTML convencional envía sus campos como datos de formulario codificados como URL en lugar de JSON, porque application/x-www-form-urlencoded es la codificación de formulario por defecto. Llamar a .json() sobre ese cuerpo falla. Léelo con Request.formData(). La guía de subida de archivos de MSW muestra el uso de formData() sobre peticiones interceptadas.
import { http } from 'msw/http'
import { html } from './pages'
export const formHandlers = [
http.post('/signup', async ({ request }) => {
const form = await request.clone().formData()
const email = form.get('email')
if (!email) {
return html('<p role="alert">Email is required.</p>', 422)
}
return html('<h1>Check your inbox</h1>')
}),
]
Devuelve una rama distinta para cada resultado del servidor que necesites probar: éxito, error de validación y una página 500. De este modo, tus aserciones cubren el documento que el usuario ve realmente.
Los flujos con formularios suelen depender de cookies de sesión. En la v3, request.headers.get('cookie') ya no devuelve una cadena de cookies simulada dentro de los handlers. Devuelve null en todos los entornos, así que utiliza el argumento cookies del resolver para cualquier lógica basada en cookies.
¿Dónde funciona la interceptación de navegaciones y formularios de MSW 3?
Las notas de la versión v3 no especifican qué entornos admiten la interceptación de navegaciones y formularios, así que contrasta el PR #2721 y la documentación con tu propia configuración antes de confiar en ella. Una navegación de página es algo que ejecuta el navegador, lo que convierte a los runners basados en navegador en el lugar natural para probar estos handlers.
La historia de MSW explica por qué. Antes de la versión 3, el script del worker omitía las peticiones cuyo modo era navigate, como explicó el mantenedor en la discusión #1578. Si tu suite de Vitest o Jest ejecuta pruebas de componentes en Node, como en simulación de llamadas a la API en pruebas de Vue con Vitest, los mocks de fetch siguen funcionando como antes. No des por hecho que el envío nativo de un formulario se comporta igual en ese entorno. La versión también añade un plugin para Vite, msw/vite (#2781), que sirve el script del worker por ti, de modo que ya no tienes que copiar mockServiceWorker.js en tu directorio público.
Conclusión
MSW 3 no requiere vocabulario nuevo para simular páginas y formularios. Usas http.get para la página, http.post para el envío, formData() para el cuerpo y un Response con text/html para el resultado. Actualiza en este orden: migra a Node.js 22, TypeScript 5.9 y ESM, renombra onUnhandledRequest a onUnhandledFrame y cambia las importaciones a msw/http. Después, añade un handler para cada resultado de tu formulario más importante, empezando por la página de error.
Preguntas frecuentes
¿Funciona request.formData() con formularios que usan enctype multipart/form-data?
Sí. Request.formData() analiza tanto los cuerpos application/x-www-form-urlencoded como los multipart/form-data y los convierte en un objeto FormData, por lo que el mismo código del handler funciona tanto si el formulario incluye un campo de archivo como si no. Los campos de texto se obtienen como cadenas mediante form.get(), y los campos de archivo se obtienen como objetos File cuyas propiedades name, size y type puedes inspeccionar.
¿Cómo leo grupos de casillas de verificación o campos de selección múltiple en un handler de formulario?
Usa form.getAll('name') en lugar de form.get('name'). Un grupo de casillas de verificación o un select con el atributo multiple envía una entrada por cada valor seleccionado bajo el mismo nombre de campo, y get() solo devuelve el primero. getAll() devuelve un array con todos los valores, o un array vacío cuando no hay nada seleccionado, porque el navegador no envía en absoluto las casillas no marcadas.
¿Cuál es la diferencia entre devolver passthrough() y no devolver nada desde un resolver de MSW?
Devolver passthrough(), que en MSW 3 se importa desde msw/utils/passthrough, envía la petición a la red real sin modificarla y la marca como gestionada, de modo que ningún handler posterior puede afectarla. No devolver nada deja la petición intacta, y MSW prueba con el siguiente handler que coincida. Usa passthrough() cuando quieras que una página o un envío de formulario concreto llegue intencionadamente a un servidor real.
¿Tengo que regenerar mockServiceWorker.js después de actualizar a MSW 3?
Sí, a menos que uses el nuevo plugin msw/vite, que sirve el script del worker por ti. En caso contrario, después de instalar MSW 3, ejecuta npx msw init seguido de tu directorio público y haz commit del nuevo archivo, para que el script del worker se corresponda con el paquete 3.x y no con la antigua copia de la 2.x.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k