Java+Selenium自动化测试 – 显示等待的自定义条件详解
目录
什么是自定义等待条件
概念
自定义等待条件是当Selenium内置的ExpectedConditions无法满足特定需求时,开发者自己编写的等待逻辑。它允许我们定义复杂的、业务相关的等待条件。
为什么需要自定义等待条件?
虽然Selenium提供了丰富的内置等待条件,但在实际项目中,我们经常遇到以下场景:
- 复杂的业务逻辑:等待特定的业务状态完成
- 多条件组合:需要同时满足多个条件
- 动态内容:等待动态生成的内容达到特定状态
- 第三方组件:等待第三方插件或组件加载完成
- 性能监控:等待页面性能指标达到预期
内置条件 vs 自定义条件对比
| 特性 | 内置ExpectedConditions | 自定义等待条件 |
|---|---|---|
| 使用难度 | 简单 | 中等到复杂 |
| 灵活性 | 有限 | 完全可控 |
| 适用场景 | 通用场景 | 特定业务场景 |
| 维护成本 | 低 | 中等 |
| 性能控制 | 标准 | 可优化 |
ExpectedCondition接口详解
接口结构
public interface ExpectedCondition<T> extends Function<WebDriver, T> {
T apply(WebDriver driver);
}
核心概念
- 泛型T:等待条件返回的数据类型
- apply方法:核心逻辑,返回null表示条件未满足,返回非null表示条件满足
- Function接口:继承自Java 8的Function接口,支持Lambda表达式
返回值类型说明
// Boolean类型:条件是否满足
ExpectedCondition<Boolean> booleanCondition = driver -> {
// 返回true表示条件满足,false表示不满足
return someCondition;
};
// WebElement类型:返回找到的元素
ExpectedCondition<WebElement> elementCondition = driver -> {
// 返回元素表示找到,null表示未找到
return driver.findElement(By.id("myId"));
};
// String类型:返回特定的字符串值
ExpectedCondition<String> stringCondition = driver -> {
// 返回字符串表示条件满足,null表示不满足
return driver.getTitle().contains("期望标题") ? driver.getTitle() : null;
};
创建自定义等待条件的方法
方法1:匿名内部类(传统方式)
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.WebDriver;
public class AnonymousClassExample {
public void customWaitWithAnonymousClass() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 等待页面标题包含特定文本
WebElement element = wait.until(new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
try {
WebElement elem = driver.findElement(By.id("myElement"));
if (elem.isDisplayed() && elem.isEnabled()) {
return elem;
}
return null;
} catch (NoSuchElementException e) {
return null;
}
}
@Override
public String toString() {
return "element to be visible and enabled";
}
});
}
}
方法2:Lambda表达式(推荐)
public class LambdaExpressionExample {
public void customWaitWithLambda() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 等待元素可见且可点击
WebElement element = wait.until(driver -> {
try {
WebElement elem = driver.findElement(By.id("myElement"));
return (elem.isDisplayed() && elem.isEnabled()) ? elem : null;
} catch (NoSuchElementException e) {
return null;
}
});
// 等待页面加载完成
wait.until(driver -> {
JavascriptExecutor js = (JavascriptExecutor) driver;
return js.executeScript("return document.readyState").equals("complete");
});
// 等待Ajax请求完成
wait.until(driver -> {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (Boolean) js.executeScript("return jQuery.active == 0");
});
}
}
方法3:静态方法封装(最佳实践)
public class CustomExpectedConditions {
// 等待元素可见且包含特定文本
public static ExpectedCondition<WebElement> elementToBeVisibleWithText(
final By locator, final String expectedText) {
return new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
try {
WebElement element = driver.findElement(locator);
if (element.isDisplayed() && element.getText().contains(expectedText)) {
return element;
}
return null;
} catch (Exception e) {
return null;
}
}
@Override
public String toString() {
return String.format("element located by %s to be visible with text '%s'",
locator, expectedText);
}
};
}
// 使用示例
public void useCustomCondition() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
CustomExpectedConditions.elementToBeVisibleWithText(
By.id("status"), "加载完成"
)
);
}
}
常见自定义等待条件实例
1. 等待元素属性值变化
public class AttributeWaitConditions {
// 等待元素的class属性包含特定值
public static ExpectedCondition<Boolean> attributeToContain(
final By locator, final String attribute, final String value) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
String attributeValue = element.getAttribute(attribute);
return attributeValue != null && attributeValue.contains(value);
} catch (Exception e) {
return false;
}
};
}
// 等待元素的style属性变化
public static ExpectedCondition<Boolean> elementStyleToContain(
final By locator, final String styleProperty, final String expectedValue) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
String styleValue = element.getCssValue(styleProperty);
return styleValue != null && styleValue.contains(expectedValue);
} catch (Exception e) {
return false;
}
};
}
// 使用示例
public void waitForAttributeChanges() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 等待按钮的class包含"active"
wait.until(attributeToContain(By.id("myButton"), "class", "active"));
// 等待元素的display样式变为block
wait.until(elementStyleToContain(By.id("myDiv"), "display", "block"));
}
}
2. 等待文本内容变化
public class TextWaitConditions {
// 等待元素文本不等于指定值
public static ExpectedCondition<Boolean> textToBeNotEqual(
final By locator, final String text) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
return !element.getText().equals(text);
} catch (Exception e) {
return false;
}
};
}
// 等待元素文本长度达到指定值
public static ExpectedCondition<Boolean> textLengthToBe(
final By locator, final int expectedLength) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
return element.getText().length() >= expectedLength;
} catch (Exception e) {
return false;
}
};
}
// 等待元素文本匹配正则表达式
public static ExpectedCondition<Boolean> textToMatchPattern(
final By locator, final String pattern) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
return element.getText().matches(pattern);
} catch (Exception e) {
return false;
}
};
}
// 使用示例
public void waitForTextChanges() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 等待状态文本不再是"加载中..."
wait.until(textToBeNotEqual(By.id("status"), "加载中..."));
// 等待描述文本长度至少50个字符
wait.until(textLengthToBe(By.id("description"), 50));
// 等待电话号码格式正确
wait.until(textToMatchPattern(By.id("phone"), "\d{3}-\d{4}-\d{4}"));
}
}
3. 等待元素数量变化
public class ElementCountConditions {
// 等待元素数量等于指定值
public static ExpectedCondition<Boolean> numberOfElementsToBe(
final By locator, final int expectedCount) {
return driver -> {
try {
List<WebElement> elements = driver.findElements(locator);
return elements.size() == expectedCount;
} catch (Exception e) {
return false;
}
};
}
// 等待元素数量大于指定值
public static ExpectedCondition<Boolean> numberOfElementsToBeMoreThan(
final By locator, final int expectedCount) {
return driver -> {
try {
List<WebElement> elements = driver.findElements(locator);
return elements.size() > expectedCount;
} catch (Exception e) {
return false;
}
};
}
// 等待列表中所有元素都可见
public static ExpectedCondition<Boolean> allElementsToBeVisible(final By locator) {
return driver -> {
try {
List<WebElement> elements = driver.findElements(locator);
if (elements.isEmpty()) return false;
for (WebElement element : elements) {
if (!element.isDisplayed()) {
return false;
}
}
return true;
} catch (Exception e) {
return false;
}
};
}
// 使用示例
public void waitForElementCounts() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
// 等待表格有5行数据
wait.until(numberOfElementsToBe(By.xpath("//table//tr"), 6)); // 包含表头
// 等待搜索结果至少有10个
wait.until(numberOfElementsToBeMoreThan(By.className("search-result"), 9));
// 等待所有菜单项都可见
wait.until(allElementsToBeVisible(By.className("menu-item")));
}
}
4. 等待JavaScript执行完成
public class JavaScriptWaitConditions {
// 等待页面完全加载
public static ExpectedCondition<Boolean> pageToBeFullyLoaded() {
return driver -> {
JavascriptExecutor js = (JavascriptExecutor) driver;
return js.executeScript("return document.readyState").equals("complete");
};
}
// 等待jQuery加载完成
public static ExpectedCondition<Boolean> jQueryToBeLoaded() {
return driver -> {
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (Boolean) js.executeScript("return typeof jQuery !== 'undefined'");
} catch (Exception e) {
return false;
}
};
}
// 等待jQuery Ajax请求完成
public static ExpectedCondition<Boolean> jQueryAjaxToBeComplete() {
return driver -> {
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (Boolean) js.executeScript("return jQuery.active == 0");
} catch (Exception e) {
return false;
}
};
}
// 等待Angular应用加载完成
public static ExpectedCondition<Boolean> angularToBeReady() {
return driver -> {
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (Boolean) js.executeScript(
"return window.angular && " +
"angular.element(document).injector() && " +
"angular.element(document).injector().get('$http').pendingRequests.length === 0"
);
} catch (Exception e) {
return false;
}
};
}
// 等待自定义JavaScript条件
public static ExpectedCondition<Boolean> customJavaScriptCondition(final String script) {
return driver -> {
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
Object result = js.executeScript(script);
return result instanceof Boolean ? (Boolean) result : false;
} catch (Exception e) {
return false;
}
};
}
// 使用示例
public void waitForJavaScriptConditions() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// 等待页面完全加载
wait.until(pageToBeFullyLoaded());
// 等待jQuery加载
wait.until(jQueryToBeLoaded());
// 等待所有Ajax请求完成
wait.until(jQueryAjaxToBeComplete());
// 等待自定义条件:购物车数量大于0
wait.until(customJavaScriptCondition(
"return document.getElementById('cart-count').innerText > 0"
));
}
}
高级自定义等待条件
1. 复合条件等待
public class CompositeWaitConditions {
// 等待多个条件同时满足(AND逻辑)
public static ExpectedCondition<Boolean> allConditionsToBeTrue(
final ExpectedCondition<?>... conditions) {
return driver -> {
for (ExpectedCondition<?> condition : conditions) {
try {
Object result = condition.apply(driver);
if (result == null || (result instanceof Boolean && !(Boolean) result)) {
return false;
}
} catch (Exception e) {
return false;
}
}
return true;
};
}
// 等待任一条件满足(OR逻辑)
public static ExpectedCondition<Boolean> anyConditionToBeTrue(
final ExpectedCondition<?>... conditions) {
return driver -> {
for (ExpectedCondition<?> condition : conditions) {
try {
Object result = condition.apply(driver);
if (result != null && (!(result instanceof Boolean) || (Boolean) result)) {
return true;
}
} catch (Exception e) {
// 继续检查下一个条件
}
}
return false;
};
}
// 使用示例
public void waitForCompositeConditions() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
// 等待页面加载完成且特定元素可见
wait.until(allConditionsToBeTrue(
JavaScriptWaitConditions.pageToBeFullyLoaded(),
ExpectedConditions.visibilityOfElementLocated(By.id("content"))
));
// 等待成功或错误消息出现
wait.until(anyConditionToBeTrue(
ExpectedConditions.visibilityOfElementLocated(By.className("success-message")),
ExpectedConditions.visibilityOfElementLocated(By.className("error-message"))
));
}
}
2. 带超时重试的等待条件
public class RetryWaitConditions {
// 带重试机制的等待条件
public static <T> ExpectedCondition<T> withRetry(
final ExpectedCondition<T> condition, final int maxRetries) {
return driver -> {
Exception lastException = null;
for (int i = 0; i <= maxRetries; i++) {
try {
T result = condition.apply(driver);
if (result != null) {
return result;
}
} catch (Exception e) {
lastException = e;
if (i < maxRetries) {
try {
Thread.sleep(500); // 重试间隔
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
if (lastException != null) {
throw new RuntimeException("条件在重试 " + maxRetries + " 次后仍未满足", lastException);
}
return null;
};
}
// 使用示例
public void waitWithRetry() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// 带重试的元素查找
WebElement element = wait.until(withRetry(
ExpectedConditions.elementToBeClickable(By.id("unstable-button")),
3 // 最多重试3次
));
}
}
3. 性能监控等待条件
public class PerformanceWaitConditions {
// 等待页面加载时间小于指定值
public static ExpectedCondition<Boolean> pageLoadTimeToBeUnder(final long maxLoadTimeMs) {
return driver -> {
JavascriptExecutor js = (JavascriptExecutor) driver;
try {
Long loadTime = (Long) js.executeScript(
"return performance.timing.loadEventEnd - performance.timing.navigationStart;"
);
return loadTime != null && loadTime > 0 && loadTime < maxLoadTimeMs;
} catch (Exception e) {
return false;
}
};
}
// 等待网络请求完成
public static ExpectedCondition<Boolean> networkRequestsToBeComplete() {
return driver -> {
JavascriptExecutor js = (JavascriptExecutor) driver;
try {
// 检查是否有正在进行的fetch请求
Boolean fetchComplete = (Boolean) js.executeScript(
"return window.fetch === undefined || " +
"!window.hasOwnProperty('activeFetchRequests') || " +
"window.activeFetchRequests === 0"
);
// 检查XMLHttpRequest
Boolean xhrComplete = (Boolean) js.executeScript(
"return !window.XMLHttpRequest || " +
"window.XMLHttpRequest.OPENED === undefined || " +
"document.readyState === 'complete'"
);
return fetchComplete && xhrComplete;
} catch (Exception e) {
return false;
}
};
}
// 使用示例
public void waitForPerformanceConditions() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// 等待页面加载时间小于3秒
wait.until(pageLoadTimeToBeUnder(3000));
// 等待所有网络请求完成
wait.until(networkRequestsToBeComplete());
}
}
自定义条件的最佳实践
1. 异常处理和容错性
public class RobustWaitConditions {
// 健壮的元素等待条件
public static ExpectedCondition<WebElement> robustElementToBeClickable(final By locator) {
return new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
try {
WebElement element = driver.findElement(locator);
// 检查元素是否存在
if (element == null) {
return null;
}
// 检查元素是否可见
if (!element.isDisplayed()) {
return null;
}
// 检查元素是否启用
if (!element.isEnabled()) {
return null;
}
// 检查元素是否被其他元素遮挡
try {
element.click();
return element;
} catch (ElementClickInterceptedException e) {
return null;
}
} catch (NoSuchElementException | StaleElementReferenceException e) {
return null;
} catch (Exception e) {
// 记录意外异常但不中断等待
System.err.println("等待元素时发生意外异常: " + e.getMessage());
return null;
}
}
@Override
public String toString() {
return "element located by " + locator + " to be clickable (robust check)";
}
};
}
}
2. 可配置的等待条件
public class ConfigurableWaitConditions {
public static class WaitConfig {
private boolean checkVisibility = true;
private boolean checkEnabled = true;
private boolean checkClickable = false;
private String expectedText = null;
private String expectedAttribute = null;
private String expectedAttributeValue = null;
// Getter和Setter方法
public WaitConfig checkVisibility(boolean check) {
this.checkVisibility = check;
return this;
}
public WaitConfig checkEnabled(boolean check) {
this.checkEnabled = check;
return this;
}
public WaitConfig checkClickable(boolean check) {
this.checkClickable = check;
return this;
}
public WaitConfig expectedText(String text) {
this.expectedText = text;
return this;
}
public WaitConfig expectedAttribute(String attribute, String value) {
this.expectedAttribute = attribute;
this.expectedAttributeValue = value;
return this;
}
// Getter方法
public boolean isCheckVisibility() { return checkVisibility; }
public boolean isCheckEnabled() { return checkEnabled; }
public boolean isCheckClickable() { return checkClickable; }
public String getExpectedText() { return expectedText; }
public String getExpectedAttribute() { return expectedAttribute; }
public String getExpectedAttributeValue() { return expectedAttributeValue; }
}
// 可配置的元素等待条件
public static ExpectedCondition<WebElement> configurableElementCondition(
final By locator, final WaitConfig config) {
return driver -> {
try {
WebElement element = driver.findElement(locator);
// 检查可见性
if (config.isCheckVisibility() && !element.isDisplayed()) {
return null;
}
// 检查是否启用
if (config.isCheckEnabled() && !element.isEnabled()) {
return null;
}
// 检查文本
if (config.getExpectedText() != null &&
!element.getText().contains(config.getExpectedText())) {
return null;
}
// 检查属性
if (config.getExpectedAttribute() != null) {
String attrValue = element.getAttribute(config.getExpectedAttribute());
if (attrValue == null || !attrValue.equals(config.getExpectedAttributeValue())) {
return null;
}
}
// 检查可点击性
if (config.isCheckClickable()) {
try {
element.click();
} catch (ElementClickInterceptedException e) {
return null;
}
}
return element;
} catch (Exception e) {
return null;
}
};
}
// 使用示例
public void useConfigurableCondition() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 配置等待条件
WaitConfig config = new WaitConfig()
.checkVisibility(true)
.checkEnabled(true)
.expectedText("提交")
.expectedAttribute("class", "btn-primary");
WebElement element = wait.until(
configurableElementCondition(By.id("submit-btn"), config)
);
}
}
3. 日志和调试支持
public class DebuggableWaitConditions {
private static final Logger logger = LoggerFactory.getLogger(DebuggableWaitConditions.class);
// 带调试信息的等待条件
public static ExpectedCondition<WebElement> debuggableElementWait(
final By locator, final String conditionName) {
return new ExpectedCondition<WebElement>() {
private int attemptCount = 0;
@Override
public WebElement apply(WebDriver driver) {
attemptCount++;
logger.debug("等待条件 '{}' 第 {} 次检查", conditionName, attemptCount);
try {
WebElement element = driver.findElement(locator);
if (!element.isDisplayed()) {
logger.debug("元素存在但不可见: {}", locator);
return null;
}
if (!element.isEnabled()) {
logger.debug("元素可见但不可用: {}", locator);
return null;
}
logger.debug("等待条件 '{}' 在第 {} 次检查时满足", conditionName, attemptCount);
return element;
} catch (NoSuchElementException e) {
logger.debug("元素未找到: {} (第 {} 次检查)", locator, attemptCount);
return null;
} catch (Exception e) {
logger.warn("等待条件检查时发生异常: {}", e.getMessage());
return null;
}
}
@Override
public String toString() {
return String.format("debuggable condition '%s' for element %s (attempts: %d)",
conditionName, locator, attemptCount);
}
};
}
// 使用示例
public void useDebuggableCondition() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
debuggableElementWait(By.id("complex-element"), "复杂元素可点击检查")
);
}
}
性能优化和调试技巧
1. 轮询间隔优化
public class PerformanceOptimizedWaits {
// 自适应轮询间隔
public static ExpectedCondition<WebElement> adaptivePollingElementWait(final By locator) {
return new ExpectedCondition<WebElement>() {
private int checkCount = 0;
private long startTime = System.currentTimeMillis();
@Override
public WebElement apply(WebDriver driver) {
checkCount++;
long elapsed = System.currentTimeMillis() - startTime;
try {
WebElement element = driver.findElement(locator);
if (element.isDisplayed() && element.isEnabled()) {
return element;
}
// 根据已等待时间调整检查频率
if (elapsed < 2000) {
// 前2秒快速检查
Thread.sleep(100);
} else if (elapsed < 5000) {
// 2-5秒中等频率
Thread.sleep(300);
} else {
// 5秒后降低频率
Thread.sleep(500);
}
return null;
} catch (Exception e) {
return null;
}
}
@Override
public String toString() {
return String.format("adaptive polling for %s (checks: %d)", locator, checkCount);
}
};
}
}
2. 条件缓存优化
public class CachedWaitConditions {
private static final Map<String, Object> conditionCache = new ConcurrentHashMap<>();
// 带缓存的JavaScript条件
public static ExpectedCondition<Boolean> cachedJavaScriptCondition(
final String script, final String cacheKey, final long cacheTimeMs) {
return driver -> {
String fullCacheKey = cacheKey + "_" + script.hashCode();
CacheEntry entry = (CacheEntry) conditionCache.get(fullCacheKey);
// 检查缓存
if (entry != null && (System.currentTimeMillis() - entry.timestamp) < cacheTimeMs) {
return entry.result;
}
// 执行脚本
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
Boolean result = (Boolean) js.executeScript(script);
// 更新缓存
conditionCache.put(fullCacheKey, new CacheEntry(result, System.currentTimeMillis()));
return result;
} catch (Exception e) {
return false;
}
};
}
private static class CacheEntry {
final Boolean result;
final long timestamp;
CacheEntry(Boolean result, long timestamp) {
this.result = result;
this.timestamp = timestamp;
}
}
// 清理缓存
public static void clearCache() {
conditionCache.clear();
}
}
3. 性能监控
public class PerformanceMonitoredWaits {
// 带性能监控的等待条件
public static <T> ExpectedCondition<T> monitoredCondition(
final ExpectedCondition<T> condition, final String conditionName) {
return new ExpectedCondition<T>() {
private long startTime = System.currentTimeMillis();
private int checkCount = 0;
@Override
public T apply(WebDriver driver) {
checkCount++;
long checkStartTime = System.currentTimeMillis();
try {
T result = condition.apply(driver);
long checkDuration = System.currentTimeMillis() - checkStartTime;
if (result != null) {
long totalDuration = System.currentTimeMillis() - startTime;
logger.info("等待条件 '{}' 完成: 总时间={}ms, 检查次数={}, 平均检查时间={}ms",
conditionName, totalDuration, checkCount,
checkCount > 0 ? totalDuration / checkCount : 0);
}
return result;
} catch (Exception e) {
long checkDuration = System.currentTimeMillis() - checkStartTime;
logger.debug("等待条件 '{}' 第{}次检查异常: {}ms", conditionName, checkCount, checkDuration);
throw e;
}
}
@Override
public String toString() {
return String.format("monitored condition '%s'", conditionName);
}
};
}
}
常见问题和解决方案
1. 内存泄漏问题
public class MemoryEfficientWaits {
// 避免内存泄漏的等待条件
public static ExpectedCondition<WebElement> memoryEfficientElementWait(final By locator) {
return driver -> {
try {
// 使用局部变量,避免持有不必要的引用
WebElement element = driver.findElement(locator);
// 立即检查条件,不保存中间状态
boolean isReady = element.isDisplayed() && element.isEnabled();
// 清理可能的大对象引用
if (!isReady) {
element = null; // 帮助GC
return null;
}
return element;
} catch (Exception e) {
// 不保存异常对象,避免内存泄漏
return null;
}
};
}
}
2. 线程安全问题
public class ThreadSafeWaitConditions {
private static final ThreadLocal<Map<String, Object>> threadLocalCache =
ThreadLocal.withInitial(HashMap::new);
// 线程安全的缓存等待条件
public static ExpectedCondition<Boolean> threadSafeCachedCondition(
final String script, final String cacheKey) {
return driver -> {
Map<String, Object> cache = threadLocalCache.get();
// 检查线程本地缓存
Boolean cachedResult = (Boolean) cache.get(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
try {
JavascriptExecutor js = (JavascriptExecutor) driver;
Boolean result = (Boolean) js.executeScript(script);
// 存储到线程本地缓存
cache.put(cacheKey, result);
return result;
} catch (Exception e) {
return false;
}
};
}
// 清理线程本地缓存
public static void clearThreadLocalCache() {
threadLocalCache.get().clear();
}
}
3. 超时处理优化
public class TimeoutOptimizedWaits {
// 分阶段超时等待
public static ExpectedCondition<WebElement> phasedTimeoutElementWait(final By locator) {
return new ExpectedCondition<WebElement>() {
private long startTime = System.currentTimeMillis();
@Override
public WebElement apply(WebDriver driver) {
long elapsed = System.currentTimeMillis() - startTime;
try {
// 第一阶段:快速查找(前3秒)
if (elapsed < 3000) {
WebElement element = driver.findElement(locator);
if (element.isDisplayed()) {
return element;
}
return null;
}
// 第二阶段:等待可见(3-8秒)
if (elapsed < 8000) {
WebElement element = driver.findElement(locator);
if (element.isDisplayed() && element.isEnabled()) {
return element;
}
return null;
}
// 第三阶段:强制等待(8秒后)
WebElement element = driver.findElement(locator);
// 即使不完全满足条件也返回,让调用者决定
return element.isDisplayed() ? element : null;
} catch (Exception e) {
// 根据阶段决定是否继续等待
return elapsed > 10000 ? null : null;
}
}
@Override
public String toString() {
long elapsed = System.currentTimeMillis() - startTime;
return String.format("phased timeout wait for %s (elapsed: %dms)", locator, elapsed);
}
};
}
}
4. 调试和故障排除
public class DebuggingWaitConditions {
// 详细调试信息的等待条件
public static ExpectedCondition<WebElement> debugElementWait(final By locator) {
return new ExpectedCondition<WebElement>() {
private final List<String> debugLog = new ArrayList<>();
@Override
public WebElement apply(WebDriver driver) {
try {
debugLog.add("开始查找元素: " + locator);
WebElement element = driver.findElement(locator);
debugLog.add("元素已找到");
// 检查元素状态
boolean isDisplayed = element.isDisplayed();
boolean isEnabled = element.isEnabled();
String tagName = element.getTagName();
String text = element.getText();
debugLog.add(String.format("元素状态: displayed=%s, enabled=%s, tag=%s, text='%s'",
isDisplayed, isEnabled, tagName, text));
if (isDisplayed && isEnabled) {
debugLog.add("元素满足条件");
return element;
} else {
debugLog.add("元素不满足条件");
return null;
}
} catch (NoSuchElementException e) {
debugLog.add("元素未找到: " + e.getMessage());
return null;
} catch (Exception e) {
debugLog.add("发生异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
return null;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Debug wait for ").append(locator).append("n");
for (String log : debugLog) {
sb.append(" ").append(log).append("n");
}
return sb.toString();
}
};
}
}
总结
关键要点
- 选择合适的实现方式:Lambda表达式简洁,静态方法封装便于复用
- 异常处理:始终处理可能的异常,返回null而不是抛出异常
- 性能考虑:合理设置轮询间隔,避免过度检查
- 可维护性:提供清晰的toString()方法,便于调试
- 线程安全:在多线程环境中注意线程安全问题
最佳实践总结
public class CustomWaitBestPractices {
// 推荐的自定义等待条件模板
public static ExpectedCondition<WebElement> bestPracticeElementWait(final By locator) {
return new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
try {
WebElement element = driver.findElement(locator);
// 检查所有必要条件
if (element.isDisplayed() && element.isEnabled()) {
return element;
}
return null; // 条件不满足时返回null
} catch (NoSuchElementException | StaleElementReferenceException e) {
return null; // 预期异常,返回null继续等待
} catch (Exception e) {
// 记录意外异常但不中断等待
logger.warn("等待元素时发生意外异常: {}", e.getMessage());
return null;
}
}
@Override
public String toString() {
return "element located by " + locator + " to be visible and enabled";
}
};
}
}
使用建议
- 优先使用内置条件:如果内置的ExpectedConditions能满足需求,优先使用
- 封装常用条件:将项目中常用的自定义条件封装成工具方法
- 添加日志支持:在复杂的等待条件中添加适当的日志
- 性能测试:对自定义等待条件进行性能测试,确保不会影响测试执行速度
- 文档化:为自定义等待条件编写清晰的文档和使用示例
通过掌握自定义等待条件的编写技巧,您可以处理各种复杂的等待场景,编写更加稳定和高效的Selenium自动化测试脚本!