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​