Back to Articles
AutomationIntermediate

Migrating from Selenium to Playwright

Step-by-step guide to migrating your existing Selenium tests to Playwright

7 min read
...
seleniumplaywrightmigrationui-testing
Banner for Migrating from Selenium to Playwright

Introduction

Playwright offers modern web automation capabilities with better performance, reliability, and developer experience compared to Selenium. This guide provides a practical approach to migrating your existing Selenium tests to Playwright.

Why Migrate to Playwright?

Key Advantages of Playwright

1. Auto-Waiting

  • Automatically waits for elements to be actionable
  • Reduces flaky tests from timing issues
  • No need for explicit waits in most cases

2. Better Performance

  • Faster test execution
  • Parallel execution out of the box
  • Efficient browser context isolation

3. Modern Features

  • Network interception and mocking
  • Built-in screenshots and videos
  • Trace viewer for debugging
  • Multiple browser contexts

4. Better Debugging

  • Inspector with step-through debugging
  • Trace viewer with timeline
  • Better error messages

When to Migrate

Good reasons to migrate:

  • Starting new test automation projects
  • Experiencing flakiness issues with Selenium
  • Need better performance and reliability
  • Want modern features (network mocking, tracing)

Reasons to stay with Selenium:

  • Large existing Selenium test suite working well
  • Team expertise in Selenium
  • Using Selenium Grid infrastructure
  • Testing very old browsers

Side-by-Side Comparison

Setup and Configuration

Selenium (WebDriver):

// Java Selenium setup
WebDriver driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.get("https://example.com");

Playwright:

// JavaScript/TypeScript Playwright
const { chromium } = require('playwright');
 
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');

Element Selectors

Selenium:

// Various locator strategies
driver.findElement(By.id("username"));
driver.findElement(By.name("password"));
driver.findElement(By.className("btn-primary"));
driver.findElement(By.cssSelector(".login-form input[type='submit']"));
driver.findElement(By.xpath("//button[text()='Login']"));

Playwright:

// Unified selector syntax
await page.locator('#username');
await page.locator('[name="password"]');
await page.locator('.btn-primary');
await page.locator('.login-form input[type="submit"]');
await page.locator('button:has-text("Login")');
 
// Playwright-specific selectors
await page.locator('text=Login'); // Text selector
await page.getByRole('button', { name: 'Login' }); // Accessibility
await page.getByPlaceholder('Enter username'); // Placeholder

Interactions

Selenium:

// Click
driver.findElement(By.id("submit")).click();
 
// Type
driver.findElement(By.id("username")).sendKeys("testuser");
 
// Select dropdown
Select dropdown = new Select(driver.findElement(By.id("country")));
dropdown.selectByVisibleText("United States");
 
// Checkbox
WebElement checkbox = driver.findElement(By.id("terms"));
if (!checkbox.isSelected()) {
    checkbox.click();
}

Playwright:

// Click
await page.locator('#submit').click();
 
// Type
await page.locator('#username').fill('testuser');
 
// Select dropdown
await page.locator('#country').selectOption('United States');
 
// Checkbox
await page.locator('#terms').check();

Waits

Selenium:

// Explicit wait
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("submit"))
);
 
// Fluent wait
Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(30))
    .pollingEvery(Duration.ofSeconds(5))
    .ignoring(NoSuchElementException.class);

Playwright:

// Auto-waiting (built-in)
await page.locator('#submit').click(); // Automatically waits
 
// Custom wait conditions
await page.locator('#submit').waitFor({ state: 'visible' });
await page.waitForSelector('#submit', { state: 'attached' });
await page.waitForLoadState('networkidle');

Assertions

Selenium:

// Using TestNG or JUnit
assertEquals("Expected Title", driver.getTitle());
assertTrue(driver.findElement(By.id("message")).isDisplayed());
 
// With explicit waits
wait.until(ExpectedConditions.textToBePresentInElement(
    driver.findElement(By.id("status")), 
    "Success"
));

Playwright:

// Built-in assertions with auto-retry
await expect(page).toHaveTitle('Expected Title');
await expect(page.locator('#message')).toBeVisible();
await expect(page.locator('#status')).toHaveText('Success');
await expect(page.locator('#username')).toHaveValue('testuser');

Migration Strategy

Phase 1: Assessment

1. Inventory Current Tests

  • Count total test cases
  • Identify test categories (smoke, regression, etc.)
  • Document dependencies and patterns

2. Prioritize Migration

  • Start with new test cases
  • Migrate critical/smoke tests first
  • Gradually migrate regression tests
  • Keep both frameworks running in parallel initially

3. Team Preparation

  • Provide Playwright training
  • Share migration guide
  • Set up new test infrastructure

Phase 2: Setup

1. Install Playwright

npm init playwright@latest
 
# Or add to existing project
npm install -D @playwright/test

2. Configure Playwright

// playwright.config.ts
import { defineConfig } from '@playwright/test';
 
export default defineConfig({
  testDir: './tests',
  timeout: 30000,
  retries: 2,
  use: {
    baseURL: 'https://example.com',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
    { name: 'webkit', use: { browserName: 'webkit' } },
  ],
});

Phase 3: Pattern Migration

Converting Page Objects

Selenium Page Object:

public class LoginPage {
    private WebDriver driver;
    
    @FindBy(id = "username")
    private WebElement usernameInput;
    
    @FindBy(id = "password")
    private WebElement passwordInput;
    
    @FindBy(css = "button[type='submit']")
    private WebElement loginButton;
    
    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }
    
    public void login(String username, String password) {
        usernameInput.sendKeys(username);
        passwordInput.sendKeys(password);
        loginButton.click();
    }
}

Playwright Page Object:

// pages/LoginPage.js
export class LoginPage {
  constructor(page) {
    this.page = page;
    this.usernameInput = page.locator('#username');
    this.passwordInput = page.locator('#password');
    this.loginButton = page.locator('button[type="submit"]');
  }
  
  async login(username, password) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
  
  async goto() {
    await this.page.goto('/login');
  }
}

Using the Page Object:

Selenium Test:

@Test
public void testLogin() {
    LoginPage loginPage = new LoginPage(driver);
    driver.get("https://example.com/login");
    loginPage.login("testuser", "password123");
    
    assertEquals("Dashboard", driver.getTitle());
}

Playwright Test:

import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
 
test('user can login', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('testuser', 'password123');
  
  await expect(page).toHaveTitle('Dashboard');
});

Phase 4: Common Patterns

1. Handling Alerts

Selenium:

Alert alert = driver.switchTo().alert();
alert.accept();

Playwright:

page.on('dialog', dialog => dialog.accept());
await page.locator('#trigger-alert').click();

2. Switching Frames

Selenium:

driver.switchTo().frame("frameName");
WebElement element = driver.findElement(By.id("element"));
driver.switchTo().defaultContent();

Playwright:

const frame = page.frame({ name: 'frameName' });
await frame.locator('#element').click();
// No need to switch back

3. Multiple Windows/Tabs

Selenium:

String originalWindow = driver.getWindowHandle();
driver.switchTo().newWindow(WindowType.TAB);
driver.get("https://example.com");
driver.close();
driver.switchTo().window(originalWindow);

Playwright:

const [newPage] = await Promise.all([
  context.waitForEvent('page'),
  page.locator('#open-new-tab').click()
]);
await newPage.goto('https://example.com');
await newPage.close();

4. File Upload

Selenium:

WebElement upload = driver.findElement(By.id("file-upload"));
upload.sendKeys("/path/to/file.txt");

Playwright:

await page.locator('#file-upload').setInputFiles('/path/to/file.txt');

Handling Complex Migrations

Custom Waits

Selenium Custom Condition:

wait.until(new ExpectedCondition<Boolean>() {
    public Boolean apply(WebDriver driver) {
        return driver.findElement(By.id("status"))
            .getAttribute("class").contains("complete");
    }
});

Playwright Equivalent:

await page.locator('#status').waitFor({
  state: 'attached',
  timeout: 10000
});
 
await expect(page.locator('#status')).toHaveClass(/complete/);

JavaScript Execution

Selenium:

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click();", element);

Playwright:

await page.locator('#element').evaluate(el => el.click());
// Or better: use Playwright's built-in click with force
await page.locator('#element').click({ force: true });

Best Practices for Migration

1. Run Tests in Parallel

  • Keep Selenium tests running
  • Add Playwright tests gradually
  • Compare results for validation
  • Decommission Selenium when confident

2. Use Playwright's Strengths

  • Leverage auto-waiting
  • Use built-in retry mechanisms
  • Take advantage of trace viewer
  • Implement network mocking

3. Update Test Structure

  • Use modern async/await patterns
  • Implement proper error handling
  • Add meaningful test descriptions
  • Use Playwright's fixture system

4. Continuous Integration

# GitHub Actions example
name: Playwright Tests
 
on: [push, pull_request]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      - name: Run Playwright tests
        run: npx playwright test
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Common Challenges and Solutions

Challenge 1: Team Learning Curve

Solution: Provide training, pair programming, and comprehensive documentation

Challenge 2: Different Language (Java → JavaScript/TypeScript)

Solution: Use TypeScript for type safety, create helper utilities, gradual transition

Challenge 3: Existing Infrastructure

Solution: Run Playwright alongside Selenium initially, share test data and helpers

Challenge 4: Test Data Dependencies

Solution: Abstract test data layer, make it framework-agnostic

Conclusion

Migrating from Selenium to Playwright offers significant benefits in test reliability, performance, and developer experience. Start small, migrate incrementally, and leverage Playwright's modern features for better test automation.

Next Steps

  1. Set up Playwright in your project
  2. Migrate 1-2 simple test cases as proof of concept
  3. Create migration patterns for your team
  4. Gradually migrate critical test suites
  5. Train team members on Playwright best practices

Comments (0)

Loading comments...