Skip to main content
/tayyab/portfolio — zsh
tayyab
TA
// dispatch.read --classified=false --access-level: public

Selenium to Playwright Migration Checklist: Step-by-Step (2026)

February 25, 2026 EST. READ: 18 MIN #Test Automation

Selenium to Playwright Migration Checklist: Step-by-Step (2026)

Migrating from Selenium to Playwright is not a rewrite—it's a phased transition.

I migrated 150 Selenium tests to Playwright on a fintech project. We did it in 6 weeks without stopping feature development or breaking existing tests.

The key: A clear migration strategy, parallelization, and running both frameworks simultaneously until full transition is complete.

This guide provides the exact checklist I used, with timelines and risk mitigation.

Table of Contents

  1. Pre-Migration Assessment
  2. Phase 1: Setup & Parallel Infrastructure
  3. Phase 2: Convert Critical Tests
  4. Phase 3: Run Both Frameworks
  5. Phase 4: Deprecate Selenium
  6. Migration Timeline
  7. Risk Mitigation
  8. Real Project Results
  9. FAQ

Pre-Migration Assessment

Step 1: Audit Existing Selenium Tests

Checklist:

☐ Count total test files: ______
☐ Count total test cases: ______
☐ List all Page Object classes: ______
☐ List all custom utilities used: ______
☐ List all external dependencies: ______
☐ Identify test grouping (suite structure): ______
☐ Check CI/CD integration points: ______
☐ Document all test environment configs: ______

Run this command:

# Count Selenium test files
find tests -name "*.java" -o -name "*.py" | wc -l

# Count test cases
grep -r "@Test\|def test_" tests | wc -l

# List all imports to find dependencies
grep -r "import org.selenium" tests | sort -u

Step 2: Categorize Tests by Risk

Critical (40% of tests):
  ├─ Payment flows
  ├─ Authentication
  ├─ Core business logic
  └─ Must migrate first

High Priority (35% of tests):
  ├─ Customer-facing features
  ├─ Public APIs
  └─ Migrate after critical

Low Priority (25% of tests):
  ├─ Admin screens
  ├─ Helper functions
  ├─ Can stay in Selenium longer
  └─ Migrate last (or never)

Step 3: Estimate Effort

Formula: (Test Count × Complexity) ÷ Team Size = Weeks

Example:
150 tests × 0.5 complexity ÷ 2 engineers = 37.5 hours = 1 week
(Assuming 7.5 hour/day productivity on migration)

Actual timeline often: 1.5x estimate (unforeseen issues)
So: 1 week → 1.5 weeks

Rule of thumb:

  • Simple CRUD tests: 15 minutes each
  • Complex multi-step flows: 30-45 minutes each
  • Tests with custom locators: 45-60 minutes each

Phase 1: Setup & Parallel Infrastructure

Step 1a: Create Playwright Project Alongside Selenium

Don't delete Selenium yet. Run both.

# Current structure
project/
├── tests-selenium/        # Keep existing
│   ├── pages/
│   ├── tests/
│   └── pom.xml
└── tests-playwright/      # Create new
    ├── pages/
    ├── tests/
    ├── playwright.config.ts
    └── package.json

Step 1b: Create Playwright Base Project

# Initialize Playwright project
mkdir tests-playwright && cd tests-playwright
npm init -y
npm install --save-dev @playwright/test @playwright/cli

# Create config
cat > playwright.config.ts << 'EOF'
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html'],
    ['allure-playwright'],
  ],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
});
EOF

Step 1c: Create Page Object Base

// pages/BasePage.ts
export class BasePage {
  constructor(public page: Page) {}

  async click(selector: string) {
    await this.page.click(selector);
  }

  async fill(selector: string, text: string) {
    await this.page.fill(selector, text);
  }

  async getText(selector: string) {
    return await this.page.textContent(selector);
  }
}

Step 1d: Setup CI/CD for Parallel Testing

# .github/workflows/test.yml
name: Tests (Selenium + Playwright)

on: [push, pull_request]

jobs:
  selenium:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Selenium Tests
        run: cd tests-selenium && mvn test

  playwright:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Playwright Tests
        run: cd tests-playwright && npm test

  # Combined status check
  integration:
    needs: [selenium, playwright]
    runs-on: ubuntu-latest
    steps:
      - name: All tests passed
        run: echo "Both frameworks passing"

Phase 2: Convert Critical Tests

Step 2a: Pick First Test to Convert

Choose a simple, critical test (15-30 minutes):

// Original Selenium (Java)
@Test
public void userCanLogin() {
  LoginPage loginPage = new LoginPage(driver);
  loginPage.navigate();
  loginPage.login("user@example.com", "password123");
  
  assertTrue(driver.findElement(By.id("dashboard")).isDisplayed());
}

Step 2b: Convert to Playwright

// Playwright (TypeScript)
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

test('User can login', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.navigate();
  await loginPage.login('user@example.com', 'password123');
  
  await expect(page.locator('#dashboard')).toBeVisible();
});

Step 2c: Create Conversion Template

Use this to speed up remaining conversions:

# Conversion Checklist (Per Test)

☐ Create new test file: `tests/feature-name.spec.ts`
☐ Import BasePage and required page objects
☐ Convert test logic (see examples below)
☐ Convert all assertions (Selenium → Playwright)
☐ Run test locally: `npx playwright test tests/feature-name.spec.ts`
☐ Verify test passes
☐ Add to CI/CD
☐ Run both frameworks: ensure both pass

# Conversion Examples

Selenium → Playwright:

driver.get(url)                → page.goto(url)
driver.findElement(By.id())    → page.locator('#id')
element.click()               → locator.click()
element.sendKeys(text)        → locator.fill(text)
element.getText()             → locator.textContent()
assertTrue(element.isDisplayed()) → await expect(locator).toBeVisible()

Step 2d: Batch Convert Critical 40%

Estimated effort: 40% of tests × 25 min average = 1,500 min = 25 hours = 3 days

# Day 1: Convert 15 tests
# Day 2: Convert 15 tests + run regressions
# Day 3: Fix flaky tests + document learnings

Phase 3: Run Both Frameworks in Parallel

Step 3a: Configure Dual Execution

# Run both test suites before deployment
#!/bin/bash

# Start application
npm run dev &
APP_PID=$!
sleep 3

# Run Selenium tests
echo "Running Selenium tests..."
cd tests-selenium && mvn test
SELENIUM_RESULT=$?

# Run Playwright tests
echo "Running Playwright tests..."
cd ../tests-playwright && npm test
PLAYWRIGHT_RESULT=$?

# Kill app
kill $APP_PID

# Report
if [ $SELENIUM_RESULT -eq 0 ] && [ $PLAYWRIGHT_RESULT -eq 0 ]; then
  echo "✅ All tests passed"
  exit 0
else
  echo "❌ Tests failed"
  exit 1
fi

Step 3b: Handle Test Divergence

Common issue: Same test passes in Selenium, fails in Playwright (or vice versa)

Diagnosis:

# Selenium pass, Playwright fail?
→ Usually: Timing issue (need explicit wait in Playwright)

# Playwright pass, Selenium fail?
→ Usually: Selectors changed or timing too strict in Selenium

# Both fail?
→ Real bug in application (good! this is why we run both)

Fix pattern:

// If Playwright test times out:
await page.waitForLoadState('networkidle');  // Explicit wait
await expect(locator).toBeVisible({ timeout: 5000 });

// If Selenium is flaky:
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.elementToBeClickable(element));

Step 3c: Run Full Suite in Parallel

# Parallel execution (4 workers)
npm test -- --workers=4

# Measure time reduction
# Sequential: 45 minutes → Parallel: 12 minutes (3.75x faster)

Phase 4: Deprecate Selenium

Step 4a: Migrate Remaining 60% of Tests

Timeline:

  • Week 1: 40% migrated (critical path)
  • Week 2: 60-80% migrated (high priority)
  • Week 3: 90%+ migrated (low priority + stragglers)
  • Week 4: 100% migrated + Selenium tests deleted

Step 4b: Delete Selenium Tests

# Once all Playwright tests verified:
rm -rf tests-selenium

# Consolidate directory structure
mv tests-playwright tests

# Final structure:
tests/
├── pages/
│   ├── BasePage.ts
│   ├── LoginPage.ts
│   └── DashboardPage.ts
├── fixtures/
│   └── test-data.json
├── specs/
│   ├── auth.spec.ts
│   ├── dashboard.spec.ts
│   └── payments.spec.ts
└── playwright.config.ts

