TL;DR
- WebdriverIO envuelve Selenium WebDriver con sintaxis moderna async/await de Node.js
- Configuración vía
wdio.conf.js— soporta Mocha, Jasmine, Cucumber out of the box- Selectores:
$('selector')para uno,$$('selector')para múltiples elementos- Esperas integradas, reintentos y poderosa librería de assertions
- Soporte first-class de TypeScript y excelente integración con VS Code
Ideal para: Equipos Node.js que quieren testing basado en Selenium con JavaScript moderno Omite si: Prefieres la velocidad de Playwright o la experiencia de debugging de Cypress Tiempo de lectura: 15 minutos
Tus tests de Selenium en Java funcionan pero se sienten anticuados. El equipo conoce JavaScript. Quieres async/await moderno, no callback hell. Necesitas soporte TypeScript sin pelear con el framework.
WebdriverIO trae Selenium al ecosistema Node.js correctamente. Automatización real de navegador, sintaxis moderna, arquitectura de plugins que realmente funciona.
Este tutorial cubre WebdriverIO desde instalación hasta CI/CD — todo para construir tests de navegador mantenibles en JavaScript.
¿Qué es WebdriverIO?
WebdriverIO (WDIO) es un framework de automatización de tests para Node.js. Implementa el protocolo WebDriver, igual que Selenium, pero con diseño nativo de JavaScript.
Por qué WebdriverIO:
- JavaScript moderno — async/await en todos lados, sin pirámides de callbacks
- Flexibilidad de protocolo — WebDriver para cross-browser, DevTools para Chrome
- Ecosistema rico — reporters, services, plugins para todo
- Multiplataforma — navegadores, móvil (Appium), desktop (Electron)
- TypeScript first — definiciones de tipos completas, autocomplete funciona
Instalación y Setup
Inicio Rápido
# Crear proyecto
mkdir wdio-tests && cd wdio-tests
npm init -y
# Instalar WebdriverIO CLI
npm install @wdio/cli --save-dev
# Ejecutar wizard de configuración
npx wdio config
Archivo de Configuración
// wdio.conf.js
export const config = {
runner: 'local',
specs: ['./test/specs/**/*.js'],
exclude: [],
maxInstances: 5,
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--disable-gpu']
}
}],
logLevel: 'info',
bail: 0,
baseUrl: 'https://example.com',
waitforTimeout: 10000,
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000
}
}
Escribiendo Tu Primer Test
// test/specs/login.spec.js
describe('Página de Login', () => {
beforeEach(async () => {
await browser.url('/login')
})
it('debería loguearse con credenciales válidas', async () => {
await $('#username').setValue('testuser')
await $('#password').setValue('password123')
await $('button[type="submit"]').click()
await expect($('.welcome-message')).toBeDisplayed()
await expect($('.welcome-message')).toHaveTextContaining('Welcome')
})
it('debería mostrar error para credenciales inválidas', async () => {
await $('#username').setValue('wrong')
await $('#password').setValue('wrong')
await $('button[type="submit"]').click()
await expect($('.error-message')).toBeDisplayed()
})
})
Selectores y Elementos
// Selectores CSS (más comunes)
const button = await $('button.submit')
const input = await $('#email')
const items = await $$('.list-item') // Retorna array
// XPath
const cell = await $('//table//tr[2]/td[3]')
// Texto de link
const link = await $('=Click Here') // Coincidencia exacta
const partialLink = await $('*=Click') // Parcial
// Trabajando con elementos
await $('#input').setValue('text')
await $('button').click()
const text = await $('p').getText()
const isDisplayed = await $('element').isDisplayed()
Esperas y Sincronización
// Esperar que elemento exista
await $('button').waitForExist({ timeout: 5000 })
// Esperar que sea visible
await $('modal').waitForDisplayed({ timeout: 10000 })
// Esperar que sea clickeable
await $('button').waitForClickable({ timeout: 5000 })
// Condición personalizada
await browser.waitUntil(
async () => (await $('counter').getText()) === '10',
{
timeout: 10000,
timeoutMsg: 'El contador nunca llegó a 10'
}
)
Page Object Model
// test/pageobjects/login.page.js
class LoginPage {
get inputUsername() { return $('#username') }
get inputPassword() { return $('#password') }
get btnSubmit() { return $('button[type="submit"]') }
async open() {
await browser.url('/login')
}
async login(username, password) {
await this.inputUsername.setValue(username)
await this.inputPassword.setValue(password)
await this.btnSubmit.click()
}
}
export default new LoginPage()
// Uso en tests
import LoginPage from '../pageobjects/login.page.js'
describe('Login', () => {
it('debería loguearse exitosamente', async () => {
await LoginPage.open()
await LoginPage.login('user@example.com', 'password123')
await expect($('.dashboard')).toBeDisplayed()
})
})
Assertions con expect-webdriverio
// Assertions de elementos
await expect($('button')).toBeDisplayed()
await expect($('button')).toBeClickable()
await expect($('input')).toHaveValue('expected')
await expect($('p')).toHaveText('texto exacto')
await expect($('p')).toHaveTextContaining('parcial')
await expect($$('li')).toBeElementsArrayOfSize(5)
// Browser assertions
await expect(browser).toHaveUrl('https://example.com/')
await expect(browser).toHaveTitle('Page Title')
// Negación
await expect($('modal')).not.toBeDisplayed()
Ejecución Paralela
// wdio.conf.js
export const config = {
maxInstances: 5, // Máx navegadores paralelos
capabilities: [{
maxInstances: 3,
browserName: 'chrome'
}, {
maxInstances: 2,
browserName: 'firefox'
}]
}
Integración con CI/CD
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx wdio run wdio.conf.js
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results
path: allure-results
WebdriverIO con Asistencia de IA
Las herramientas de IA pueden ayudar a escribir y mantener tests de WebdriverIO.
Lo que la IA hace bien:
- Generar page objects desde estructura HTML
- Convertir casos de prueba manuales a código de automatización
- Sugerir selectores para elementos complejos
- Crear variaciones de tests data-driven
Lo que aún necesita humanos:
- Elegir qué testear vs omitir
- Depurar tests inestables
- Entender lógica de negocio
- Decisiones de optimización de rendimiento
FAQ
¿Qué es WebdriverIO?
WebdriverIO es un framework progresivo de automatización para Node.js. Implementa los protocolos WebDriver y DevTools para controlar navegadores y dispositivos móviles. A diferencia de los bindings crudos de Selenium, WebdriverIO provee sintaxis moderna async/await, assertions integrados, esperas automáticas y un rico ecosistema de plugins.
¿WebdriverIO es mejor que Selenium?
WebdriverIO está construido sobre el protocolo Selenium WebDriver, así que es más una mejora que un reemplazo. Agrega patrones modernos de JavaScript (async/await), mejores mensajes de error, esperas automáticas y extenso soporte de plugins. Para desarrolladores Node.js, WebdriverIO es generalmente más fácil que usar el paquete selenium-webdriver directamente.
¿Puede WebdriverIO testear apps móviles?
Sí. WebdriverIO se integra perfectamente con Appium para testing móvil. Instala @wdio/appium-service y configura capabilities para iOS o Android. La misma sintaxis de selectores y comandos funciona para apps móviles.
¿WebdriverIO vs Playwright vs Cypress?
WebdriverIO usa protocolo WebDriver (comportamiento real del navegador, todos los navegadores soportados, móvil vía Appium). Playwright usa Chrome DevTools Protocol (más rápido, pero navegadores parcheados). Cypress corre dentro del navegador (excelente debugging, pero solo same-origin, sin móvil). Elige WebdriverIO para necesidades cross-browser/móvil.
Recursos Oficiales
Ver También
- Selenium Tutorial - Fundamentos de WebDriver
- Playwright Tutorial - Alternativa moderna
- Cypress Tutorial - Testing in-browser
- GitHub Actions para QA - Integración CI/CD
