05.JavascriptExecutor

Java + Selenium 自动化测试 – JavascriptExecutor详解

目录

  1. 什么是JavascriptExecutor
  2. JavascriptExecutor的作用和优势
  3. 基本使用方法
  4. 常用JavaScript操作
  5. 实际应用场景
  6. 高级用法和技巧
  7. 最佳实践
  8. 常见问题和解决方案

什么是JavascriptExecutor

JavascriptExecutor 是Selenium WebDriver提供的一个接口,它允许我们在浏览器中执行JavaScript代码。通过这个接口,我们可以直接与浏览器的JavaScript引擎交互,执行各种JavaScript操作。

基本概念

// JavascriptExecutor是一个接口
public interface JavascriptExecutor {
    Object executeScript(String script, Object... args);
    Object executeAsyncScript(String script, Object... args);
}

为什么需要JavascriptExecutor?

  1. 突破WebDriver限制 – 有些操作WebDriver无法直接完成
  2. 提高执行效率 – JavaScript执行速度更快
  3. 处理复杂交互 – 处理一些特殊的页面元素和行为
  4. 获取页面信息 – 获取WebDriver无法直接获取的信息
  5. 模拟用户行为 – 更精确地模拟用户操作

JavascriptExecutor的作用和优势

主要作用

功能类别 具体作用 使用场景
元素操作 点击、输入、滚动 处理被遮挡的元素
页面控制 滚动、刷新、导航 页面滚动和跳转
信息获取 获取属性、文本、状态 获取隐藏信息
样式修改 修改CSS样式 高亮显示元素
事件触发 触发各种事件 模拟复杂交互

优势对比

// WebDriver方式 vs JavascriptExecutor方式

// 1. 点击被遮挡的元素
// WebDriver方式(可能失败)
WebElement button = driver.findElement(By.id("hiddenButton"));
button.click(); // 可能抛出ElementClickInterceptedException

// JavascriptExecutor方式(更可靠)
JavascriptExecutor js = (JavascriptExecutor) driver;
WebElement button = driver.findElement(By.id("hiddenButton"));
js.executeScript("arguments[0].click();", button);

// 2. 滚动到元素
// WebDriver方式(复杂)
Actions actions = new Actions(driver);
actions.moveToElement(element).perform();

// JavascriptExecutor方式(简单)
js.executeScript("arguments[0].scrollIntoView(true);", element);

基本使用方法

1. 创建JavascriptExecutor实例

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class JavascriptExecutorDemo {
    public static void main(String[] args) {
        // 创建WebDriver实例
        WebDriver driver = new ChromeDriver();

        // 将WebDriver转换为JavascriptExecutor
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 现在可以使用js执行JavaScript代码
        driver.get("https://example.com");

        // 执行简单的JavaScript
        js.executeScript("alert('Hello from Selenium!');");

        driver.quit();
    }
}

2. executeScript() 方法详解

语法:

Object executeScript(String script, Object... args)

参数说明:

  • script: 要执行的JavaScript代码字符串
  • args: 传递给JavaScript的参数(可选)

返回值:

  • JavaScript执行的结果,类型为Object

基本示例:

JavascriptExecutor js = (JavascriptExecutor) driver;

// 1. 执行简单的JavaScript代码
js.executeScript("console.log('Hello World');");

// 2. 执行带返回值的JavaScript
String title = (String) js.executeScript("return document.title;");
System.out.println("页面标题: " + title);

// 3. 传递参数给JavaScript
WebElement element = driver.findElement(By.id("myElement"));
js.executeScript("arguments[0].style.border = '3px solid red';", element);

// 4. 传递多个参数
String text = "Hello";
Long number = 123L;
js.executeScript("console.log(arguments[0] + ' ' + arguments[1]);", text, number);

3. executeAsyncScript() 方法详解

特点:

  • 用于执行异步JavaScript代码
  • 需要手动调用callback函数来返回结果
  • 适用于需要等待的操作(如AJAX请求)

基本示例:

// 设置异步脚本超时时间
driver.manage().timeouts().setScriptTimeout(Duration.ofSeconds(10));

JavascriptExecutor js = (JavascriptExecutor) driver;

// 执行异步JavaScript
Object result = js.executeAsyncScript(
    "var callback = arguments[arguments.length - 1];" +
    "setTimeout(function() {" +
    "    callback('异步操作完成');" +
    "}, 2000);"
);

System.out.println("异步结果: " + result);

常用JavaScript操作

1. 页面滚动操作

JavascriptExecutor js = (JavascriptExecutor) driver;

// 1. 滚动到页面顶部
js.executeScript("window.scrollTo(0, 0);");

// 2. 滚动到页面底部
js.executeScript("window.scrollTo(0, document.body.scrollHeight);");

// 3. 滚动指定像素
js.executeScript("window.scrollBy(0, 500);"); // 向下滚动500像素

// 4. 滚动到指定元素
WebElement element = driver.findElement(By.id("targetElement"));
js.executeScript("arguments[0].scrollIntoView(true);", element);

// 5. 平滑滚动到元素
js.executeScript("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", element);

// 6. 获取滚动位置
Long scrollTop = (Long) js.executeScript("return window.pageYOffset;");
System.out.println("当前滚动位置: " + scrollTop);

2. 元素操作

JavascriptExecutor js = (JavascriptExecutor) driver;
WebElement element = driver.findElement(By.id("myElement"));

// 1. 点击元素(绕过遮挡问题)
js.executeScript("arguments[0].click();", element);

// 2. 设置元素值
js.executeScript("arguments[0].value = arguments[1];", element, "新的值");

// 3. 获取元素文本
String text = (String) js.executeScript("return arguments[0].innerText;", element);

// 4. 获取元素HTML内容
String html = (String) js.executeScript("return arguments[0].innerHTML;", element);

// 5. 检查元素是否可见
Boolean isVisible = (Boolean) js.executeScript(
    "return arguments[0].offsetWidth > 0 && arguments[0].offsetHeight > 0;", element);

// 6. 移除元素
js.executeScript("arguments[0].remove();", element);

// 7. 高亮显示元素
js.executeScript(
    "arguments[0].style.border = '3px solid red';" +
    "arguments[0].style.backgroundColor = 'yellow';", element);

3. 页面信息获取

JavascriptExecutor js = (JavascriptExecutor) driver;

// 1. 获取页面标题
String title = (String) js.executeScript("return document.title;");

// 2. 获取页面URL
String url = (String) js.executeScript("return window.location.href;");

// 3. 获取页面高度
Long pageHeight = (Long) js.executeScript("return document.body.scrollHeight;");

// 4. 获取窗口大小
Long windowWidth = (Long) js.executeScript("return window.innerWidth;");
Long windowHeight = (Long) js.executeScript("return window.innerHeight;");

// 5. 获取所有链接
List<WebElement> links = (List<WebElement>) js.executeScript(
    "return document.querySelectorAll('a');");

// 6. 检查页面加载状态
String readyState = (String) js.executeScript("return document.readyState;");
System.out.println("页面加载状态: " + readyState);

// 7. 获取用户代理
String userAgent = (String) js.executeScript("return navigator.userAgent;");

4. 表单操作

JavascriptExecutor js = (JavascriptExecutor) driver;

// 1. 提交表单
WebElement form = driver.findElement(By.id("myForm"));
js.executeScript("arguments[0].submit();", form);

// 2. 重置表单
js.executeScript("arguments[0].reset();", form);

// 3. 设置下拉框选中项
WebElement select = driver.findElement(By.id("mySelect"));
js.executeScript("arguments[0].selectedIndex = 2;", select);

// 4. 设置复选框状态
WebElement checkbox = driver.findElement(By.id("myCheckbox"));
js.executeScript("arguments[0].checked = true;", checkbox);

// 5. 设置单选按钮
WebElement radio = driver.findElement(By.id("myRadio"));
js.executeScript("arguments[0].checked = true;", radio);

// 6. 触发change事件
js.executeScript("arguments[0].dispatchEvent(new Event('change'));", select);

实际应用场景

1. 处理文件上传

public class FileUploadExample {

    public void uploadFileWithJS(WebDriver driver, String filePath) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 方法1: 直接设置文件路径(适用于input[type="file"])
        WebElement fileInput = driver.findElement(By.xpath("//input[@type='file']"));
        js.executeScript("arguments[0].style.display = 'block';", fileInput);
        fileInput.sendKeys(filePath);

