Playwright Selenium Grid Example

The Playwright selenium grid example below can quickly be run on a free Gridlastic selenium grid and at much lower cost per minute than other cloud services.

PlaywrightGridTests.java

package com.gridlastic.samples;

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Playwright.CreateOptions;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PlaywrightGridTests {

    private String browserChannel;
    private CreateOptions createOptions;
    private String seleniumCapabilities;
    private String videoUrlBase;
    private String videoUrl;

    @Parameters({"browserChannel"})
    @BeforeSuite
    public void initializeTestSuite(String browserChannel) {
        this.browserChannel = browserChannel;

        String seleniumRemoteUrl = System.getenv("SELENIUM_REMOTE_URL");
        seleniumCapabilities = System.getenv("SELENIUM_REMOTE_CAPABILITIES");
        videoUrlBase = System.getenv("VIDEO_URL_BASE");
        String gridUsername = System.getenv("GRIDLASTIC_GRID_USERNAME");
        String gridPassword = System.getenv("GRIDLASTIC_GRID_PASSWORD");

        if (seleniumRemoteUrl == null
                || gridUsername == null
                || gridPassword == null
                || seleniumCapabilities == null
                || videoUrlBase == null) {
            throw new IllegalStateException("Missing required environment variables: SELENIUM_REMOTE_URL, GRIDLASTIC_GRID_USERNAME, GRIDLASTIC_GRID_PASSWORD, SELENIUM_REMOTE_CAPABILITIES, or VIDEO_URL_BASE");
        }

        String credentials = gridUsername + ":" + gridPassword;
        String encoded = Base64.getEncoder().encodeToString(credentials.getBytes());
        String seleniumRemoteHeaders = "{\"Authorization\":\"Basic " + encoded + "\"}";

        Map env = new HashMap<>(System.getenv());
        env.put("SELENIUM_REMOTE_HEADERS", seleniumRemoteHeaders);

        this.createOptions = new CreateOptions().setEnv(env);
    }

    @Test
    public void test_1() {
        try (Playwright playwright = createRemotePlaywright()) {
            BrowserType.LaunchOptions launchOptions = createLaunchOptions();

            try (Browser browser = playwright.chromium().launch(launchOptions);
                 BrowserContext context = browser.newContext(createNewContextOptions());
                 Page page = context.newPage()) {
                logRemoteSession(page);
                page.navigate("https://www.gridlastic.com/?demo");
                Assert.assertTrue(page.title().toLowerCase().contains("selenium grid"),
                        browserChannel + " should open the Gridlastic demo page");
                page.waitForTimeout(5000); // Wait 5 seconds for demo purposes
            }
        }
    }

    @Test
    public void test_2_bidi() {
        try (Playwright playwright = createRemotePlaywright()) {
            BrowserType.LaunchOptions launchOptions = createLaunchOptions();

            try (Browser browser = playwright.chromium().launch(launchOptions);
                 BrowserContext context = browser.newContext(createNewContextOptions());
                 Page page = context.newPage()) {
                logRemoteSession(page);

                // 1. BiDi Demo Feature: Asynchronous Console Listener
                page.onConsoleMessage(msg -> System.out.println(String.format(
                        "[BiDi Console Event] [%s] Text: %s",
                        msg.type().toUpperCase(),
                        msg.text()
                )));

                // 2. BiDi Demo Feature: Asynchronous Network Request Listener
                page.onRequest(request -> System.out.println(String.format(
                        "[BiDi Network Event] Outbound -> Method: %s | URL: %s",
                        request.method(),
                        request.url()
                )));

                System.out.println("--- Starting BiDi Traffic Interception ---");

                // Navigate to a page to trigger network requests and intrinsic page logs
                page.navigate("https://www.gridlastic.com/?demo");

                // Execute some JavaScript to manually fire a browser log action
                page.evaluate("console.error('Simulated Critical App Failure message via BiDi!');");

                // Give the async WebSocket streams a brief window to complete delivery
                page.waitForTimeout(2000);

                System.out.println("--- Demonstration Complete ---");
            }
        }
    }

    private Playwright createRemotePlaywright() {
        return Playwright.create(createOptions);
    }

    private BrowserType.LaunchOptions createLaunchOptions() {
        return new BrowserType.LaunchOptions()
                .setChannel(browserChannel)
                .setHeadless(false)
                .setArgs(List.of("--start-maximized"))
                .setTimeout(600000); // give time to launch grid nodes and start session
    }

    private Browser.NewContextOptions createNewContextOptions() {
        Browser.NewContextOptions options = new Browser.NewContextOptions();
        if ("linux".equalsIgnoreCase(getCapabilityValue("platformName"))) {
            options.setViewportSize(1920, 1080);
        } else {
            options.setViewportSize(null);
        }
        return options;
    }

    @AfterMethod(alwaysRun = true)
    public void logTestCompletion(ITestResult result, ITestContext context) {
        String suiteName = context.getSuite().getName();
        String testName = result.getMethod().getMethodName();
        String platformName = getCapabilityValue("platformName");
        String browserName = getCapabilityValue("browserName");
        String browserVersion = getCapabilityValue("browserVersion");
        boolean videoEnabled = isVideoEnabled();

        StringBuilder output = new StringBuilder()
                .append("Test suite: ").append(suiteName)
                .append(", Test name: ").append(testName)
                .append(", Capabilities: platformName=").append(platformName)
                .append(", browserName=").append(browserName)
                .append(", browserVersion=").append(browserVersion)
                .append(", video=").append(videoEnabled);

        if (videoEnabled) {
            output.append(", Video URL=")
                    .append(videoUrl != null ? videoUrl : "not available");
        }

        System.out.println(output.toString());
    }

    private String getCapabilityValue(String key) {
        if (seleniumCapabilities == null) {
            return "unknown";
        }
        String regex = "[\\\"']" + key + "[\\\"']\\s*:\\s*[\\\"']([^\\\"']+)[\\\"']";
        java.util.regex.Matcher matcher = java.util.regex.Pattern.compile(regex).matcher(seleniumCapabilities);
        return matcher.find() ? matcher.group(1) : "unknown";
    }

    private boolean isVideoEnabled() {
        return seleniumCapabilities != null && seleniumCapabilities.matches("(?i).*\\\"video\\\"\\s*:\\s*true.*");
    }

    private String logRemoteSession(Page page) {
        page.navigate("http://localhost:62000/get-selenium-session-id");
        String seleniumSessionId = page.innerText("body").trim();
        videoUrl = videoUrlBase + seleniumSessionId;
        return seleniumSessionId;
    }
}

Example Resources - EdgeSuite.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="EdgeGridSuite" parallel="methods" thread-count="2">
    <parameter name="browserChannel" value="msedge"/>
    <test name="EdgeRemoteTests">
        <classes>
            <class name="com.gridlastic.samples.PlaywrightGridTests"/>
        </classes>
    </test>
</suite>

Main POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gridlastic.samples</groupId>
    <artifactId>Playwright_Selenium_Bridge</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.microsoft.playwright</groupId>
            <artifactId>playwright</artifactId>
            <version>1.59.0</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.8.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-invoker-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <projectsDirectory>${project.basedir}</projectsDirectory>
                    <cloneProjectsTo>${project.build.directory}/invoker-tests</cloneProjectsTo>
                    <pomIncludes>
                        <pomInclude>test-poms/chrome-win10-pom.xml</pomInclude>
                        <pomInclude>test-poms/chrome-win11-pom.xml</pomInclude>
                        <pomInclude>test-poms/chrome-linux-pom.xml</pomInclude>
                        <pomInclude>test-poms/edge-win10-pom.xml</pomInclude>
                        <pomInclude>test-poms/edge-win11-pom.xml</pomInclude>
                        <pomInclude>test-poms/edge-linux-pom.xml</pomInclude>
                    </pomIncludes>
                    <parallelThreads>6</parallelThreads>
                    <goals>
                        <goal>test</goal>
                    </goals>
                    <streamLogs>true</streamLogs>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Example test POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gridlastic.samples</groupId>
    <artifactId>test-edge-linux</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>com.microsoft.playwright</groupId>
            <artifactId>playwright</artifactId>
            <version>1.59.0</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.8.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <sourceDirectory>../src/main/java</sourceDirectory>
        <testSourceDirectory>../src/test/java</testSourceDirectory>
        <testResources>
            <testResource>
                <directory>../src/test/resources</directory>
            </testResource>
        </testResources>
        
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>../src/test/resources/EdgeSuite.xml</suiteXmlFile>
                    </suiteXmlFiles>
                    <environmentVariables>
                       <SELENIUM_REMOTE_CAPABILITIES>{"webSocketUrl":true,"platformName":"linux","browserName":"MicrosoftEdge","browserVersion":"latest","gridlastic:options":{"video":true}}</SELENIUM_REMOTE_CAPABILITIES>
                    </environmentVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

The Gridlastic grid hub, credentials and video url used in these code examples are presented after launching your Gridlastic selenium grid.

Get a free account and launch your Gridlastic selenium grid, then run this code locally and test your grid!