Saltar al contenido principal

Web Testing with Serenity Screenplay and Playwright

Introduction

Playwright is a modern browser automation library that provides cross-browser testing capabilities with excellent support for modern web applications. The serenity-screenplay-playwright module brings the power of Playwright to Serenity BDD's Screenplay pattern, offering an alternative to the traditional WebDriver integration.

Playwright offers several advantages over WebDriver:

  • Auto-waiting: Automatically waits for elements to be actionable before performing actions
  • Cross-browser: Native support for Chromium, Firefox, and WebKit
  • Network interception: Built-in support for mocking and intercepting network requests
  • Tracing: Record detailed traces for debugging test failures
  • Device emulation: Easy mobile and tablet device emulation
  • Modern architecture: Event-driven design with better reliability

Getting Started

Maven Dependency

Add the following dependency to your project:

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

The BrowseTheWebWithPlaywright Ability

To use Playwright with Screenplay, actors need the BrowseTheWebWithPlaywright ability:

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));

Configuration

You can configure Playwright behavior using environment variables or programmatically:

// Launch with options
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions()
.setHeadless(false)
.setSlowMo(100);

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

Opening a URL

Opening a URL directly

In Screenplay, you open a new page using the Navigate interaction class:

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

Opening the base URL

If you have configured a base URL, you can navigate to it:

alice.attemptsTo(Navigate.toTheBaseUrl());

Locating Elements on a Page

The Target Class

The Target class in Playwright integration uses Playwright's powerful selector engine. Unlike WebDriver, Playwright provides multiple built-in selector strategies.

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

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

Playwright Selectors

Playwright supports a rich set of selectors:

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

// Text selector
Target.the("Submit button").locatedBy("text=Submit")

// Role selector (ARIA)
Target.the("Submit button").locatedBy("role=button[name='Submit']")

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

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

Dynamic Targets

You can use parameterized targets for dynamic element location:

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

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

UI Element Factories

Serenity Playwright provides convenient factory classes for locating common UI elements using Playwright's powerful selector syntax.

Button

Locate buttons using various strategies:

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

// By visible text (case-insensitive, uses role selector)
alice.attemptsTo(Click.on(Button.withText("Submit")));

// By name or ID attribute
alice.attemptsTo(Click.on(Button.withNameOrId("submit-btn")));

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

// Containing specific text
alice.attemptsTo(Click.on(Button.containingText("Add to")));

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

InputField

Locate input fields:

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

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

// By placeholder text
alice.attemptsTo(Enter.theValue("Search...").into(InputField.withPlaceholder("Search products")));

// By associated label text
alice.attemptsTo(Enter.theValue("password123").into(InputField.withLabel("Password")));

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

Locate anchor elements:

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

// By exact text
alice.attemptsTo(Click.on(Link.withText("Learn more")));

// Containing text
alice.attemptsTo(Click.on(Link.containingText("documentation")));

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

Checkbox

Locate checkbox inputs:

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

// By label text
alice.attemptsTo(Click.on(Checkbox.withLabel("Accept terms")));

// By name or ID
alice.attemptsTo(Click.on(Checkbox.withNameOrId("newsletter")));

// By value attribute
alice.attemptsTo(Click.on(Checkbox.withValue("premium")));

RadioButton

Locate radio button inputs:

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

// By label text
alice.attemptsTo(Click.on(RadioButton.withLabel("Express shipping")));

// By value attribute
alice.attemptsTo(Click.on(RadioButton.withValue("express")));

Locate select elements:

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

// By label text
alice.attemptsTo(
SelectFromOptions.byVisibleText("Canada").from(Dropdown.withLabel("Country"))
);

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

Label

Locate label elements:

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

// By text content
String labelText = alice.asksFor(Text.of(Label.withText("Email")));

// For a specific field ID
Target emailLabel = Label.forFieldId("email-input");

Image

Locate image elements:

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

// By alt text
alice.attemptsTo(Click.on(Image.withAltText("Product thumbnail")));

// By source URL
alice.attemptsTo(Click.on(Image.withSrc("/images/logo.png")));

// By partial source URL
alice.attemptsTo(Click.on(Image.withSrcContaining("product-123")));

Interacting with Elements

Core Interactions

The following interaction classes are available in the net.serenitybdd.screenplay.playwright.interactions package:

InteractionPurposeExample
ClickClick on an elementactor.attemptsTo(Click.on("#button"))
DoubleClickDouble-click on an elementactor.attemptsTo(DoubleClick.on("#item"))
RightClickRight-click (context menu)actor.attemptsTo(RightClick.on("#menu"))
EnterType into an input fieldactor.attemptsTo(Enter.theValue("text").into("#field"))
ClearClear an input fieldactor.attemptsTo(Clear.field("#field"))
HoverHover over an elementactor.attemptsTo(Hover.over("#menu"))
PressPress keyboard keysactor.attemptsTo(Press.key("Enter"))
CheckCheck a checkboxactor.attemptsTo(Check.checkbox("#agree"))
UncheckUncheck a checkboxactor.attemptsTo(Uncheck.checkbox("#agree"))
FocusFocus on an elementactor.attemptsTo(Focus.on("#input"))
NavigateNavigate to a URLactor.attemptsTo(Navigate.to("https://..."))
UploadUpload a fileactor.attemptsTo(Upload.file(path).to("#upload"))

Click

Click on an element. Playwright automatically waits for the element to be actionable:

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

Double-Click

Double-click on an element:

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

Right-Click

Right-click to open context menus:

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

Enter Text

Type values into input fields:

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

You can also clear the field first:

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

Keyboard Interactions

Press keyboard keys:

// Single key
alice.attemptsTo(Press.key("Enter"));

// Key combinations
alice.attemptsTo(Press.key("Control+a"));

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

Hover

Hover over elements to trigger hover states:

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

Check and Uncheck

Work with checkboxes:

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

Focus

Focus on an element:

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

Selecting from Dropdowns

Select options from dropdown menus:

// By visible text
alice.attemptsTo(SelectFromOptions.byVisibleText("Red").from("#color"));

// By value attribute
alice.attemptsTo(SelectFromOptions.byValue("red").from("#color"));

// By index
alice.attemptsTo(SelectFromOptions.byIndex(2).from("#color"));

// Multiple values (for multi-select)
alice.attemptsTo(SelectFromOptions.byValue("red", "blue", "green").from("#colors"));

Deselecting from Dropdowns

For multi-select dropdowns, you can deselect options:

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

// Deselect by value
alice.attemptsTo(DeselectFromOptions.byValue("red").from("#colors"));

// Deselect by visible text
alice.attemptsTo(DeselectFromOptions.byVisibleText("Red").from("#colors"));

// Deselect by index
alice.attemptsTo(DeselectFromOptions.byIndex(0).from("#colors"));

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

Scrolling

Comprehensive scrolling capabilities:

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

// Scroll to an element
alice.attemptsTo(Scroll.to("#terms-and-conditions"));

// Scroll with alignment
alice.attemptsTo(Scroll.to("#section").andAlignToTop());
alice.attemptsTo(Scroll.to("#section").andAlignToCenter());
alice.attemptsTo(Scroll.to("#section").andAlignToBottom());

// Page-level scrolling
alice.attemptsTo(Scroll.toTop());
alice.attemptsTo(Scroll.toBottom());

// Scroll by specific amount (deltaX, deltaY)
alice.attemptsTo(Scroll.by(0, 500));

// Scroll to specific position
alice.attemptsTo(Scroll.toPosition(0, 1000));

Drag and Drop

Drag elements from one location to another:

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

// Basic drag and drop
alice.attemptsTo(Drag.from("#source").to("#target"));

// Alternative fluent syntax
alice.attemptsTo(Drag.the("#draggable").onto("#droppable"));

// With Targets
alice.attemptsTo(Drag.from(SOURCE_ELEMENT).to(TARGET_LOCATION));

File Uploads

Upload files:

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

JavaScript Execution

Execute JavaScript in the page context:

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

// With return value
Object result = alice.asksFor(
Evaluate.javascript("return document.title")
);

Waiting

Wait for elements or conditions:

// Wait for element to be visible
alice.attemptsTo(WaitUntil.the("#loading").isNotVisible());

// Wait for element to be hidden
alice.attemptsTo(WaitUntil.the("#spinner").isHidden());

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

Querying the Web Page

Bundled Questions

Serenity Playwright provides Question classes for querying page state:

QuestionPurposeExample
TextGet element textactor.asksFor(Text.of("#title"))
ValueGet input valueactor.asksFor(Value.of("#email"))
AttributeGet attribute valueactor.asksFor(Attribute.of("#link").named("href"))
PresenceCheck if element existsactor.asksFor(Presence.of("#modal"))
AbsenceCheck if element is absentactor.asksFor(Absence.of("#error"))
VisibilityCheck if element is visibleactor.asksFor(Visibility.of("#popup"))
EnabledCheck if element is enabledactor.asksFor(Enabled.of("#submit"))
SelectedStatusCheck if checkbox is selectedactor.asksFor(SelectedStatus.of("#agree"))
CSSValueGet CSS propertyactor.asksFor(CSSValue.of("#box").named("color"))
CurrentUrlGet current page URLactor.asksFor(CurrentUrl.ofThePage())
PageTitleGet page titleactor.asksFor(PageTitle.ofThePage())

Text

Get the text content of an element:

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

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

Presence and Absence

Check whether elements exist on the page:

// Check if present
boolean isPresent = alice.asksFor(Presence.of("#modal"));

// Check if absent
boolean isAbsent = alice.asksFor(Absence.of("#error-message"));

Attributes

Get attribute values:

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

Current Page Information

Query page-level information:

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

Network Interception and Mocking

Playwright provides powerful network interception capabilities for testing.

Intercepting Requests

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

// Intercept and fulfill with mock response
alice.attemptsTo(
InterceptNetwork.requestsTo("**/api/users")
.andRespondWith(
new Route.FulfillOptions()
.setStatus(200)
.setBody("{\"users\": []}")
.setContentType("application/json")
)
);

// Intercept and respond with JSON
alice.attemptsTo(
InterceptNetwork.requestsTo("**/api/user/123")
.andRespondWithJson(200, Map.of(
"id", 123,
"name", "John Doe",
"email", "john@example.com"
))
);

Custom Request Handlers

For more control, use custom handlers:

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();
}
})
);

Aborting Requests

Block specific requests (useful for testing error handling):

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

Removing Routes

Remove previously registered route handlers:

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

// Remove all route handlers
alice.attemptsTo(RemoveRoutes.all());

// Remove routes for specific pattern
alice.attemptsTo(RemoveRoutes.forUrl("**/api/**"));

API Testing Integration

New in 5.2.2

API testing integration was added in Serenity BDD 5.2.2.

Make API requests that share the browser's session context (cookies, authentication). This enables hybrid UI + API testing scenarios where you can set up data via API, perform UI actions, and verify state through API calls.

Basic API Requests

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

// Initialize browser context first (required for API requests)
alice.attemptsTo(Open.url("about:blank"));

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

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

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

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

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

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

Request Configuration

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

// Add query parameters
alice.attemptsTo(
APIRequest.get("https://api.example.com/search")
.withQueryParam("q", "test")
.withQueryParam("limit", "10")
);

// Set content type
alice.attemptsTo(
APIRequest.post("https://api.example.com/data")
.withBody("<xml>data</xml>")
.withContentType("application/xml")
);

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

// Fail on non-2xx status codes
alice.attemptsTo(
APIRequest.get("https://api.example.com/data")
.failOnStatusCode(true)
);

Querying Responses

// Get status code
int status = alice.asksFor(LastAPIResponse.statusCode());

// Check if response is OK (2xx)
boolean isOk = alice.asksFor(LastAPIResponse.ok());

// Get response body as string
String body = alice.asksFor(LastAPIResponse.body());

