Introduction: The Post-Protractor Era
With Protractor officially deprecated in 2021, Angular teams have been migrating to modern testing frameworks. This guide compares the top alternatives—Playwright (as discussed in Percy, Applitools & BackstopJS: Visual Regression Testing Solutions Compared), Cypress, and WebdriverIO—providing migration strategies and decision frameworks for choosing the right tool in 2025.
Top Protractor Alternatives Comparison
Feature Matrix
Feature | Playwright | Cypress | WebdriverIO | Protractor (Legacy) |
---|---|---|---|---|
Angular Support | Good (generic) | Good (generic) | Excellent (native) | Excellent (native) |
Auto-wait | Built-in | Built-in | Configurable | Built-in |
Cross-browser | Excellent | Good | Excellent | Good |
Parallel Execution | Free | Paid (Cloud) | Free | Limited |
TypeScript | Excellent | Good | Excellent | Good |
Learning Curve | Medium | Low | Medium | Low |
Active Development | Very Active | Very Active | Very Active | Deprecated |
Angular-specific APIs | No | No | Yes (via plugins) | Yes |
Migration Effort | Medium | Medium-High | Low-Medium | N/A |
Playwright for Angular
Setup
// playwright.config.ts
import { defineConfig } from '@playwright/test' (as discussed in [Cypress Deep Dive: Architecture, Debugging, and Network Stubbing Mastery](/blog/cypress-deep-dive));
export default defineConfig({
testDir: './e2e',
use: {
baseURL: 'http://localhost:4200',
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},
webServer: {
command: 'npm run start',
port: 4200,
reuseExistingServer: !process.env.CI
}
});
Angular Component Testing
// e2e/login.spec.ts
import { test, expect } from '@playwright/test' (as discussed in [Selenium WebDriver in 2025: Still Relevant?](/blog/selenium-webdriver-2025-still-relevant));
test.describe('Login Component', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.waitForLoadState('networkidle');
});
test('should login with valid credentials', async ({ page }) => {
await page.fill('[formControlName="email"]', 'user@example.com');
await page.fill('[formControlName="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.welcome-message')).toBeVisible();
});
test('should show validation errors', async ({ page }) => {
await page.click('button[type="submit"]');
await expect(page.locator('mat-error')).toHaveCount(2);
await expect(page.locator('mat-error').first()).toContainText('Email is required');
});
});
Cypress for Angular
Setup with Component Testing
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
supportFile: 'cypress/support/e2e.ts'
},
component: {
devServer: {
framework: 'angular',
bundler: 'webpack'
},
specPattern: '**/*.cy.ts'
}
});
E2E Testing
// cypress/e2e/products.cy.ts
describe('Product Catalog', () => {
beforeEach(() => {
cy.visit('/products');
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts');
});
it('should filter products by category', () => {
cy.wait('@getProducts');
cy.get('[data-cy=category-filter]').select('Electronics');
cy.get('[data-cy=product-card]').should('have.length', 5);
cy.get('[data-cy=product-card]').first()
.should('contain', 'Laptop');
});
it('should add product to cart', () => {
cy.get('[data-cy=add-to-cart]').first().click();
cy.get('[data-cy=cart-count]').should('contain', '1');
cy.get('[data-cy=cart-total]').should('contain', '$999.99');
});
});
WebdriverIO for Angular
Setup with Angular-specific Support
// wdio.conf.js
exports.config = {
specs: ['./test/specs/**/*.ts'],
framework: 'mocha',
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--disable-gpu']
}
}],
baseUrl: 'http://localhost:4200',
// Angular-specific configuration
sync: false,
waitforTimeout: 10000,
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
beforeSession: function() {
// Configure Angular synchronization
browser.setTimeout({ 'script': 60000 });
},
before: function() {
// Wait for Angular to be ready
browser.executeAsync((done) => {
if (window.getAllAngularTestabilities) {
window.getAllAngularTestabilities().forEach(testability => {
testability.whenStable(done);
});
} else {
done();
}
});
}
};
Angular Testing
// test/specs/checkout.spec.ts
describe('Checkout Flow', () => {
it('should complete purchase', async () => {
await browser.url('/checkout');
// Wait for Angular to be stable
await browser.waitUntil(async () => {
return await browser.executeAsync((done) => {
const testability = window.getAllAngularTestabilities()[0];
testability.whenStable(() => done(true));
});
});
// Fill form
await $('[formControlName="firstName"]').setValue('John');
await $('[formControlName="lastName"]').setValue('Doe');
await $('[formControlName="email"]').setValue('john@example.com');
// Submit
await $('button[type="submit"]').click();
// Verify
await expect($('.confirmation-message')).toBeDisplayed();
await expect($('.order-number')).toHaveTextContaining('ORDER-');
});
});
Migration Strategies
From Protractor to Playwright
// Before (Protractor)
import { browser, element, by } from 'protractor';
describe('Login', () => {
it('should login', async () => {
await browser.get('/login');
await element(by.model('username')).sendKeys('user@example.com');
await element(by.model('password')).sendKeys('pass123');
await element(by.buttonText('Login')).click();
expect(await browser.getCurrentUrl()).toContain('/dashboard');
});
});
// After (Playwright)
import { test, expect } from '@playwright/test';
test.describe('Login', () => {
test('should login', async ({ page }) => {
await page.goto('/login');
await page.fill('[ng-model="username"]', 'user@example.com');
await page.fill('[ng-model="password"]', 'pass123');
await page.click('button:has-text("Login")');
await expect(page).toHaveURL(/dashboard/);
});
});
From Protractor to Cypress
// Before (Protractor)
element(by.css('[data-test="add-to-cart"]')).click();
expect(element(by.css('.cart-count')).getText()).toEqual('1');
// After (Cypress)
cy.get('[data-test="add-to-cart"]').click();
cy.get('.cart-count').should('have.text', '1');
Decision Framework
Choose Playwright When:
- Cross-browser testing is critical
- Need modern auto-wait capabilities
- Want built-in parallel execution
- Prefer TypeScript-first approach
- Testing multiple frameworks (React, Vue, Angular)
Choose Cypress When:
- Team prioritizes developer experience
- Time-travel debugging is valuable
- Component testing needed
- Real-time reload during development
- Willing to pay for advanced features (parallelization, cloud)
Choose WebdriverIO When:
- Already familiar with WebDriver
- Need Angular-specific synchronization
- Want flexibility with test runners (Mocha, Jasmine, Cucumber)
- Existing Selenium Grid infrastructure
- Gradual migration from Protractor preferred
Practical Migration Plan
Phase 1: Setup (Week 1)
# Install chosen framework
npm install --save-dev @playwright/test
# Keep Protractor temporarily
# npm uninstall protractor (not yet)
# Create new test directory
mkdir e2e-playwright
Phase 2: Parallel Running (Weeks 2-4)
// package.json
{
"scripts": {
"test:e2e:old": "protractor conf.js",
"test:e2e:new": "playwright test",
"test:e2e:all": "npm run test:e2e:old && npm run test:e2e:new"
}
}
Phase 3: Gradual Migration (Weeks 5-12)
// Migration priority order:
// 1. Smoke tests (critical paths)
// 2. Regression tests (stable features)
// 3. Edge cases
// 4. Visual/accessibility tests
Phase 4: Decommission (Week 13)
# Remove Protractor
npm uninstall protractor
# Update CI/CD
# Replace protractor commands with new framework
# Archive old tests
git mv e2e e2e-archived
Conclusion
The deprecation of Protractor has pushed Angular teams toward modern, actively maintained testing frameworks. Playwright offers the best cross-browser support and modern features, Cypress provides exceptional developer experience with component testing, and WebdriverIO offers the smoothest migration path with Angular-specific capabilities.
Recommendation for 2025:
- New projects: Playwright (best overall features)
- Quick migration: WebdriverIO (minimal changes)
- DX priority: Cypress (best developer experience)
All three alternatives surpass Protractor in active development, community support, and modern features. The key is choosing based on your team’s specific needs and migration constraints.