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
- Pre-Migration Assessment
- Phase 1: Setup & Parallel Infrastructure
- Phase 2: Convert Critical Tests
- Phase 3: Run Both Frameworks
- Phase 4: Deprecate Selenium
- Migration Timeline
- Risk Mitigation
- Real Project Results
- 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
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.