Tuesday, July 28, 2026

Playwright Visual Testing Using Java (Screenshot Comparison & UI Regression Testing)

Introduction

Functional testing verifies whether an application behaves correctly, but it cannot detect visual issues such as broken layouts, overlapping text, incorrect colours, missing icons, or unexpected UI changes.

Visual Testing addresses this gap by comparing screenshots of the application against previously approved baseline images. If any unexpected visual differences are detected, the test fails, helping teams identify UI regressions before they reach production.

Playwright includes built-in support for screenshot capture and comparison, making visual regression testing simple and reliable.

In this guide, you'll learn how to implement visual testing using Playwright with Java.


What is Visual Testing?

Visual Testing is the process of comparing the current appearance of an application with a known baseline image.

Instead of validating only HTML elements, visual testing verifies:

  • Layout
  • Colours
  • Fonts
  • Images
  • Buttons
  • Icons
  • Spacing
  • Alignment
  • Responsive design

Why Visual Testing is Important

Visual bugs often escape traditional automation because the page is technically functional.

Examples include:

  • Button shifted outside the screen
  • Text overlapping an image
  • Missing company logo
  • Incorrect font size
  • Broken navigation menu
  • Hidden labels
  • Responsive layout issues

Visual testing helps detect these problems automatically.


When Should You Use Visual Testing?

Visual testing is particularly useful for:

  • Home pages
  • Dashboards
  • Checkout pages
  • Reports
  • Responsive layouts
  • Marketing pages
  • Admin portals

Avoid relying solely on visual tests for pages with highly dynamic content unless that content can be controlled.


Types of Screenshots in Playwright

Playwright supports several screenshot strategies.

Page Screenshot

Capture the visible portion of the page.

page.screenshot(
    new Page.ScreenshotOptions()
        .setPath(Paths.get("screenshots/homepage.png"))
);

Full Page Screenshot

Capture the entire page, including content below the fold.

page.screenshot(
    new Page.ScreenshotOptions()
        .setFullPage(true)
        .setPath(Paths.get("screenshots/fullpage.png"))
);

This is ideal for validating long pages.


Element Screenshot

Capture only a specific component.

page.locator("#loginForm")
    .screenshot(
        new Locator.ScreenshotOptions()
            .setPath(Paths.get("screenshots/loginForm.png"))
    );

This reduces maintenance because only the target component is compared.


Creating Baseline Images

The first successful execution establishes the approved UI.

Example:

baseline/

├── homepage.png

├── login.png

├── dashboard.png

Future executions compare the latest screenshots against these baseline images.


Comparing Screenshots

Typical visual testing workflow:

  1. Capture the current screenshot.
  2. Load the baseline image.
  3. Compare both images.
  4. Generate a difference image if changes exist.
  5. Fail the test when differences exceed the accepted threshold.

Handling Dynamic Content

Some page elements change every time.

Examples:

  • Current date
  • Current time
  • Advertisements
  • User avatars
  • Rotating banners
  • Notifications

These should not cause test failures.


Mask Dynamic Elements

Mask unstable regions before capturing screenshots.

Example:

page.locator(".notification")
    .screenshot(
        new Locator.ScreenshotOptions()
            .setMask(List.of(page.locator(".timestamp")))
            .setPath(Paths.get("screenshots/notification.png"))
    );

This helps eliminate false positives.


Responsive UI Testing

Validate multiple viewport sizes.

Example:

BrowserContext context =
    browser.newContext(
        new Browser.NewContextOptions()
            .setViewportSize(390, 844)
    );

Capture screenshots for:

  • Mobile
  • Tablet
  • Desktop

This ensures the application renders correctly across devices.


Organising Screenshot Files

Recommended project structure:

src

└── test

    ├── baseline

    ├── actual

    ├── diff

    └── reports

Keeping these directories separate makes reviewing results easier.


Running Visual Tests in CI/CD

Visual regression testing integrates well with:

  • Jenkins
  • GitHub Actions
  • Azure DevOps
  • GitLab CI

Typical workflow:

  1. Build application.
  2. Execute Playwright tests.
  3. Capture screenshots.
  4. Compare against baseline.
  5. Publish reports.
  6. Fail the build if unexpected visual changes are detected.

Best Practices

Use Stable Test Data

Ensure the application displays predictable data before taking screenshots.


Test Individual Components

Prefer component-level screenshots over full-page screenshots where possible.

Component comparisons are faster and easier to maintain.


Control Browser Environment

Keep the following consistent:

  • Browser version
  • Viewport size
  • Font availability
  • Operating system
  • Zoom level

Consistency reduces false failures.


Review Baseline Changes Carefully

Update baseline images only when UI changes are intentional and approved.

Treat baseline updates as part of your code review process.


Avoid Visual Tests for Highly Dynamic Pages

Pages with constantly changing content may require masking or alternative validation strategies.


Common Challenges

Different Fonts

Missing fonts can cause text rendering differences.

Install the same fonts across all environments.


Animation Effects

Animations can produce inconsistent screenshots.

Disable animations during testing whenever possible.


Dynamic Advertisements

Mask or disable advertisement sections to improve stability.


Browser Differences

Small rendering differences may exist between Chromium, Firefox and WebKit.

Maintain separate baselines if cross-browser visual validation is required.


Common Interview Questions

What is visual regression testing?

Visual regression testing compares the current appearance of an application with a baseline image to detect unintended UI changes.


Why can't functional tests detect visual bugs?

Functional tests verify behaviour, not appearance. They may pass even when the UI layout is broken.


What is a baseline image?

A baseline image is the approved screenshot used as the reference for future comparisons.


How do you handle dynamic elements in visual testing?

Mask dynamic regions, use stable test data, or exclude changing content from comparisons.


Should visual testing replace functional testing?

No. Visual testing complements functional automation by validating appearance, while functional tests verify business behaviour.


