La mayoría de los tests de Playwright no necesitan nada más que click() y fill(), pero los dropdowns que se activan con hover, el multi-select con Ctrl+click y las implementaciones de drag que rastrean eventos mousemove a lo largo del recorrido requieren las APIs de más bajo nivel de keyboard y mouse. page.keyboard.type() dispara eventos keydown, keypress, keyup e input por cada carácter, lo que importa para los autocompletados y la validación en tiempo real que fill() omite. Este artículo cubre pulsaciones de teclas, combinaciones con teclas modificadoras, hover, drag-and-drop con dragTo() y eventos de mouse directos, scroll, y el patrón de multi-select con Ctrl+click.

Eventos de teclado

Pulsaciones básicas de teclas

// Presionar una tecla individual
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.press('Escape');
await page.keyboard.press('ArrowDown');

// Combinaciones con teclas modificadoras
await page.keyboard.press('Control+a');  // Seleccionar todo
await page.keyboard.press('Control+c');  // Copiar
await page.keyboard.press('Control+v');  // Pegar
await page.keyboard.press('Shift+Tab'); // Tab en reversa
await page.keyboard.press('Meta+k');    // Cmd+K en Mac

Los nombres de teclas siguen la especificación KeyboardEvent.key. Los más comunes: 'Enter', 'Tab', 'Escape', 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'F1' a 'F12'.

Escribir texto

// Escribir en el elemento enfocado
await page.getByRole('searchbox').focus();
await page.keyboard.type('playwright testing');

// Escribir con retraso entre teclas (simula escritura real)
await page.keyboard.type('escritura lenta', { delay: 50 });

keyboard.type() dispara eventos keydown, keypress, keyup e input por cada carácter. Esto importa para aplicaciones que manejan pulsaciones individuales (autocompletado, validación en tiempo real, editores de texto enriquecido).

Mantener teclas modificadoras presionadas

// Shift + clic para seleccionar un rango
await page.keyboard.down('Shift');
await page.getByRole('row').nth(5).click();
await page.keyboard.up('Shift');

Testear navegación por teclado

Testear que tu aplicación es accesible por teclado es tanto un requisito de accesibilidad como una preocupación de QA:

test('el modal se cierra con Escape', async ({ page }) => {
  await page.getByRole('button', { name: 'Abrir modal' }).click();
  await expect(page.getByRole('dialog')).toBeVisible();

  await page.keyboard.press('Escape');

  await expect(page.getByRole('dialog')).not.toBeVisible();
});

test('el dropdown navega con las teclas de flecha', async ({ page }) => {
  await page.getByRole('combobox', { name: 'País' }).focus();
  await page.keyboard.press('ArrowDown'); // Abre el dropdown, selecciona el primero
  await page.keyboard.press('ArrowDown'); // Selecciona el segundo
  await page.keyboard.press('Enter');     // Confirma la selección

  await expect(page.getByRole('combobox', { name: 'País' })).toHaveValue('Argentina');
});

Eventos de mouse

Variantes de clic

// Doble clic
await page.getByRole('row').first().dblclick();

// Clic derecho (menú contextual)
await page.getByText('Documento.pdf').click({ button: 'right' });
await expect(page.getByRole('menuitem', { name: 'Descargar' })).toBeVisible();

// Clic en coordenadas específicas relativas al elemento
await page.getByRole('slider').click({ position: { x: 10, y: 0 } });

// Clic mientras se mantiene una tecla modificadora
await page.getByRole('checkbox', { name: 'Ítem 3' }).click({ modifiers: ['Shift'] });

Hover

// Hover para revelar un tooltip o dropdown
await page.getByRole('button', { name: 'Ayuda' }).hover();
await expect(page.getByRole('tooltip')).toBeVisible();
await expect(page.getByRole('tooltip')).toHaveText('Clic para ver la documentación');

// Hover para revelar un botón de acción oculto en una fila de tabla
await page.getByRole('row', { name: 'Ana García' }).hover();
await page.getByRole('button', { name: 'Editar' }).click();

Movimiento del mouse

// Mover el mouse a coordenadas absolutas de la página
await page.mouse.move(100, 200);

// Arrastrar de una posición a otra
await page.mouse.move(100, 200);
await page.mouse.down();
await page.mouse.move(300, 200, { steps: 10 }); // steps lo hace más suave
await page.mouse.up();

El parámetro steps de mouse.move() divide el movimiento en puntos intermedios, lo que importa para implementaciones de drag-and-drop que rastrean eventos mousemove a lo largo del recorrido.

Drag and drop

Para drag-and-drop estándar de HTML5:

// Usando dragTo: el enfoque más simple
await page.getByText('Tarjeta A').dragTo(page.getByText('Columna B'));

// Con posición específica de drop dentro del objetivo
await page.getByText('Archivo.pdf').dragTo(page.locator('.upload-zone'), {
  targetPosition: { x: 50, y: 50 },
});

Para implementaciones de drag personalizadas que usan eventos mousedown/mousemove/mouseup:

const source = page.getByTestId('draggable-card');
const target = page.getByTestId('drop-zone');

const sourceBounds = await source.boundingBox();
const targetBounds = await target.boundingBox();

await page.mouse.move(sourceBounds!.x + sourceBounds!.width / 2, sourceBounds!.y + sourceBounds!.height / 2);
await page.mouse.down();
await page.mouse.move(targetBounds!.x + targetBounds!.width / 2, targetBounds!.y + targetBounds!.height / 2, { steps: 20 });
await page.mouse.up();

Scroll

// Hacer scroll en la página
await page.mouse.wheel(0, 500); // Scroll hacia abajo 500px

// Scroll dentro de un elemento específico
await page.getByRole('log').hover();
await page.mouse.wheel(0, 300);

// Scroll hasta un elemento (usalo antes de interactuar con elementos fuera de pantalla)
await page.getByTestId('submit-section').scrollIntoViewIfNeeded();

Combinar teclado y mouse

Los patrones de interacción más realistas combinan ambos:

test('multi-seleccionar ítems con Ctrl+click', async ({ page }) => {
  await page.goto('/files');

  // Seleccionar el primer ítem
  await page.getByRole('row').nth(0).click();

  // Ctrl+click para agregar a la selección
  await page.getByRole('row').nth(2).click({ modifiers: ['Control'] });
  await page.getByRole('row').nth(4).click({ modifiers: ['Control'] });

  // Eliminar los ítems seleccionados
  await page.keyboard.press('Delete');

  await expect(page.getByRole('row')).toHaveCount(7); // Empezó con 10, eliminó 3
});

Cuándo usar estas APIs

La mayoría de tus tests deberían usar métodos de locator de alto nivel (click(), fill(), selectOption()). Recurre a las APIs de teclado y mouse cuando:

  • Testeas atajos de teclado o accesibilidad
  • Testeas elementos de UI que se activan con hover
  • Testeas interacciones de drag-and-drop
  • Testeas menús contextuales
  • Testeas editores de texto enriquecido
  • Simulas interacciones complejas de múltiples pasos

Los eventos directos de teclado y mouse son más lentos y más frágiles que las acciones de alto nivel. Úsalos solo cuando la API de alto nivel no puede expresar lo que necesitas.

→ See also: Locators de Playwright: getByRole, getByLabel, getByText, getByTestId Comparados | Testing de Accesibilidad con Playwright: Verificaciones a11y Automatizadas | Carga y Descarga de Archivos en Tests de Playwright