DEV Community

Shefali R
Shefali R

Posted on

Step-by-Step: Migrating a Legacy Selenium Java Suite to Modular Playwright TypeScript

1. Introduction & Motivation

Migrating a legacy Selenium suite isn't just about changing syntax—it's about fixing structural test flakiness and execution lag.

Legacy Selenium Java Approach

Selenium requires explicit driver management, verbose setup, and manual wait configurations:

public class LoginTest {
    WebDriver driver;

    @BeforeMethod
    public void setUp() {
        System.setProperty("webdriver.chrome.driver", "path/to/chromedriver");
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @Test
    public void testLogin() {
        driver.get("https://app.example.com/login");
        WebElement emailInput = driver.findElement(By.id("username"));
        emailInput.sendKeys("user@example.com");

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
        WebElement submitBtn = wait.until(ExpectedConditions.elementToBeClickable(By.id("submit-btn")));
        submitBtn.click();
    }
}
Enter fullscreen mode Exit fullscreen mode

Modern Playwright TypeScript Approach

Playwright provides built-in browser contexts, automatic waiting, and clean user-visible locators out of the box:

import { test, expect } from '@playwright/test';

test('login test', async ({ page }) => {
  await page.goto('https://app.example.com/login');
  await page.getByLabel('Username').fill('user@example.com');
  await page.getByRole('button', { name: 'Submit' }).click();
  await expect(page.getByText('Dashboard')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

2. Core Migration Steps

1.Directory Coexistence: Run npm init playwright@latest inside your root repository alongside Maven/Gradle to allow incremental test migration.

2.Page Object Model Translation: Convert Java classes into modular TypeScript exports:

   import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly emailInput: Locator;
  readonly submitButton: Locator;

  constructor(private readonly page: Page) {
    this.emailInput = this.page.getByLabel('Username');
    this.submitButton = this.page.getByRole('button', { name: 'Submit' });
  }

  async login(email: string) {
    await this.emailInput.fill(email);
    await this.submitButton.click();
  }
}
Enter fullscreen mode Exit fullscreen mode

3.Eliminating Explicit Waits: Replace WebDriverWait logic with Playwright’s auto-waiting locators, which automatically check for visibility, actionability, and stability before executing interactions.

3. Edge Cases & Gotchas

  • Cross-Domain iFrames: Selenium requires explicit context switching via driver.switchTo().frame(). Playwright handles frame navigation natively using frame locators:
   const frame = page.frameLocator('#my-iframe');
   await frame.getByRole('button', { name: 'Confirm' }).click();
Enter fullscreen mode Exit fullscreen mode
  • Cookie and Storage State Persistence: Bypass repetitive UI login steps by generating a single storageState.json during global setup and reusing it across spec files.

Top comments (0)