        // 方法2: 创建文件输入元素
        js.executeScript(
            "var input = document.createElement('input');" +
            "input.type = 'file';" +
            "input.style.display = 'none';" +
            "document.body.appendChild(input);" +
            "return input;"
        );
    }
}

2. 处理弹窗和对话框

public class PopupHandler {

    public void handlePopups(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 阻止alert弹窗
        js.executeScript("window.alert = function() {};");

        // 2. 阻止confirm弹窗(总是返回true)
        js.executeScript("window.confirm = function() { return true; };");

        // 3. 阻止prompt弹窗
        js.executeScript("window.prompt = function() { return 'default value'; };");

        // 4. 监听弹窗事件
        js.executeScript(
            "window.originalAlert = window.alert;" +
            "window.alert = function(msg) {" +
            "    console.log('Alert triggered: ' + msg);" +
            "    window.lastAlertMessage = msg;" +
            "};"
        );

        // 获取最后一个alert消息
        String lastAlert = (String) js.executeScript("return window.lastAlertMessage;");
    }
}

3. 等待页面加载完成

public class PageLoadWaiter {

    public void waitForPageLoad(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));

        // 等待页面完全加载
        wait.until(webDriver -> js.executeScript("return document.readyState").equals("complete"));

        // 等待jQuery加载完成(如果页面使用jQuery)
        wait.until(webDriver -> {
            Boolean jqueryReady = (Boolean) js.executeScript("return typeof jQuery != 'undefined' && jQuery.active == 0");
            return jqueryReady;
        });

        // 等待所有AJAX请求完成
        wait.until(webDriver -> {
            Boolean ajaxComplete = (Boolean) js.executeScript(
                "return (typeof jQuery != 'undefined') ? jQuery.active == 0 : true");
            return ajaxComplete;
        });
    }

    public void waitForElementToLoad(WebDriver driver, String elementId) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        // 等待特定元素出现
        wait.until(webDriver -> {
            Boolean elementExists = (Boolean) js.executeScript(
                "return document.getElementById(arguments[0]) != null;", elementId);
            return elementExists;
        });
    }
}

4. 操作隐藏元素

public class HiddenElementHandler {

    public void handleHiddenElements(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 显示隐藏元素
        WebElement hiddenElement = driver.findElement(By.id("hiddenElement"));
        js.executeScript("arguments[0].style.display = 'block';", hiddenElement);
        js.executeScript("arguments[0].style.visibility = 'visible';", hiddenElement);

        // 2. 点击不可见的元素
        js.executeScript("arguments[0].click();", hiddenElement);

        // 3. 获取隐藏元素的属性
        String hiddenValue = (String) js.executeScript(
            "return arguments[0].getAttribute('data-value');", hiddenElement);

        // 4. 修改元素的CSS属性
        js.executeScript(
            "arguments[0].style.opacity = '1';" +
            "arguments[0].style.transform = 'scale(1)';", hiddenElement);
    }
}

5. 处理iframe

public class IframeHandler {

    public void handleIframes(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 获取iframe数量
        Long iframeCount = (Long) js.executeScript("return window.frames.length;");
        System.out.println("页面中iframe数量: " + iframeCount);

        // 2. 切换到iframe
        js.executeScript("window.focus();");

        // 3. 在iframe中执行操作
        driver.switchTo().frame(0);
        js.executeScript("document.getElementById('iframeElement').click();");

        // 4. 切回主页面
        driver.switchTo().defaultContent();

        // 5. 获取iframe的src属性
        WebElement iframe = driver.findElement(By.tagName("iframe"));
        String iframeSrc = (String) js.executeScript("return arguments[0].src;", iframe);
    }
}

6. 性能监控

public class PerformanceMonitor {