Step 4c: Update CI/CD

# Remove Selenium job, keep Playwright only
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm install && npx playwright install
      - run: npm test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Migration Timeline

6-Week Migration Plan (Real Project)

Week 1: Foundation

  • Day 1-2: Audit Selenium suite, create assessment
  • Day 3-4: Set up Playwright project, parallel CI/CD
  • Day 5: Convert first 3 critical tests, validate both frameworks
  • Output: Parallel infrastructure ready, 3 tests converted

Week 2-3: Critical Path (40% of tests)

  • Convert 25-30 critical payment/auth tests
  • Fix flaky tests, document learnings
  • Run both frameworks in all PRs
  • Output: Critical path fully migrated, high confidence

Week 4-5: High Priority (35% of tests)

  • Convert customer-facing feature tests
  • Build out page objects library
  • Document Playwright patterns
  • Output: 75% migrated, Selenium tests catching fewer bugs

Week 6: Cleanup & Finalization

  • Convert remaining 25% tests
  • Delete Selenium framework
  • Remove from CI/CD
  • Document migration for team
  • Output: 100% Playwright, Selenium removed

Risk Mitigation

Risk 1: "Tests Pass in Playwright but Fail in Production"

Cause: Playwright is faster, misses real-world timing issues

Mitigation:

// Add explicit waits
await page.waitForLoadState('networkidle');
await expect(locator).toBeVisible({ timeout: 5000 });

// Test with network throttling
await page.route('**/*', route => {
  setTimeout(() => route.continue(), 1000);
});

Risk 2: "Test Conversion Takes Longer Than Expected"

Mitigation:

  • Batch similar tests together (same patterns)
  • Create conversion templates
  • Pair programming (faster knowledge transfer)
  • Allocate buffer time (1.5x estimate)

Risk 3: "Flaky Tests Reappear in Playwright"

Mitigation:

// Use explicit waits, never hardcoded sleeps
// ❌ Bad
await page.waitForTimeout(2000);

// ✅ Good
await expect(locator).toBeVisible();
await page.waitForLoadState('networkidle');

Risk 4: "Team Doesn't Know Playwright"

Mitigation:

  • Hire/train 1 Playwright expert
  • Pair programming sessions
  • Documentation with examples
  • Gradual migration (not big bang)

Real Project Results

Before Migration (Selenium Only)

Tests: 150 (all Selenium Java)
Test Execution: 45 minutes sequential
Flaky Tests: 12% (18 tests)
Coverage: 70%
Maintenance Time: 20 hours/week
CI/CD Status: Frequent failures due to timeouts

After Migration (Playwright Only)

Tests: 150 (all Playwright TypeScript)
Test Execution: 12 minutes (4 workers)
Flaky Tests: 2% (3 tests)
Coverage: 85%
Maintenance Time: 8 hours/week
CI/CD Status: 99% pass rate, reliable

Impact Metrics

Execution Speed: 45 min → 12 min (3.75x faster)
Maintenance: 20 hrs/week → 8 hrs/week (60% reduction)
Flakiness: 18 tests → 3 tests (83% reduction)
Test Coverage: 70% → 85% (added 15% + fixed existing)
Team Happiness: "Finally, tests we trust"

FAQ

Q: Should I migrate all tests at once or gradually?
A: Gradually. Run both frameworks in parallel until confident. Slow rollout = lower risk.

Q: What if a test can't be migrated (unsupported feature)?
A: Keep the Selenium test, document why, plan to refactor the feature.

Q: How do I handle tests that depend on each other?
A: Refactor them. Tests should be independent. Use fixtures/setup instead.

Q: Will team productivity drop during migration?
A: Yes, temporarily (10-15% slower for 2-3 weeks). But pay back is fast (60% improvement long-term).

Q: How much faster is Playwright really?
A: 3-5x faster execution + 50% reduction in flakiness + 70% faster test writing.

Tayyab Akmal
// author

Tayyab Akmal

AI & QA Automation Engineer

I've caught critical bugs in fintech, e-commerce, and SaaS platforms — then built the automation that prevents them from shipping again. 6+ years scaling test automation and AI-driven QA.

// feedback_channel

FOUND THIS USEFUL?

Share your thoughts or let's discuss automation testing strategies.

→ Start Conversation
Available for hire