Saltar al contenido principal

Pruebas Web con Serenity Screenplay y Playwright

Introduccion

Playwright es una biblioteca moderna de automatizacion de navegadores que proporciona capacidades de pruebas cross-browser con excelente soporte para aplicaciones web modernas. El modulo serenity-screenplay-playwright trae el poder de Playwright al Screenplay Pattern de Serenity BDD, ofreciendo una alternativa a la integracion tradicional con WebDriver.

Playwright ofrece varias ventajas sobre WebDriver:

  • Auto-waiting: Espera automaticamente a que los elementos sean accionables antes de realizar acciones
  • Cross-browser: Soporte nativo para Chromium, Firefox y WebKit
  • Interceptacion de red: Soporte integrado para mockear e interceptar peticiones de red
  • Tracing: Registra trazas detalladas para depurar fallos de pruebas
  • Emulacion de dispositivos: Facil emulacion de dispositivos moviles y tablets
  • Arquitectura moderna: Diseno basado en eventos con mejor confiabilidad

Primeros Pasos

Dependencia Maven

Agrega la siguiente dependencia a tu proyecto:

<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay-playwright</artifactId>
<version>${serenity.version}</version>
</dependency>

La Ability BrowseTheWebWithPlaywright

Para usar Playwright con Screenplay, los Actor necesitan la Ability BrowseTheWebWithPlaywright:

import net.serenitybdd.screenplay.playwright.abilities.BrowseTheWebWithPlaywright;
import com.microsoft.playwright.*;

Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();

Actor alice = Actor.named("Alice");
alice.can(BrowseTheWebWithPlaywright.using(browser));

Configuracion

Puedes configurar el comportamiento de Playwright usando variables de entorno o programaticamente:

// Iniciar con opciones
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions()
.setHeadless(false)
.setSlowMo(100);

Browser browser = playwright.chromium().launch(options);

Abriendo una URL

Abriendo una URL directamente

En Screenplay, abres una nueva pagina usando la clase de Interaction Navigate:

alice.attemptsTo(Navigate.to("https://todomvc.com/examples/react/"));

Abriendo la URL base

Si has configurado una URL base, puedes navegar a ella:

alice.attemptsTo(Navigate.toTheBaseUrl());

Localizando Elementos en una Pagina

La Clase Target

La clase Target en la integracion con Playwright usa el potente motor de selectores de Playwright. A diferencia de WebDriver, Playwright proporciona multiples estrategias de selectores integradas.

Target SUBMIT_BUTTON = Target.the("Submit button").locatedBy("#submit-btn");

alice.attemptsTo(Click.on(SUBMIT_BUTTON));

Selectores de Playwright

Playwright soporta un rico conjunto de selectores:

// Selector CSS
Target.the("Login button").locatedBy("#login-btn")

// Selector de texto
Target.the("Submit button").locatedBy("text=Submit")

// Selector de rol (ARIA)
Target.the("Submit button").locatedBy("role=button[name='Submit']")

// Selector XPath
Target.the("Email field").locatedBy("xpath=//input[@type='email']")

// Combinando selectores
Target.the("Form submit").locatedBy("form >> button[type='submit']")

Target Dinamicos

Puedes usar Target parametrizados para localizacion dinamica de elementos:

Target MENU_ITEM = Target.the("{0} menu item").locatedBy("text={0}");

alice.attemptsTo(Click.on(MENU_ITEM.of("Settings")));

Factorias de Elementos UI

Serenity Playwright proporciona clases de factoria convenientes para localizar elementos UI comunes usando la poderosa sintaxis de selectores de Playwright.

Button

Localiza botones usando varias estrategias:

import net.serenitybdd.screenplay.playwright.ui.Button;

// Por texto visible (no distingue mayusculas/minusculas, usa selector de rol)
alice.attemptsTo(Click.on(Button.withText("Submit")));

// Por atributo name o ID
alice.attemptsTo(Click.on(Button.withNameOrId("submit-btn")));

// Por aria-label
alice.attemptsTo(Click.on(Button.withAriaLabel("Close dialog")));

// Conteniendo texto especifico
alice.attemptsTo(Click.on(Button.containingText("Add to")));

// Localizador personalizado
alice.attemptsTo(Click.on(Button.locatedBy("[data-testid='primary-action']")));

InputField

Localiza campos de entrada:

import net.serenitybdd.screenplay.playwright.ui.InputField;

// Por name o ID
alice.attemptsTo(Enter.theValue("john@example.com").into(InputField.withNameOrId("email")));

// Por texto de placeholder
alice.attemptsTo(Enter.theValue("Search...").into(InputField.withPlaceholder("Search products")));

// Por texto de etiqueta asociada
alice.attemptsTo(Enter.theValue("password123").into(InputField.withLabel("Password")));

// Por aria-label
alice.attemptsTo(Enter.theValue("John").into(InputField.withAriaLabel("First name")));

Localiza elementos anchor:

import net.serenitybdd.screenplay.playwright.ui.Link;

// Por texto exacto
alice.attemptsTo(Click.on(Link.withText("Learn more")));

// Conteniendo texto
alice.attemptsTo(Click.on(Link.containingText("documentation")));

// Por atributo title
alice.attemptsTo(Click.on(Link.withTitle("View user profile")));

Checkbox

Localiza inputs de tipo checkbox:

import net.serenitybdd.screenplay.playwright.ui.Checkbox;

// Por texto de etiqueta
alice.attemptsTo(Click.on(Checkbox.withLabel("Accept terms")));

// Por name o ID
alice.attemptsTo(Click.on(Checkbox.withNameOrId("newsletter")));

// Por atributo value
alice.attemptsTo(Click.on(Checkbox.withValue("premium")));

RadioButton

Localiza inputs de tipo radio button:

import net.serenitybdd.screenplay.playwright.ui.RadioButton;

// Por texto de etiqueta
alice.attemptsTo(Click.on(RadioButton.withLabel("Express shipping")));

// Por atributo value
alice.attemptsTo(Click.on(RadioButton.withValue("express")));

Localiza elementos select:

import net.serenitybdd.screenplay.playwright.ui.Dropdown;

// Por texto de etiqueta
alice.attemptsTo(
SelectFromOptions.byVisibleText("Canada").from(Dropdown.withLabel("Country"))
);

// Por name o ID
alice.attemptsTo(
SelectFromOptions.byValue("us").from(Dropdown.withNameOrId("country"))
);

Label

Localiza elementos label:

import net.serenitybdd.screenplay.playwright.ui.Label;

// Por contenido de texto
String labelText = alice.asksFor(Text.of(Label.withText("Email")));

// Para un ID de campo especifico
Target emailLabel = Label.forFieldId("email-input");

Image

Localiza elementos imagen:

import net.serenitybdd.screenplay.playwright.ui.Image;

// Por texto alt
alice.attemptsTo(Click.on(Image.withAltText("Product thumbnail")));

// Por URL de origen
alice.attemptsTo(Click.on(Image.withSrc("/images/logo.png")));

// Por URL de origen parcial
alice.attemptsTo(Click.on(Image.withSrcContaining("product-123")));

Interactuando con Elementos

Interaction Principales

Las siguientes clases de Interaction estan disponibles en el paquete net.serenitybdd.screenplay.playwright.interactions:

InteractionPropositoEjemplo
ClickHacer clic en un elementoactor.attemptsTo(Click.on("#button"))
DoubleClickDoble clic en un elementoactor.attemptsTo(DoubleClick.on("#item"))
RightClickClic derecho (menu contextual)actor.attemptsTo(RightClick.on("#menu"))
EnterEscribir en un campo de entradaactor.attemptsTo(Enter.theValue("text").into("#field"))
ClearLimpiar un campo de entradaactor.attemptsTo(Clear.field("#field"))
HoverPasar el mouse sobre un elementoactor.attemptsTo(Hover.over("#menu"))
PressPresionar teclas del tecladoactor.attemptsTo(Press.key("Enter"))
CheckMarcar un checkboxactor.attemptsTo(Check.checkbox("#agree"))
UncheckDesmarcar un checkboxactor.attemptsTo(Uncheck.checkbox("#agree"))
FocusEnfocar un elementoactor.attemptsTo(Focus.on("#input"))
NavigateNavegar a una URLactor.attemptsTo(Navigate.to("https://..."))
UploadSubir un archivoactor.attemptsTo(Upload.file(path).to("#upload"))

Click

Hacer clic en un elemento. Playwright automaticamente espera a que el elemento sea accionable:

alice.attemptsTo(Click.on("#submit-button"));
alice.attemptsTo(Click.on(SUBMIT_BUTTON));

Double-Click

Doble clic en un elemento:

alice.attemptsTo(DoubleClick.on("#item"));

Right-Click

Clic derecho para abrir menus contextuales:

alice.attemptsTo(RightClick.on("#file-item"));

Enter Text

Escribir valores en campos de entrada:

alice.attemptsTo(Enter.theValue("john@example.com").into("#email"));

Tambien puedes limpiar el campo primero:

alice.attemptsTo(
Clear.field("#email"),
Enter.theValue("new-email@example.com").into("#email")
);

Interaction de Teclado

Presionar teclas del teclado:

// Tecla individual
alice.attemptsTo(Press.key("Enter"));

// Combinaciones de teclas
alice.attemptsTo(Press.key("Control+a"));

// Multiples teclas
alice.attemptsTo(Press.keys("Tab", "Tab", "Enter"));

Hover

Pasar el mouse sobre elementos para activar estados hover:

alice.attemptsTo(Hover.over("#dropdown-menu"));

Check y Uncheck

Trabajar con checkbox:

alice.attemptsTo(Check.checkbox("#newsletter"));
alice.attemptsTo(Uncheck.checkbox("#marketing-emails"));

Focus

Enfocar un elemento:

alice.attemptsTo(Focus.on("#search-input"));

Seleccionando de Dropdown

Seleccionar opciones de menus desplegables:

// Por texto visible
alice.attemptsTo(SelectFromOptions.byVisibleText("Red").from("#color"));

// Por atributo value
alice.attemptsTo(SelectFromOptions.byValue("red").from("#color"));

// Por indice
alice.attemptsTo(SelectFromOptions.byIndex(2).from("#color"));

// Multiples valores (para multi-select)
alice.attemptsTo(SelectFromOptions.byValue("red", "blue", "green").from("#colors"));

Deseleccionando de Dropdown

Para dropdown multi-select, puedes deseleccionar opciones:

import net.serenitybdd.screenplay.playwright.interactions.DeselectFromOptions;

// Deseleccionar por valor
alice.attemptsTo(DeselectFromOptions.byValue("red").from("#colors"));

// Deseleccionar por texto visible
alice.attemptsTo(DeselectFromOptions.byVisibleText("Red").from("#colors"));

// Deseleccionar por indice
alice.attemptsTo(DeselectFromOptions.byIndex(0).from("#colors"));

// Deseleccionar todo
alice.attemptsTo(DeselectFromOptions.all().from("#colors"));

Desplazamiento (Scrolling)

Capacidades completas de desplazamiento:

import net.serenitybdd.screenplay.playwright.interactions.Scroll;

// Desplazar hasta un elemento
alice.attemptsTo(Scroll.to("#terms-and-conditions"));

// Desplazar con alineacion
alice.attemptsTo(Scroll.to("#section").andAlignToTop());
alice.attemptsTo(Scroll.to("#section").andAlignToCenter());
alice.attemptsTo(Scroll.to("#section").andAlignToBottom());

// Desplazamiento a nivel de pagina
alice.attemptsTo(Scroll.toTop());
alice.attemptsTo(Scroll.toBottom());

// Desplazar una cantidad especifica (deltaX, deltaY)
alice.attemptsTo(Scroll.by(0, 500));

// Desplazar a posicion especifica
alice.attemptsTo(Scroll.toPosition(0, 1000));

Arrastrar y Soltar (Drag and Drop)

Arrastrar elementos de una ubicacion a otra:

import net.serenitybdd.screenplay.playwright.interactions.Drag;

// Arrastrar y soltar basico
alice.attemptsTo(Drag.from("#source").to("#target"));

// Sintaxis fluida alternativa
alice.attemptsTo(Drag.the("#draggable").onto("#droppable"));

// Con Target
alice.attemptsTo(Drag.from(SOURCE_ELEMENT).to(TARGET_LOCATION));

Subida de Archivos

Subir archivos:

Path fileToUpload = Paths.get("path/to/file.pdf");
alice.attemptsTo(Upload.file(fileToUpload).to("#file-input"));

Ejecucion de JavaScript

Ejecutar JavaScript en el contexto de la pagina:

alice.attemptsTo(
Evaluate.javascript("window.scrollTo(0, document.body.scrollHeight)")
);

// Con valor de retorno
Object result = alice.asksFor(
Evaluate.javascript("return document.title")
);

Esperando

Esperar por elementos o condiciones:

// Esperar a que el elemento sea visible
alice.attemptsTo(WaitUntil.the("#loading").isNotVisible());

// Esperar a que el elemento este oculto
alice.attemptsTo(WaitUntil.the("#spinner").isHidden());

// Esperar con timeout
alice.attemptsTo(
WaitUntil.the("#content").isVisible()
.forNoMoreThan(Duration.ofSeconds(10))
);

Consultando la Pagina Web

Question Incluidas

Serenity Playwright proporciona clases Question para consultar el estado de la pagina:

QuestionPropositoEjemplo
TextObtener texto del elementoactor.asksFor(Text.of("#title"))
ValueObtener valor de inputactor.asksFor(Value.of("#email"))
AttributeObtener valor de atributoactor.asksFor(Attribute.of("#link").named("href"))
PresenceVerificar si el elemento existeactor.asksFor(Presence.of("#modal"))
AbsenceVerificar si el elemento esta ausenteactor.asksFor(Absence.of("#error"))
VisibilityVerificar si el elemento es visibleactor.asksFor(Visibility.of("#popup"))
EnabledVerificar si el elemento esta habilitadoactor.asksFor(Enabled.of("#submit"))
SelectedStatusVerificar si el checkbox esta seleccionadoactor.asksFor(SelectedStatus.of("#agree"))
CSSValueObtener propiedad CSSactor.asksFor(CSSValue.of("#box").named("color"))
CurrentUrlObtener URL de la pagina actualactor.asksFor(CurrentUrl.ofThePage())
PageTitleObtener titulo de la paginaactor.asksFor(PageTitle.ofThePage())

Text

Obtener el contenido de texto de un elemento:

String heading = alice.asksFor(Text.of("#main-heading"));

// Multiples elementos
List<String> items = alice.asksFor(Text.ofEach(".list-item"));

Presence y Absence

Verificar si los elementos existen en la pagina:

// Verificar si esta presente
boolean isPresent = alice.asksFor(Presence.of("#modal"));

// Verificar si esta ausente
boolean isAbsent = alice.asksFor(Absence.of("#error-message"));

Attributes

Obtener valores de atributos:

String href = alice.asksFor(Attribute.of("#link").named("href"));
String placeholder = alice.asksFor(Attribute.of("#input").named("placeholder"));

Informacion de la Pagina Actual

Consultar informacion a nivel de pagina:

String url = alice.asksFor(CurrentUrl.ofThePage());
String title = alice.asksFor(PageTitle.ofThePage());

Interceptacion y Mockeo de Red

Playwright proporciona capacidades poderosas de interceptacion de red para pruebas.

Interceptando Peticiones

import net.serenitybdd.screenplay.playwright.network.InterceptNetwork;

// Interceptar y responder con respuesta mock
alice.attemptsTo(
InterceptNetwork.requestsTo("**/api/users")
.andRespondWith(
new Route.FulfillOptions()
.setStatus(200)
.setBody("{\"users\": []}")
.setContentType("application/json")
)
);

// Interceptar y responder con JSON
alice.attemptsTo(
InterceptNetwork.requestsTo("**/api/user/123")
.andRespondWithJson(200, Map.of(
"id", 123,
"name", "John Doe",
"email", "john@example.com"
))
);

Manejadores de Peticiones Personalizados

Para mayor control, usa manejadores personalizados:

alice.attemptsTo(
InterceptNetwork.requestsTo("**/api/**")
.andHandle(route -> {
if (route.request().method().equals("DELETE")) {
route.fulfill(new Route.FulfillOptions()
.setStatus(403)
.setBody("{\"error\": \"Forbidden\"}"));
} else {
route.resume();
}
})
);

Abortando Peticiones

Bloquear peticiones especificas (util para probar manejo de errores):

alice.attemptsTo(
InterceptNetwork.requestsTo("**/analytics/**").andAbort()
);

Eliminando Rutas

Eliminar manejadores de rutas previamente registrados:

import net.serenitybdd.screenplay.playwright.network.RemoveRoutes;

// Eliminar todos los manejadores de rutas
alice.attemptsTo(RemoveRoutes.all());

// Eliminar rutas para un patron especifico
alice.attemptsTo(RemoveRoutes.forUrl("**/api/**"));

Integracion de Pruebas de API

Nuevo en 5.2.2

La integracion de pruebas de API fue agregada en Serenity BDD 5.2.2.

Realiza peticiones de API que comparten el contexto de sesion del navegador (cookies, autenticacion). Esto habilita escenarios de pruebas hibridas UI + API donde puedes configurar datos via API, realizar acciones de UI, y verificar estado a traves de llamadas API.

Peticiones de API Basicas

import net.serenitybdd.screenplay.playwright.interactions.api.APIRequest;
import net.serenitybdd.screenplay.playwright.questions.api.LastAPIResponse;

// Inicializar contexto del navegador primero (requerido para peticiones de API)
alice.attemptsTo(Open.url("about:blank"));

// Peticion GET
alice.attemptsTo(
APIRequest.get("https://api.example.com/users/1")
);

// Peticion POST con cuerpo JSON
alice.attemptsTo(
APIRequest.post("https://api.example.com/users")
.withJsonBody(Map.of(
"name", "John Doe",
"email", "john@example.com"
))
);

// Peticion PUT
alice.attemptsTo(
APIRequest.put("https://api.example.com/users/1")
.withJsonBody(Map.of("name", "Jane Doe"))
);

// Peticion PATCH
alice.attemptsTo(
APIRequest.patch("https://api.example.com/users/1")
.withJsonBody(Map.of("status", "active"))
);

// Peticion DELETE
alice.attemptsTo(
APIRequest.delete("https://api.example.com/users/1")
);

// Peticion HEAD
alice.attemptsTo(
APIRequest.head("https://api.example.com/users/1")
);

Configuracion de Peticiones

// Agregar headers personalizados
alice.attemptsTo(
APIRequest.get("https://api.example.com/data")
.withHeader("Authorization", "Bearer token123")
.withHeader("X-Custom-Header", "value")
);

// Agregar parametros de consulta
alice.attemptsTo(
APIRequest.get("https://api.example.com/search")
.withQueryParam("q", "test")
.withQueryParam("limit", "10")
);

// Establecer tipo de contenido
alice.attemptsTo(
APIRequest.post("https://api.example.com/data")
.withBody("<xml>data</xml>")
.withContentType("application/xml")
);

// Establecer timeout
alice.attemptsTo(
APIRequest.get("https://api.example.com/slow")
.withTimeout(30000) // 30 segundos
);

// Fallar en codigos de estado no-2xx
alice.attemptsTo(
APIRequest.get("https://api.example.com/data")
.failOnStatusCode(true)
);

Consultando Respuestas

// Obtener codigo de estado
int status = alice.asksFor(LastAPIResponse.statusCode());

// Verificar si la respuesta es OK (2xx)
boolean isOk = alice.asksFor(LastAPIResponse.ok());

// Obtener cuerpo de respuesta como string
String body = alice.asksFor(LastAPIResponse.body());

// Parsear respuesta JSON como Map
Map<String, Object> json = alice.asksFor(LastAPIResponse.jsonBody());

// Parsear respuesta de array JSON como List
List<Map<String, Object>> items = alice.asksFor(LastAPIResponse.jsonBodyAsList());

// Obtener headers de respuesta
Map<String, String> headers = alice.asksFor(LastAPIResponse.headers());
String contentType = alice.asksFor(LastAPIResponse.header("Content-Type"));

// Obtener URL final (despues de redirecciones)
String url = alice.asksFor(LastAPIResponse.url());

Pruebas Hibridas UI + API

Las peticiones de API automaticamente comparten cookies con el contexto del navegador:

@Test
void shouldUseAuthenticatedSession() {
// Login via UI
alice.attemptsTo(
Navigate.to("https://myapp.com/login"),
Enter.theValue("user@example.com").into("#email"),
Enter.theValue("password").into("#password"),
Click.on(Button.withText("Login"))
);

// Las llamadas API ahora incluyen cookies de autenticacion
alice.attemptsTo(
APIRequest.get("https://myapp.com/api/profile")
);

// Verificar respuesta de API autenticada
Map<String, Object> profile = alice.asksFor(LastAPIResponse.jsonBody());
assertThat(profile.get("email")).isEqualTo("user@example.com");
}

Llamadas API en Reportes de Serenity

Las peticiones de API realizadas via APIRequest se registran automaticamente en los reportes de Serenity, similar a RestAssured. Los reportes muestran:

  • Metodo HTTP y URL
  • Headers y cuerpo de la peticion
  • Codigo de estado de la respuesta
  • Headers y cuerpo de la respuesta

Esto proporciona visibilidad completa de las interacciones de API durante la ejecucion de pruebas.

Ejemplo Completo

@Test
void shouldCreateAndVerifyUser() {
alice.attemptsTo(Open.url("about:blank"));

// Crear usuario via API
alice.attemptsTo(
APIRequest.post("https://jsonplaceholder.typicode.com/users")
.withJsonBody(Map.of(
"name", "Test User",
"email", "test@example.com",
"username", "testuser"
))
);

// Verificar creacion
assertThat(alice.asksFor(LastAPIResponse.statusCode())).isEqualTo(201);

Map<String, Object> createdUser = alice.asksFor(LastAPIResponse.jsonBody());
assertThat(createdUser.get("name")).isEqualTo("Test User");
assertThat(createdUser.get("id")).isNotNull();

// Obtener el usuario creado
String userId = String.valueOf(((Double) createdUser.get("id")).intValue());
alice.attemptsTo(
APIRequest.get("https://jsonplaceholder.typicode.com/users/" + userId)
);

assertThat(alice.asksFor(LastAPIResponse.statusCode())).isEqualTo(200);
}

Manejando Descargas

Esperar y manejar descargas de archivos:

import net.serenitybdd.screenplay.playwright.interactions.WaitForDownload;
import net.serenitybdd.screenplay.playwright.questions.DownloadedFile;

// Esperar descarga activada al hacer clic
alice.attemptsTo(
WaitForDownload.whilePerforming(Click.on("#download-btn"))
);

// Consultar informacion de descarga
String filename = alice.asksFor(DownloadedFile.suggestedFilename());
String url = alice.asksFor(DownloadedFile.url());
Path path = alice.asksFor(DownloadedFile.path());

// Verificar fallos
String error = alice.asksFor(DownloadedFile.failure());
if (error == null) {
// Descarga exitosa
}

// Guardar en ubicacion especifica
alice.attemptsTo(
WaitForDownload.whilePerforming(Click.on("#export-btn"))
.andSaveTo(Paths.get("/downloads/report.pdf"))
);

Captura de Mensajes de Consola

Capturar y consultar mensajes de la consola del navegador para depuracion:

import net.serenitybdd.screenplay.playwright.interactions.CaptureConsoleMessages;
import net.serenitybdd.screenplay.playwright.questions.ConsoleMessages;

// Comenzar a capturar mensajes de consola
alice.attemptsTo(CaptureConsoleMessages.duringTest());

// ... realizar acciones que pueden escribir en consola ...

// Consultar mensajes capturados
List<String> allMessages = alice.asksFor(ConsoleMessages.all());
List<String> errors = alice.asksFor(ConsoleMessages.errors());
List<String> warnings = alice.asksFor(ConsoleMessages.warnings());
List<String> logs = alice.asksFor(ConsoleMessages.logs());

// Filtrar por contenido
List<String> apiErrors = alice.asksFor(ConsoleMessages.containing("API error"));

// Obtener conteos de mensajes
int totalCount = alice.asksFor(ConsoleMessages.count());
int errorCount = alice.asksFor(ConsoleMessages.errorCount());

// Limpiar mensajes capturados entre fases de prueba
alice.attemptsTo(CaptureConsoleMessages.clear());

Verificando Errores de Consola

Nuevo en 5.2.2

CheckConsole fue agregado en Serenity BDD 5.2.2.

Usa CheckConsole para fallar automaticamente las pruebas cuando ocurren errores o advertencias de JavaScript. Esta es la forma recomendada de asegurar que tu aplicacion no tiene errores de consola durante los flujos de usuario:

import net.serenitybdd.screenplay.playwright.interactions.CheckConsole;

// Comenzar captura, luego verificar errores al final del flujo
alice.attemptsTo(
CaptureConsoleMessages.duringTest(),

// Realizar acciones de usuario
Navigate.to("https://myapp.com/checkout"),
Enter.theValue("4111111111111111").into("#card-number"),
Click.on(Button.withText("Pay")),

// Fallar el test si ocurrieron errores de JavaScript
CheckConsole.forErrors()
);

Opciones de CheckConsole

MetodoDescripcion
CheckConsole.forErrors()Falla si se encuentran errores de consola
CheckConsole.forWarnings()Falla si se encuentran advertencias de consola
CheckConsole.forErrorsAndWarnings()Falla si se encuentran errores O advertencias

Modo Solo-Reporte

A veces quieres documentar problemas de consola sin fallar el test (por ejemplo, para problemas conocidos o cuando monitoreas tendencias de errores):

// Reportar errores a Serenity pero no fallar el test
alice.attemptsTo(
CheckConsole.forErrors().andReportOnly()
);

Cuando se encuentran errores, se adjuntan automaticamente al reporte de Serenity como evidencia, ya sea que el test falle o no.

Reportando Mensajes de Consola

Nuevo en 5.2.2

ReportConsoleMessages fue agregado en Serenity BDD 5.2.2.

Usa ReportConsoleMessages para agregar explicitamente mensajes de consola capturados al reporte de Serenity:

import net.serenitybdd.screenplay.playwright.interactions.ReportConsoleMessages;

alice.attemptsTo(
CaptureConsoleMessages.duringTest(),

// ... realizar acciones ...

// Reportar errores y advertencias a Serenity
ReportConsoleMessages.errorsAndWarnings()
);

Opciones de ReportConsoleMessages

MetodoDescripcion
ReportConsoleMessages.all()Reportar todos los mensajes de consola
ReportConsoleMessages.errors()Reportar solo errores
ReportConsoleMessages.warnings()Reportar solo advertencias
ReportConsoleMessages.errorsAndWarnings()Reportar errores y advertencias

Ejemplo: Verificacion Completa de Errores de Consola

Aqui hay un patron tipico para asegurar que no ocurran errores de JavaScript durante un flujo critico de usuario:

@Test
void checkoutFlowShouldHaveNoJavaScriptErrors() {
alice.attemptsTo(
// Comenzar captura al inicio
CaptureConsoleMessages.duringTest(),

// Completar el flujo de checkout
Navigate.to("https://myapp.com/cart"),
Click.on(Button.withText("Checkout")),
Enter.theValue("john@example.com").into(InputField.withLabel("Email")),
Enter.theValue("4111111111111111").into(InputField.withLabel("Card Number")),
Click.on(Button.withText("Place Order")),

// Verificar confirmacion de orden
WaitFor.theElement(".order-confirmation").toBeVisible(),

// Fallar el test si ocurrieron errores de JavaScript durante el flujo
CheckConsole.forErrors()
);
}

Captura de Peticiones de Red

Nuevo en 5.2.2

La captura de peticiones de red fue agregada en Serenity BDD 5.2.2.

Captura y analiza todas las peticiones de red realizadas durante las pruebas. Esto es util para depuracion, verificar llamadas API realizadas por el frontend, y detectar peticiones fallidas.

import net.serenitybdd.screenplay.playwright.interactions.CaptureNetworkRequests;
import net.serenitybdd.screenplay.playwright.interactions.CaptureNetworkRequests.CapturedRequest;
import net.serenitybdd.screenplay.playwright.questions.NetworkRequests;

// Comenzar a capturar peticiones de red
alice.attemptsTo(CaptureNetworkRequests.duringTest());

// Realizar acciones que activan peticiones de red
alice.attemptsTo(Navigate.to("https://example.com"));

// Consultar todas las peticiones capturadas
List<CapturedRequest> allRequests = alice.asksFor(NetworkRequests.all());
int requestCount = alice.asksFor(NetworkRequests.count());

// Filtrar por metodo HTTP
List<CapturedRequest> getRequests = alice.asksFor(NetworkRequests.withMethod("GET"));
List<CapturedRequest> postRequests = alice.asksFor(NetworkRequests.withMethod("POST"));

// Filtrar por URL
List<CapturedRequest> apiRequests = alice.asksFor(
NetworkRequests.toUrlContaining("/api/")
);

// Filtrar por patron glob
List<CapturedRequest> cssRequests = alice.asksFor(
NetworkRequests.matching("**/*.css")
);

// Encontrar peticiones fallidas (4xx, 5xx, o errores de red)
List<CapturedRequest> failedRequests = alice.asksFor(NetworkRequests.failed());
int failedCount = alice.asksFor(NetworkRequests.failedCount());

// Encontrar errores de cliente (4xx)
List<CapturedRequest> clientErrors = alice.asksFor(NetworkRequests.clientErrors());

// Encontrar errores de servidor (5xx)
List<CapturedRequest> serverErrors = alice.asksFor(NetworkRequests.serverErrors());

// Limpiar peticiones capturadas entre fases de prueba
alice.attemptsTo(CaptureNetworkRequests.clear());

Propiedades de CapturedRequest

Cada CapturedRequest contiene:

PropiedadDescripcion
getUrl()La URL de la peticion
getMethod()Metodo HTTP (GET, POST, etc.)
getResourceType()Tipo de recurso (document, xhr, fetch, stylesheet, etc.)
getRequestHeaders()Map de headers de peticion
getStatus()Codigo de estado de respuesta (o null si pendiente/fallido)
getStatusText()Texto de estado de respuesta
getFailureText()Razon del fallo para errores de red
isFailed()True si la peticion fallo (4xx, 5xx, o error de red)
isClientError()True si estado 4xx
isServerError()True si estado 5xx

Ejemplo: Verificando Llamadas API

@Test
void shouldMakeCorrectApiCalls() {
alice.attemptsTo(
CaptureNetworkRequests.duringTest(),
Navigate.to("https://myapp.com"),
Click.on(Button.withText("Load Data"))
);

// Verificar que la llamada API esperada fue realizada
List<CapturedRequest> apiCalls = alice.asksFor(
NetworkRequests.toUrlContaining("/api/data")
);

assertThat(apiCalls).hasSize(1);
assertThat(apiCalls.get(0).getMethod()).isEqualTo("GET");
assertThat(apiCalls.get(0).getStatus()).isEqualTo(200);
}

Captura de Evidencia de Fallos

Nuevo en 5.2.2

La captura automatica de evidencia de fallos fue agregada en Serenity BDD 5.2.2.

Cuando un test falla, Serenity Playwright automaticamente captura informacion de diagnostico y la adjunta al reporte. Esto hace que depurar fallos de pruebas sea mucho mas facil.

Recoleccion Automatica de Evidencia

Cuando la captura de mensajes de consola o peticiones de red esta habilitada, la siguiente evidencia se recolecta automaticamente en fallos de test:

  • Informacion de Pagina: URL actual y titulo de pagina
  • Errores de Consola: Todos los mensajes console.error() y console.warn()
  • Peticiones de Red Fallidas: Peticiones que devolvieron estado 4xx/5xx o errores de red

Esta evidencia se adjunta al reporte de Serenity como "Playwright Failure Evidence".

Habilitando Captura de Evidencia

Simplemente habilita la captura de consola y/o red al inicio de tus pruebas:

@BeforeEach
void setup() {
alice = Actor.named("Alice")
.whoCan(BrowseTheWebWithPlaywright.usingTheDefaultConfiguration());

// Habilitar captura para evidencia de fallos
alice.attemptsTo(
CaptureConsoleMessages.duringTest(),
CaptureNetworkRequests.duringTest()
);
}

