Java + Selenium 自动化测试 – JavascriptExecutor详解
目录
什么是JavascriptExecutor
JavascriptExecutor 是Selenium WebDriver提供的一个接口,它允许我们在浏览器中执行JavaScript代码。通过这个接口,我们可以直接与浏览器的JavaScript引擎交互,执行各种JavaScript操作。
基本概念
// JavascriptExecutor是一个接口
public interface JavascriptExecutor {
Object executeScript(String script, Object... args);
Object executeAsyncScript(String script, Object... args);
}
为什么需要JavascriptExecutor?
- 突破WebDriver限制 – 有些操作WebDriver无法直接完成
- 提高执行效率 – JavaScript执行速度更快
- 处理复杂交互 – 处理一些特殊的页面元素和行为
- 获取页面信息 – 获取WebDriver无法直接获取的信息
- 模拟用户行为 – 更精确地模拟用户操作
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中非常强大的功能,它为自动化测试提供了极大的灵活性。
核心优势:
- 突破限制 – 解决WebDriver无法处理的复杂场景
- 提高效率 – 直接操作DOM,执行速度更快
- 增强功能 – 实现更复杂的交互和操作
- 调试辅助 – 提供强大的调试和信息获取能力
使用原则:
- 优先使用WebDriver原生方法 – 只在必要时使用JavaScript
- 注意浏览器兼容性 – 确保JavaScript代码在目标浏览器中正常工作
- 处理异常情况 – 添加适当的错误处理机制
- 保持代码简洁 – 避免过于复杂的JavaScript逻辑
最佳实践:
- 创建可复用的JavaScript函数库
- 使用适当的等待机制
- 注意性能优化
- 建立良好的错误处理机制
通过掌握JavascriptExecutor,您将能够处理更复杂的自动化测试场景,编写出更加稳定和高效的测试脚本!