// Parse JSON response as Map
Map<String, Object> json = alice.asksFor(LastAPIResponse.jsonBody());

// Parse JSON array response as List
List<Map<String, Object>> items = alice.asksFor(LastAPIResponse.jsonBodyAsList());

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

// Get final URL (after redirects)
String url = alice.asksFor(LastAPIResponse.url());

Hybrid UI + API Testing

API requests automatically share cookies with the browser context:

@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"))
);

// API calls now include authentication cookies
alice.attemptsTo(
APIRequest.get("https://myapp.com/api/profile")
);

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

API Calls in Serenity Reports

API requests made via APIRequest are automatically recorded in Serenity reports, similar to RestAssured. The reports show:

  • HTTP method and URL
  • Request headers and body
  • Response status code
  • Response headers and body

This provides full visibility into API interactions during test execution.

Complete Example

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

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

// Verify creation
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();

// Fetch the created user
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);
}

Handling Downloads

Wait for and handle file downloads:

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

// Wait for download triggered by clicking
alice.attemptsTo(
WaitForDownload.whilePerforming(Click.on("#download-btn"))
);

// Query download information
String filename = alice.asksFor(DownloadedFile.suggestedFilename());
String url = alice.asksFor(DownloadedFile.url());
Path path = alice.asksFor(DownloadedFile.path());

// Check for failures
String error = alice.asksFor(DownloadedFile.failure());
if (error == null) {
// Download succeeded
}

// Save to specific location
alice.attemptsTo(
WaitForDownload.whilePerforming(Click.on("#export-btn"))
.andSaveTo(Paths.get("/downloads/report.pdf"))
);

Console Message Capture

Capture and query browser console messages for debugging:

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

// Start capturing console messages
alice.attemptsTo(CaptureConsoleMessages.duringTest());

// ... perform actions that may log to console ...

// Query captured messages
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());

// Filter by content
List<String> apiErrors = alice.asksFor(ConsoleMessages.containing("API error"));

// Get message counts
int totalCount = alice.asksFor(ConsoleMessages.count());
int errorCount = alice.asksFor(ConsoleMessages.errorCount());

// Clear captured messages between test phases
alice.attemptsTo(CaptureConsoleMessages.clear());

Checking for Console Errors

New in 5.2.2

CheckConsole was added in Serenity BDD 5.2.2.

Use CheckConsole to automatically fail tests when JavaScript errors or warnings occur. This is the recommended way to ensure your application doesn't have console errors during user flows:

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

// Start capturing, then check for errors at the end of the flow
alice.attemptsTo(
CaptureConsoleMessages.duringTest(),

// Perform user actions
Navigate.to("https://myapp.com/checkout"),
Enter.theValue("4111111111111111").into("#card-number"),
Click.on(Button.withText("Pay")),

// Fail the test if any JavaScript errors occurred
CheckConsole.forErrors()
);

CheckConsole Options

MethodDescription
CheckConsole.forErrors()Fail if any console errors are found
CheckConsole.forWarnings()Fail if any console warnings are found
CheckConsole.forErrorsAndWarnings()Fail if any errors OR warnings are found

Report-Only Mode

Sometimes you want to document console issues without failing the test (e.g., for known issues or when monitoring error trends):

// Report errors to Serenity but don't fail the test
alice.attemptsTo(
CheckConsole.forErrors().andReportOnly()
);

When errors are found, they are automatically attached to the Serenity report as evidence, whether the test fails or not.

Reporting Console Messages

New in 5.2.2

ReportConsoleMessages was added in Serenity BDD 5.2.2.

Use ReportConsoleMessages to explicitly add captured console messages to the Serenity report:

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

alice.attemptsTo(
CaptureConsoleMessages.duringTest(),

// ... perform actions ...

// Report errors and warnings to Serenity
ReportConsoleMessages.errorsAndWarnings()
);

ReportConsoleMessages Options

MethodDescription
ReportConsoleMessages.all()Report all console messages
ReportConsoleMessages.errors()Report only errors
ReportConsoleMessages.warnings()Report only warnings
ReportConsoleMessages.errorsAndWarnings()Report errors and warnings

Example: Complete Console Error Checking

Here's a typical pattern for ensuring no JavaScript errors occur during a critical user flow:

@Test
void checkoutFlowShouldHaveNoJavaScriptErrors() {
alice.attemptsTo(
// Start capturing at the beginning
CaptureConsoleMessages.duringTest(),

// Complete the checkout flow
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")),

// Verify order confirmation
WaitFor.theElement(".order-confirmation").toBeVisible(),

// Fail the test if any JavaScript errors occurred during the flow
CheckConsole.forErrors()
);
}

Network Request Capture

New in 5.2.2

Network request capture was added in Serenity BDD 5.2.2.

Capture and analyze all network requests made during tests. This is useful for debugging, verifying API calls made by the frontend, and detecting failed requests.

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

// Start capturing network requests
alice.attemptsTo(CaptureNetworkRequests.duringTest());

// Perform actions that trigger network requests
alice.attemptsTo(Navigate.to("https://example.com"));

// Query all captured requests
List<CapturedRequest> allRequests = alice.asksFor(NetworkRequests.all());
int requestCount = alice.asksFor(NetworkRequests.count());

// Filter by HTTP method
List<CapturedRequest> getRequests = alice.asksFor(NetworkRequests.withMethod("GET"));
List<CapturedRequest> postRequests = alice.asksFor(NetworkRequests.withMethod("POST"));

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

// Filter by glob pattern
List<CapturedRequest> cssRequests = alice.asksFor(
NetworkRequests.matching("**/*.css")
);

// Find failed requests (4xx, 5xx, or network errors)
List<CapturedRequest> failedRequests = alice.asksFor(NetworkRequests.failed());
int failedCount = alice.asksFor(NetworkRequests.failedCount());

// Find client errors (4xx)
List<CapturedRequest> clientErrors = alice.asksFor(NetworkRequests.clientErrors());