    public void monitorPagePerformance(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 获取页面加载时间
        Long loadTime = (Long) js.executeScript(
            "return window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;");
        System.out.println("页面加载时间: " + loadTime + "ms");

        // 2. 获取DOM解析时间
        Long domTime = (Long) js.executeScript(
            "return window.performance.timing.domContentLoadedEventEnd - window.performance.timing.domLoading;");
        System.out.println("DOM解析时间: " + domTime + "ms");

        // 3. 获取网络请求信息
        List<Map<String, Object>> resources = (List<Map<String, Object>>) js.executeScript(
            "return window.performance.getEntriesByType('resource').map(r => ({" +
            "    name: r.name," +
            "    duration: r.duration," +
            "    size: r.transferSize" +
            "}));"
        );

        // 4. 监控内存使用
        if ((Boolean) js.executeScript("return 'memory' in window.performance;")) {
            Map<String, Object> memory = (Map<String, Object>) js.executeScript(
                "return {" +
                "    used: window.performance.memory.usedJSHeapSize," +
                "    total: window.performance.memory.totalJSHeapSize," +
                "    limit: window.performance.memory.jsHeapSizeLimit" +
                "};"
            );
            System.out.println("内存使用情况: " + memory);
        }
    }
}

高级用法和技巧

1. 创建自定义JavaScript函数

public class CustomJSFunctions {

    public void setupCustomFunctions(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 创建全局辅助函数
        js.executeScript(
            "window.seleniumHelper = {" +
            "    highlight: function(element) {" +
            "        element.style.border = '3px solid red';" +
            "        element.style.backgroundColor = 'yellow';" +
            "        setTimeout(() => {" +
            "            element.style.border = '';" +
            "            element.style.backgroundColor = '';" +
            "        }, 2000);" +
            "    }," +
            "    " +
            "    getElementInfo: function(element) {" +
            "        return {" +
            "            tag: element.tagName," +
            "            id: element.id," +
            "            className: element.className," +
            "            text: element.innerText," +
            "            visible: element.offsetWidth > 0 && element.offsetHeight > 0" +
            "        };" +
            "    }," +
            "    " +
            "    waitForElement: function(selector, timeout = 5000) {" +
            "        return new Promise((resolve, reject) => {" +
            "            const startTime = Date.now();" +
            "            const check = () => {" +
            "                const element = document.querySelector(selector);" +
            "                if (element) {" +
            "                    resolve(element);" +
            "                } else if (Date.now() - startTime > timeout) {" +
            "                    reject(new Error('Element not found: ' + selector));" +
            "                } else {" +
            "                    setTimeout(check, 100);" +
            "                }" +
            "            };" +
            "            check();" +
            "        });" +
            "    }" +
            "};"
        );
    }

    public void useCustomFunctions(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 使用自定义函数
        WebElement element = driver.findElement(By.id("myElement"));

        // 高亮元素
        js.executeScript("window.seleniumHelper.highlight(arguments[0]);", element);

        // 获取元素信息
        Map<String, Object> elementInfo = (Map<String, Object>) js.executeScript(
            "return window.seleniumHelper.getElementInfo(arguments[0]);", element);
        System.out.println("元素信息: " + elementInfo);
    }
}

2. 处理复杂的用户交互

public class ComplexInteractions {

    public void simulateComplexInteractions(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 模拟鼠标悬停
        WebElement element = driver.findElement(By.id("hoverElement"));
        js.executeScript(
            "var event = new MouseEvent('mouseover', {" +
            "    view: window," +
            "    bubbles: true," +
            "    cancelable: true" +
            "});" +
            "arguments[0].dispatchEvent(event);", element);

        // 2. 模拟键盘事件
        js.executeScript(
            "var event = new KeyboardEvent('keydown', {" +
            "    key: 'Enter'," +
            "    code: 'Enter'," +
            "    keyCode: 13," +
            "    bubbles: true" +
            "});" +
            "arguments[0].dispatchEvent(event);", element);

        // 3. 模拟拖拽操作
        WebElement source = driver.findElement(By.id("source"));
        WebElement target = driver.findElement(By.id("target"));

        js.executeScript(
            "function simulateDragDrop(sourceElement, targetElement) {" +
            "    var dragStartEvent = new DragEvent('dragstart', { bubbles: true });" +
            "    var dropEvent = new DragEvent('drop', { bubbles: true });" +
            "    var dragEndEvent = new DragEvent('dragend', { bubbles: true });" +
            "    " +
            "    sourceElement.dispatchEvent(dragStartEvent);" +
            "    targetElement.dispatchEvent(dropEvent);" +
            "    sourceElement.dispatchEvent(dragEndEvent);" +
            "}" +
            "simulateDragDrop(arguments[0], arguments[1]);", source, target);

        // 4. 模拟触摸事件(移动端)
        js.executeScript(
            "var touchStart = new TouchEvent('touchstart', {" +
            "    bubbles: true," +
            "    cancelable: true," +
            "    touches: [{" +
            "        clientX: arguments[1]," +
            "        clientY: arguments[2]" +
            "    }]" +
            "});" +
            "arguments[0].dispatchEvent(touchStart);", element, 100, 100);
    }
}

3. 数据提取和处理

public class DataExtraction {

    public void extractPageData(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 1. 提取表格数据
        List<Map<String, Object>> tableData = (List<Map<String, Object>>) js.executeScript(
            "var table = document.querySelector('table');" +
            "var data = [];" +
            "var rows = table.querySelectorAll('tr');" +
            "var headers = Array.from(rows[0].querySelectorAll('th')).map(th => th.innerText);" +
            "for (var i = 1; i < rows.length; i++) {" +
            "    var row = {};" +
            "    var cells = rows[i].querySelectorAll('td');" +
            "    for (var j = 0; j < cells.length; j++) {" +
            "        row[headers[j]] = cells[j].innerText;" +
            "    }" +
            "    data.push(row);" +
            "}" +
            "return data;"
        );

        // 2. 提取所有链接信息
        List<Map<String, String>> links = (List<Map<String, String>>) js.executeScript(
            "return Array.from(document.querySelectorAll('a')).map(link => ({" +
            "    text: link.innerText," +
            "    href: link.href," +
            "    target: link.target" +
            "}));"
        );

        // 3. 提取表单数据
        Map<String, Object> formData = (Map<String, Object>) js.executeScript(
            "var form = document.querySelector('form');" +
            "var data = {};" +
            "var inputs = form.querySelectorAll('input, select, textarea');" +
            "inputs.forEach(input => {" +
            "    if (input.type === 'checkbox' || input.type === 'radio') {" +
            "        data[input.name] = input.checked;" +
            "    } else {" +
            "        data[input.name] = input.value;" +
            "    }" +
            "});" +
            "return data;"
        );

        // 4. 获取页面所有图片信息
        List<Map<String, String>> images = (List<Map<String, String>>) js.executeScript(
            "return Array.from(document.querySelectorAll('img')).map(img => ({" +
            "    src: img.src," +
            "    alt: img.alt," +
            "    width: img.width," +
            "    height: img.height" +
            "}));"
        );
    }
}

最佳实践

1. 错误处理和异常管理

public class JSExecutorBestPractices {

    public Object safeExecuteScript(WebDriver driver, String script, Object... args) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        try {
            return js.executeScript(script, args);
        } catch (JavascriptException e) {
            System.err.println("JavaScript执行错误: " + e.getMessage());
            return null;
        } catch (WebDriverException e) {
            System.err.println("WebDriver错误: " + e.getMessage());
            return null;
        }
    }

    public boolean isElementClickable(WebDriver driver, WebElement element) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        try {
            Boolean clickable = (Boolean) js.executeScript(
                "var element = arguments[0];" +
                "var rect = element.getBoundingClientRect();" +
                "return rect.width > 0 && rect.height > 0 && " +
                "       element.style.visibility !== 'hidden' && " +
                "       element.style.display !== 'none' && " +
                "       !element.disabled;", element);
            return clickable != null && clickable;
        } catch (Exception e) {
            return false;
        }
    }
}

2. 性能优化

public class PerformanceOptimization {

    // 1. 批量操作
    public void batchOperations(WebDriver driver, List<WebElement> elements) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 不好的做法:逐个操作
        // for (WebElement element : elements) {
        //     js.executeScript("arguments[0].style.color = 'red';", element);
        // }