Real-World Enterprise Example

Imagine an online shopping application.

After a CSS update:

  • The Add to Cart button becomes partially hidden.
  • Functional automation still clicks the button successfully.
  • Customers on smaller screens cannot see the button.

A visual regression test immediately highlights the layout difference, allowing the issue to be fixed before release.


Conclusion

Visual testing is an essential addition to any modern automation framework.

Playwright's screenshot capabilities make it easy to detect unintended UI changes, improve application quality, and reduce production defects.

In this guide, you learned how to:

  • Capture page, full-page, and element screenshots
  • Create and maintain baseline images
  • Compare screenshots
  • Handle dynamic content
  • Test responsive layouts
  • Integrate visual testing into CI/CD
  • Apply enterprise best practices

By incorporating visual regression testing into your Playwright framework, you'll catch UI issues that traditional functional tests often miss, delivering a more polished and reliable user experience.

Monday, July 27, 2026

Playwright Shadow DOM Handling Using Java (Complete Guide)

Introduction

Modern web applications increasingly use Web Components to build reusable, encapsulated UI elements. Frameworks such as SAP Fiori Web Components, Salesforce Lightning, Ionic, Material UI (selected components), Adobe Spectrum, and many custom enterprise applications rely on Shadow DOM to isolate styles and behaviour.

For automation engineers, Shadow DOM introduces a new challenge. Elements that appear visible in the browser cannot always be located using traditional selectors because they exist inside a shadow tree.

Fortunately, Playwright has native Shadow DOM support, making automation much simpler than with many traditional automation tools.

In this guide, you'll learn how to locate, inspect, and automate Shadow DOM elements using Playwright with Java.


What is Shadow DOM?

Shadow DOM is a browser technology that allows developers to encapsulate HTML, CSS and JavaScript inside an isolated component.

This isolation prevents:

  • CSS conflicts
  • JavaScript conflicts
  • Accidental DOM manipulation
  • Style leakage

Think of a Web Component as a small application with its own private DOM.


Why Do Developers Use Shadow DOM?

Developers use Shadow DOM because it provides:

  • Component reusability
  • Encapsulated styling
  • Better maintainability
  • Improved modularity
  • Protection from external CSS

For example, a company may create a reusable custom button component that behaves identically across hundreds of pages.


Understanding Shadow DOM Structure

Example HTML:

<user-card>

    #shadow-root (open)

        <div class="profile">

            <button>View Profile</button>

        </div>

</user-card>

The View Profile button exists inside the shadow root rather than the main document.

Traditional automation tools often struggle to locate such elements.


Types of Shadow DOM

There are two types of Shadow DOM.

Open Shadow DOM

Open Shadow DOM exposes its shadow root to JavaScript.

Example:

element.shadowRoot

Playwright can automatically locate elements inside an open Shadow DOM.


Closed Shadow DOM

Closed Shadow DOM hides its internal structure.

Example:

element.shadowRoot

returns:

null

Closed Shadow DOM is intentionally inaccessible.

No browser automation framework, including Playwright, can directly inspect or interact with elements inside a truly closed Shadow DOM unless the application provides another mechanism.


Why Playwright Excels with Shadow DOM

Unlike Selenium, Playwright automatically traverses open Shadow DOM boundaries.

This means your locators work naturally without requiring custom JavaScript.

Benefits include:

  • Less code
  • Better readability
  • Improved reliability
  • Easier maintenance

Example Application

Suppose the page contains:

<login-component>

    #shadow-root (open)

        <input id="username">

        <input id="password">

        <button>Login</button>

</login-component>

The inputs exist inside the shadow root.


Locating Shadow DOM Elements

Playwright automatically searches inside open shadow roots.

Example:

page.locator("#username")
        .fill("admin");

page.locator("#password")
        .fill("Password123");

page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions()
                .setName("Login"))
    .click();

No additional APIs are required.


Using CSS Selectors

You can also use standard CSS selectors.

page.locator("input[type='email']")
        .fill("user@example.com");

Playwright searches across open shadow boundaries automatically.


Using Accessible Locators

Role-based locators are highly recommended.

page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions()
                .setName("Submit"))
    .click();

These locators are resilient to UI changes and improve test readability.


Handling Nested Shadow DOM

Some applications contain multiple nested shadow roots.

Example:

app-root

└── #shadow-root

    └── login-panel

        └── #shadow-root

            └── login-form

                └── #shadow-root

                    └── Login Button

Even with multiple levels, Playwright automatically pierces open shadow roots when resolving locators.


Waiting for Shadow Elements

Playwright's auto-waiting works inside Shadow DOM.

Example:

page.getByText("Dashboard")
        .waitFor();

The test waits until the element is visible and ready for interaction.


Assertions Inside Shadow DOM

Assertions work exactly the same.

assertThat(
    page.getByText("Welcome")
).isVisible();

No special handling is required.


Debugging Shadow DOM

Use browser developer tools:

  1. Open DevTools.
  2. Inspect the component.
  3. Expand the #shadow-root node.
  4. Identify unique attributes or accessible roles.
  5. Build your Playwright locator.

This approach helps create stable selectors.


Common Challenges

Duplicate Elements

The same locator may exist inside multiple components.

Use a parent locator to narrow the search.

page.locator("user-card")
    .getByRole(
        AriaRole.BUTTON,
        new Locator.GetByRoleOptions()
            .setName("Edit"))
    .click();

Dynamic Components

Shadow DOM components may render asynchronously.

Use Playwright's auto-waiting or explicit waits instead of Thread.sleep().


Closed Shadow DOM

Closed Shadow DOM cannot be traversed directly.

Possible approaches include:

  • Test through public UI interactions.
  • Request test-friendly hooks from developers.
  • Use accessible APIs exposed by the component.

Best Practices

Prefer Accessible Locators

Use:

  • getByRole()
  • getByLabel()
  • getByPlaceholder()
  • getByTestId()