Acceso Programatico a Evidencia

Tambien puedes consultar evidencia programaticamente para aserciones o reportes personalizados:

import net.serenitybdd.screenplay.playwright.evidence.PlaywrightFailureEvidence;

// Obtener errores de consola
List<String> consoleErrors = PlaywrightFailureEvidence.getConsoleErrors(alice);

// Obtener peticiones de red fallidas
List<String> failedRequests = PlaywrightFailureEvidence.getFailedRequests(alice);

// Usar en aserciones
assertThat(consoleErrors).isEmpty();
assertThat(failedRequests).isEmpty();

Ejemplo: Detectando Errores de JavaScript

@Test
void pageShouldNotHaveJavaScriptErrors() {
alice.attemptsTo(
CaptureConsoleMessages.duringTest(),
Navigate.to("https://myapp.com"),
Click.on(Button.withText("Submit"))
);

// Verificar que no ocurrieron errores de JavaScript
List<String> errors = alice.asksFor(ConsoleMessages.errors());
assertThat(errors)
.describedAs("JavaScript errors on page")
.isEmpty();
}

Ejemplo: Detectando Llamadas API Fallidas

@Test
void allApiCallsShouldSucceed() {
alice.attemptsTo(
CaptureNetworkRequests.duringTest(),
Navigate.to("https://myapp.com/dashboard")
);

// Verificar que ninguna llamada API fallo
List<CapturedRequest> failedRequests = alice.asksFor(NetworkRequests.failed());
assertThat(failedRequests)
.describedAs("Failed network requests")
.isEmpty();
}

Pruebas de Accesibilidad

Probar cumplimiento de accesibilidad usando snapshots ARIA:

import net.serenitybdd.screenplay.playwright.questions.AccessibilitySnapshot;
import com.microsoft.playwright.options.AriaRole;

// Obtener snapshot de accesibilidad de toda la pagina
String pageSnapshot = alice.asksFor(AccessibilitySnapshot.ofThePage());

// Obtener snapshot de accesibilidad de un elemento especifico
String navSnapshot = alice.asksFor(AccessibilitySnapshot.of("#main-nav"));
String formSnapshot = alice.asksFor(AccessibilitySnapshot.of(LOGIN_FORM));

// Obtener todos los elementos con un rol ARIA especifico
List<String> buttons = alice.asksFor(AccessibilitySnapshot.allWithRole(AriaRole.BUTTON));
List<String> links = alice.asksFor(AccessibilitySnapshot.allWithRole(AriaRole.LINK));
List<String> headings = alice.asksFor(AccessibilitySnapshot.allWithRole(AriaRole.HEADING));

Pruebas de Regresion Visual

Comparar capturas de pantalla contra imagenes de linea base:

import net.serenitybdd.screenplay.playwright.assertions.visual.CompareScreenshot;

// Comparacion de pagina completa
alice.attemptsTo(
CompareScreenshot.ofPage()
.againstBaseline("homepage-baseline.png")
.withThreshold(0.01) // 1% de diferencia permitida
);

// Comparacion de elemento
alice.attemptsTo(
CompareScreenshot.of("#product-card")
.againstBaseline("product-card-baseline.png")
);

// Con mascara para contenido dinamico
alice.attemptsTo(
CompareScreenshot.ofPage()
.againstBaseline("dashboard.png")
.withMask("#timestamp", "#user-avatar")
);

Emulacion de Dispositivos

Probar disenos responsivos con emulacion de dispositivos:

import net.serenitybdd.screenplay.playwright.interactions.EmulateDevice;

// Emular dispositivo especifico
alice.attemptsTo(EmulateDevice.device("iPhone 14"));
alice.attemptsTo(EmulateDevice.device("Pixel 7"));
alice.attemptsTo(EmulateDevice.device("iPad Pro 11"));

// Viewport personalizado
alice.attemptsTo(EmulateDevice.withViewport(375, 812));

// Con factor de escala del dispositivo
alice.attemptsTo(
EmulateDevice.withViewport(375, 812).andDeviceScaleFactor(2)
);

// Con user agent movil
alice.attemptsTo(
EmulateDevice.withViewport(375, 812).asMobile()
);

Geolocalizacion

Probar funcionalidades basadas en ubicacion:

import net.serenitybdd.screenplay.playwright.interactions.SetGeolocation;
import net.serenitybdd.screenplay.playwright.interactions.GrantPermissions;

// Primero otorgar permiso de geolocalizacion
alice.attemptsTo(GrantPermissions.for_("geolocation"));

// Establecer coordenadas especificas
alice.attemptsTo(SetGeolocation.to(51.5074, -0.1278)); // Londres

// Usar ubicaciones predefinidas
alice.attemptsTo(SetGeolocation.toNewYork());
alice.attemptsTo(SetGeolocation.toLondon());
alice.attemptsTo(SetGeolocation.toTokyo());
alice.attemptsTo(SetGeolocation.toSanFrancisco());
alice.attemptsTo(SetGeolocation.toSydney());
alice.attemptsTo(SetGeolocation.toParis());

// Con precision
alice.attemptsTo(
SetGeolocation.to(40.7128, -74.0060).withAccuracy(100)
);

// Limpiar geolocalizacion
alice.attemptsTo(SetGeolocation.clear());

Gestion de Permisos

Otorgar o limpiar permisos del navegador:

import net.serenitybdd.screenplay.playwright.interactions.GrantPermissions;
import net.serenitybdd.screenplay.playwright.interactions.ClearPermissions;

// Otorgar permisos especificos
alice.attemptsTo(GrantPermissions.for_("geolocation"));
alice.attemptsTo(GrantPermissions.for_("notifications", "camera", "microphone"));

// Limpiar todos los permisos
alice.attemptsTo(ClearPermissions.all());

Control del Reloj

Probar funcionalidad dependiente del tiempo:

import net.serenitybdd.screenplay.playwright.interactions.ControlClock;
import java.time.Instant;

// Instalar reloj falso
alice.attemptsTo(ControlClock.install());

// Establecer a tiempo especifico
alice.attemptsTo(
ControlClock.setTo(Instant.parse("2024-01-15T10:30:00Z"))
);

// Avanzar el tiempo
alice.attemptsTo(ControlClock.advanceBy(Duration.ofHours(2)));
alice.attemptsTo(ControlClock.advanceBy(Duration.ofMinutes(30)));

// Reanudar flujo de tiempo normal
alice.attemptsTo(ControlClock.resume());

Tracing

Registrar trazas detalladas para depuracion:

import net.serenitybdd.screenplay.playwright.interactions.tracing.StartTracing;
import net.serenitybdd.screenplay.playwright.interactions.tracing.StopTracing;

// Comenzar tracing con opciones
alice.attemptsTo(
StartTracing.withScreenshots()
.andSnapshots()
.named("login-flow")
);

// ... realizar acciones de prueba ...

// Detener y guardar traza
alice.attemptsTo(
StopTracing.andSaveTo(Paths.get("traces/login-flow.zip"))
);

// Ver traza: npx playwright show-trace traces/login-flow.zip

Opciones de traza:

  • withScreenshots() - Incluir capturas de pantalla en la traza
  • andSnapshots() - Incluir snapshots del DOM
  • andSources() - Incluir archivos fuente
  • named(String) - Establecer nombre de la traza

Persistencia de Estado de Sesion

Nuevo en 5.2.2

La persistencia de estado de sesion fue agregada en Serenity BDD 5.2.2.

Guardar y restaurar estado de sesion del navegador (cookies, localStorage, sessionStorage) para acelerar pruebas y compartir sesiones autenticadas.

Guardando Estado de Sesion

import net.serenitybdd.screenplay.playwright.interactions.SaveSessionState;

// Guardar en una ruta especifica
Path sessionPath = Paths.get("target/sessions/authenticated.json");
alice.attemptsTo(
SaveSessionState.toPath(sessionPath)
);

// Guardar en ubicacion por defecto con un nombre
// Guarda en: target/playwright/session-state/{name}.json
alice.attemptsTo(
SaveSessionState.toFile("admin-session")
);

Restaurando Estado de Sesion

import net.serenitybdd.screenplay.playwright.interactions.RestoreSessionState;

