Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusJavascriptExecutor interface
La interfaz JavascriptExecutor representa una de las capacidades más versátiles de Selenium WebDriver para interactuar con el navegador a un nivel que trasciende las limitaciones de los métodos nativos del framework. Esta interfaz permite ejecutar código JavaScript directamente en el contexto de la página web cargada, proporcionando acceso completo al DOM y a las funcionalidades nativas del navegador.
Fundamentos de la interfaz
JavascriptExecutor no es implementada directamente por las clases de driver, sino que se accede a través de un casting explícito. Todos los drivers principales de Selenium (ChromeDriver, FirefoxDriver, EdgeDriver) implementan esta interfaz de forma implícita, lo que permite realizar la conversión de tipos de manera segura.
La sintaxis fundamental para acceder a esta funcionalidad requiere un casting del objeto WebDriver:
JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
Este patrón de casting se debe a que JavascriptExecutor es una interfaz separada que extiende las capacidades base del WebDriver, manteniendo la separación de responsabilidades en el diseño de la API.
Métodos principales
La interfaz JavascriptExecutor define dos métodos esenciales que cubren diferentes paradigmas de ejecución:
executeScript(String script, Object... args)
: Ejecuta JavaScript de forma síncronaexecuteAsyncScript(String script, Object... args)
: Ejecuta JavaScript de forma asíncrona con callback
El primer parámetro siempre es una cadena de texto que contiene el código JavaScript a ejecutar, mientras que los parámetros adicionales se pasan como argumentos al script y están disponibles a través del array arguments
en JavaScript.
@Test
void ejemploBasicoJavaScriptExecutor() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Ejecutar script simple sin parámetros
String titulo = (String) js.executeScript("return document.title;");
// Ejecutar script con parámetros
String resultado = (String) js.executeScript(
"return arguments[0] + ' - ' + arguments[1];",
"Selenium", "WebDriver"
);
}
Gestión de tipos de retorno
Los scripts JavaScript pueden devolver diferentes tipos de datos que Selenium convierte automáticamente a tipos Java compatibles. Esta conversión sigue reglas específicas:
- Valores primitivos JavaScript (string, number, boolean) se mapean a String, Long, Boolean
- Arrays JavaScript se convierten en List
- Objetos JavaScript se transforman en Map<String, Object>
- Elementos DOM se convierten automáticamente en WebElement
@Test
void tiposDeRetornoJavaScript() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Retorno de string
String texto = (String) js.executeScript("return 'Hola Selenium';");
// Retorno de número
Long numero = (Long) js.executeScript("return 42;");
// Retorno de elemento DOM
WebElement elemento = (WebElement) js.executeScript(
"return document.querySelector('h1');"
);
// Retorno de array
List<Object> lista = (List<Object>) js.executeScript(
"return ['item1', 'item2', 'item3'];"
);
}
Paso de argumentos
El mecanismo de paso de argumentos permite que los valores Java se transfieran al contexto JavaScript de manera transparente. Los argumentos se pasan como parámetros adicionales al método y están disponibles en JavaScript a través del array arguments
.
@Test
void pasoDeArgumentos() {
JavascriptExecutor js = (JavascriptExecutor) driver;
WebElement inputElement = driver.findElement(By.id("username"));
// Pasar WebElement y String como argumentos
js.executeScript(
"arguments[0].value = arguments[1];",
inputElement,
"usuario@ejemplo.com"
);
// Pasar múltiples tipos de argumentos
js.executeScript("""
const elemento = arguments[0];
const texto = arguments[1];
const activo = arguments[2];
elemento.textContent = texto;
elemento.style.display = activo ? 'block' : 'none';
""",
inputElement,
"Texto actualizado",
true
);
}
Consideraciones sobre el contexto de ejecución
El código JavaScript ejecutado a través de JavascriptExecutor se ejecuta en el contexto de la página actual, lo que significa que tiene acceso completo al DOM, variables globales y funciones definidas en la página. Sin embargo, existen limitaciones importantes:
- El script se ejecuta en el contexto del frame actual
- Las variables definidas no persisten entre llamadas a executeScript
- El acceso a recursos externos está sujeto a las políticas de seguridad del navegador
- Los scripts deben completarse dentro del timeout configurado
@Test
void contextoPaginaActual() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Acceder a variables globales de la página
Object valorGlobal = js.executeScript("return window.miVariableGlobal;");
// Llamar funciones definidas en la página
js.executeScript("if (typeof validarFormulario === 'function') validarFormulario();");
// Modificar el estado del DOM
js.executeScript("""
document.body.style.backgroundColor = '#f0f0f0';
document.querySelectorAll('.error').forEach(el => el.remove());
""");
}
La interfaz JavascriptExecutor establece los cimientos para técnicas avanzadas de automatización que van más allá de las capacidades estándar de Selenium, proporcionando un puente directo entre el código Java de pruebas y el entorno JavaScript del navegador.
Ejecutar scripts síncronos y asíncronos
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La ejecución de JavaScript en Selenium WebDriver se divide en dos paradigmas fundamentales que determinan cómo y cuándo se completa la ejecución del código. La comprensión de estas diferencias es crucial para implementar automatizaciones efectivas que manejen correctamente el timing y la respuesta de las operaciones.
Scripts síncronos con executeScript()
El método executeScript()
ejecuta código JavaScript de forma síncrona, lo que significa que el hilo de ejecución de Java se bloquea hasta que el script JavaScript termine completamente. Este comportamiento es ideal para operaciones que se completan inmediatamente o que no requieren esperar eventos asíncronos.
@Test
void scriptsSincronos() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Operación inmediata: obtener información del DOM
String userAgent = (String) js.executeScript("return navigator.userAgent;");
// Modificación directa del DOM
js.executeScript("""
const elemento = document.querySelector('#miElemento');
elemento.style.backgroundColor = '#ffcc00';
elemento.textContent = 'Modificado por JavaScript';
""");
// Cálculo y retorno inmediato
Long altura = (Long) js.executeScript("""
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight
);
""");
}
Los scripts síncronos son perfectos para tareas como manipulación del DOM, cálculos matemáticos, acceso a propiedades del navegador y cualquier operación que no dependa de eventos externos o temporizadores.
Casos de uso típicos para scripts síncronos
Las operaciones síncronas cubren la mayoría de necesidades en automatización web, especialmente cuando se requiere interacción inmediata con elementos o extracción de datos:
@Test
void casosUsoSincronos() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Scroll hasta un elemento específico
WebElement elemento = driver.findElement(By.id("target"));
js.executeScript("arguments[0].scrollIntoView(true);", elemento);
// Simular click en elemento oculto o no interactuable
js.executeScript("arguments[0].click();", elemento);
// Obtener propiedades CSS computadas
String color = (String) js.executeScript("""
return window.getComputedStyle(arguments[0]).backgroundColor;
""", elemento);
// Modificar atributos que Selenium no puede cambiar directamente
js.executeScript("""
arguments[0].setAttribute('data-testid', arguments[1]);
arguments[0].removeAttribute('disabled');
""", elemento, "elemento-modificado");
}
Scripts asíncronos con executeAsyncScript()
El método executeAsyncScript()
maneja situaciones donde el código JavaScript necesita tiempo para completarse o depende de callbacks y eventos asíncronos. A diferencia de los scripts síncronos, estos requieren que el script llame explícitamente a una función callback para indicar su finalización.
@Test
void scriptsAsincronos() {
JavascriptExecutor js = (JavascriptExecutor) driver;
// Configurar timeout para scripts asíncronos
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(10));
// Script que espera un evento o temporizador
String resultado = (String) js.executeAsyncScript("""
const callback = arguments[arguments.length - 1];
setTimeout(() => {
const datos = 'Operación completada tras espera';
callback(datos);
}, 2000);
""");
System.out.println("Resultado asíncrono: " + resultado);
}
En los scripts asíncronos, el último elemento del array arguments
siempre es la función callback que debe invocarse para señalar la finalización del script. Esta función acepta un parámetro que se convierte en el valor de retorno del método executeAsyncScript()
.
Manejo de operaciones con AJAX y Fetch
Los scripts asíncronos resultan especialmente útiles para esperar respuestas de peticiones HTTP realizadas desde JavaScript, como llamadas AJAX o Fetch API:
@Test
void esperarPeticionAjax() {
JavascriptExecutor js = (JavascriptExecutor) driver;
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(15));
// Esperar que una petición AJAX termine
Boolean peticionCompleta = (Boolean) js.executeAsyncScript("""
const callback = arguments[arguments.length - 1];
fetch('/api/datos')
.then(response => response.json())
.then(data => {
// Procesar los datos recibidos
document.querySelector('#resultado').textContent = data.mensaje;
callback(true);
})
.catch(error => {
console.error('Error en petición:', error);
callback(false);
});
""");
assertTrue(peticionCompleta, "La petición AJAX debería completarse exitosamente");
}
Esperar eventos del DOM
Los scripts asíncronos también permiten esperar eventos específicos del DOM que pueden ocurrir en momentos impredecibles:
@Test
void esperarEventoDOM() {
JavascriptExecutor js = (JavascriptExecutor) driver;
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(8));
// Esperar que se dispare un evento personalizado
String datosEvento = (String) js.executeAsyncScript("""
const callback = arguments[arguments.length - 1];
document.addEventListener('miEventoPersonalizado', (event) => {
callback(event.detail.mensaje);
}, { once: true });
// Simular que algo dispara el evento después de un tiempo
setTimeout(() => {
const evento = new CustomEvent('miEventoPersonalizado', {
detail: { mensaje: 'Evento disparado correctamente' }
});
document.dispatchEvent(evento);
}, 1500);
""");
assertEquals("Evento disparado correctamente", datosEvento);
}
Configuración de timeouts
La gestión de timeouts es crucial para los scripts asíncronos, ya que previene que las pruebas se bloqueen indefinidamente esperando callbacks que nunca se ejecutan:
@Test
void configuracionTimeouts() {
// Configurar timeout específico para scripts asíncronos
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5));
JavascriptExecutor js = (JavascriptExecutor) driver;
assertThrows(TimeoutException.class, () -> {
// Este script nunca llama al callback, causará timeout
js.executeAsyncScript("""
const callback = arguments[arguments.length - 1];
// Intencionalmente no llamamos callback() para demostrar timeout
console.log('Script iniciado pero nunca termina');
""");
});
}
Mejores prácticas y consideraciones
Al trabajar con scripts asíncronos, es importante seguir patrones que garanticen la robustez y mantenibilidad del código:
- Siempre configurar timeouts apropiados antes de ejecutar scripts asíncronos
- Manejar casos de error dentro del script JavaScript y comunicarlos através del callback
- Evitar lógica compleja en scripts asíncronos; preferir múltiples scripts simples
- Documentar claramente qué eventos o condiciones debe esperar cada script
@Test
void patronRobustoAsincrono() {
JavascriptExecutor js = (JavascriptExecutor) driver;
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(10));
Map<String, Object> resultado = (Map<String, Object>) js.executeAsyncScript("""
const callback = arguments[arguments.length - 1];
try {
// Lógica asíncrona con manejo de errores
const operacion = new Promise((resolve, reject) => {
setTimeout(() => {
const exito = Math.random() > 0.3; // Simular éxito/fallo
if (exito) {
resolve({ estado: 'exitoso', datos: 'Información procesada' });
} else {
reject(new Error('Operación falló'));
}
}, 1000);
});
operacion
.then(datos => callback({ exito: true, resultado: datos }))
.catch(error => callback({ exito: false, error: error.message }));
} catch (error) {
callback({ exito: false, error: error.message });
}
""");
assertTrue((Boolean) resultado.get("exito"),
"La operación asíncrona debería completarse exitosamente");
}
La elección entre scripts síncronos y asíncronos depende fundamentalmente de la naturaleza de la operación: utiliza executeScript() para acciones inmediatas y executeAsyncScript() cuando necesites esperar eventos, temporizadores o respuestas de red.
Aprendizajes de esta lección de Selenium
- Comprender la interfaz JavascriptExecutor y su integración con WebDriver mediante casting.
- Aprender a ejecutar scripts JavaScript síncronos y asíncronos usando executeScript() y executeAsyncScript().
- Gestionar correctamente los tipos de retorno de los scripts JavaScript en Java.
- Pasar argumentos desde Java a JavaScript y manipular el DOM mediante scripts.
- Configurar y manejar timeouts en la ejecución de scripts asíncronos para evitar bloqueos.
Completa este curso de Selenium y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs