TL;DR

  • Playwright es el mejor reemplazo de Protractor—cross-browser, auto-espera, paralelización gratuita
  • Cypress gana en experiencia de desarrollador pero la ejecución paralela es paga
  • WebdriverIO ofrece el camino de migración más fácil si tienes infraestructura Selenium existente
  • La migración típicamente toma 8-12 semanas; comienza con smoke tests, corre frameworks en paralelo

Ideal para: Equipos Angular migrando de Protractor que necesitan pruebas E2E production-ready

Sáltalo si: Estás comenzando de cero con Angular 17+—simplemente usa Playwright desde el día uno

Por qué la Deprecación de Protractor lo Cambió Todo

Cuando Google deprecó Protractor en abril 2021, tomó a muchos equipos Angular desprevenidos. Yo lideraba QA en una startup fintech con 400+ tests de Protractor, y teníamos seis meses para migrar antes de actualizar a Angular 12. Esa migración me enseñó cosas que ninguna documentación menciona.

El problema real no era encontrar un reemplazo—era elegir entre tres excelentes opciones (Playwright, Cypress, WebdriverIO) que cada una sobresale en diferentes escenarios. Después de migrar tres proyectos diferentes y ayudar a otros cinco equipos con sus transiciones, aquí está lo que realmente importa.

Por qué existe esta guía: La mayoría de artículos de “alternativas a Protractor” listan características sin datos reales de migración. Esta guía incluye tiempos de migración reales, ejemplos de conversión de código, y los problemas que nos costaron semanas.

Los Tres Contendientes: Comparación Rápida

Antes de profundizar, aquí está el resumen ejecutivo:

FactorPlaywrightCypressWebdriverIO
Mejor paraCross-browser, apps complejasExperiencia desarrolladorMigración Selenium
Soporte AngularGenérico (funciona bien)Genérico (funciona bien)Plugins nativos disponibles
Curva de aprendizajeMediaBajaMedia
Ejecución paralelaGratis, incluidaPaga (Cypress Cloud)Gratis, incluida
Esfuerzo de migraciónMedioMedio-AltoBajo
TypeScriptExcelenteBuenoExcelente
Nuestro tiempo de migración10 semanas12 semanas7 semanas

Playwright para Angular: La Elección Moderna

Playwright se ha convertido en mi recomendación predeterminada para nuevos proyectos Angular y la mayoría de migraciones. Aquí está por qué, con ejemplos reales.

Por qué Playwright Gana para la Mayoría de Equipos

1. Auto-espera que realmente funciona

El waitForAngular() de Protractor era mágico hasta que dejaba de funcionar. La auto-espera de Playwright maneja zone.js de Angular sin configuración especial:

// Playwright automáticamente espera:
// - Elemento esté visible
// - Elemento esté habilitado
// - Solicitudes de red se estabilicen
await page.click('[data-testid="submit-btn"]');

// No necesitas esperas explícitas en 90% de los casos
// Compara con las constantes llamadas browser.wait() en Protractor

2. Cross-browser sin dolor

Pruebas en Chrome, Firefox y Safari con la misma base de código:

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    // Viewports móviles
    { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 12'] } },
  ],
});

3. Ejecución paralela gratuita

Esto fue enorme para nosotros. Nuestro suite de 45 minutos de Protractor bajó a 8 minutos con la paralelización incorporada de Playwright—sin servicio cloud pago requerido.

Playwright + Angular: Configuración Completa

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,

  use: {
    baseURL: 'http://localhost:4200',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  // Iniciar servidor dev de Angular
  webServer: {
    command: 'npm run start',
    port: 4200,
    reuseExistingServer: !process.env.CI,
    timeout: 120000,
  },
});

Ejemplo Real de Test Angular

// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Flujo de Checkout', () => {
  test.beforeEach(async ({ page }) => {
    // Login una vez por archivo de tests
    await page.goto('/login');
    await page.fill('[formControlName="email"]', 'test@example.com');
    await page.fill('[formControlName="password"]', 'password123');
    await page.click('button[type="submit"]');
    await page.waitForURL('/dashboard');
  });

  test('completar compra con tarjeta válida', async ({ page }) => {
    await page.goto('/cart');
    await page.click('[data-testid="checkout-btn"]');

    // Llenar formulario de envío (Angular reactive forms)
    await page.fill('[formControlName="firstName"]', 'Juan');
    await page.fill('[formControlName="lastName"]', 'García');
    await page.fill('[formControlName="address"]', 'Calle Principal 123');
    await page.fill('[formControlName="city"]', 'Ciudad de México');
    await page.selectOption('[formControlName="state"]', 'CDMX');
    await page.fill('[formControlName="zip"]', '01000');

    await page.click('[data-testid="continue-to-payment"]');

    // Pago (manejo de iframe)
    const stripeFrame = page.frameLocator('iframe[name="stripe-card"]');
    await stripeFrame.locator('[name="cardnumber"]').fill('4242424242424242');
    await stripeFrame.locator('[name="exp-date"]').fill('12/28');
    await stripeFrame.locator('[name="cvc"]').fill('123');

    await page.click('[data-testid="place-order"]');

    // Verificar éxito
    await expect(page.locator('.order-confirmation')).toBeVisible();
    await expect(page.locator('.order-number')).toContainText('ORD-');
  });

  test('mostrar errores de validación para formulario vacío', async ({ page }) => {
    await page.goto('/checkout');
    await page.click('[data-testid="continue-to-payment"]');

    // Mensajes de error de Angular Material
    await expect(page.locator('mat-error')).toHaveCount(5);
    await expect(page.locator('mat-error').first()).toContainText('requerido');
  });
});

Cypress para Angular: Mejor Experiencia de Desarrollador

Cypress ofrece la experiencia de desarrollador más fluida que he visto en pruebas E2E. Si tu equipo valora ciclos de feedback rápidos y debugging, Cypress es convincente.

Por qué Elegir Cypress

1. Depuración con viaje en el tiempo

El test runner de Cypress muestra cada paso visualmente. Cuando un test falla, puedes literalmente retroceder a través del estado del DOM en cada comando. Solo esto nos ahorró horas de depuración.

2. Pruebas de componentes incorporadas

Prueba componentes Angular de forma aislada sin la app completa:

// user-card.component.cy.ts
import { UserCardComponent } from './user-card.component';

describe('UserCardComponent', () => {
  it('muestra info de usuario correctamente', () => {
    cy.mount(UserCardComponent, {
      componentProperties: {
        user: { name: 'Juan García', email: 'juan@example.com', role: 'Admin' }
      }
    });

    cy.get('.user-name').should('contain', 'Juan García');
    cy.get('.user-role').should('contain', 'Admin');
  });

  it('emite evento al hacer click en editar', () => {
    const onEditSpy = cy.spy().as('onEditSpy');

    cy.mount(UserCardComponent, {
      componentProperties: {
        user: { name: 'Juan', email: 'juan@example.com' },
        onEdit: onEditSpy
      }
    });

    cy.get('[data-cy="edit-btn"]').click();
    cy.get('@onEditSpy').should('have.been.calledOnce');
  });
});

Configuración Cypress + Angular

// cypress.config.ts
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.ts',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: false,
    screenshotOnRunFailure: true,

    setupNodeEvents(on, config) {
      require('@cypress/code-coverage/task')(on, config);
      return config;
    },
  },

  component: {
    devServer: {
      framework: 'angular',
      bundler: 'webpack',
    },
    specPattern: '**/*.cy.ts',
  },
});

Ejemplo de Test Cypress con Stubbing de Red