// Find server errors (5xx)
List<CapturedRequest> serverErrors = alice.asksFor(NetworkRequests.serverErrors());

// Clear captured requests between test phases
alice.attemptsTo(CaptureNetworkRequests.clear());

CapturedRequest Properties

Each CapturedRequest contains:

PropertyDescription
getUrl()The request URL
getMethod()HTTP method (GET, POST, etc.)
getResourceType()Resource type (document, xhr, fetch, stylesheet, etc.)
getRequestHeaders()Map of request headers
getStatus()Response status code (or null if pending/failed)
getStatusText()Response status text
getFailureText()Failure reason for network errors
isFailed()True if request failed (4xx, 5xx, or network error)
isClientError()True if 4xx status
isServerError()True if 5xx status

Example: Verifying API Calls

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

// Verify the expected API call was made
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);
}

Failure Evidence Capture

New in 5.2.2

Automatic failure evidence capture was added in Serenity BDD 5.2.2.

When a test fails, Serenity Playwright automatically captures diagnostic information and attaches it to the report. This makes debugging test failures much easier.

Automatic Evidence Collection

When console message or network request capture is enabled, the following evidence is automatically collected on test failure:

  • Page Information: Current URL and page title
  • Console Errors: All console.error() and console.warn() messages
  • Failed Network Requests: Requests that returned 4xx/5xx status or network errors

This evidence is attached to the Serenity report as "Playwright Failure Evidence".

Enabling Evidence Capture

Simply enable console and/or network capture at the start of your tests:

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

// Enable capture for failure evidence
alice.attemptsTo(
CaptureConsoleMessages.duringTest(),
CaptureNetworkRequests.duringTest()
);
}

Programmatic Access to Evidence

You can also query evidence programmatically for assertions or custom reporting:

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

// Get console errors
List<String> consoleErrors = PlaywrightFailureEvidence.getConsoleErrors(alice);

// Get failed network requests
List<String> failedRequests = PlaywrightFailureEvidence.getFailedRequests(alice);

// Use in assertions
assertThat(consoleErrors).isEmpty();
assertThat(failedRequests).isEmpty();

Example: Detecting JavaScript Errors

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

// Verify no JavaScript errors occurred
List<String> errors = alice.asksFor(ConsoleMessages.errors());
assertThat(errors)
.describedAs("JavaScript errors on page")
.isEmpty();
}

Example: Detecting Failed API Calls

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

// Verify no API calls failed
List<CapturedRequest> failedRequests = alice.asksFor(NetworkRequests.failed());
assertThat(failedRequests)
.describedAs("Failed network requests")
.isEmpty();
}

Accessibility Testing

Test accessibility compliance using ARIA snapshots:

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

// Get accessibility snapshot of the entire page
String pageSnapshot = alice.asksFor(AccessibilitySnapshot.ofThePage());

// Get accessibility snapshot of a specific element
String navSnapshot = alice.asksFor(AccessibilitySnapshot.of("#main-nav"));
String formSnapshot = alice.asksFor(AccessibilitySnapshot.of(LOGIN_FORM));

// Get all elements with a specific ARIA role
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));

Visual Regression Testing

Compare screenshots against baseline images:

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

// Full page comparison
alice.attemptsTo(
CompareScreenshot.ofPage()
.againstBaseline("homepage-baseline.png")
.withThreshold(0.01) // 1% difference allowed
);

// Element comparison
alice.attemptsTo(
CompareScreenshot.of("#product-card")
.againstBaseline("product-card-baseline.png")
);

// With mask for dynamic content
alice.attemptsTo(
CompareScreenshot.ofPage()
.againstBaseline("dashboard.png")
.withMask("#timestamp", "#user-avatar")
);

Device Emulation

Test responsive designs with device emulation:

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

// Emulate specific device
alice.attemptsTo(EmulateDevice.device("iPhone 14"));
alice.attemptsTo(EmulateDevice.device("Pixel 7"));
alice.attemptsTo(EmulateDevice.device("iPad Pro 11"));

// Custom viewport
alice.attemptsTo(EmulateDevice.withViewport(375, 812));

// With device scale factor
alice.attemptsTo(
EmulateDevice.withViewport(375, 812).andDeviceScaleFactor(2)
);

// With mobile user agent
alice.attemptsTo(
EmulateDevice.withViewport(375, 812).asMobile()
);

Geolocation

Test location-based features:

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

// First grant geolocation permission
alice.attemptsTo(GrantPermissions.for_("geolocation"));

// Set specific coordinates
alice.attemptsTo(SetGeolocation.to(51.5074, -0.1278)); // London

// Use predefined locations
alice.attemptsTo(SetGeolocation.toNewYork());
alice.attemptsTo(SetGeolocation.toLondon());
alice.attemptsTo(SetGeolocation.toTokyo());
alice.attemptsTo(SetGeolocation.toSanFrancisco());
alice.attemptsTo(SetGeolocation.toSydney());
alice.attemptsTo(SetGeolocation.toParis());

// With accuracy
alice.attemptsTo(
SetGeolocation.to(40.7128, -74.0060).withAccuracy(100)
);

// Clear geolocation
alice.attemptsTo(SetGeolocation.clear());

Permission Management

Grant or clear browser permissions:

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

// Grant specific permissions
alice.attemptsTo(GrantPermissions.for_("geolocation"));
alice.attemptsTo(GrantPermissions.for_("notifications", "camera", "microphone"));

// Clear all permissions
alice.attemptsTo(ClearPermissions.all());

Clock Control

Test time-dependent functionality:

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

// Install fake clock
alice.attemptsTo(ControlClock.install());

// Set to specific time
alice.attemptsTo(
ControlClock.setTo(Instant.parse("2024-01-15T10:30:00Z"))
);

// Advance time
alice.attemptsTo(ControlClock.advanceBy(Duration.ofHours(2)));
alice.attemptsTo(ControlClock.advanceBy(Duration.ofMinutes(30)));

// Resume normal time flow
alice.attemptsTo(ControlClock.resume());