// Restaurar desde una ruta especifica
alice.attemptsTo(
RestoreSessionState.fromPath(Paths.get("target/sessions/authenticated.json"))
);

// Restaurar desde ubicacion por defecto
alice.attemptsTo(
RestoreSessionState.fromFile("admin-session")
);

Caso de Uso: Reutilizando Sesiones Autenticadas

Un patron comun es iniciar sesion una vez y reutilizar la sesion a traves de multiples pruebas:

public class AuthenticationSetup {

private static final Path SESSION_FILE = Paths.get("target/sessions/logged-in.json");

@BeforeAll
static void setupAuthenticatedSession() {
Actor setup = Actor.named("Setup")
.whoCan(BrowseTheWebWithPlaywright.usingTheDefaultConfiguration());

setup.attemptsTo(
Navigate.to("https://myapp.com/login"),
Enter.theValue("admin@example.com").into("#email"),
Enter.theValue("password123").into("#password"),
Click.on(Button.withText("Login")),

// Guardar la sesion autenticada
SaveSessionState.toPath(SESSION_FILE)
);

setup.wrapUp();
}
}

// En tus pruebas
@Test
void shouldAccessDashboard() {
alice.attemptsTo(
// Restaurar la sesion pre-autenticada
RestoreSessionState.fromPath(SESSION_FILE),

// Navegar directamente a pagina protegida
Navigate.to("https://myapp.com/dashboard")
);

// El usuario ya esta logueado!
assertThat(alice.asksFor(Text.of("h1"))).isEqualTo("Dashboard");
}

Contenido del Estado de Sesion

El estado de sesion guardado es un archivo JSON que contiene:

  • Cookies: Todas las cookies para el contexto del navegador
  • Origins: Datos de localStorage y sessionStorage para cada origen

Esto te permite:

  • Omitir pasos de login en pruebas (ejecucion mas rapida)
  • Compartir autenticacion entre clases de prueba
  • Crear fixtures de sesion para diferentes roles de usuario
  • Probar escenarios de expiracion y renovacion de sesion

Ejemplo: Multiples Roles de Usuario

public class SessionFixtures {

public static void createAdminSession() {
// Login como admin y guardar sesion
Actor admin = createActor();
admin.attemptsTo(
Navigate.to("https://myapp.com/login"),
Enter.theValue("admin@example.com").into("#email"),
Enter.theValue("adminpass").into("#password"),
Click.on(Button.withText("Login")),
SaveSessionState.toFile("admin-session")
);
admin.wrapUp();
}

public static void createUserSession() {
// Login como usuario regular y guardar sesion
Actor user = createActor();
user.attemptsTo(
Navigate.to("https://myapp.com/login"),
Enter.theValue("user@example.com").into("#email"),
Enter.theValue("userpass").into("#password"),
Click.on(Button.withText("Login")),
SaveSessionState.toFile("user-session")
);
user.wrapUp();
}
}

// En pruebas de admin
@Test
void adminShouldSeeAdminPanel() {
alice.attemptsTo(
RestoreSessionState.fromFile("admin-session"),
Navigate.to("https://myapp.com/admin")
);
assertThat(alice.asksFor(Presence.of("#admin-panel"))).isTrue();
}

// En pruebas de usuario
@Test
void userShouldNotSeeAdminPanel() {
alice.attemptsTo(
RestoreSessionState.fromFile("user-session"),
Navigate.to("https://myapp.com/admin")
);
assertThat(alice.asksFor(Text.of("h1"))).isEqualTo("Access Denied");
}

Generacion de PDF

Generar PDFs desde paginas (solo Chromium, modo headless):

import net.serenitybdd.screenplay.playwright.interactions.GeneratePDF;

// Generacion basica de PDF
alice.attemptsTo(
GeneratePDF.ofCurrentPage()
.andSaveTo(Paths.get("output/report.pdf"))
);

// Con opciones
alice.attemptsTo(
GeneratePDF.ofCurrentPage()
.withFormat("A4")
.inLandscape()
.withMargins("1cm", "1cm", "1cm", "1cm")
.withHeaderTemplate("<div>Header</div>")
.withFooterTemplate("<div>Page <span class='pageNumber'></span></div>")
.displayHeaderAndFooter()
.printBackground()
.andSaveTo(Paths.get("output/report.pdf"))
);

Cambiando Contextos

Frames

Trabajar con iframes:

// Cambiar a frame por nombre o ID
alice.attemptsTo(Switch.toFrame("payment-iframe"));

// Cambiar a frame por Target
alice.attemptsTo(Switch.toFrame(Target.the("payment").locatedBy("#payment-frame")));

// Volver al frame principal
alice.attemptsTo(Switch.toMainFrame());

Ventanas y Pestanas

Manejar multiples ventanas y pestanas:

// Cambiar a nueva ventana/pestana
alice.attemptsTo(Switch.toNewWindow());

// Cerrar ventana actual
alice.attemptsTo(CloseCurrentWindow.now());

Mejores Practicas

Usar Constantes Target

Define Target como constantes para reusabilidad y mantenibilidad:

public class LoginPage {
public static Target EMAIL_FIELD = Target.the("email field")
.locatedBy("#email");
public static Target PASSWORD_FIELD = Target.the("password field")
.locatedBy("#password");
public static Target LOGIN_BUTTON = Target.the("login button")
.locatedBy("role=button[name='Log in']");
}

Preferir Selectores de Rol

Usa selectores de rol ARIA para pruebas mas resilientes:

// En lugar de CSS
Target.the("Submit").locatedBy("button.primary-btn")

// Preferir selector de rol
Target.the("Submit").locatedBy("role=button[name='Submit']")

Usar Factorias de Elementos UI

Para elementos comunes, usa las factorias de elementos UI:

// En lugar de
Click.on(Target.the("Add button").locatedBy("role=button[name='Add to Cart']"))

// Usar
Click.on(Button.withText("Add to Cart"))

Mockeo de Red para Aislamiento

Mockea respuestas de API para aislar pruebas de UI:

@BeforeEach
void setupMocks() {
actor.attemptsTo(
InterceptNetwork.requestsTo("**/api/products")
.andRespondWithJson(200, mockProducts)
);
}

Usar Tracing para Depuracion

Habilita tracing cuando depures fallos de pruebas:

alice.attemptsTo(
StartTracing.withScreenshots().andSnapshots()
);

// Ejecuta tu prueba...

alice.attemptsTo(
StopTracing.andSaveTo(Paths.get("trace.zip"))
);
// Ver con: npx playwright show-trace trace.zip

Tablas de Referencia Rapida

Todas las Interaction

Las siguientes tablas proporcionan una referencia completa de todas las Interaction de Screenplay Playwright disponibles.

Interaction de Elementos

InteractionDescripcionEjemplo
Click.on(target)Clic en un elementoClick.on("#submit")
DoubleClick.on(target)Doble clic en un elementoDoubleClick.on("#item")
RightClick.on(target)Clic derecho (menu contextual) en un elementoRightClick.on("#file")
Hover.over(target)Mover mouse sobre un elementoHover.over("#menu")
Focus.on(target)Establecer foco en un elementoFocus.on("#search")

Interaction de Entrada de Texto

InteractionDescripcionEjemplo
Enter.theValue(text).into(target)Escribir texto en un campoEnter.theValue("john@test.com").into("#email")
Clear.field(target)Limpiar un campo de entradaClear.field("#search")
Press.key(key)Presionar una tecla del tecladoPress.key("Enter")
Press.key(combo)Presionar una combinacion de teclasPress.key("Control+a")
Press.keys(keys...)Presionar multiples teclas en secuenciaPress.keys("Tab", "Tab", "Enter")

Interaction de Checkbox y Radio

InteractionDescripcionEjemplo
Check.checkbox(target)Marcar un checkboxCheck.checkbox("#agree")
Uncheck.checkbox(target)Desmarcar un checkboxUncheck.checkbox("#newsletter")

Interaction de Dropdown

InteractionDescripcionEjemplo
SelectFromOptions.byVisibleText(text).from(target)Seleccionar por texto visibleSelectFromOptions.byVisibleText("Red").from("#color")
SelectFromOptions.byValue(value).from(target)Seleccionar por atributo valueSelectFromOptions.byValue("red").from("#color")
SelectFromOptions.byIndex(index).from(target)Seleccionar por indice (base 0)SelectFromOptions.byIndex(2).from("#color")
DeselectFromOptions.byValue(value).from(target)Deseleccionar por valorDeselectFromOptions.byValue("red").from("#colors")
DeselectFromOptions.byVisibleText(text).from(target)Deseleccionar por texto visibleDeselectFromOptions.byVisibleText("Red").from("#colors")
DeselectFromOptions.byIndex(index).from(target)Deseleccionar por indiceDeselectFromOptions.byIndex(0).from("#colors")
DeselectFromOptions.all().from(target)Deseleccionar todas las opcionesDeselectFromOptions.all().from("#colors")

Interaction de Desplazamiento

InteractionDescripcionEjemplo
Scroll.to(target)Desplazar elemento a la vistaScroll.to("#footer")
Scroll.to(target).andAlignToTop()Desplazar con alineacion superiorScroll.to("#section").andAlignToTop()
Scroll.to(target).andAlignToCenter()Desplazar con alineacion centralScroll.to("#section").andAlignToCenter()
Scroll.to(target).andAlignToBottom()Desplazar con alineacion inferiorScroll.to("#section").andAlignToBottom()
Scroll.toTop()Desplazar al inicio de la paginaScroll.toTop()
Scroll.toBottom()Desplazar al final de la paginaScroll.toBottom()
Scroll.by(deltaX, deltaY)Desplazar por cantidad de pixelesScroll.by(0, 500)
Scroll.toPosition(x, y)Desplazar a posicion absolutaScroll.toPosition(0, 1000)

Interaction de Arrastrar y Soltar

InteractionDescripcionEjemplo
Drag.from(source).to(target)Arrastrar de origen a destinoDrag.from("#item").to("#dropzone")
Drag.the(source).onto(target)Sintaxis fluida alternativaDrag.the("#card").onto("#column")

Interaction de Navegacion

InteractionDescripcionEjemplo
Navigate.to(url)Navegar a una URLNavigate.to("https://example.com")
Navigate.toTheBaseUrl()Navegar a URL base configuradaNavigate.toTheBaseUrl()

Interaction de Frame y Ventana

InteractionDescripcionEjemplo
Switch.toFrame(nameOrId)Cambiar a iframe por nombre/IDSwitch.toFrame("payment-iframe")
Switch.toFrame(target)Cambiar a iframe por TargetSwitch.toFrame(PAYMENT_FRAME)
Switch.toMainFrame()Volver al frame principalSwitch.toMainFrame()
Switch.toNewWindow()Cambiar a nueva ventana/pestanaSwitch.toNewWindow()
CloseCurrentWindow.now()Cerrar ventana actualCloseCurrentWindow.now()

Interaction de Archivos

InteractionDescripcionEjemplo
Upload.file(path).to(target)Subir un archivoUpload.file(Paths.get("doc.pdf")).to("#upload")
WaitForDownload.whilePerforming(action)Esperar descarga durante accionWaitForDownload.whilePerforming(Click.on("#download"))
WaitForDownload...andSaveTo(path)Guardar descarga en rutaWaitForDownload.whilePerforming(...).andSaveTo(path)

Interaction de JavaScript

InteractionDescripcionEjemplo
Evaluate.javascript(script)Ejecutar JavaScriptEvaluate.javascript("window.scrollTo(0,0)")

Interaction de Espera

InteractionDescripcionEjemplo
WaitUntil.the(target).isVisible()Esperar visibilidad del elementoWaitUntil.the("#modal").isVisible()
WaitUntil.the(target).isNotVisible()Esperar a que el elemento se oculteWaitUntil.the("#spinner").isNotVisible()
WaitUntil.the(target).isHidden()Esperar a que el elemento este ocultoWaitUntil.the("#loading").isHidden()
WaitUntil...forNoMoreThan(duration)Establecer timeout personalizadoWaitUntil.the("#data").isVisible().forNoMoreThan(Duration.ofSeconds(10))

Interaction de Red

InteractionDescripcionEjemplo
InterceptNetwork.requestsTo(pattern).andRespondWith(options)Mockear respuesta con opcionesInterceptNetwork.requestsTo("**/api/**").andRespondWith(...)
InterceptNetwork.requestsTo(pattern).andRespondWithJson(status, data)Mockear respuesta JSONInterceptNetwork.requestsTo("**/users").andRespondWithJson(200, users)
InterceptNetwork.requestsTo(pattern).andHandle(handler)Manejador de peticion personalizadoInterceptNetwork.requestsTo("**/api/**").andHandle(route -> ...)
InterceptNetwork.requestsTo(pattern).andAbort()Bloquear peticionesInterceptNetwork.requestsTo("**/analytics/**").andAbort()
RemoveRoutes.all()Eliminar todos los manejadores de rutasRemoveRoutes.all()
RemoveRoutes.forUrl(pattern)Eliminar rutas para un patronRemoveRoutes.forUrl("**/api/**")

Interaction de Dispositivo y Entorno

InteractionDescripcionEjemplo
EmulateDevice.device(name)Emular un dispositivoEmulateDevice.device("iPhone 14")
EmulateDevice.withViewport(width, height)Establecer viewport personalizadoEmulateDevice.withViewport(375, 812)
SetGeolocation.to(lat, lng)Establecer geolocalizacionSetGeolocation.to(51.5074, -0.1278)
GrantPermissions.for_(permissions...)Otorgar permisos del navegadorGrantPermissions.for_("geolocation", "camera")
ClearPermissions.all()Limpiar todos los permisosClearPermissions.all()

Interaction de Control de Reloj

InteractionDescripcionEjemplo
ControlClock.install()Instalar reloj falsoControlClock.install()
ControlClock.setTo(instant)Establecer reloj a tiempo especificoControlClock.setTo(Instant.parse("2024-01-15T10:30:00Z"))
ControlClock.advanceBy(duration)Avanzar relojControlClock.advanceBy(Duration.ofHours(2))
ControlClock.resume()Reanudar flujo de tiempo normalControlClock.resume()

Interaction de Depuracion y Tracing

InteractionDescripcionEjemplo
StartTracing.withScreenshots()Iniciar traza con capturas de pantallaStartTracing.withScreenshots()
StopTracing.andSaveTo(path)Detener y guardar trazaStopTracing.andSaveTo(Paths.get("trace.zip"))
CaptureConsoleMessages.duringTest()Comenzar a capturar consolaCaptureConsoleMessages.duringTest()
CaptureConsoleMessages.clear()Limpiar mensajes capturadosCaptureConsoleMessages.clear()
CheckConsole.forErrors()Fallar si se encuentran errores de consolaCheckConsole.forErrors()
CaptureNetworkRequests.duringTest()Comenzar a capturar redCaptureNetworkRequests.duringTest()
CaptureNetworkRequests.clear()Limpiar peticiones capturadasCaptureNetworkRequests.clear()

Interaction de Estado de Sesion

InteractionDescripcionEjemplo
SaveSessionState.toPath(path)Guardar sesion en ruta especificaSaveSessionState.toPath(Paths.get("session.json"))
SaveSessionState.toFile(name)Guardar sesion en ubicacion por defectoSaveSessionState.toFile("admin-session")
RestoreSessionState.fromPath(path)Restaurar sesion desde rutaRestoreSessionState.fromPath(Paths.get("session.json"))
RestoreSessionState.fromFile(name)Restaurar sesion desde ubicacion por defectoRestoreSessionState.fromFile("admin-session")

Interaction de Peticiones API

InteractionDescripcionEjemplo
APIRequest.get(url)Realizar peticion GETAPIRequest.get("https://api.example.com/users")
APIRequest.post(url)Realizar peticion POSTAPIRequest.post("https://api.example.com/users")
APIRequest.put(url)Realizar peticion PUTAPIRequest.put("https://api.example.com/users/1")
APIRequest.patch(url)Realizar peticion PATCHAPIRequest.patch("https://api.example.com/users/1")
APIRequest.delete(url)Realizar peticion DELETEAPIRequest.delete("https://api.example.com/users/1")

Interaction de Generacion de PDF