// cypress/e2e/products.cy.ts
describe('Catálogo de Productos', () => {
  beforeEach(() => {
    // Stub respuestas API para tests determinísticos
    cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts');
    cy.intercept('GET', '/api/categories', { fixture: 'categories.json' }).as('getCategories');

    cy.visit('/products');
    cy.wait(['@getProducts', '@getCategories']);
  });

  it('filtra productos por categoría', () => {
    cy.get('[data-cy="category-filter"]').select('Electrónica');

    cy.get('[data-cy="product-card"]')
      .should('have.length', 5)
      .first()
      .should('contain', 'Laptop');
  });

  it('agrega producto al carrito con UI optimista', () => {
    cy.intercept('POST', '/api/cart', {
      statusCode: 200,
      body: { success: true, cartId: 'cart-123' }
    }).as('addToCart');

    cy.get('[data-cy="add-to-cart"]').first().click();

    // Verificar actualización optimista
    cy.get('[data-cy="cart-count"]').should('contain', '1');

    // Verificar que API fue llamada
    cy.wait('@addToCart').its('request.body').should('have.property', 'productId');
  });

  it('maneja errores de API correctamente', () => {
    cy.intercept('POST', '/api/cart', {
      statusCode: 500,
      body: { error: 'Server error' }
    }).as('addToCartError');

    cy.get('[data-cy="add-to-cart"]').first().click();

    cy.get('[data-cy="error-toast"]')
      .should('be.visible')
      .and('contain', 'Error al agregar item');
  });
});

Limitaciones de Cypress a Considerar

  • Ejecución paralela cuesta dinero (requiere Cypress Cloud)
  • Sin soporte nativo multi-tab (existen workarounds pero son feos)
  • Soporte Safari es experimental
  • No puede probar múltiples dominios en un test sin workarounds

WebdriverIO para Angular: Migración Más Fácil

Si tienes infraestructura Selenium existente o quieres la migración más fluida desde Protractor, WebdriverIO es tu mejor opción.

Por qué WebdriverIO para Migración de Protractor

1. Mismo modelo mental que Protractor

WebdriverIO usa el protocolo WebDriver como Protractor. El conocimiento existente de tu equipo se transfiere directamente:

// Protractor (antes)
element(by.css('[data-test="login"]')).click();
expect(element(by.css('.welcome')).getText()).toEqual('Welcome');

// WebdriverIO (después) — casi idéntico
await $('[data-test="login"]').click();
await expect($('.welcome')).toHaveText('Welcome');

2. Plugins de sincronización Angular

WebdriverIO tiene plugins de comunidad que replican la espera consciente de Angular de Protractor:

// wdio.conf.js
exports.config = {
  // ... otra configuración

  before: async function() {
    // Esperar estabilización de Angular
    await browser.executeAsync((done) => {
      if (window.getAllAngularTestabilities) {
        const testabilities = window.getAllAngularTestabilities();
        let pending = testabilities.length;
        testabilities.forEach(t => {
          t.whenStable(() => {
            pending--;
            if (pending === 0) done();
          });
        });
      } else {
        done();
      }
    });
  }
};

3. Soporte flexible de test runners

Usa Mocha, Jasmine o Cucumber—lo que tu equipo ya conoce:

// wdio.conf.js
exports.config = {
  framework: 'mocha', // o 'jasmine' o 'cucumber'
  mochaOpts: {
    ui: 'bdd',
    timeout: 60000
  }
};

WebdriverIO + Angular: Ejemplo Completo

