Pruebas de dApps Web3 con Synpress
La regla más importante en Web3: la interfaz de usuario no es la fuente de verdad, la cadena lo es. Una dApp puede mostrar "Éxito" para una transacción que fue revertida, reemplazada o nunca salió de la billetera. Cada prueba que cambie el estado debe terminar con una verificación en cadena.
Configuración
Primero verifica qué usa el proyecto. Estas notas son para Synpress v4 (basado en Playwright). v3 estaba basado en Cypress y su API es diferente.
npm i -D @synthetixio/synpress @playwright/test viem
Usa "type": "module" en package.json y "moduleResolution": "Bundler" en tsconfig.json
(consulta examples/tsconfig.json); Synpress distribuye módulos ES. Instala la versión de @playwright/test
de la que depende Synpress (npm ls @playwright/test), de lo contrario obtendrás dos copias de Playwright con
tipos incompatibles.
Ejecuta una cadena local para que las pruebas sean rápidas, gratuitas y repetibles. Anvil de Foundry es la opción habitual:
anvil --chain-id 31337 --block-time 1 # 10 cuentas financiadas de la frase mnemotécnica de prueba predeterminada
# o bifurca una red real en un bloque fijo, para que el estado del contrato sea el mismo en cada ejecución:
anvil --fork-url $RPC_URL --fork-block-number 21000000
Nunca uses una frase semilla que haya tenido fondos reales. Usa la frase mnemotécnica de prueba de Anvil o una billetera desechable dedicada, mantenida en variables de entorno, nunca comprometida.
Configuración de billetera y caché
Synpress construye un perfil de navegador con MetaMask ya configurado y lo almacena en caché, para que las pruebas no
tengan que reimportar la billetera cada vez. Consulta examples/wallet-setup/basic.setup.ts.
npx synpress # construye el caché a partir de cada *.setup.ts en la carpeta wallet-setup
Reconstruye el caché cuando cambie el archivo de configuración o la versión de MetaMask. En CI, la extensión necesita una
pantalla: ejecuta las pruebas bajo xvfb-run.
Qué cubrir
| Flujo | Casos que encuentran bugs reales |
|---|---|
| Conexión | primera conexión, rechazar conexión, desconectar y reconectar, billetera bloqueada, cuenta incorrecta seleccionada |
| Red | dApp en la cadena incorrecta, el usuario cambia de cadena en medio del flujo, agregar una red que la billetera no tiene |
| Firma (SIWE, permisos) | confirmar, rechazar, firmar con la cuenta A y luego cambiar a B antes de enviar |
| Transacción | confirmar, rechazar, saldo insuficiente, aumento de gas, transacción reemplazada o acelerada, revertir desde el contrato |
| Aprobación de tokens | aprobar exacto vs ilimitado, aprobar y luego gastar, gastar sin aprobación |
| Estado pendiente | recargar mientras está pendiente, cerrar pestaña y volver, dos transacciones seguidas |
| Visualización | saldos después de la transacción (UI vs cadena), decimales y redondeo, cantidades muy grandes y residuales |
Rechazar en la billetera es tan importante como confirmar. Muchas dApps se quedan en "Esperando confirmación" para siempre después de un rechazo.
Escribir una prueba
Patrón (archivo completo en examples/transfer.spec.ts):
- Lee el estado inicial desde la cadena con
viem(getBalance,readContract). - Controla la interfaz de la dApp con los localizadores de Playwright, y la billetera con el ayudante
MetaMask(connectToDapp,confirmSignature,confirmTransaction,rejectTransaction,switchNetwork). - Obtén el hash de la transacción desde la interfaz, un evento o la respuesta de la red, luego
waitForTransactionReceipt. - Afirma sobre el estado del recibo y el nuevo estado en cadena. Luego afirma que la interfaz está de acuerdo.
Mantén la lógica independiente de la billetera (validación de cantidades, formato, texto de error) en pruebas más rápidas con el simulacro de billetera Ethereum de Synpress o en Playwright simple con un proveedor simulado. Usa MetaMask real solo para los flujos donde la extensión en sí misma importa.
Bugs típicos a buscar
- La interfaz muestra éxito mientras el estado del recibo es
revertido, o antes de que el recibo exista. - El saldo en la interfaz está en caché y no se actualiza después de la transacción.
- Precisión de cantidades: tokens de 18 decimales mostrados con redondeo de punto flotante; el botón máximo deja residuos o excede el saldo en 1 wei.
- Después de un cambio de cadena, la dApp sigue llamando a la dirección del contrato de la cadena anterior.
- Una firma rechazada deja el botón deshabilitado hasta recargar.
- La misma acción se puede enviar dos veces mientras la primera transacción está pendiente.
- Gobernanza: voto contado en la interfaz pero el estado de la propuesta en cadena no cambió (ID de propuesta incorrecto, voto emitido después del bloque de cierre).
Reportar un bug de Web3 con
Red e ID de cadena, dirección del contrato, dirección de la billetera (solo billetera de prueba), hash de la transacción y
número de bloque, estado del recibo, lo que mostró la interfaz vs lo que muestra la cadena, versión de MetaMask.
Un enlace al explorador de bloques (o traza de Anvil con cast run <txhash>) es la mejor evidencia.