These are generally more stable than complex CSS selectors.


Avoid Deep CSS Chains

Instead of:

app-root > div > custom-card > div > button

Prefer meaningful locators based on roles or test IDs.


Use Test IDs for Custom Components

If your development team supports testing, ask them to add:

<button data-testid="saveButton">

Then locate it using:

page.getByTestId("saveButton")
        .click();

Keep Components Independent

Write tests that interact with components through their public interface rather than relying on internal implementation details.


Test Real User Behaviour

Focus on user outcomes rather than component internals.

Examples:

  • Login succeeds.
  • Product is added to cart.
  • Settings are saved.

Selenium vs. Playwright

FeatureSeleniumPlaywright
Open Shadow DOM SupportRequires JavaScript execution in many casesNative support
Closed Shadow DOMNot supportedNot supported
Auto WaitingLimitedBuilt-in
Role-Based LocatorsLimitedExcellent
Nested Shadow DOMMore complexHandled automatically for open roots

Real-World Enterprise Use Cases

Shadow DOM is commonly found in:

  • SAP Fiori Web Components
  • Salesforce Lightning Web Components
  • Adobe Experience Manager Components
  • Ionic applications
  • Design systems built with Web Components
  • Internal enterprise UI libraries

Understanding Shadow DOM is valuable when working on modern enterprise applications.


Common Interview Questions

What is Shadow DOM?

Shadow DOM is a browser feature that encapsulates a component's HTML, CSS and JavaScript, preventing external interference.


Does Playwright support Shadow DOM?

Yes. Playwright automatically traverses open Shadow DOM boundaries when locating elements.


Can Playwright automate Closed Shadow DOM?

No. Closed Shadow DOM is intentionally inaccessible to browser automation frameworks.


Why are accessible locators recommended?

They improve readability, are more resilient to UI changes, and reflect how users interact with the application.


Why is Playwright considered easier than Selenium for Shadow DOM?

Playwright natively handles open Shadow DOM, reducing the need for custom JavaScript and simplifying locator strategies.


Conclusion

Shadow DOM is becoming increasingly common in modern web applications, and automation engineers need to understand how it affects element location and interaction.

Playwright greatly simplifies Shadow DOM automation through native support for open shadow roots, automatic waiting, and powerful locator strategies.

In this guide, you learned how to:

  • Understand Shadow DOM architecture
  • Differentiate between open and closed Shadow DOM
  • Locate elements inside open shadow roots
  • Work with nested Shadow DOM
  • Use stable locator strategies
  • Apply enterprise best practices
  • Prepare for common interview questions

Mastering Shadow DOM handling will help you automate modern component-based applications with greater confidence and significantly reduce maintenance effort in large-scale Playwright automation frameworks.

Friday, July 24, 2026

Playwright File Upload, Download & Drag-and-Drop Automation Using Java (Complete Guide)

Introduction

Modern web applications frequently require users to upload documents, download reports, or drag and drop files between different sections of the application.

Examples include:

  • Uploading profile pictures
  • Importing Excel spreadsheets
  • Downloading invoices or reports
  • Uploading resumes in job portals
  • Attaching files in support tickets
  • Dragging files into cloud storage applications

Automating these scenarios reliably is an essential skill for every automation engineer.

In this tutorial, you'll learn how to automate file uploads, file downloads, and drag-and-drop functionality using Playwright with Java.


Why File Handling Automation is Important

Many business-critical workflows depend on files.

Examples include:

  • Banking statement uploads
  • Insurance document verification
  • HR resume uploads
  • Medical report downloads
  • Invoice generation
  • Tax document processing

Automating these scenarios improves regression coverage and reduces manual effort.


Project Setup

Ensure your project includes the Playwright dependency.

<dependency>
    <groupId>com.microsoft.playwright</groupId>
    <artifactId>playwright</artifactId>
    <version>1.55.0</version>
</dependency>

Understanding File Upload in Playwright

Unlike Selenium, Playwright does not interact with the operating system's native file chooser.

Instead, it directly uploads files using the input element.

This makes uploads faster and more reliable.


Upload a Single File

Example:

page.navigate("https://example.com/upload");

page.setInputFiles(
    "input[type='file']",
    Paths.get("files/resume.pdf")
);

The selected file is uploaded immediately after it is assigned to the input element.


Upload Multiple Files

Some applications allow users to upload several files simultaneously.

page.setInputFiles(
    "input[type='file']",
    new Path[] {
        Paths.get("files/report.pdf"),
        Paths.get("files/image.png"),
        Paths.get("files/data.xlsx")
    }
);

Playwright uploads all selected files in one operation.


Remove Uploaded Files

To clear a previously selected file:

page.setInputFiles(
    "input[type='file']",
    new Path[] {}
);

This resets the file input field.


Handling Hidden File Inputs

Some applications hide the HTML file input and trigger it using a button.

Playwright can still upload files because it interacts directly with the input element.

Locate the hidden input and call:

page.setInputFiles(
    "#hiddenFileInput",
    Paths.get("files/document.pdf")
);

No JavaScript execution is required.


Working with File Chooser

Some websites open the file chooser after clicking a button.

Example:

FileChooser chooser =
    page.waitForFileChooser(() -> {

        page.click("#uploadButton");

    });

chooser.setFiles(
    Paths.get("files/profile.png")
);

Playwright waits for the chooser and then selects the file programmatically.


Downloading Files

Playwright provides built-in support for file downloads.

Example:

Download download =
    page.waitForDownload(() -> {

        page.click("#downloadReport");

    });

The Download object provides information about the downloaded file.


Save Downloaded File

Store the downloaded file in a specific directory.

download.saveAs(
    Paths.get(
        "downloads/report.pdf"
    )
);

This makes it easy to validate downloaded reports or archive test artifacts.


Verify Download

After saving the file, verify its existence.

Path path =
    Paths.get("downloads/report.pdf");

assert Files.exists(path);

You can also verify:

  • File name
  • File size
  • File extension
  • File content
  • Creation date

Drag and Drop

Many modern applications use drag-and-drop interactions.

Examples include:

  • Trello boards
  • Kanban applications
  • Cloud storage systems
  • Dashboard widgets

Playwright simplifies these actions.

page.dragAndDrop(
    "#source",
    "#target"
);

This performs the complete drag-and-drop interaction.


Drag Files into Upload Areas

Many websites support dragging files into a drop zone.

Typical flow:

  1. User selects a file.
  2. User drags it over the drop zone.
  3. Application uploads the file automatically.

In Playwright, this is usually implemented by uploading through the underlying file input element associated with the drop zone.


Validate Upload Success

Always verify that the upload completed successfully.

Example:

assertThat(
    page.locator(".success-message")
).containsText("Upload completed");

Validation is more reliable than assuming the upload succeeded.


Organizing Test Files

Store reusable files in a dedicated directory.

Example:

src

└── test

    └── resources

        └── testdata

            ├── sample.pdf

            ├── image.png

            ├── resume.docx

            └── report.xlsx

This structure keeps your project clean and portable.


Common Upload Challenges

File Size Restrictions

Applications may reject files larger than the allowed limit.

Test:

  • Small files
  • Large files
  • Boundary values

Unsupported File Types

Validate that unsupported formats produce appropriate validation messages.

Example:

  • Upload .exe
  • Upload .zip
  • Upload .bat

Verify that the application blocks invalid uploads.


Duplicate Uploads

Ensure the application correctly handles repeated uploads of the same file.


Interrupted Uploads

Test behavior during:

  • Network interruptions
  • Session expiration
  • Browser refresh
  • Upload cancellation

Best Practices

Store Test Files in Version Control

Keep sample files inside the project so that all team members use the same test data.


Use Relative Paths

Avoid absolute paths such as:

C:\Users\John\Desktop\file.pdf

Instead, use project-relative paths to improve portability.


Verify Download Content

Checking only the file name is insufficient.

Validate:

  • Content
  • Size
  • Format
  • Generated values

Keep Test Files Small

Large files increase execution time and repository size.

Use compact sample files unless testing file size limits.


Clean Up Download Directories

Delete temporary files after execution to prevent unnecessary storage growth.


Common Interview Questions

How does Playwright upload files?

Playwright uploads files by assigning them directly to the HTML file input element using setInputFiles(), without interacting with the native operating system file picker.


Can Playwright upload multiple files?

Yes. Pass an array of Path objects to setInputFiles().


How do you verify a downloaded file?

Save the file using download.saveAs() and validate its existence, size, name, or contents.


Does Playwright support drag-and-drop?

Yes. The dragAndDrop() method performs drag-and-drop interactions between source and target elements.


Why use relative file paths?

Relative paths make the framework portable across different operating systems, developers' machines, and CI/CD environments.


Conclusion

File handling is a critical aspect of end-to-end web automation.

Playwright provides simple and reliable APIs for uploading files, downloading reports, handling file chooser dialogs, and automating drag-and-drop interactions.

In this guide, you learned how to:

  • Upload single and multiple files
  • Handle hidden file inputs
  • Work with the file chooser
  • Download and verify files
  • Perform drag-and-drop actions
  • Organize test resources
  • Apply enterprise best practices

By mastering these techniques, you'll be able to automate many real-world business workflows involving documents, reports, and media files with confidence.

Wednesday, July 22, 2026

Playwright Authentication Handling (Basic Auth, OAuth, JWT & Session Management) Using Java

Introduction

Authentication is one of the first challenges automation engineers face when testing modern web applications. Whether you're working with an internal enterprise application, an e-commerce platform, or a cloud-based SaaS product, your tests often need to log in before performing business operations.

Repeatedly logging in before every test slows execution and increases maintenance. Playwright provides several mechanisms to handle authentication efficiently, including Basic Authentication, OAuth flows, JWT tokens, cookies, and session reuse.

In this guide, you'll learn the most common authentication techniques used in Playwright with Java and when to use each approach.


Why Authentication Matters in Automation

Authentication enables secure access to protected resources and user-specific functionality.

Common authentication methods include:

  • Username and password
  • HTTP Basic Authentication
  • OAuth 2.0
  • JWT (JSON Web Tokens)
  • Session cookies
  • Single Sign-On (SSO)

A good automation framework should support all of these without duplicating login logic across tests.


HTTP Basic Authentication

Basic Authentication sends a username and password with every request.

Playwright supports it during browser context creation.

BrowserContext context =
    browser.newContext(
        new Browser.NewContextOptions()
            .setHttpCredentials(
                "admin",
                "password123"
            )
    );

Page page = context.newPage();

page.navigate("https://example.com");

This is commonly used for internal tools or protected staging environments.


Form-Based Login

Most applications use login forms.

Example:

page.navigate("https://example.com/login");

page.fill("#username", "admin");

page.fill("#password", "password123");

page.click("#loginButton");

page.waitForURL("**/dashboard");

Playwright automatically waits for navigation and element readiness.


Reusing Logged-In Sessions

Logging in before every test increases execution time.

Instead, log in once and reuse the authenticated session.

After a successful login:

context.storageState(
    new BrowserContext.StorageStateOptions()
        .setPath(Paths.get("storageState.json"))
);

This saves cookies and local storage to a file.


Loading a Saved Session

Reuse the saved session in future tests.

BrowserContext context =
    browser.newContext(
        new Browser.NewContextOptions()
            .setStorageStatePath(
                Paths.get("storageState.json")
            )
    );

Page page = context.newPage();

This allows tests to start in an authenticated state without repeating the login process.


Working with Cookies

Applications often use cookies to maintain user sessions.

Retrieve cookies:

List<BrowserContext.Cookie> cookies =
        context.cookies();

System.out.println(cookies.size());

You can also inspect individual cookie properties for debugging or validation.


Adding Cookies Manually

Sometimes it's useful to inject cookies directly.

BrowserContext.Cookie cookie =
        new BrowserContext.Cookie();

cookie.setName("sessionId");
cookie.setValue("ABC123");
cookie.setDomain("example.com");
cookie.setPath("/");

context.addCookies(
        List.of(cookie)
);

This is useful when preparing pre-authenticated sessions.


JWT Token Authentication

Many REST APIs and Single Page Applications (SPAs) use JSON Web Tokens.

You can send a JWT token in request headers.

APIRequestContext request =
    playwright.request().newContext(
        new APIRequest.NewContextOptions()
            .setExtraHTTPHeaders(
                Map.of(
                    "Authorization",
                    "Bearer YOUR_JWT_TOKEN"
                )
            )
    );

This approach is common for API testing and backend integration.


OAuth Authentication

OAuth is widely used for social login providers such as:

  • Google
  • Microsoft
  • GitHub
  • LinkedIn

The typical flow is:

  1. User clicks Sign in.
  2. Browser redirects to the identity provider.
  3. User authenticates.
  4. Identity provider redirects back with an authorization code or token.

Automating OAuth directly can be complex because it often involves multi-factor authentication or third-party security policies.

In enterprise environments, a common practice is to authenticate once and reuse the saved session instead of repeating the entire OAuth flow.


API Login for Faster Tests

Instead of logging in through the UI, authenticate using an API and then open the browser with the authenticated session.

Typical flow:

  1. Send login API request.
  2. Receive session token or cookie.
  3. Save authentication data.
  4. Launch browser using the authenticated state.

This reduces execution time and avoids repetitive UI interactions.


Handling Session Expiration

Sessions may expire during long-running test suites.

To avoid failures:

  • Refresh the session when necessary.
  • Regenerate authentication tokens.
  • Recreate the storage state periodically.
  • Monitor token expiration times.

Multi-User Testing

Enterprise applications often require testing with multiple roles.

Example roles:

  • Administrator
  • Manager
  • Employee
  • Customer

Create a separate storage state file for each role.

storage/

├── admin.json

├── manager.json

├── employee.json

This keeps user sessions isolated and easy to manage.


Secure Credential Management

Never hardcode usernames or passwords in source code.

Instead, use:

  • Environment variables
  • Configuration files
  • Secret management tools
  • CI/CD pipeline secrets

This improves security and simplifies maintenance.


Best Practices

Reuse Sessions

Save authenticated sessions and reuse them whenever possible.

This significantly reduces test execution time.


Prefer API Login

When available, use backend authentication APIs instead of UI login for setup.


Isolate User Sessions

Each test should use its own browser context and session data.

Avoid sharing authenticated contexts across parallel tests.


Rotate Test Accounts

Use dedicated automation accounts and reset their state regularly to prevent unexpected failures.


Monitor Authentication Failures

Log authentication errors clearly so that expired credentials or configuration issues are easy to identify.


Common Interview Questions

What is storageState in Playwright?

storageState saves cookies and local storage so that authenticated sessions can be reused in later tests.


Why reuse authenticated sessions?

It speeds up execution, reduces repeated login operations, and improves test stability.


Can Playwright automate OAuth?

Yes, but many OAuth providers use additional security mechanisms such as MFA. In practice, teams often authenticate once and reuse the saved session.


How do you authenticate API requests using JWT?

Include the JWT token in the Authorization header using the Bearer scheme.


Why shouldn't credentials be hardcoded?

Hardcoded credentials create security risks and make maintenance more difficult. Externalized configuration is the recommended approach.


Conclusion

Authentication is a foundational aspect of web automation.

Playwright provides flexible options for handling Basic Authentication, form-based login, OAuth, JWT tokens, cookies, and reusable sessions, enabling automation engineers to build secure, fast, and maintainable test frameworks.

In this guide, you learned how to:

  • Implement HTTP Basic Authentication
  • Automate form-based login
  • Save and reuse authenticated sessions
  • Work with cookies
  • Authenticate API requests using JWT
  • Understand OAuth automation strategies
  • Apply enterprise authentication best practices

By mastering these techniques, you'll build automation suites that execute faster, scale more effectively, and align with the authentication patterns used in modern enterprise applications.

Monday, July 20, 2026

Playwright Network Interception and API Mocking with Java (Complete Guide)

Introduction

Modern web applications communicate extensively with backend services through REST APIs. Every time you log in, search for products, submit forms, or load dashboards, your browser sends multiple HTTP requests behind the scenes.

Traditionally, UI automation relied on live backend services, making tests slow and vulnerable to network issues. Playwright changes this by allowing you to intercept network traffic, modify requests, mock responses, and even simulate server failures—all from your test code.

In this guide, you'll learn how to use Playwright's network interception and API mocking capabilities with Java to build faster, more reliable automation tests.


What is Network Interception?

Network interception allows you to inspect or control HTTP requests made by the browser during test execution.

You can:

  • Observe outgoing requests
  • Modify request headers
  • Change request payloads
  • Mock API responses
  • Abort requests
  • Simulate backend failures
  • Improve test reliability

Why Use API Mocking?

API mocking allows frontend testing even when backend services are:

  • Still under development
  • Temporarily unavailable
  • Returning inconsistent data
  • Difficult to reproduce specific scenarios
  • Rate limited

Instead of calling the real server, Playwright returns a predefined response.

Benefits include:

  • Faster execution
  • Stable tests
  • Repeatable scenarios
  • Easier edge-case testing

Understanding Playwright Routes

Playwright uses the route() method to intercept requests.

Basic syntax:

page.route("**/*", route -> {

    route.continue_();

});

This intercepts every request and allows it to continue normally.