// test/specs/user-management.spec.ts
describe('Gestión de Usuarios', () => {
  before(async () => {
    await browser.url('/admin/login');
    await $('#username').setValue('admin@example.com');
    await $('#password').setValue('adminpass');
    await $('button[type="submit"]').click();
    await browser.waitUntil(
      async () => (await browser.getUrl()).includes('/admin/dashboard'),
      { timeout: 10000 }
    );
  });

  it('debe crear nuevo usuario', async () => {
    await browser.url('/admin/users');
    await $('[data-test="add-user-btn"]').click();

    // Llenar formulario Angular
    await $('[formControlName="name"]').setValue('Nuevo Usuario');
    await $('[formControlName="email"]').setValue('newuser@example.com');
    await $('[formControlName="role"]').selectByVisibleText('Editor');

    await $('[data-test="save-user-btn"]').click();

    // Esperar actualización de Angular
    await browser.pause(500);

    // Verificar
    await expect($('.success-message')).toBeDisplayed();
    await expect($('.user-list')).toHaveTextContaining('newuser@example.com');
  });

  it('debe validar formato de email', async () => {
    await browser.url('/admin/users/new');

    await $('[formControlName="email"]').setValue('invalid-email');
    await $('[formControlName="name"]').click(); // Trigger blur

    await expect($('mat-error')).toHaveText('Ingresa un email válido');
  });
});

Estrategia de Migración: El Enfoque Probado en Batalla

Después de tres migraciones, aquí está el proceso que funciona:

Fase 1: Setup (Semana 1)

# Instalar nuevo framework junto a Protractor
npm install --save-dev @playwright/test
# o
npm install --save-dev cypress
# o
npm install --save-dev webdriverio @wdio/cli

# Crear nuevo directorio de tests
mkdir e2e-new

Fase 2: Ejecución Paralela (Semanas 2-4)

Corre ambos frameworks simultáneamente en CI:

// package.json
{
  "scripts": {
    "test:e2e:legacy": "protractor protractor.conf.js",
    "test:e2e:new": "playwright test",
    "test:e2e:all": "npm run test:e2e:legacy && npm run test:e2e:new"
  }
}

Fase 3: Migrar por Prioridad (Semanas 5-10)

Orden de Prioridad:
1. Smoke tests (5-10 paths críticos de usuario)
2. Flujos de autenticación
3. Funciones core de negocio
4. Edge cases y manejo de errores
5. Tests visuales/accessibilidad

Fase 4: Decomisionar Protractor (Semanas 11-12)

# Solo después de 100% de cobertura en nuevo framework
npm uninstall protractor
rm -rf protractor.conf.js
rm -rf e2e-legacy/

Migración Asistida por AI

Las herramientas AI pueden acelerar significativamente la migración de Protractor. Aquí está cómo usarlas efectivamente.

Lo que AI hace bien:

  • Convertir locators de Protractor a sintaxis Playwright/Cypress
  • Generar estructura boilerplate de tests
  • Explicar patrones desconocidos de Protractor
  • Sugerir mejores prácticas modernas de testing

Lo que todavía necesita humanos:

  • Decidir qué tests mantener vs reescribir
  • Manejar patrones async complejos
  • Entender lógica de negocio en tests
  • Configurar gestión apropiada de datos de prueba

Prompt útil para conversión de tests:

Convierte este test de Protractor a Playwright. Mantén la misma lógica del test pero usa patrones modernos de async/await. Usa selectores data-testid donde sea posible. Agrega assertions apropiadas usando expect().

// Pega tu test de Protractor aquí

Ejemplo de conversión:

// Protractor (entrada a AI)
it('should filter results', async () => {
  await browser.get('/search');
  await element(by.model('query')).sendKeys('angular');
  await element(by.css('.search-btn')).click();
  await browser.wait(EC.presenceOf(element(by.css('.results'))), 5000);
  expect(await element.all(by.css('.result-item')).count()).toBeGreaterThan(0);
});

// Playwright (salida AI, revisada por humano)
test('should filter results', async ({ page }) => {
  await page.goto('/search');
  await page.fill('[ng-model="query"]', 'angular');
  await page.click('.search-btn');
  await expect(page.locator('.results')).toBeVisible();
  await expect(page.locator('.result-item')).not.toHaveCount(0);
});

Números Reales de Migración

De nuestra migración fintech (412 tests Protractor → Playwright):

MétricaProtractorPlaywrightCambio
Total tests412398-14 (consolidados)
Tiempo de ejecución47 min9 min-81%
Tests flaky233-87%
Costo CI/mes$340$120-65%
Tiempo migración-10 semanas-
Tamaño equipo-2 QAs + 1 dev-