Tracing

Record detailed traces for debugging:

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

// Start tracing with options
alice.attemptsTo(
StartTracing.withScreenshots()
.andSnapshots()
.named("login-flow")
);

// ... perform test actions ...

// Stop and save trace
alice.attemptsTo(
StopTracing.andSaveTo(Paths.get("traces/login-flow.zip"))
);

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

Trace options:

  • withScreenshots() - Include screenshots in trace
  • andSnapshots() - Include DOM snapshots
  • andSources() - Include source files
  • named(String) - Set trace name

Session State Persistence

New in 5.2.2

Session state persistence was added in Serenity BDD 5.2.2.

Save and restore browser session state (cookies, localStorage, sessionStorage) to speed up tests and share authenticated sessions.

Saving Session State

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

// Save to a specific path
Path sessionPath = Paths.get("target/sessions/authenticated.json");
alice.attemptsTo(
SaveSessionState.toPath(sessionPath)
);

// Save to default location with a name
// Saves to: target/playwright/session-state/{name}.json
alice.attemptsTo(
SaveSessionState.toFile("admin-session")
);

Restoring Session State

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

// Restore from a specific path
alice.attemptsTo(
RestoreSessionState.fromPath(Paths.get("target/sessions/authenticated.json"))
);

// Restore from default location
alice.attemptsTo(
RestoreSessionState.fromFile("admin-session")
);

Use Case: Reusing Authenticated Sessions

A common pattern is to log in once and reuse the session across multiple tests:

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")),

// Save the authenticated session
SaveSessionState.toPath(SESSION_FILE)
);

setup.wrapUp();
}
}

// In your tests
@Test
void shouldAccessDashboard() {
alice.attemptsTo(
// Restore the pre-authenticated session
RestoreSessionState.fromPath(SESSION_FILE),

// Navigate directly to protected page
Navigate.to("https://myapp.com/dashboard")
);

// User is already logged in!
assertThat(alice.asksFor(Text.of("h1"))).isEqualTo("Dashboard");
}

Session State Contents

The saved session state is a JSON file containing:

  • Cookies: All cookies for the browser context
  • Origins: localStorage and sessionStorage data for each origin

This allows you to:

  • Skip login steps in tests (faster execution)
  • Share authentication across test classes
  • Create session fixtures for different user roles
  • Test session expiration and refresh scenarios

Example: Multiple User Roles

public class SessionFixtures {

public static void createAdminSession() {
// Login as admin and save session
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 as regular user and save session
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();
}
}

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

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

PDF Generation

Generate PDFs from pages (Chromium only, headless mode):

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

// Basic PDF generation
alice.attemptsTo(
GeneratePDF.ofCurrentPage()
.andSaveTo(Paths.get("output/report.pdf"))
);

// With options
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"))
);

Switching Contexts

Frames

Work with iframes:

// Switch to frame by name or ID
alice.attemptsTo(Switch.toFrame("payment-iframe"));

// Switch to frame by Target
alice.attemptsTo(Switch.toFrame(Target.the("payment").locatedBy("#payment-frame")));

// Switch back to main frame
alice.attemptsTo(Switch.toMainFrame());

Windows and Tabs

Handle multiple windows and tabs:

// Switch to new window/tab
alice.attemptsTo(Switch.toNewWindow());

// Close current window
alice.attemptsTo(CloseCurrentWindow.now());

Best Practices

Use Target Constants

Define targets as constants for reusability and maintainability:

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']");
}

Prefer Role Selectors

Use ARIA role selectors for more resilient tests:

// Instead of CSS
Target.the("Submit").locatedBy("button.primary-btn")

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

Use UI Element Factories

For common elements, use the UI element factories:

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

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

Network Mocking for Isolation

Mock API responses to isolate UI tests:

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

Use Tracing for Debugging

Enable tracing when debugging test failures:

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

// Run your test...

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

Quick Reference Tables

All Interactions

The following tables provide a complete reference of all available Playwright Screenplay interactions.

Element Interactions

InteractionDescriptionExample
Click.on(target)Click on an elementClick.on("#submit")
DoubleClick.on(target)Double-click on an elementDoubleClick.on("#item")
RightClick.on(target)Right-click (context menu) on an elementRightClick.on("#file")
Hover.over(target)Move mouse over an elementHover.over("#menu")
Focus.on(target)Set focus to an elementFocus.on("#search")

Text Input Interactions

InteractionDescriptionExample
Enter.theValue(text).into(target)Type text into a fieldEnter.theValue("john@test.com").into("#email")
Clear.field(target)Clear an input fieldClear.field("#search")
Press.key(key)Press a keyboard keyPress.key("Enter")
Press.key(combo)Press a key combinationPress.key("Control+a")
Press.keys(keys...)Press multiple keys in sequencePress.keys("Tab", "Tab", "Enter")

Checkbox & Radio Interactions

InteractionDescriptionExample
Check.checkbox(target)Check a checkboxCheck.checkbox("#agree")
Uncheck.checkbox(target)Uncheck a checkboxUncheck.checkbox("#newsletter")
InteractionDescriptionExample
SelectFromOptions.byVisibleText(text).from(target)Select by visible textSelectFromOptions.byVisibleText("Red").from("#color")
SelectFromOptions.byValue(value).from(target)Select by value attributeSelectFromOptions.byValue("red").from("#color")
SelectFromOptions.byIndex(index).from(target)Select by index (0-based)SelectFromOptions.byIndex(2).from("#color")
DeselectFromOptions.byValue(value).from(target)Deselect by valueDeselectFromOptions.byValue("red").from("#colors")
DeselectFromOptions.byVisibleText(text).from(target)Deselect by visible textDeselectFromOptions.byVisibleText("Red").from("#colors")
DeselectFromOptions.byIndex(index).from(target)Deselect by indexDeselectFromOptions.byIndex(0).from("#colors")
DeselectFromOptions.all().from(target)Deselect all optionsDeselectFromOptions.all().from("#colors")

