Migrating from Selenium to Playwright
Step-by-step guide to migrating your existing Selenium tests 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'); // PlaceholderInteractions
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/test2. 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 back3. 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
- Set up Playwright in your project
- Migrate 1-2 simple test cases as proof of concept
- Create migration patterns for your team
- Gradually migrate critical test suites
- Train team members on Playwright best practices
Related Articles
Comments (0)
Loading comments...