        // 好的做法:批量操作
        js.executeScript(
            "var elements = arguments[0];" +
            "for (var i = 0; i < elements.length; i++) {" +
            "    elements[i].style.color = 'red';" +
            "}", elements.toArray());
    }

    // 2. 缓存JavaScript函数
    private boolean jsHelperLoaded = false;

    public void loadJSHelper(WebDriver driver) {
        if (!jsHelperLoaded) {
            JavascriptExecutor js = (JavascriptExecutor) driver;
            js.executeScript(
                "window.seleniumUtils = {" +
                "    clickElement: function(el) { el.click(); }," +
                "    setText: function(el, text) { el.value = text; }," +
                "    isVisible: function(el) { return el.offsetWidth > 0; }" +
                "};"
            );
            jsHelperLoaded = true;
        }
    }

    // 3. 减少DOM查询
    public void efficientDOMQuery(WebDriver driver) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 不好的做法:多次查询
        // js.executeScript("document.getElementById('id1').click();");
        // js.executeScript("document.getElementById('id2').click();");

        // 好的做法:一次查询,多次使用
        js.executeScript(
            "var el1 = document.getElementById('id1');" +
            "var el2 = document.getElementById('id2');" +
            "if (el1) el1.click();" +
            "if (el2) el2.click();"
        );
    }
}

3. 代码组织和复用

public class JSScriptLibrary {

    // 常用JavaScript脚本库
    public static class Scripts {

        public static final String SCROLL_TO_ELEMENT = 
            "arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});";

        public static final String HIGHLIGHT_ELEMENT = 
            "var el = arguments[0];" +
            "var original = el.style.cssText;" +
            "el.style.border = '3px solid red';" +
            "el.style.backgroundColor = 'yellow';" +
            "setTimeout(function() { el.style.cssText = original; }, 2000);";

        public static final String GET_ELEMENT_INFO = 
            "var el = arguments[0];" +
            "return {" +
            "    tag: el.tagName," +
            "    id: el.id," +
            "    className: el.className," +
            "    text: el.innerText || el.textContent," +
            "    visible: el.offsetWidth > 0 && el.offsetHeight > 0," +
            "    enabled: !el.disabled," +
            "    rect: el.getBoundingClientRect()" +
            "};";

        public static final String WAIT_FOR_AJAX = 
            "return (typeof jQuery !== 'undefined') ? jQuery.active === 0 : true;";

        public static final String REMOVE_ELEMENT = 
            "if (arguments[0].parentNode) arguments[0].parentNode.removeChild(arguments[0]);";
    }

    // 脚本执行器
    public static class Executor {
        private JavascriptExecutor js;

        public Executor(WebDriver driver) {
            this.js = (JavascriptExecutor) driver;
        }

        public void scrollToElement(WebElement element) {
            js.executeScript(Scripts.SCROLL_TO_ELEMENT, element);
        }

        public void highlightElement(WebElement element) {
            js.executeScript(Scripts.HIGHLIGHT_ELEMENT, element);
        }

        public Map<String, Object> getElementInfo(WebElement element) {
            return (Map<String, Object>) js.executeScript(Scripts.GET_ELEMENT_INFO, element);
        }

        public boolean isAjaxComplete() {
            return (Boolean) js.executeScript(Scripts.WAIT_FOR_AJAX);
        }

        public void removeElement(WebElement element) {
            js.executeScript(Scripts.REMOVE_ELEMENT, element);
        }
    }
}

4. 测试辅助工具

public class TestingUtilities {

    public static class DebugHelper {
        private JavascriptExecutor js;

        public DebugHelper(WebDriver driver) {
            this.js = (JavascriptExecutor) driver;
        }

        // 在页面上显示调试信息
        public void showDebugInfo(String message) {
            js.executeScript(
                "var debugDiv = document.getElementById('selenium-debug');" +
                "if (!debugDiv) {" +
                "    debugDiv = document.createElement('div');" +
                "    debugDiv.id = 'selenium-debug';" +
                "    debugDiv.style.cssText = '" +
                "        position: fixed; top: 10px; right: 10px; " +
                "        background: black; color: white; padding: 10px; " +
                "        z-index: 9999; font-family: monospace; " +
                "        max-width: 300px; border-radius: 5px;';" +
                "    document.body.appendChild(debugDiv);" +
                "}" +
                "debugDiv.innerHTML = arguments[0];", message);
        }

        // 截图前高亮所有找到的元素
        public void highlightAllElements(List<WebElement> elements) {
            js.executeScript(
                "var elements = arguments[0];" +
                "for (var i = 0; i < elements.length; i++) {" +
                "    elements[i].style.outline = '2px solid red';" +
                "    elements[i].style.outlineOffset = '2px';" +
                "}", elements.toArray());
        }

        // 在控制台输出页面信息
        public void logPageInfo() {
            js.executeScript(
                "console.log('=== 页面信息 ===');" +
                "console.log('URL:', window.location.href);" +
                "console.log('标题:', document.title);" +
                "console.log('元素数量:', document.querySelectorAll('*').length);" +
                "console.log('表单数量:', document.forms.length);" +
                "console.log('链接数量:', document.links.length);" +
                "console.log('图片数量:', document.images.length);"
            );
        }
    }
}

常见问题和解决方案

1. JavaScript执行异常

问题: JavascriptException: javascript error

解决方案:

public class JSExceptionHandler {

    public Object executeScriptSafely(WebDriver driver, String script, Object... args) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        try {
            // 1. 验证脚本语法
            if (script == null || script.trim().isEmpty()) {
                throw new IllegalArgumentException("JavaScript脚本不能为空");
            }

            // 2. 添加错误处理
            String wrappedScript = 
                "try {" +
                "    " + script +
                "} catch (e) {" +
                "    return 'ERROR: ' + e.message;" +
                "}";

            Object result = js.executeScript(wrappedScript, args);

            // 3. 检查执行结果
            if (result instanceof String && ((String) result).startsWith("ERROR:")) {
                System.err.println("JavaScript执行错误: " + result);
                return null;
            }

            return result;

        } catch (JavascriptException e) {
            System.err.println("JavaScript语法错误: " + e.getMessage());
            return null;
        } catch (WebDriverException e) {
            System.err.println("WebDriver错误: " + e.getMessage());
            return null;
        }
    }
}

2. 元素引用失效

问题: StaleElementReferenceException

解决方案:

public class StaleElementHandler {

    public void handleStaleElement(WebDriver driver, By locator) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 方法1: 重新查找元素
        WebElement element = driver.findElement(locator);
        js.executeScript("arguments[0].click();", element);

        // 方法2: 使用JavaScript直接操作
        js.executeScript(
            "var element = document.querySelector(arguments[0]);" +
            "if (element) element.click();", 
            convertByToSelector(locator));
    }

    private String convertByToSelector(By locator) {
        String locatorString = locator.toString();
        if (locatorString.startsWith("By.id:")) {
            return "#" + locatorString.substring(7);
        } else if (locatorString.startsWith("By.className:")) {
            return "." + locatorString.substring(14);
        }
        // 添加更多转换逻辑...
        return locatorString;
    }
}

3. 异步操作超时

问题: 异步JavaScript执行超时

解决方案:

public class AsyncScriptHandler {

    public Object executeAsyncScriptWithRetry(WebDriver driver, String script, int timeoutSeconds, int maxRetries) {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 设置超时时间
        driver.manage().timeouts().setScriptTimeout(Duration.ofSeconds(timeoutSeconds));

        for (int i = 0; i < maxRetries; i++) {
            try {
                return js.executeAsyncScript(script);
            } catch (TimeoutException e) {
                System.out.println("第" + (i + 1) + "次尝试超时,重试中...");
                if (i == maxRetries - 1) {
                    throw e;
                }
            }
        }
        return null;
    }

    public void waitForAsyncOperation(WebDriver driver, String conditionScript, int timeoutSeconds) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds));

        wait.until(webDriver -> {
            Boolean condition = (Boolean) js.executeScript("return " + conditionScript);
            return condition != null && condition;
        });
    }
}

4. 完整的实战示例

import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.List;
import java.util.Map;

public class JavascriptExecutorDemo {
    private WebDriver driver;
    private JavascriptExecutor js;
    private WebDriverWait wait;

    public void setUp() {
        System.setProperty("webdriver.chrome.driver", "path/to/chromedriver.exe");
        driver = new ChromeDriver();
        js = (JavascriptExecutor) driver;
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    public void demonstrateJavaScriptExecutor() {
        try {
            // 访问测试页面
            driver.get("https://example.com");

            // 1. 基本信息获取
            System.out.println("=== 页面基本信息 ===");
            String title = (String) js.executeScript("return document.title;");
            String url = (String) js.executeScript("return window.location.href;");
            Long pageHeight = (Long) js.executeScript("return document.body.scrollHeight;");

            System.out.println("页面标题: " + title);
            System.out.println("页面URL: " + url);
            System.out.println("页面高度: " + pageHeight + "px");

            // 2. 元素操作演示
            System.out.println("n=== 元素操作演示 ===");
            WebElement searchBox = driver.findElement(By.name("q"));

            // 高亮元素
            js.executeScript(
                "arguments[0].style.border = '3px solid red';" +
                "arguments[0].style.backgroundColor = 'yellow';", searchBox);

            // 设置值
            js.executeScript("arguments[0].value = arguments[1];", searchBox, "Selenium JavascriptExecutor");

            // 滚动到元素
            js.executeScript("arguments[0].scrollIntoView(true);", searchBox);

            Thread.sleep(2000); // 暂停观察效果

            // 3. 页面滚动演示
            System.out.println("n=== 页面滚动演示 ===");
            js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); // 滚动到底部
            Thread.sleep(1000);
            js.executeScript("window.scrollTo(0, 0);"); // 滚动到顶部
            Thread.sleep(1000);

            // 4. 获取页面性能信息
            System.out.println("n=== 页面性能信息 ===");
            Long loadTime = (Long) js.executeScript(
                "return window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;");
            System.out.println("页面加载时间: " + loadTime + "ms");

            // 5. 处理表单
            System.out.println("n=== 表单处理演示 ===");
            // 创建一个测试表单
            js.executeScript(
                "var form = document.createElement('form');" +
                "form.innerHTML = '<input type="text" name="username" placeholder="用户名">' +" +
                "                 '<input type="password" name="password" placeholder="密码">' +" +
                "                 '<button type="submit">提交</button>';" +
                "document.body.appendChild(form);"
            );

            // 填写表单
            js.executeScript("document.querySelector('input[name="username"]').value = 'testuser';");
            js.executeScript("document.querySelector('input[name="password"]').value = 'testpass';");

            // 6. 等待和条件检查
            System.out.println("n=== 等待条件演示 ===");

            // 等待页面完全加载
            wait.until(webDriver -> js.executeScript("return document.readyState").equals("complete"));
            System.out.println("页面加载完成");

            // 检查jQuery是否可用
            Boolean jqueryAvailable = (Boolean) js.executeScript("return typeof jQuery !== 'undefined';");
            System.out.println("jQuery可用: " + jqueryAvailable);

            // 7. 错误处理演示
            System.out.println("n=== 错误处理演示 ===");
            try {
                js.executeScript("return nonExistentVariable;"); // 故意的错误
            } catch (JavascriptException e) {
                System.out.println("捕获到JavaScript错误: " + e.getMessage());
            }

            // 8. 异步操作演示
            System.out.println("n=== 异步操作演示 ===");
            driver.manage().timeouts().setScriptTimeout(Duration.ofSeconds(5));

            Object asyncResult = js.executeAsyncScript(
                "var callback = arguments[arguments.length - 1];" +
                "setTimeout(function() {" +
                "    callback('异步操作完成');" +
                "}, 1000);"
            );
            System.out.println("异步结果: " + asyncResult);

        } catch (Exception e) {
            System.err.println("演示过程中发生错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    public static void main(String[] args) {
        JavascriptExecutorDemo demo = new JavascriptExecutorDemo();
        demo.setUp();
        demo.demonstrateJavaScriptExecutor();
        demo.tearDown();
    }
}

总结

JavascriptExecutor是Selenium WebDriver中非常强大的功能,它为自动化测试提供了极大的灵活性。

核心优势:

  1. 突破限制 – 解决WebDriver无法处理的复杂场景
  2. 提高效率 – 直接操作DOM,执行速度更快
  3. 增强功能 – 实现更复杂的交互和操作
  4. 调试辅助 – 提供强大的调试和信息获取能力

使用原则:

  1. 优先使用WebDriver原生方法 – 只在必要时使用JavaScript
  2. 注意浏览器兼容性 – 确保JavaScript代码在目标浏览器中正常工作
  3. 处理异常情况 – 添加适当的错误处理机制
  4. 保持代码简洁 – 避免过于复杂的JavaScript逻辑

最佳实践:

  • 创建可复用的JavaScript函数库
  • 使用适当的等待机制
  • 注意性能优化
  • 建立良好的错误处理机制

通过掌握JavascriptExecutor,您将能够处理更复杂的自动化测试场景,编写出更加稳定和高效的测试脚本!

发表评论