Nuestro punto de vista, dicho sin rodeos: somos Geonode y vendemos proxies, y Playwright se ejecuta con frecuencia a través de ellos para realizar scraping y pruebas geográficas. La verdad es que la inmensa mayoría de los tiempos de espera de Playwright no tienen nada que ver con los proxies. Un selector que no encuentra nada, un elemento oculto por un banner de cookies, una animación que nunca se estabiliza… todo ello produce el mismo error, tanto si el tráfico va directo como si pasa por seis intermediarios. Hay un caso real relacionado con los proxies, que se trata al final: las conexiones residenciales añaden latencia real, por lo que los ajustes predeterminados para pruebas locales producen falsos fallos. Pero comprueba primero el selector. Si tu prueba falla de la misma manera sin el proxy configurado, el proxy no es el problema.
Los seis tiempos de espera y sus valores predeterminados
Lo primero que hay que entender es que se trata de mecanismos independientes con valores predeterminados distintos, y saber cuál se ha activado te indica dónde debes buscar.
| Tiempo de espera | Valor predeterminado | Se configura mediante |
|---|---|---|
| Prueba | 30 000 ms | testConfig.timeout, test.setTimeout() |
| Expect | 5 000 ms | testConfig.expect.timeout, opción por aserción |
| Acción | Sin tiempo de espera | testOptions.actionTimeout, opción por llamada |
| Navegación | Sin tiempo de espera | testOptions.navigationTimeout, opción por llamada |
| Hooks beforeAll / afterAll | 30 000 ms | test.setTimeout() dentro del hook |
| Global | Ninguno | testConfig.globalTimeout |
Valores extraídos de la documentación sobre tiempos de espera de Playwright.
Dos de estos valores sorprenden a la gente.
Las acciones y la navegación no tienen tiempo de espera por defecto. Solo están limitadas por el tiempo de espera de la prueba. Por lo tanto, un page.click() sin calificar esperará hasta agotar el tiempo restante de la prueba, y el error que se obtiene se debe a que la prueba agota el tiempo de espera, no al clic. Por eso el mensaje indica 30 000 ms, aunque nadie haya configurado un clic de 30 segundos.
El tiempo de espera global no tiene ningún valor por defecto. La documentación describe su finalidad como la prevención del «uso excesivo de recursos cuando todo sale mal»; merece la pena configurarlo en la integración continua (CI) para que una suite bloqueada falle en lugar de ocupar un ejecutor indefinidamente.
Qué significa realmente «Se ha superado el tiempo de espera de 30 000 ms»
Ese mensaje concreto se refiere al tiempo de espera de la prueba, y dicho tiempo de espera es un límite, no un diagnóstico. Algo dentro de él tardó demasiado, y el mensaje indica el límite, no la causa.
Clasificados según la frecuencia con la que cada uno es la causa real:
1. Un localizador no ha encontrado ninguna coincidencia. El selector es incorrecto, o el elemento no ha aparecido, o se encuentra dentro de un iframe o una raíz de sombra que no has tenido en cuenta. Playwright espera pacientemente algo que nunca existirá.
2. El elemento existe, pero no se puede interactuar con él. Está cubierto por una superposición, un banner de cookies o un encabezado fijo. Está desactivado. Sigue animándose. Playwright espera a que se pueda hacer clic en él, pero eso nunca ocurre.
3. Una navegación que nunca se completó. Una solicitud de red que se cuelga, un bucle de redireccionamiento o una condición de «waitUntil» —en concreto, «networkidle»— que una página con conexiones persistentes nunca cumplirá.
4. Una aserción que nunca se cumplió. Un «expect» que comprueba una condición a la que la aplicación nunca llega.
5. La prueba realmente hace demasiado. Es un caso real, y el menos habitual.
El orden es importante porque la solución varía por completo. Solo el caso 5 se resuelve aumentando el tiempo de espera. En los otros cuatro, aumentarlo significa esperar más tiempo para que se produzca el mismo fallo.
La «actionability» es la razón por la que tu clic tiene que esperar
Entender esto aclara gran parte de la confusión, ya que explica lo que hace Playwright durante esos treinta segundos.
La documentación sobre la ejecutabilidad indica que Playwright «realiza una serie de comprobaciones de ejecutabilidad en los elementos antes de llevar a cabo las acciones, para garantizar que estas se comporten según lo esperado», y que «espera automáticamente a que se superen todas las comprobaciones pertinentes y solo entonces ejecuta la acción solicitada». Cuando las comprobaciones no se superan a tiempo, «la acción falla con el error TimeoutError».
Las comprobaciones necesarias varían según la acción, y esa diferencia es clave para el diagnóstico:
| Acción | Comprobaciones necesarias |
|---|---|
click, dblclick, check, uncheck, tap, setChecked | visible, estable, recibe eventos, habilitado |
hover, dragTo | visible, estable, recibe eventos |
fill, clear | visible, habilitado |
selectOption | visible, habilitado |
screenshot, selectText | visible |
scrollIntoViewIfNeeded | estable |
blur, focus, press, pressSequentially, dispatchEvent, setInputFiles | ninguna |
Hay dos cosas que se desprenden inmediatamente de esta tabla.
Un click que agota el tiempo de espera mientras fill sobre el mismo elemento funciona apunta a «estable» o «recibe eventos»: el elemento se está moviendo o hay algo encima de él. Las animaciones y las superposiciones son las sospechosas habituales.
Las acciones sin comprobaciones son una vía de escape y una señal de advertencia. Si locator.click() agota el tiempo de espera pero dispatchEvent('click') funciona, no has solucionado nada: has eludido la comprobación que te indicaba que un usuario real tampoco podría hacer clic en ese elemento. A veces eso es aceptable. Por lo general, significa que hay un problema real de superposición que tu prueba simplemente ha dejado de detectar.
Modificar cada tiempo de espera en el lugar adecuado
La configuración se encuentra en varios niveles, y colocarla en el nivel equivocado da lugar a resultados confusos.
Configuración global, en playwright.config.ts
:
export default defineConfig({
timeout: 60_000,
globalTimeout: 60 * 60 * 1000,
expect: { timeout: 10_000 },
use: {
actionTimeout: 15_000,
navigationTimeout: 30_000,
},
});
Fíjate en dónde se encuentra cada uno. timeout
y globalTimeout
son configuraciones de primer nivel; expect.timeout
se encuentra bajo expect
; actionTimeout
y navigationTimeout
se encuentran bajo use
, ya que son opciones de prueba en lugar de configuración del ejecutor. Si se colocan en el nivel incorrecto, se ignoran sin previo aviso.
Por prueba:
test('slow one', async ({ page }) => {
test.setTimeout(120_000);
// ...
});
**test.slow()
** triplica el tiempo de espera predeterminado —un buen valor por defecto para una prueba que sabes que es realmente larga, sin tener que elegir un número arbitrario.
Por aserción:
await expect(page.getByRole('status')).toHaveText('Done', { timeout: 30_000 });
Por acción:
await page.getByRole('button', { name: 'Export' }).click({ timeout: 15_000 });
**En beforeAll
y afterAll
**, que tienen su propio límite de 30 segundos, llama a test.setTimeout()
dentro del propio hook.
Para una prueba lenta, asígnale su propio tiempo de espera en test.extend()
en lugar de alargar el de todas las pruebas que la utilizan:
export const test = base.extend<{ seeded: void }>({
seeded: [async ({}, use) => {
await seedDatabase();
await use();
}, { timeout: 60_000 }],
});
El principio general: establece el ámbito más reducido que resuelva el problema. Aumentar el tiempo de espera global de las pruebas para adaptarse a una prueba lenta hace que el resto de pruebas tarden más en fallar, lo que supone una pérdida de tiempo real en la integración continua (CI).
Qué se tiene en cuenta para el tiempo de espera de la prueba
Es un aspecto que suele malinterpretarse y que explica por qué algunas pruebas agotan el tiempo de espera «antes de hacer nada».
La documentación es clara: «El tiempo empleado por la función de prueba, la configuración de los fixtures y los hooks de beforeEach se incluye en el tiempo de espera de la prueba».
Así pues, una prueba de «beforeEach» que inicia sesión, introduce datos iniciales y navega por la página consume los mismos 30 segundos que necesita el cuerpo de la prueba. Una prueba que parece agotar el tiempo de espera en su primera línea puede haber dedicado 28 segundos a la configuración.
Por defecto, los fixtures comparten el tiempo de espera de la prueba, lo que supone la misma trampa con otra forma: un fixture que consume muchos recursos agota el tiempo de espera de todas las pruebas que dependen de él. Asigna a los fixtures lentos su propio tiempo de espera en lugar de aumentar el tiempo de espera de la prueba en general.
La fase de desmontaje está separada: los desmontajes de los fixtures y los hooks de afterEach disponen de su propio tiempo de ejecución una vez finalizada la función de prueba, por lo que un desmontaje lento no consume el tiempo de la prueba.
La implicación práctica para la depuración: cuando una prueba agota el tiempo de espera, examina toda la cadena —los fixtures, los «beforeEach» y el cuerpo de la prueba— y no solo la línea a la que apunta el error.
Diagnostica antes de aumentar el tiempo de espera
Una secuencia que resuelve la mayoría de los tiempos de espera en pocos minutos.
Ejecútala con el visor de trazas. Esta es la herramienta más valiosa y, sin embargo, se utiliza muy poco:
npx playwright test --trace on
npx playwright show-trace trace.zip
La traza muestra cada acción, su duración, instantáneas del DOM antes y después, y la actividad de red. Un localizador que no ha encontrado ninguna coincidencia se ve de inmediato; lo mismo ocurre con el banner de cookies que aparece sobre tu botón.
Ejecútalo en modo «headed» y ralentizado cuando quieras ver cómo ocurre:
npx playwright test --headed --debug
Comprueba si el localizador se resuelve:
console.log(await page.getByRole('button', { name: 'Save' }).count());
Un valor de cero indica un problema con el selector, y ningún valor de tiempo de espera lo solucionará.
Comprueba si se trata de un problema de estabilidad utilizando una acción sin comprobaciones como diagnóstico, no como solución. Si dispatchEvent('click')
funciona mientras que click()
agota el tiempo de espera, algo está cubriendo o moviendo el elemento.
**Comprueba si hay una espera de networkidle
.** Las páginas con balizas de análisis, websockets o sondeos pueden no llegar nunca al estado de inactividad de la red. Es preferible esperar a lo que realmente te interesa:
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
Lee el mensaje de error en su totalidad. Los errores de tiempo de espera de Playwright incluyen el localizador, el recuento de elementos resueltos y qué comprobación de accionabilidad estaba pendiente. Este último detalle suele identificar el problema de forma directa.
Aumentar los tiempos de espera suele agravar la inestabilidad
La parte contraintuitiva, pero importante.
Una prueba inestable es aquella cuyo resultado depende del tiempo. Aumentar el tiempo de espera amplía el margen en el que la prueba se supera, por lo que la inestabilidad se vuelve menos frecuente —y, en consecuencia, más difícil de reproducir, más difícil de diagnosticar y más lenta cuando falla—.
Mientras tanto, el coste se paga con cada fallo. Un conjunto de 200 pruebas con un tiempo de espera de 30 segundos tarda como máximo 100 minutos en fallar por completo; con 120 segundos, tarda 400. En la integración continua (CI), eso supone dinero real y tiempo de espera real.
Lo que realmente soluciona la inestabilidad:
Espera a que se establezca el estado, no a que pase el tiempo. «waitForTimeout» casi siempre es incorrecto. Verifica la condición que te interesa y deja que Playwright realice el sondeo.
Utiliza verificaciones «web-first». «expect(locator).toBeVisible()» vuelve a intentarlo automáticamente. «expect(await locator.isVisible()).toBe(true)» comprueba una vez y falla al primer fallo —una fuente sutil pero muy común de inestabilidad—.
Gestiona las superposiciones de forma determinista. Cierra los avisos de cookies en un fixture en lugar de esperar a que desaparezcan.
Desactiva las animaciones en tu configuración siempre que puedas, en lugar de esperar a que terminen.
Espera a recibir la respuesta específica de la red de la que dependes, en lugar de esperar a que la red se quede en silencio.
Estabiliza los datos. Las pruebas que dependen de un estado mutable compartido son inestables por razones que el tiempo de espera no resuelve.
Cuándo debe activarse realmente un tiempo de espera: cuando la operación es realmente lenta y no hay forma de evitarlo —una subida de un archivo grande, un informe que tarda un minuto en generarse, un perfil de red deliberadamente limitado—. En esos casos, actívalo de forma específica, solo para esa prueba o esa aserción, y no modifiques los valores por defecto.
Tiempos de espera al ejecutar a través de un proxy
El caso en el que es legítimo lanzar una excepción «raise», y la configuración correspondiente.
Playwright admite ajustes de proxy en la configuración de red:
export default defineConfig({
use: {
proxy: {
server: 'http://proxy.example.com:9000',
username: 'user',
password: 'pass',
},
},
});
O por contexto, que es lo que te conviene cuando diferentes pruebas necesitan diferentes ubicaciones de salida:
const context = await browser.newContext({
proxy: { server: 'http://proxy.example.com:9000' },
});
Tres consecuencias prácticas.
Los proxies residenciales añaden una latencia real. El tráfico sale a través de una conexión de un usuario real, por lo que varios cientos de milisegundos adicionales por solicitud son normales y no un fallo. Una página que realiza ochenta solicitudes acumula esa latencia ochenta veces. Los valores predeterminados ajustados para localhost producirán fallos que parecen deberse a proxies defectuosos, pero que en realidad se deben simplemente a la distancia.
La respuesta correcta es medir en lugar de adivinar: ejecuta el conjunto de pruebas a través del proxy, analiza los tiempos de rastreo y configura navigationTimeout
y actionTimeout
a partir de lo que observes, dejando un margen generoso.
El ancho de banda es el verdadero coste, y es enorme con un navegador. Playwright descarga todas las imágenes, fuentes, scripts y vídeos precargados. En un tráfico residencial medido a 0,79 $/GB, esto supera con creces cualquier otro gasto. Bloquear los tipos de recursos innecesarios es la mayor fuente de ahorro disponible:
await page.route('**/*.{png,jpg,jpeg,webp,gif,woff,woff2,mp4}', r => r.abort());
Esto reduce habitualmente la mayor parte del tráfico total y, como efecto secundario, acelera tus pruebas.
Un bloqueo no es un tiempo de espera agotado. Si un destino muestra una página de desafío, Playwright agotará el tiempo de espera esperando un elemento que no se encuentra en dicha página —lo que parece exactamente un tiempo de espera agotado, pero no lo es—. Haz una captura de pantalla cuando se produzca un error y fíjate en lo que realmente se ha renderizado:
use: { screenshot: 'only-on-failure', trace: 'retain-on-failure' }
Este es el patrón de «fallo silencioso» que describimos en por qué es importante probar los proxies: la solicitud se completó con éxito, la página se renderizó, pero era la página equivocada.
Preguntas frecuentes
¿Cuál es el tiempo de espera predeterminado en Playwright?
30 000 ms para una prueba y para los hooks de beforeAll/afterAll, y 5 000 ms para las aserciones de expect. Los tiempos de espera de las acciones y la navegación no tienen un valor predeterminado y solo están limitados por el tiempo de espera de la prueba; por eso, un clic lento muestra los 30 segundos de la prueba en lugar de su propio límite.
¿Cómo puedo aumentar el tiempo de espera de una prueba de Playwright?
Llama a test.setTimeout(120_000) dentro de la prueba, o a test.slow() para triplicar el valor por defecto. Es preferible utilizar estas opciones en lugar de aumentar el tiempo de espera global, ya que esto hace que el resto de pruebas tarden más en dar error.
¿Por qué se agota el tiempo de espera de mi prueba de Playwright cuando el elemento está en la página?
Normalmente, porque no es accionable. click requiere que el elemento sea visible, estable, que reciba eventos y esté habilitado; por lo tanto, un elemento cubierto por un banner o que aún esté animado se detectará, pero nunca se hará clic en él. El mensaje de error indica qué comprobación estaba pendiente.
¿Cuál es la diferencia entre el tiempo de espera de la prueba y el tiempo de espera de «expect»?
El tiempo de espera de la prueba es el tiempo total asignado a la función de prueba, las configuraciones de los fixtures y los hooks de beforeEach combinados, con un valor predeterminado de 30 segundos. El tiempo de espera esperado es el tiempo que tarda en sondear una única aserción «web-first», con un valor predeterminado de 5 segundos. Si una aserción falla tras 5 segundos, se trata del tiempo de espera esperado, no del tiempo de espera de la prueba.
¿Debería utilizar waitForTimeout en Playwright?
Casi nunca. Un tiempo de espera fijo es o bien demasiado corto, lo que hace que la prueba sea inestable, o bien demasiado largo, lo que ralentiza el conjunto de pruebas —normalmente ambas cosas en máquinas diferentes—. En su lugar, espera a que se cumpla la condición con una aserción «web-first», que reintenta automáticamente hasta que se agota el tiempo de espera.
¿Por qué «networkidle» nunca se resuelve?
Porque la página sigue realizando solicitudes: balizas de análisis, websockets, sondeos, conexiones de larga duración. «networkidle» requiere silencio, y muchas aplicaciones modernas nunca se quedan en silencio. En su lugar, espera al elemento o a la respuesta específicos que te interesen.
¿Aumentar el tiempo de espera soluciona las pruebas inestables?
Las oculta. Los fallos esporádicos se vuelven más raros, más difíciles de reproducir y tardan más en producirse, mientras que cada fallo real en el conjunto de pruebas tarda ahora más tiempo en manifestarse. Soluciona la causa: espera a que se establezca el estado en lugar de esperar un tiempo determinado, utiliza aserciones de reintento, elimina las superposiciones de forma determinista y desactiva las animaciones.
¿Necesito tiempos de espera más largos cuando utilizo un proxy?
A menudo sí, para la navegación y las acciones, porque los proxies residenciales añaden una latencia real por solicitud que se acumula a lo largo de las numerosas solicitudes que realiza una página. Mídela a través del proxy con el rastreo activado y establece los valores en función de lo que observes, en lugar de aumentar todo de forma preventiva.
Conclusión
El mensaje indica que la prueba superó los 30 segundos, y eso es más bien un límite que una explicación. Algo en su interior esperó una condición que nunca se cumplió, y en cuatro de cada cinco casos esa condición es un selector que no coincide con nada o un elemento que nunca llegó a ser procesable.
Así pues, el orden que ahorra tiempo es el siguiente: lee el error completo, que indica la comprobación de ejecutabilidad pendiente; abre el rastreo, que te muestra el DOM en el momento del fallo; confirma que el localizador se resuelve; y solo entonces ten en cuenta el número. Generar un tiempo de espera es la solución correcta para una única causa —una operación que realmente tarda más de lo previsto— y la solución errónea para las otras cuatro, en las que solo se consigue un fallo más lento.
Cuando lo actives, hazlo de forma selectiva: por prueba, por aserción, por fixture. Aumentar los valores predeterminados globales para adaptarse a una única carga lenta encarece cada fallo de la suite, y el tiempo de CI es el único recurso que nunca se recupera.