Intercept a Specific API Request

Suppose the application calls:

GET /api/users

Intercept only that endpoint:

page.route("**/api/users", route -> {

    route.continue_();

});

Other requests continue without interception.


Block Images to Speed Up Tests

Large images increase page load time.

Block them during automation:

page.route("**/*.{png,jpg,jpeg,gif}", route -> {

    route.abort();

});

Benefits:

  • Faster execution
  • Lower bandwidth usage
  • Reduced memory consumption

Mock an API Response

Instead of calling the real server:

page.route("**/api/users", route -> {

    route.fulfill(

        new Route.FulfillOptions()

            .setStatus(200)

            .setContentType("application/json")

            .setBody("""
                [
                  {
                    "id":1,
                    "name":"John Doe"
                  }
                ]
            """)
    );

});

The application receives the mocked response immediately.


Mock an Error Response

Testing error handling is easy.

Example:

page.route("**/api/users", route -> {

    route.fulfill(

        new Route.FulfillOptions()

            .setStatus(500)

            .setBody("Internal Server Error")

    );

});

Now you can verify whether the application displays the correct error message.


Simulate Network Failure

To simulate a server outage:

page.route("**/api/users", route -> {

    route.abort();

});

This allows you to validate retry logic and offline behavior.


Modify Request Headers

You can add or replace HTTP headers.

page.route("**/api/users", route -> {

    Map<String, String> headers =
        new HashMap<>(route.request().headers());

    headers.put("X-Test-User", "Automation");

    route.resume(

        new Route.ResumeOptions()
            .setHeaders(headers)

    );

});

Useful for testing feature flags or custom authentication.


Modify POST Request Data

Intercept a request before it reaches the server.

page.route("**/api/orders", route -> {

    System.out.println(
        route.request().postData()
    );

    route.continue_();

});

This helps verify request payloads during automation.


Log Network Requests

Monitor all requests:

page.onRequest(request ->

    System.out.println(
        request.method() + " " + request.url()

    )
);

Useful for debugging complex applications.


Log Network Responses

Capture server responses:

page.onResponse(response ->

    System.out.println(

        response.status() +

        " " +

        response.url()

    )
);

This helps identify failed API calls.


Wait for a Specific Response

Instead of using delays:

page.waitForResponse(

    "**/api/users"

);

This ensures the test proceeds only after the required response is received.


Verify API Response Status

Example:

page.onResponse(response -> {

    if (response.url().contains("/users")) {

        System.out.println(

            response.status()

        );

    }

});

You can also validate response headers or body content.


Practical Scenario

Imagine an e-commerce application.

Flow:

  1. User searches for a product.
  2. Frontend calls:
/api/search
  1. Instead of calling the real backend, Playwright returns:
[
  {
    "product":"Laptop",
    "price":899
  }
]

The UI behaves exactly as if the backend had returned the data.

This approach enables testing even when backend systems are unavailable.


Best Practices

Mock Only What Is Necessary

Avoid mocking every request.

Use real backend services when integration testing is required.

Mock only external or unstable dependencies.


Keep Mock Data Realistic

Use responses that resemble production data.

Include:

  • IDs
  • Dates
  • Nested objects
  • Validation messages

This improves test quality.


Separate Mock Files

Store large mock responses in JSON files instead of embedding them in Java code.

Example:

src

└── test

    └── resources

        └── mocks

            ├── users.json

            ├── products.json

            └── orders.json

Log Requests During Debugging

Request and response logging simplifies troubleshooting.

Disable verbose logging in production pipelines unless needed.


Combine with API Tests

Use Playwright's built-in API testing features alongside network interception to create comprehensive end-to-end automation.


Common Interview Questions

What is network interception in Playwright?

Network interception allows tests to inspect, modify, block, or mock HTTP requests and responses during browser automation.


What is API mocking?

API mocking replaces real backend responses with predefined data, enabling reliable frontend testing without depending on live services.


Can Playwright block network requests?

Yes. The route.abort() method can block requests such as images, analytics scripts, or API calls.


Why use mocked APIs?

Mocked APIs make tests faster, more stable, and easier to execute when backend services are unavailable or difficult to control.


How do you wait for a specific network response?

Use page.waitForResponse() to synchronize test execution with a specific HTTP response.


Conclusion

Network interception and API mocking are among Playwright's most powerful capabilities.

They enable you to build faster, more reliable, and more maintainable automation suites by reducing dependency on external services and allowing precise control over browser communication.

In this guide, you learned how to:

  • Intercept network requests
  • Mock API responses
  • Modify headers and request payloads
  • Block unwanted resources
  • Simulate server failures
  • Wait for network responses
  • Apply enterprise best practices

Mastering these techniques will help you create robust automation frameworks capable of testing complex applications under a wide variety of real-world conditions.

Saturday, July 18, 2026

Playwright Retry Mechanism and Flaky Test Handling in Java (Complete Guide)

Have you ever executed your automation suite twice without changing anything, only to see different results?

If the answer is yes, you've encountered flaky tests.

A flaky test sometimes passes and sometimes fails under the same conditions. These inconsistent failures reduce confidence in the automation framework, waste debugging time, and slow down CI/CD pipelines.

Fortunately, Playwright provides powerful synchronization features that minimize flaky tests, while TestNG offers retry capabilities for handling unavoidable transient failures.

In this tutorial, you'll learn how to identify flaky tests, implement retry mechanisms, and build stable Playwright automation frameworks using Java.


What is a Flaky Test?

A flaky test is an automated test that produces inconsistent results without any changes to the application or test code.

For example:

  • First execution → ✅ Pass
  • Second execution → ❌ Fail
  • Third execution → ✅ Pass

Since the application hasn't changed, the failure is caused by timing, synchronization, or environmental issues rather than a real defect.


Common Causes of Flaky Tests

The most common reasons include:

  • Slow page loading
  • Dynamic UI elements
  • Network latency
  • Asynchronous JavaScript execution
  • Poor synchronization
  • Hardcoded waits
  • Shared test data
  • Browser resource limitations
  • External service dependencies

Understanding these causes is the first step toward creating reliable automation.


Why Playwright Has Fewer Flaky Tests

Unlike many traditional automation tools, Playwright automatically waits for elements to become ready before interacting with them.

Playwright waits for elements to be:

  • Visible
  • Stable
  • Enabled
  • Ready to receive user actions

This built-in auto-waiting significantly reduces synchronization issues.


Avoid Hard Waits

One of the biggest causes of flaky tests is using fixed delays.

Avoid:

Thread.sleep(5000);

Problems with hard waits:

  • Tests become slower.
  • Five seconds may not always be enough.
  • Sometimes the page loads much faster, wasting execution time.

Instead, rely on Playwright's automatic waiting.


Using Playwright's Built-in Waiting

Example:

page.locator("#loginButton").click();

Playwright automatically waits until the button is ready before clicking it.

No explicit wait is required.


Waiting for Page Navigation

Instead of adding delays after clicking a button, wait for navigation.

page.waitForURL("**/dashboard");

This ensures the test continues only after the expected page is loaded.


Waiting for Elements

When necessary, explicitly wait for an element.

page.locator("#successMessage")
        .waitFor();

This is more reliable than using arbitrary delays.


Implementing Retry Mechanism in TestNG

TestNG provides the IRetryAnalyzer interface.

Create a retry class.

public class RetryAnalyzer
implements IRetryAnalyzer {

    private int count = 0;

    private static final int MAX_RETRY = 2;

    @Override
    public boolean retry(ITestResult result) {

        if (count < MAX_RETRY) {

            count++;

            return true;
        }

        return false;
    }
}

This configuration retries a failed test up to two additional times.


Using RetryAnalyzer

Apply it to your test.

@Test(retryAnalyzer = RetryAnalyzer.class)

public void loginTest() {

    page.navigate("https://example.com");

}

If the test fails due to a temporary issue, TestNG retries it automatically.


Retry Flow

Example:

Attempt 1 → Failed

↓

Retry 1 → Failed

↓

Retry 2 → Passed

The final execution result is considered successful.


Capturing Screenshots on Failure

Before retrying, capture evidence.

Example:

page.screenshot(
    new Page.ScreenshotOptions()
        .setPath(Paths.get(
        "screenshots/login.png")));

Screenshots simplify debugging and help determine whether a failure was caused by the application or the test.


Logging Useful Information

When failures occur, log:

  • Browser name
  • URL
  • Timestamp
  • Test method
  • Exception message
  • Screenshot location

Rich logs make troubleshooting much faster.


Isolate Test Data

Never allow parallel tests to share the same user account or database records.

Instead:

  • Generate unique usernames.
  • Use isolated test environments.
  • Clean up test data after execution.

Shared state is a common source of flaky failures.


Use Stable Locators

Prefer reliable locators.

Good:

page.getByTestId("login-button");

Also good:

page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions()
                .setName("Login"));

Avoid brittle XPath expressions based on layout or index positions.


Best Practices for Stable Tests

Keep Tests Independent

Each test should be able to run successfully regardless of execution order.


Avoid Chained Dependencies

Do not make one test depend on the success of another.

Each test should set up its own prerequisites.


Use Explicit Assertions

Verify business outcomes, such as:

  • Successful login
  • Order confirmation
  • Payment completion

Avoid weak assertions that only check page titles.


Retry Only Temporary Failures

Retries should handle transient issues, not hide genuine defects.

If a test fails consistently, investigate and fix the root cause rather than increasing the retry count.


Monitor Flaky Tests

Track tests that require frequent retries.

Recurring flaky tests should be prioritized for improvement or refactoring.


Common Interview Questions

What is a flaky test?

A flaky test produces inconsistent results even when the application and test code have not changed.


Does Playwright automatically wait for elements?

Yes. Playwright automatically waits for elements to become visible, stable, enabled, and ready before interacting with them.


Why should you avoid Thread.sleep()?

Fixed waits slow down execution and are unreliable because page load times vary.


What is the purpose of TestNG's RetryAnalyzer?

RetryAnalyzer automatically re-executes failed tests a specified number of times, helping recover from temporary failures.


Should retries replace proper synchronization?

No. Retries are a fallback mechanism. Stable synchronization, robust locators, and isolated test data should always be the primary strategy for reliable automation.


Conclusion

Building a reliable automation framework requires more than writing test scripts—it requires writing stable test scripts.

Playwright's automatic waiting mechanisms eliminate many common synchronization issues, while TestNG's retry capabilities provide a safety net for temporary failures.

In this guide, you learned how to:

  • Identify flaky tests
  • Understand their root causes
  • Use Playwright's built-in synchronization
  • Implement a TestNG RetryAnalyzer
  • Capture screenshots for debugging
  • Follow best practices for reliable automation

By focusing on stability first and using retries only when appropriate, you'll create Playwright automation suites that are faster, more maintainable, and trusted by development and QA teams.

Thursday, July 16, 2026

Playwright Cross-Browser Testing with Java (Chromium, Firefox & WebKit)

Introduction

Modern users access web applications using different browsers such as Google Chrome, Mozilla Firefox, Microsoft Edge, and Apple Safari. A feature that works perfectly in one browser may behave differently in another due to differences in rendering engines, JavaScript implementations, or browser-specific behaviors.

Cross-browser testing ensures your application delivers a consistent user experience across all supported browsers.

Playwright makes this process simple by providing built-in support for Chromium, Firefox, and WebKit using a single automation API.

In this guide, you'll learn how to execute the same Playwright test across multiple browsers using Java.


Why Cross-Browser Testing is Important

