Ejecución de JavaScript

Avanzado
Selenium
Selenium
Actualizado: 05/09/2025

¡Desbloquea el curso de Selenium completo!

IA
Ejercicios
Certificado
Entrar

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 Plus

JavascriptExecutor 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íncrona
  • executeAsyncScript(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.

    Progreso guardado
    Asistente IA
    Ejercicios
    Iniciar sesión gratis

    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

    ⭐⭐⭐⭐⭐
    4.9/5 valoración