Scrolling Interactions

InteractionDescriptionExample
Scroll.to(target)Scroll element into viewScroll.to("#footer")
Scroll.to(target).andAlignToTop()Scroll with top alignmentScroll.to("#section").andAlignToTop()
Scroll.to(target).andAlignToCenter()Scroll with center alignmentScroll.to("#section").andAlignToCenter()
Scroll.to(target).andAlignToBottom()Scroll with bottom alignmentScroll.to("#section").andAlignToBottom()
Scroll.toTop()Scroll to page topScroll.toTop()
Scroll.toBottom()Scroll to page bottomScroll.toBottom()
Scroll.by(deltaX, deltaY)Scroll by pixel amountScroll.by(0, 500)
Scroll.toPosition(x, y)Scroll to absolute positionScroll.toPosition(0, 1000)

Drag and Drop Interactions

InteractionDescriptionExample
Drag.from(source).to(target)Drag from source to targetDrag.from("#item").to("#dropzone")
Drag.the(source).onto(target)Alternative fluent syntaxDrag.the("#card").onto("#column")
InteractionDescriptionExample
Navigate.to(url)Navigate to a URLNavigate.to("https://example.com")
Navigate.toTheBaseUrl()Navigate to configured base URLNavigate.toTheBaseUrl()

Frame & Window Interactions

InteractionDescriptionExample
Switch.toFrame(nameOrId)Switch to iframe by name/IDSwitch.toFrame("payment-iframe")
Switch.toFrame(target)Switch to iframe by TargetSwitch.toFrame(PAYMENT_FRAME)
Switch.toMainFrame()Switch back to main frameSwitch.toMainFrame()
Switch.toNewWindow()Switch to new window/tabSwitch.toNewWindow()
CloseCurrentWindow.now()Close current windowCloseCurrentWindow.now()

File Interactions

InteractionDescriptionExample
Upload.file(path).to(target)Upload a fileUpload.file(Paths.get("doc.pdf")).to("#upload")
WaitForDownload.whilePerforming(action)Wait for download during actionWaitForDownload.whilePerforming(Click.on("#download"))
WaitForDownload...andSaveTo(path)Save download to pathWaitForDownload.whilePerforming(...).andSaveTo(path)

JavaScript Interactions

InteractionDescriptionExample
Evaluate.javascript(script)Execute JavaScriptEvaluate.javascript("window.scrollTo(0,0)")

Wait Interactions

InteractionDescriptionExample
WaitUntil.the(target).isVisible()Wait for element visibilityWaitUntil.the("#modal").isVisible()
WaitUntil.the(target).isNotVisible()Wait for element to hideWaitUntil.the("#spinner").isNotVisible()
WaitUntil.the(target).isHidden()Wait for element to be hiddenWaitUntil.the("#loading").isHidden()
WaitUntil...forNoMoreThan(duration)Set custom timeoutWaitUntil.the("#data").isVisible().forNoMoreThan(Duration.ofSeconds(10))

Network Interactions

InteractionDescriptionExample
InterceptNetwork.requestsTo(pattern).andRespondWith(options)Mock response with optionsInterceptNetwork.requestsTo("**/api/**").andRespondWith(...)
InterceptNetwork.requestsTo(pattern).andRespondWithJson(status, data)Mock JSON responseInterceptNetwork.requestsTo("**/users").andRespondWithJson(200, users)
InterceptNetwork.requestsTo(pattern).andHandle(handler)Custom request handlerInterceptNetwork.requestsTo("**/api/**").andHandle(route -> ...)
InterceptNetwork.requestsTo(pattern).andAbort()Block requestsInterceptNetwork.requestsTo("**/analytics/**").andAbort()
RemoveRoutes.all()Remove all route handlersRemoveRoutes.all()
RemoveRoutes.forUrl(pattern)Remove routes for patternRemoveRoutes.forUrl("**/api/**")

Device & Environment Interactions

InteractionDescriptionExample
EmulateDevice.device(name)Emulate a deviceEmulateDevice.device("iPhone 14")
EmulateDevice.withViewport(width, height)Set custom viewportEmulateDevice.withViewport(375, 812)
EmulateDevice...andDeviceScaleFactor(factor)Set device scaleEmulateDevice.withViewport(375, 812).andDeviceScaleFactor(2)
EmulateDevice...asMobile()Add mobile user agentEmulateDevice.withViewport(375, 812).asMobile()
SetGeolocation.to(lat, lng)Set geolocationSetGeolocation.to(51.5074, -0.1278)
SetGeolocation.to(lat, lng).withAccuracy(m)Set geolocation with accuracySetGeolocation.to(40.7128, -74.0060).withAccuracy(100)
SetGeolocation.toNewYork()Set to New YorkSetGeolocation.toNewYork()
SetGeolocation.toLondon()Set to LondonSetGeolocation.toLondon()
SetGeolocation.toTokyo()Set to TokyoSetGeolocation.toTokyo()
SetGeolocation.toSanFrancisco()Set to San FranciscoSetGeolocation.toSanFrancisco()
SetGeolocation.toSydney()Set to SydneySetGeolocation.toSydney()
SetGeolocation.toParis()Set to ParisSetGeolocation.toParis()
SetGeolocation.clear()Clear geolocationSetGeolocation.clear()
GrantPermissions.for_(permissions...)Grant browser permissionsGrantPermissions.for_("geolocation", "camera")
ClearPermissions.all()Clear all permissionsClearPermissions.all()

Clock Control Interactions

InteractionDescriptionExample
ControlClock.install()Install fake clockControlClock.install()
ControlClock.setTo(instant)Set clock to specific timeControlClock.setTo(Instant.parse("2024-01-15T10:30:00Z"))
ControlClock.advanceBy(duration)Advance clockControlClock.advanceBy(Duration.ofHours(2))
ControlClock.resume()Resume normal time flowControlClock.resume()

Debugging & Tracing Interactions

InteractionDescriptionExample
StartTracing.withScreenshots()Start trace with screenshotsStartTracing.withScreenshots()
StartTracing...andSnapshots()Include DOM snapshotsStartTracing.withScreenshots().andSnapshots()
StartTracing...andSources()Include source filesStartTracing.withScreenshots().andSources()
StartTracing...named(name)Set trace nameStartTracing.withScreenshots().named("login-test")
StopTracing.andSaveTo(path)Stop and save traceStopTracing.andSaveTo(Paths.get("trace.zip"))
CaptureConsoleMessages.duringTest()Start capturing consoleCaptureConsoleMessages.duringTest()
CaptureConsoleMessages.clear()Clear captured messagesCaptureConsoleMessages.clear()
CheckConsole.forErrors()Fail if console errors foundCheckConsole.forErrors()
CheckConsole.forWarnings()Fail if console warnings foundCheckConsole.forWarnings()
CheckConsole.forErrorsAndWarnings()Fail if errors or warnings foundCheckConsole.forErrorsAndWarnings()
CheckConsole...andReportOnly()Report without failingCheckConsole.forErrors().andReportOnly()
ReportConsoleMessages.all()Report all console messagesReportConsoleMessages.all()
ReportConsoleMessages.errors()Report console errorsReportConsoleMessages.errors()
ReportConsoleMessages.errorsAndWarnings()Report errors and warningsReportConsoleMessages.errorsAndWarnings()
CaptureNetworkRequests.duringTest()Start capturing networkCaptureNetworkRequests.duringTest()
CaptureNetworkRequests.clear()Clear captured requestsCaptureNetworkRequests.clear()

Session State Interactions

InteractionDescriptionExample
SaveSessionState.toPath(path)Save session to specific pathSaveSessionState.toPath(Paths.get("session.json"))
SaveSessionState.toFile(name)Save session to default locationSaveSessionState.toFile("admin-session")
RestoreSessionState.fromPath(path)Restore session from pathRestoreSessionState.fromPath(Paths.get("session.json"))
RestoreSessionState.fromFile(name)Restore session from default locationRestoreSessionState.fromFile("admin-session")

API Request Interactions

InteractionDescriptionExample
APIRequest.get(url)Make GET requestAPIRequest.get("https://api.example.com/users")
APIRequest.post(url)Make POST requestAPIRequest.post("https://api.example.com/users")
APIRequest.put(url)Make PUT requestAPIRequest.put("https://api.example.com/users/1")
APIRequest.patch(url)Make PATCH requestAPIRequest.patch("https://api.example.com/users/1")
APIRequest.delete(url)Make DELETE requestAPIRequest.delete("https://api.example.com/users/1")
APIRequest.head(url)Make HEAD requestAPIRequest.head("https://api.example.com/users/1")
APIRequest...withJsonBody(object)Set JSON request bodyAPIRequest.post(url).withJsonBody(Map.of("name", "John"))
APIRequest...withBody(string)Set string request bodyAPIRequest.post(url).withBody("<xml>data</xml>")
APIRequest...withHeader(name, value)Add request headerAPIRequest.get(url).withHeader("Authorization", "Bearer token")
APIRequest...withQueryParam(name, value)Add query parameterAPIRequest.get(url).withQueryParam("page", "1")
APIRequest...withContentType(type)Set Content-TypeAPIRequest.post(url).withContentType("application/xml")
APIRequest...withTimeout(ms)Set request timeoutAPIRequest.get(url).withTimeout(30000)
APIRequest...failOnStatusCode(boolean)Fail on non-2xx statusAPIRequest.get(url).failOnStatusCode(true)

PDF Generation Interactions

InteractionDescriptionExample
GeneratePDF.ofCurrentPage().andSaveTo(path)Generate PDFGeneratePDF.ofCurrentPage().andSaveTo(Paths.get("page.pdf"))
GeneratePDF...withFormat(format)Set paper formatGeneratePDF.ofCurrentPage().withFormat("A4")
GeneratePDF...inLandscape()Use landscape orientationGeneratePDF.ofCurrentPage().inLandscape()
GeneratePDF...withMargins(t, r, b, l)Set marginsGeneratePDF.ofCurrentPage().withMargins("1cm", "1cm", "1cm", "1cm")
GeneratePDF...printBackground()Include background graphicsGeneratePDF.ofCurrentPage().printBackground()
GeneratePDF...displayHeaderAndFooter()Show header/footerGeneratePDF.ofCurrentPage().displayHeaderAndFooter()

Visual Testing Interactions

InteractionDescriptionExample
CompareScreenshot.ofPage().againstBaseline(name)Compare full pageCompareScreenshot.ofPage().againstBaseline("home.png")
CompareScreenshot.of(target).againstBaseline(name)Compare elementCompareScreenshot.of("#card").againstBaseline("card.png")
CompareScreenshot...withThreshold(threshold)Set diff thresholdCompareScreenshot.ofPage().againstBaseline("x.png").withThreshold(0.01)
CompareScreenshot...withMask(targets...)Mask dynamic elementsCompareScreenshot.ofPage().againstBaseline("x.png").withMask("#time")

All Questions

The following tables provide a complete reference of all available Playwright Screenplay questions.

Element State Questions

QuestionReturn TypeDescriptionExample
Presence.of(target)BooleanElement exists in DOMactor.asksFor(Presence.of("#modal"))
Absence.of(target)BooleanElement not presentactor.asksFor(Absence.of("#error"))
Visibility.of(target)BooleanElement is visibleactor.asksFor(Visibility.of("#popup"))
Enabled.of(target)BooleanElement is enabledactor.asksFor(Enabled.of("#submit"))
SelectedStatus.of(target)BooleanCheckbox/radio is selectedactor.asksFor(SelectedStatus.of("#agree"))

Element Content Questions

QuestionReturn TypeDescriptionExample
Text.of(target)StringGet element text contentactor.asksFor(Text.of("#title"))
Text.ofEach(target)List<String>Get text of all matching elementsactor.asksFor(Text.ofEach(".item"))
Value.of(target)StringGet input field valueactor.asksFor(Value.of("#email"))
Attribute.of(target).named(attr)StringGet attribute valueactor.asksFor(Attribute.of("#link").named("href"))
CSSValue.of(target).named(prop)StringGet CSS property valueactor.asksFor(CSSValue.of("#box").named("color"))

Page Information Questions

QuestionReturn TypeDescriptionExample
CurrentUrl.ofThePage()StringGet current page URLactor.asksFor(CurrentUrl.ofThePage())
PageTitle.ofThePage()StringGet page titleactor.asksFor(PageTitle.ofThePage())

Download Questions

QuestionReturn TypeDescriptionExample
DownloadedFile.suggestedFilename()StringGet suggested filenameactor.asksFor(DownloadedFile.suggestedFilename())
DownloadedFile.url()StringGet download URLactor.asksFor(DownloadedFile.url())
DownloadedFile.path()PathGet download file pathactor.asksFor(DownloadedFile.path())
DownloadedFile.failure()StringGet failure reason (null if success)actor.asksFor(DownloadedFile.failure())
DownloadedFile.download()DownloadGet Playwright Download objectactor.asksFor(DownloadedFile.download())

Console Message Questions

QuestionReturn TypeDescriptionExample
ConsoleMessages.all()List<String>Get all console messagesactor.asksFor(ConsoleMessages.all())
ConsoleMessages.errors()List<String>Get console errorsactor.asksFor(ConsoleMessages.errors())
ConsoleMessages.warnings()List<String>Get console warningsactor.asksFor(ConsoleMessages.warnings())
ConsoleMessages.logs()List<String>Get console logsactor.asksFor(ConsoleMessages.logs())
ConsoleMessages.info()List<String>Get console info messagesactor.asksFor(ConsoleMessages.info())
ConsoleMessages.containing(text)List<String>Get messages containing textactor.asksFor(ConsoleMessages.containing("error"))
ConsoleMessages.count()IntegerGet total message countactor.asksFor(ConsoleMessages.count())
ConsoleMessages.errorCount()IntegerGet error countactor.asksFor(ConsoleMessages.errorCount())
ConsoleMessages.allCaptured()List<CapturedConsoleMessage>Get full message objectsactor.asksFor(ConsoleMessages.allCaptured())

Accessibility Questions

QuestionReturn TypeDescriptionExample
AccessibilitySnapshot.ofThePage()StringGet page accessibility treeactor.asksFor(AccessibilitySnapshot.ofThePage())
AccessibilitySnapshot.of(target)StringGet element accessibility treeactor.asksFor(AccessibilitySnapshot.of("#nav"))
AccessibilitySnapshot.allWithRole(role)List<String>Get elements by ARIA roleactor.asksFor(AccessibilitySnapshot.allWithRole(AriaRole.BUTTON))

Network Request Questions

QuestionReturn TypeDescriptionExample
NetworkRequests.all()List<CapturedRequest>Get all captured requestsactor.asksFor(NetworkRequests.all())
NetworkRequests.count()IntegerGet total request countactor.asksFor(NetworkRequests.count())
NetworkRequests.withMethod(method)List<CapturedRequest>Filter by HTTP methodactor.asksFor(NetworkRequests.withMethod("POST"))
NetworkRequests.toUrlContaining(text)List<CapturedRequest>Filter by URL substringactor.asksFor(NetworkRequests.toUrlContaining("/api/"))
NetworkRequests.matching(pattern)List<CapturedRequest>Filter by glob patternactor.asksFor(NetworkRequests.matching("**/*.js"))
NetworkRequests.failed()List<CapturedRequest>Get failed requestsactor.asksFor(NetworkRequests.failed())
NetworkRequests.failedCount()IntegerGet failed request countactor.asksFor(NetworkRequests.failedCount())
NetworkRequests.clientErrors()List<CapturedRequest>Get 4xx errorsactor.asksFor(NetworkRequests.clientErrors())
NetworkRequests.serverErrors()List<CapturedRequest>Get 5xx errorsactor.asksFor(NetworkRequests.serverErrors())

API Response Questions

QuestionReturn TypeDescriptionExample
LastAPIResponse.statusCode()IntegerGet response status codeactor.asksFor(LastAPIResponse.statusCode())
LastAPIResponse.ok()BooleanCheck if status is 2xxactor.asksFor(LastAPIResponse.ok())
LastAPIResponse.body()StringGet response body as stringactor.asksFor(LastAPIResponse.body())
LastAPIResponse.jsonBody()Map<String, Object>Parse JSON response as Mapactor.asksFor(LastAPIResponse.jsonBody())
LastAPIResponse.jsonBodyAsList()List<Map<String, Object>>Parse JSON array responseactor.asksFor(LastAPIResponse.jsonBodyAsList())
LastAPIResponse.headers()Map<String, String>Get all response headersactor.asksFor(LastAPIResponse.headers())
LastAPIResponse.header(name)StringGet specific headeractor.asksFor(LastAPIResponse.header("Content-Type"))
LastAPIResponse.url()StringGet final URL (after redirects)actor.asksFor(LastAPIResponse.url())

UI Element Factories

Factory classes for locating common UI elements.

FactoryMethodsExample
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")

Migration from WebDriver

When migrating from serenity-screenplay-webdriver:

  1. Ability Change:

    • Replace BrowseTheWeb with BrowseTheWebWithPlaywright
  2. Selector Syntax:

    • CSS and XPath work similarly
    • Add role selectors for more robust tests
    • Use >> for chaining selectors instead of nesting
  3. Waiting:

    • Playwright auto-waits; explicit waits are less necessary
    • Remove most WaitUntil calls
  4. Locator Methods:

    • By.id("x") becomes "#x"
    • By.cssSelector("x") becomes "x"
    • By.xpath("x") becomes "xpath=x"
  5. New Capabilities:

    • Use network interception for mocking APIs
    • Use tracing for debugging
    • Use device emulation for responsive testing

Complete Example

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");
}
}

Further Reading