InteractionDescripcionEjemplo
GeneratePDF.ofCurrentPage().andSaveTo(path)Generar PDFGeneratePDF.ofCurrentPage().andSaveTo(Paths.get("page.pdf"))
GeneratePDF...withFormat(format)Establecer formato de papelGeneratePDF.ofCurrentPage().withFormat("A4")
GeneratePDF...inLandscape()Usar orientacion horizontalGeneratePDF.ofCurrentPage().inLandscape()

Interaction de Pruebas Visuales

InteractionDescripcionEjemplo
CompareScreenshot.ofPage().againstBaseline(name)Comparar pagina completaCompareScreenshot.ofPage().againstBaseline("home.png")
CompareScreenshot.of(target).againstBaseline(name)Comparar elementoCompareScreenshot.of("#card").againstBaseline("card.png")

Todas las Question

Las siguientes tablas proporcionan una referencia completa de todas las Question de Screenplay Playwright disponibles.

Question de Estado de Elementos

QuestionTipo de RetornoDescripcionEjemplo
Presence.of(target)BooleanElemento existe en DOMactor.asksFor(Presence.of("#modal"))
Absence.of(target)BooleanElemento no presenteactor.asksFor(Absence.of("#error"))
Visibility.of(target)BooleanElemento es visibleactor.asksFor(Visibility.of("#popup"))
Enabled.of(target)BooleanElemento esta habilitadoactor.asksFor(Enabled.of("#submit"))
SelectedStatus.of(target)BooleanCheckbox/radio esta seleccionadoactor.asksFor(SelectedStatus.of("#agree"))

Question de Contenido de Elementos

QuestionTipo de RetornoDescripcionEjemplo
Text.of(target)StringObtener contenido de texto del elementoactor.asksFor(Text.of("#title"))
Text.ofEach(target)List<String>Obtener texto de todos los elementos coincidentesactor.asksFor(Text.ofEach(".item"))
Value.of(target)StringObtener valor del campo de entradaactor.asksFor(Value.of("#email"))
Attribute.of(target).named(attr)StringObtener valor de atributoactor.asksFor(Attribute.of("#link").named("href"))
CSSValue.of(target).named(prop)StringObtener valor de propiedad CSSactor.asksFor(CSSValue.of("#box").named("color"))

Question de Informacion de Pagina

QuestionTipo de RetornoDescripcionEjemplo
CurrentUrl.ofThePage()StringObtener URL de pagina actualactor.asksFor(CurrentUrl.ofThePage())
PageTitle.ofThePage()StringObtener titulo de paginaactor.asksFor(PageTitle.ofThePage())

Question de Descarga

QuestionTipo de RetornoDescripcionEjemplo
DownloadedFile.suggestedFilename()StringObtener nombre de archivo sugeridoactor.asksFor(DownloadedFile.suggestedFilename())
DownloadedFile.url()StringObtener URL de descargaactor.asksFor(DownloadedFile.url())
DownloadedFile.path()PathObtener ruta del archivo descargadoactor.asksFor(DownloadedFile.path())
DownloadedFile.failure()StringObtener razon del fallo (null si exitoso)actor.asksFor(DownloadedFile.failure())

Question de Mensajes de Consola

QuestionTipo de RetornoDescripcionEjemplo
ConsoleMessages.all()List<String>Obtener todos los mensajes de consolaactor.asksFor(ConsoleMessages.all())
ConsoleMessages.errors()List<String>Obtener errores de consolaactor.asksFor(ConsoleMessages.errors())
ConsoleMessages.warnings()List<String>Obtener advertencias de consolaactor.asksFor(ConsoleMessages.warnings())
ConsoleMessages.count()IntegerObtener conteo total de mensajesactor.asksFor(ConsoleMessages.count())

Question de Peticiones de Red

QuestionTipo de RetornoDescripcionEjemplo
NetworkRequests.all()List<CapturedRequest>Obtener todas las peticiones capturadasactor.asksFor(NetworkRequests.all())
NetworkRequests.count()IntegerObtener conteo total de peticionesactor.asksFor(NetworkRequests.count())
NetworkRequests.withMethod(method)List<CapturedRequest>Filtrar por metodo HTTPactor.asksFor(NetworkRequests.withMethod("POST"))
NetworkRequests.failed()List<CapturedRequest>Obtener peticiones fallidasactor.asksFor(NetworkRequests.failed())

Question de Respuesta API

QuestionTipo de RetornoDescripcionEjemplo
LastAPIResponse.statusCode()IntegerObtener codigo de estado de respuestaactor.asksFor(LastAPIResponse.statusCode())
LastAPIResponse.ok()BooleanVerificar si estado es 2xxactor.asksFor(LastAPIResponse.ok())
LastAPIResponse.body()StringObtener cuerpo de respuesta como stringactor.asksFor(LastAPIResponse.body())
LastAPIResponse.jsonBody()Map<String, Object>Parsear respuesta JSON como Mapactor.asksFor(LastAPIResponse.jsonBody())

Factorias de Elementos UI

Clases de factoria para localizar elementos UI comunes.

FactoriaMetodosEjemplo
ButtonwithText(text), withNameOrId(id), withAriaLabel(label), containingText(text), locatedBy(selector)Button.withText("Submit")
InputFieldwithNameOrId(id), withPlaceholder(text), withLabel(label), withAriaLabel(label), locatedBy(selector)InputField.withPlaceholder("Email")
LinkwithText(text), containingText(text), withTitle(title), locatedBy(selector)Link.withText("Learn more")
CheckboxwithLabel(label), withNameOrId(id), withValue(value), locatedBy(selector)Checkbox.withLabel("I agree")
RadioButtonwithLabel(label), withNameOrId(id), withValue(value), locatedBy(selector)RadioButton.withValue("express")
DropdownwithLabel(label), withNameOrId(id), locatedBy(selector)Dropdown.withLabel("Country")
LabelwithText(text), withExactText(text), forFieldId(id), locatedBy(selector)Label.forFieldId("email")
ImagewithAltText(alt), withSrc(src), withSrcContaining(text), locatedBy(selector)Image.withAltText("Logo")

Migracion desde WebDriver

Al migrar desde serenity-screenplay-webdriver:

  1. Cambio de Ability:

    • Reemplaza BrowseTheWeb con BrowseTheWebWithPlaywright
  2. Sintaxis de Selectores:

    • CSS y XPath funcionan de manera similar
    • Agrega selectores de rol para pruebas mas robustas
    • Usa >> para encadenar selectores en lugar de anidar
  3. Esperas:

    • Playwright auto-espera; las esperas explicitas son menos necesarias
    • Elimina la mayoria de llamadas WaitUntil
  4. Metodos de Localizacion:

    • By.id("x") se convierte en "#x"
    • By.cssSelector("x") se convierte en "x"
    • By.xpath("x") se convierte en "xpath=x"
  5. Nuevas Capacidades:

    • Usa interceptacion de red para mockear APIs
    • Usa tracing para depuracion
    • Usa emulacion de dispositivos para pruebas responsivas

Ejemplo Completo

import net.serenitybdd.screenplay.Actor;
import net.serenitybdd.screenplay.playwright.abilities.BrowseTheWebWithPlaywright;
import net.serenitybdd.screenplay.playwright.interactions.*;
import net.serenitybdd.screenplay.playwright.questions.*;
import net.serenitybdd.screenplay.playwright.ui.*;
import static org.assertj.core.api.Assertions.assertThat;

public class ShoppingCartTest {

Actor alice = Actor.named("Alice");

@BeforeEach
void setup() {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
alice.can(BrowseTheWebWithPlaywright.using(browser));
}

@Test
void shouldAddItemToCart() {
alice.attemptsTo(
Navigate.to("https://shop.example.com"),
Click.on(Button.withText("Electronics")),
Click.on(Link.containingText("Laptop")),
SelectFromOptions.byVisibleText("16GB").from(Dropdown.withLabel("Memory")),
Click.on(Button.withText("Add to Cart"))
);

String cartCount = alice.asksFor(Text.of("#cart-count"));
assertThat(cartCount).isEqualTo("1");

List<String> cartItems = alice.asksFor(Text.ofEach(".cart-item-name"));
assertThat(cartItems).contains("Laptop");
}
}

Lectura Adicional