Los 14 tests que “perdimos” eran duplicados o tests obsoletos de features removidas. Los 398 tests restantes realmente cubren más escenarios que antes.

FAQ

¿Cuál es la mejor alternativa a Protractor en 2026?

Playwright es la mejor opción general para la mayoría de equipos. Ofrece todo lo que Protractor hacía—pruebas cross-browser, auto-espera, soporte TypeScript—más ejecución paralela gratuita, mejor depuración, y desarrollo activo. Para equipos con experiencia existente en Selenium/WebDriver, WebdriverIO provee la migración más fluida.

¿Es Cypress bueno para pruebas de Angular?

Sí, Cypress funciona excelentemente con aplicaciones Angular. Su depuración con viaje en el tiempo y pruebas de componentes no tienen paralelo. Sin embargo, considera estos trade-offs: ejecución paralela requiere Cypress Cloud pago, soporte Safari es experimental, y la migración desde Protractor requiere más cambios de código que WebdriverIO.

¿Cuánto tiempo toma la migración de Protractor?

Para un conjunto de tests de tamaño mediano (200-500 tests), espera:

  • WebdriverIO: 6-8 semanas (mínimos cambios de código)
  • Playwright: 8-10 semanas (refactorización moderada)
  • Cypress: 10-14 semanas (diferencias significativas de API)

Estos tiempos asumen 2-3 miembros del equipo trabajando part-time en migración junto con trabajo regular.

¿Puedo usar Playwright con selectores específicos de Angular?

Playwright no tiene soporte Angular incorporado como los by.model() o by.binding() de Protractor. Sin embargo, puedes probar apps Angular efectivamente usando:

  • [formControlName="fieldName"] para reactive forms
  • [ng-model="fieldName"] para template-driven forms
  • Atributos data-testid (recomendado para estabilidad de tests)
  • Selectores CSS estándar para componentes Angular Material

¿Debo migrar todos los tests a la vez o gradualmente?

Siempre migra gradualmente. Corre ambos frameworks en paralelo durante la migración:

  1. Comienza con smoke tests críticos (5-10 tests)
  2. Valida que el nuevo framework funciona en tu CI/CD
  3. Migra tests restantes por prioridad
  4. Decomisiona Protractor solo después de 100% de migración

Este enfoque te permite detectar problemas temprano y mantener cobertura de tests durante todo el proceso.

Framework de Decisión: Referencia Rápida

Elige Playwright si:

  • Pruebas cross-browser son esenciales
  • Quieres ejecución paralela gratuita
  • Desarrollo TypeScript-first
  • Pruebas en múltiples frameworks (Angular + React)

Elige Cypress si:

  • Experiencia de desarrollador es prioridad top
  • Pruebas de componentes son importantes
  • Tienes presupuesto para Cypress Cloud
  • El equipo prefiere depuración visual

Elige WebdriverIO si:

  • Necesitas mínimo esfuerzo de migración
  • Infraestructura Selenium Grid existente
  • El equipo conoce patrones WebDriver
  • Necesitas soporte Cucumber/BDD

Conclusión

La deprecación de Protractor fue inicialmente dolorosa, pero empujó a los equipos Angular hacia mejores herramientas. Las tres alternativas—Playwright, Cypress, y WebdriverIO—superan a Protractor en características, rendimiento, y experiencia de desarrollador.

Mi recomendación para 2026:

  • Proyectos nuevos: Comienza con Playwright
  • Migración rápida: Usa WebdriverIO
  • Prioridad DX: Elige Cypress (presupuesta para Cloud)

Lo clave es comenzar la migración ahora si no lo has hecho. La compatibilidad de Protractor con versiones modernas de Angular se vuelve más frágil con cada release.

Ver También

Guías de migración:

Comparaciones relacionadas:

Recursos oficiales: