Me he metido de lleno en un trabajo más intenso con Cursor que ha requerido muchas partes complejas.
Como resultado, las pruebas se han vuelto más críticas y pensé en escribir una entrada para mostraros cómo podéis automatizar vuestras pruebas con Cursor.
Esta entrada del blog os será útil si:
- Queréis crear proyectos más complicados con Cursor.
- Queréis reducir las solicitudes y las comprobaciones de errores y que el LLM se encargue de ello.
- Queréis aprender sobre las pruebas automatizadas.
- Os da dolor de cabeza que la IA lo estropee todo aleatoriamente.
Vamos a utilizar un proyecto financiero como caso de prueba. Para empezar, vamos a mostrar un ejemplo de cómo conseguir que el sistema ayude a automatizar las pruebas de nuestro proceso de creación de facturas.
Configuración de nuestras pruebas
En primer lugar, definamos qué queremos que el sistema pruebe automáticamente:
- El usuario puede navegar a la página de facturas
- Envío de formularios cuando crean una factura
- Actualizaciones de estado y órdenes de compra
Para ello, utilizaremos una herramienta llamada Playwright
Me encanta Playwright, ya que ofrece un panel de control muy útil con todas nuestras pruebas y su estado.
¿Cómo es una prueba?
La prueba es solo un pequeño archivo de código que Cursor puede escribir por ti.
test("User can navigate to invoices page", async ({ page }) => {
// Start from the homepage
await page.goto("/");
// Should be redirected to dashboard
await expect(page).toHaveURL("/dashboard");
// Navigate to invoices
await page.click("text=Invoices");
await expect(page).toHaveURL("/dashboard/invoices");
// Verify the page has loaded correctly
await expect(page.locator("h1")).toContainText("Invoices");
await expect(page.locator("a:has-text('New Invoice')")).toBeVisible();
});
El archivo anterior:
- Navega desde la página de inicio al panel de control y, a continuación, va a la página de facturas.
- A continuación, comprueba que la página de facturas se ha cargado correctamente.
Una prueba más complicada con un formulario, que muestra una prueba que impide el envío de datos no válidos.
test("Form validation prevents submission of invalid data", async ({ page }) => {
// Go to new invoice page
await page.goto("/dashboard/invoices/new");
// Try to submit without filling the form
await page.click("button[type='submit']");
// Should show validation errors
await expect(page.locator("text=Supplier is required")).toBeVisible();
await expect(page.locator("text=Due date is required")).toBeVisible();
await expect(page.locator("text=Amount must be a positive number")).toBeVisible();
// Should not navigate away from the form
await expect(page).toHaveURL("/dashboard/invoices/new");
// Fill in just the supplier field
await page.fill("input[name='supplier']", "Test Supplier");
// Submit again
await page.click("button[type='submit']");
// Should still show some validation errors
await expect(page.locator("text=Due date is required")).toBeVisible();
await expect(page.locator("text=Amount must be a positive number")).toBeVisible();
// Supplier error should be gone
await expect(page.locator("text=Supplier is required")).not.toBeVisible();
La prueba anterior es excelente, ya que muestra un formulario que se intenta enviar sin datos.
Personalmente, me encanta hacer esto con Cursor, ya que piensa en todo tipo de casos de uso extraños (y realistas).
¿Qué ocurre si una prueba no se supera?
Aquí es donde este enfoque destaca.
Si se produce un error, es estupendo, ya que Cursor tiene el contexto exacto del caso de prueba sobre el error y, si lo combinas con la descripción general de tu proyecto, verás que empieza a corregir los errores por sí solo.
Bajo el capó hay una carpeta llamada «datos de prueba» y, si inspeccionas esa carpeta, podrás ver un registro mucho más completo de la prueba y por qué falló.
- navigation:
- text: LedgerLite
- list:
- listitem:
- link "Dashboard":
- /url: /dashboard
- listitem:
- link "Invoices":
- /url: /dashboard/invoices
- listitem:
- link "Purchase Orders":
- /url: /dashboard/pos
- main:
- heading "Purchase Orders" [level=1]
- link "New Purchase Order":
- /url: /dashboard/pos/new
- textbox "Search by supplier..."
- table:
- rowgroup:
- row "Supplier Reference Amount Status Actions":
- cell "Supplier"
- cell "Reference"
- cell "Amount"
- cell "Status"
- cell "Actions"
- rowgroup:
- row "No purchase orders found. Create your first purchase order!":
- cell "No purchase orders found. Create your first purchase order!"
- link "< Back to Dashboard":
- /url: /dashboard
- alert
- button "Open Next.js Dev Tools":
- img
También conoce el origen de la prueba y dónde falló.
import { test, expect } from "@playwright/test";
test("User can navigate to purchase orders page", async ({ page }) => {
// Start from the dashboard
await page.goto("/dashboard");
// Navigate to purchase orders - increase timeout for navigation
await page.click("text=Purchase Orders", { timeout: 10000 });
await expect(page).toHaveURL("/dashboard/pos");
// Wait for the page to load completely
await page.waitForLoadState('networkidle');
// Verify the page has loaded correctly - increase timeout for visibility check
await expect(page.locator("h1")).toContainText("Purchase Orders", { timeout: 10000 });
await expect(page.locator("a:has-text('New Purchase Order')")).toBeVisible({ timeout: 10000 });
});
También proporciona una descripción del error.
# Error details
Error: Timed out 10000ms waiting for expect(locator).toBeVisible()
Locator: locator('text=Status Test 1745482362373')
Expected: visible
Received: <element(s) not found>
Call log:
- expect.toBeVisible with timeout 10000ms
- waiting for locator('text=Status Test 1745482362373')
at C:\Users\User\Documents\GitHub\testrepo\playwright-tests\purchase-order-status.spec.ts:80:56
Todo esto es mucho más limpio que el proceso de encontrar un error, copiar el código aleatorio e intentar decirle a Cursor que llegue al origen del error.
Aquí hay un buen ejemplo: mi API de órdenes de compra falló.
Cursor probó una consulta en otro archivo que funcionaba y entonces se dio cuenta de por qué fallaba la API. No tuve que decirle nada.
Esto es mucho mejor para los LLM, ya que a veces un LLM arregla una cosa y luego estropea otras.
Esto resulta muy útil, ya que permite probar todo el producto y detecta errores molestos que quizá no se te ocurriría probar en un primer momento. Además, permite a la IA comprender por qué ese cambio radical era negativo.
Recuerda que durante todo este tiempo solo se trata de sugerir y corregir, básicamente he estado en piloto automático todo este tiempo.
Creo que esto ha sido mucho más eficaz a la hora de resolver flujos de pruebas complicados de múltiples pasos.
Los datos de prueba son reales y se muestran en tu interfaz
Es muy satisfactorio ver cómo tu producto se va llenando con todos estos datos.
Si estás creando cualquier tipo de sistema complejo y lo estás probando manualmente, esto te encantará. Se acabaron los clics repetitivos.
Vamos a trabajar con un ejemplo de código sencillo y lo explicaremos, y luego mostraremos cómo funciona con Cursor.
Tengo otra teoría: también es una forma mucho más rentable de desarrollar, ya que el LLM realiza muchas más solicitudes de herramientas por cada comando que le das.
Una gran ventaja que he descubierto es que se gasta menos energía mental en Cursor.
He descubierto que, cuando se realizan estas pruebas unitarias, se dedica mucho menos tiempo a introducir comandos y mucho más tiempo a dejar que la IA haga su trabajo.
Esto hace que sea mucho más fácil ejecutar varios proyectos en paralelo.
Por ejemplo, el error anterior es molesto de probar, pero este proceso ayuda mucho, ya que realiza la navegación por varias páginas por ti.
Las pruebas automatizadas son excelentes cuando se inicia un nuevo hilo de chat
¿Alguna vez te cansas de iniciar un nuevo hilo de chat y tener que pasar el contexto de nuevo y «calentar» el LLM para que entienda lo que estás haciendo?
Las pruebas son excelentes para esto, simplemente indícale que ejecute las pruebas de extremo a extremo cuando abras la ventana de chat y el modelo verá inmediatamente las cosas que no funcionan y sabrá exactamente qué hay que arreglar.
Ahora es una de mis cosas favoritas.
Lo bueno es que, cuando se completa una prueba, se abre una nueva pestaña que muestra los resultados.
Finalmente, tras unos 15 minutos, Cursor resolvió todos sus problemas y recibí un agradable mensaje con una luz verde.
Ahora puedo abrir mi producto y ver los resultados.
Puntos clave
- Pide a Cursor que te escriba un conjunto de pruebas exhaustivas.
- Piensa críticamente cuáles son importantes y empieza por ahí.
- Si tienes un producto enorme, divide tus pruebas en unidades más pequeñas y resuelve una sección cada vez.
- Este es un marco ideal si estás empezando un nuevo repositorio desde cero con un LLM.
Las ventajas de este enfoque
- Después de escribir un par de pruebas pequeñas, verás las ventajas.
- Ayudan a que tu código base se sienta más estable.
- Las cosas van un poco más lentas, pero todo se siente más ordenado.
- La IA tiene mucho más contexto.
- Maximizas el uso de la herramienta y, en última instancia, te cuesta menos en reelaboraciones o refactorizaciones.
Las desventajas de este enfoque.
El desarrollo basado en pruebas puede ser lento, ya que hay que pasar por múltiples etapas antes de continuar. Si solo estás creando un prototipo y quieres obtener algo rápidamente, no siempre es necesario hacerlo.
En mi caso, la línea divisoria es que si estoy trabajando en un proyecto o una función en la que ni siquiera conozco el resultado final y utilizo la IA para ayudarme a averiguarlo, es menos probable que utilice este enfoque, ya que, en esencia, hay muchas cosas que podrían cambiar.
Si estoy trabajando en algo que tenga que ver remotamente con un proyecto para un cliente o con algo de lo que conozco la respuesta de antemano y tengo una idea general de cómo debería funcionar el proyecto, lo implementaré desde el principio.
Gracias por leer Código en Casa.