Testing across multiple browsers helps you:

  • Detect browser-specific bugs
  • Validate UI consistency
  • Improve customer experience
  • Increase application reliability
  • Meet enterprise quality standards

Instead of maintaining separate automation frameworks for different browsers, Playwright allows you to use one codebase for all supported browsers.


Browsers Supported by Playwright

Playwright supports three browser engines:

Browser EngineCommon Browsers
ChromiumGoogle Chrome, Microsoft Edge, Brave
FirefoxMozilla Firefox
WebKitApple Safari

This broad support enables comprehensive browser compatibility testing.


Project Setup

Ensure your Maven project includes the Playwright dependency.

<dependency>
    <groupId>com.microsoft.playwright</groupId>
    <artifactId>playwright</artifactId>
    <version>1.55.0</version>
</dependency>

After adding the dependency, install the required browser binaries.

mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install"

Launching Chromium

Chromium is the default browser used by many Playwright projects.

Playwright playwright = Playwright.create();

Browser browser =
        playwright.chromium().launch(
                new BrowserType.LaunchOptions()
                        .setHeadless(false));

BrowserContext context =
        browser.newContext();

Page page = context.newPage();

page.navigate("https://example.com");

Launching Firefox

To execute the same test in Firefox, change only the browser initialization.

Browser browser =
        playwright.firefox().launch(
                new BrowserType.LaunchOptions()
                        .setHeadless(false));

Everything else remains unchanged.


Launching WebKit

Testing Safari compatibility is equally straightforward.

Browser browser =
        playwright.webkit().launch(
                new BrowserType.LaunchOptions()
                        .setHeadless(false));

WebKit uses the same rendering engine as Safari, making it ideal for validating Safari behavior.


Parameterizing Browser Selection

Instead of maintaining separate test classes, you can parameterize the browser using TestNG.

@Parameters("browser")

@BeforeMethod

public void setup(String browserName) {

    playwright = Playwright.create();

    switch (browserName.toLowerCase()) {

        case "chromium":
            browser = playwright.chromium().launch();
            break;

        case "firefox":
            browser = playwright.firefox().launch();
            break;

        case "webkit":
            browser = playwright.webkit().launch();
            break;

        default:
            throw new IllegalArgumentException(
                    "Unsupported browser: " + browserName);
    }

    context = browser.newContext();

    page = context.newPage();
}

This approach keeps your framework flexible and avoids code duplication.


Configuring testng.xml

Pass the browser name through TestNG parameters.

<suite name="CrossBrowserSuite">

    <test name="Chromium">

        <parameter name="browser"
                   value="chromium"/>

        <classes>

            <class name="tests.LoginTest"/>

        </classes>

    </test>

    <test name="Firefox">

        <parameter name="browser"
                   value="firefox"/>

        <classes>

            <class name="tests.LoginTest"/>

        </classes>

    </test>

    <test name="WebKit">

        <parameter name="browser"
                   value="webkit"/>

        <classes>

            <class name="tests.LoginTest"/>

        </classes>

    </test>

</suite>

The same test class runs against all three browsers.


Running Tests in Parallel

To reduce execution time, enable parallel execution in TestNG.

<suite name="CrossBrowserSuite"
       parallel="tests"
       thread-count="3">

Each browser executes in its own thread.

Ensure every thread creates a separate BrowserContext and Page.


Example Login Test

@Test

public void verifyLogin() {

    page.navigate("https://example.com/login");

    page.fill("#username", "admin");

    page.fill("#password", "password123");

    page.click("#loginButton");

    assertThat(page)
            .hasURL("https://example.com/dashboard");
}

This single test can now validate functionality across Chromium, Firefox, and WebKit.


Best Practices

Use Separate Browser Contexts

Create a new BrowserContext for every test to isolate cookies, storage, and session data.


Avoid Browser-Specific Logic

Write browser-agnostic tests whenever possible.

If browser-specific behavior exists, document it clearly and minimize conditional code.


Run Headless in CI/CD

Headless mode is faster and better suited for build servers.

new BrowserType.LaunchOptions()
        .setHeadless(true);

Verify Critical User Flows

Prioritize cross-browser testing for high-value scenarios such as:

  • Login
  • Registration
  • Checkout
  • Payment
  • Search
  • File uploads
  • Form submission

Capture Screenshots on Failure

Integrate screenshot capture with your reporting framework (such as Allure) to simplify debugging across browsers.


Common Browser Differences

Although Playwright abstracts many browser differences, you may still encounter:

  • Font rendering variations
  • CSS layout differences
  • Native dialog behavior
  • Download handling
  • Browser security policies

Test these scenarios carefully when supporting multiple browsers.


Common Interview Questions

Which browsers are supported by Playwright?

Playwright supports Chromium, Firefox, and WebKit.


Can the same Playwright test run on multiple browsers?

Yes. Playwright uses a single API, allowing the same test code to execute across supported browser engines.


Why use BrowserContext instead of sharing a Browser instance?

A BrowserContext provides isolated sessions, preventing cookies and local storage from leaking between tests.


How do you execute Playwright tests on multiple browsers using TestNG?

Pass the browser as a TestNG parameter in testng.xml and initialize the corresponding browser in the setup method.


Is Microsoft Edge supported?

Yes. Microsoft Edge is Chromium-based, so Playwright can automate it using the Chromium engine or by launching the Edge executable if required.


Conclusion

Cross-browser testing is essential for delivering reliable web applications.

Playwright simplifies this process by providing a unified API for Chromium, Firefox, and WebKit, enabling teams to validate browser compatibility without maintaining separate automation frameworks.

In this tutorial, you learned how to:

  • Launch Chromium, Firefox, and WebKit
  • Parameterize browser selection with TestNG
  • Execute cross-browser tests in parallel
  • Follow best practices for browser isolation
  • Build a scalable, enterprise-ready automation framework

With these techniques, your automation suite will be better equipped to catch browser-specific issues before they reach production.