Playwright es un framework que nos ayuda a automatizar test, y que ademas esta basado en Puppeteer, por lo tanto, si sabes como usar Puppeteer, te resultara muy familiar la API de Playwright.
Durante el curso se estuvo usando la API, pero llego un momento en donde se nos pone un reto, el cual es hacer un test de una pagina de ecommerce. Primero se nos indican las instrucciones y que se espera que hagamos, para que despues la maestra nos enseñe su solucion. Sin embargo, cuando se trata de la creacion de codigo, hay muchas soluciones, es este caso yo voy a presentar y explicar mi solucion para que veas otras maneras de solucionar el problema.
<h1>El reto</h1>La idea es entrar a https://automationexercise.com/category_products y verificar que podemos hacer cada una de las siguientes tareas:
Ahora es momento de pasar todo esto a codigo:
<h1>Setup del test suit</h1>Primero vamos a empezar por hacer el setup del projecto, lo que significa asegurarnos de que las cosas mas sencillas se pueden hacer para pasar a hacer cosas mas complejas:
import { test, expect } from"@playwright/test"const BASE_URL = "https://automationexercise.com/"
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL)
})
Lo que se esta haciendo es entrando a la pagina antes de cada una de las pruebas, esto se hace con el hook beforeEach
, tambien agregue la BASE_URL para que no estemos escribiendo la url de la pagina mas de una vez en todo nuestro suit de pruebas.
Una vez que tenemos lo mas basico hecho, es hora de hacer un scroll hacia abajo para poder ver las product cards, esto se hace de la siguiente manera:
test.describe("Testing e-commerce page", () => {
test("Challenge steps", async ({ page }) => {
await page.mouse.wheel(0, 500)
})
})
Aqui, primero estamos haciendo un describe, que es lo que nos ayuda a organizar nuestras pruebas en distintos modulos. Despues, hacemos el scroll con el page.mouse.wheel(0, 500)
donde el primer parametro es el eje en x y el segundo es el eje en y. Nosotros queremos ir hacia abajo, por lo que aumentamos en valor en y.
Despues de esto, es muy recomendable hacer una verificacion de que esto funciona como deberia, por lo que ahora lo que quieres hacer es correr el test con head, para que asi se abra el navegador y veas como se hace scroll hacia abajo.
Para hacer esto, debes correr el comando npx playwright test [test_name] --headed
, en mi caso, el nombre del archivo de mi test es: “commerce.spec.ts”, por lo que mi comando quedaria de la siguiente manera: npx playwright test commerce --headed
, de esa forma, se va a abrir el navegador para que veas toda la automatizacion.
Probablemente haya funcionado, pero fue demasiado rapido, para bajar la velocidad de la prueba es necesario modificar el archivo de configuracion:
// playwright.config.tsconstconfig: PlaywrightTestConfig = {
...
use: {
actionTimeout: 0,
launchOptions: {
slowMo: 1000
},
trace: 'on-first-retry',
},
Los ...
significan que hay mas configuracion en el archivo, pero lo que nos importa es la seccion de use
, ya que en launchOptions
podemos configurar slowMo
, que se refiere al tiempo que debe pasar para que se ejecute cada accion, en este caso estamos pidiendole que tenga un tiempo de espera de 1000 milisegundos, lo que seria lo mismo que un segundo.
Despues de esto, ahora si puedes correr el comando de nuevo npx playwright test commerce --headed
y ver como se hace un scroll para abajo.
Una vez que hayas terminado de ver en accion tu test, es importante quitar esta configuracion, ya que esto puede perjudicar el tiempo en el que tu prueba termine de ejecutarse.
Asi mismo, es importante quitar el flag --headed
para cuando hagas CI/CD, ya que estos tests se van a estar corriendo en un servidor, por lo que no habra GUI para que se levante un browser, ademas de que tambien afectara el rendimiento de la prueba y de todo el proceso agil.
Despues de que aprendiste a correr la prueba con head, o con el navegador abierto, es hora de enfocarnos en el codigo de la misma.
Otro de los pasos para hacer el reto era hacer hover de una productCard. Esto lo hacemos de la siguiente manera:
test.describe("Testing e-commerce page", () => {
test("Challenge steps", async ({ page }) => {
await page.mouse.wheel(0, 500)
const firstProductCard = page.locator("#cartModal + .col-sm-4 .single-products")
await firstProductCard.hover()
})
})
Primero hacemos una variable con la card de la cual vamos a hacer hover, en este caso la card tiene el css selector de “#cartModal + .col-sm-4 .single-products”. Ya depues solo hacemos hover de la card.
<h1>Verificando que el elemento onHover se haya mostrado</h1>En la aplicacion, una vez que se hace hover a un productCard, se muestra un div con fondo naranja que nos habilita la posibilidad de agregar el producto al carrito
Como puedes ver, tenemos un rectangulo en naranja y dos cards de productos. El rectangulo en naranja no es mas que un producto al que se le esta haciendo hover
Lo que te gustaria hacer ahora es asegurarte de que este div en naranja se esta mostrando. Eso lo hacemos de la siguiente manera:
const cardOverlay = page.locator("#cartModal + .col-sm-4 .product-overlay")
const cardOverlayDisplayAttribute = await cardOverlay.evaluate((e) =>
window.getComputedStyle((e as HTMLDivElement)).display
)
await expect(cardOverlayDisplayAttribute).toBe("block")
Aqui, lo que se esta haciendo es una variable (cardOverlay) donde se guarda este div naranja, cuyo selector de css es #cartModal + .col-sm-4 .product-overlay, despues de eso, se esta consiguiendo su valor actual de display, esto se esta haciendo con el metodo evaluate
, el cual nos permite manipular el elemento html y devolver un valor, en este caso se esta accediendo a sus estilos mediante window.getComputedStyle
, para finalmente hacer la aserción de que la propiedad display de cardOverlay
es block.
Aqui tu te podrias hacer muchas preguntas, ya que esta aserción se ve muy compleja y tu te podrias preguntar, ¿no podriamos hacer un toBeVisible
, para que quede algo como lo siguiente?
const cardOverlay = page.locator("#cartModal + .col-sm-4 .product-overlay")
await expect(cardOverlay).toBeVisible()
Bueno, lamento decepcionarte pero esto esta mal, ya que la aserción de “toBeVisible” solamente evalua que el elemento exista en el DOM, el problema es que el div naranja SIEMPRE esta presente en el dom. Por lo tanto, lo unico que cambia es su valor en la propiedad “display”, por eso se complicó un poco mas esa aserción.
<h1>Haciendo click y asegurandose de que el modal se muestre</h1>Despues de hacer hover sobre un producto, podemos ser capaces de agregar ese producto al carrito gracias al boton que se muestra dentro del div naranja. Una vez que se agrega algo al carrito nos paparece un modal, todo este proceso hay que pasarlo a codigo:
await page.click("#cartModal + .col-sm-4 .overlay-content > a.add-to-cart")
const modal = page.locator("#cartModal")
await expect(modal).toHaveClass(/show/)
Esta aserción es mucho mas sencilla, ya que despues de hacer el click, creamos una variable con el modal (no hagas variable de todos los elementos a no ser que los vayas a usar de nuevo, en este caso el modal se va a volver a usar despues en el test) y se esta esperando a que este tenga una clase que haga match con la palabra “show”, esto es porque el modal cambia de clase dependiendo si deberia ser visible o no. Cuando es visible, tiene la clase “show”, cuando no, tiene la clase “fade”.
<h1>Asegurarse de que el titulo del modal sea correcto</h1>No solo es necesario asegurarse de que el modal se abrio, sino que el mensaje del modal debe ser el correcto
awaitexpect(
page.locator(".modal-content.modal-title")
).toHaveText("Added!")
Aqui hacemos un clasico expect donde le pasamos el elemento que representa el titulo y esperamos que tenga el texto “Added”
<h1>Quitar el modal y asegurarnos que no esta visible</h1>Este test deberia ser facil para este momento, ya que se han realizado test similares en este articulo antes
await page.click("button[data-dismiss='modal']")
await expect(modal).toHaveClass(/fade/)
Se hace click del boton que esta dentro del modal y se espera que el modal tenga la clase que haga match con la palabra “fade”
<h1>Todo el codigo junto</h1>Al final todo el archivo commerce.spec.ts
se ve de la siguiente manera
import { test, expect } from "@playwright/test"const BASE_URL = "https://automationexercise.com/"
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL)
})
test.describe("Testing e-commerce page", () => {
test("Challenge steps", async ({ page }) => {
await page.mouse.wheel(0, 500)
const firstProductCard = page.locator("#cartModal + .col-sm-4 .single-products")
await firstProductCard.hover()
const cardOverlay = page.locator("#cartModal + .col-sm-4 .product-overlay")
const cardOverlayDisplayAttribute = await cardOverlay.evaluate((e) =>
window.getComputedStyle((e as HTMLDivElement)).display
)
// await expect(cardOverlay).toBeVisible()await expect(cardOverlayDisplayAttribute).toBe("block")
await page.click("#cartModal + .col-sm-4 .overlay-content > a.add-to-cart")
const modal = page.locator("#cartModal")
await expect(modal).toHaveClass(/show/)
await expect(
page.locator(".modal-content .modal-title")
).toHaveText("Added!")
await page.click("button[data-dismiss='modal']")
await expect(modal).toHaveClass(/fade/)
})
})