原文地址:http://blog.csdn.net/ant_yan/article/details/8185899
(一) To Handle Ajax
Selenium WebDriver在加载页面的时候,无论是driver.get(url)或者driver.getPageSource(),会立即返回当时页面的数据。但当代的网页技术都大量使用了异步Ajax调用,这使得许多DOM元素的创建和加载,都分布在页面load结束后的许多零散的时间点,让WebDriver的findElement经常无功而返。这个时候,一般有两种方式来解决问题:
1. 利用Selenium WebDriver原生的API,进行预判等待元素出现
演示代码如下:
使用FluentWait + Predicate接口
[java]
view plain
copy
print
?
- FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA"));
- fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS);
- fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS);
- fluentWait.until(new Predicate<By>() {
- public boolean apply(By by) {
- try {
- return browser.findElement(by).isDisplayed();
- } catch (NoSuchElementException ex) {
- return false;
- }
- }
- });
-
- browser.findElement(By.tagName("TEXTAREA")).sendKeys("text to enter");
FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA"));
fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS);
fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS);
fluentWait.until(new Predicate<By>() {
public boolean apply(By by) {
try {
return browser.findElement(by).isDisplayed();
} catch (NoSuchElementException ex) {
return false;
}
}
});
browser.findElement(By.tagName("TEXTAREA")).sendKeys("text to enter");
或者也可以直接使用Wait接口的until方法,传入一个ExpectedCondition
[java]
view plain
copy
print
?
- public ExpectedCondition<WebElement> visibilityOfElementLocated(final By by) {
- return new ExpectedCondition<WebElement>() {
- public WebElement apply(WebDriver driver) {
- WebElement element = driver.findElement(by);
- return element.isDisplayed() ? element :null;
- }
- };
- }
-
- public void performSomeAction() {
- ..
- ..
- Wait<WebDriver> wait = new WebDriverWait(driver,20);
- WebElement element = wait.until(visibilityOfElementLocated(By.tagName("a")));
- ..
- }
public ExpectedCondition<WebElement> visibilityOfElementLocated(final By by) {
return new ExpectedCondition<WebElement>() {
public WebElement apply(WebDriver driver) {
WebElement element = driver.findElement(by);
return element.isDisplayed() ? element : null;
}
};
}
public void performSomeAction() {
..
..
Wait<WebDriver> wait = new WebDriverWait(driver, 20);
WebElement element = wait.until(visibilityOfElementLocated(By.tagName("a")));
..
}
2. 利用Javascript注入,来读取不同Ajax调用框架的Ajax request status,一直等到Ajax调用全部返回才开始分析操作Dom元素
演示代码如下:
[java]
view plain
copy
print
?
- protected void syncAjaxByJQuery(String timeout) {
- boolean isSucceed = false;
- try {
- selenium.waitForCondition(
- "selenium.browserbot.getCurrentWindow().jQuery.active == 0", timeout);
- isSucceed = true;
- } catch (SeleniumException se) {
- LOG.error(se);
- } catch (Exception re) {
- throw new RuntimeException(re.getMessage());
- }
- operationCheck(isSucceed);
- }
-
-
- protected void syncAjaxByPrototype(String timeout) {
- boolean isSucceed = false;
- try {
- selenium.waitForCondition(
- "selenium.browserbot.getCurrentWindow().Ajax.activeRequestCount == 0",
- timeout);
- isSucceed = true;
- } catch (SeleniumException se) {
- LOG.error(se);
- } catch (Exception re) {
- throw new RuntimeException(re.getMessage());
- }
- operationCheck(isSucceed);
- }
-
-
- protected void syncAjaxByDojo(String timeout) {
- boolean isSucceed = false;
- try {
- selenium.waitForCondition(
- "selenium.browserbot.getCurrentWindow().dojo.io.XMLHTTPTransport.inFlight.length == 0",
- timeout);
- isSucceed = true;
- } catch (SeleniumException se) {
- LOG.error(se);
- } catch (Exception re) {
- throw new RuntimeException(re.getMessage());
- }
- operationCheck(isSucceed);
- }
protected void syncAjaxByJQuery(String timeout) {
boolean isSucceed = false;
try {
selenium.waitForCondition(
"selenium.browserbot.getCurrentWindow().jQuery.active == 0", timeout);
isSucceed = true;
} catch (SeleniumException se) {
LOG.error(se);
} catch (Exception re) {
throw new RuntimeException(re.getMessage());
}
operationCheck(isSucceed);
}
protected void syncAjaxByPrototype(String timeout) {
boolean isSucceed = false;
try {
selenium.waitForCondition(
"selenium.browserbot.getCurrentWindow().Ajax.activeRequestCount == 0",
timeout);
isSucceed = true;
} catch (SeleniumException se) {
LOG.error(se);
} catch (Exception re) {
throw new RuntimeException(re.getMessage());
}
operationCheck(isSucceed);
}
protected void syncAjaxByDojo(String timeout) {
boolean isSucceed = false;
try {
selenium.waitForCondition(
"selenium.browserbot.getCurrentWindow().dojo.io.XMLHTTPTransport.inFlight.length == 0",
timeout);
isSucceed = true;
} catch (SeleniumException se) {
LOG.error(se);
} catch (Exception re) {
throw new RuntimeException(re.getMessage());
}
operationCheck(isSucceed);
}
(二) To Handle Alert
最近遇到许多网页用Selenium操作的时候,网页本身会弹出许多Alert模态窗口,这种模态窗口会阻断driver继续执行。虽然WebDriver已经提供了些许方法来处理模态Alert窗口,但是依然会在不同的浏览器上存在未修复的Bug。先来看看WebDriver提供的处理Alert的方式,基本就是通过driver.switchTo()方法获得Alert模态窗口的句柄,然后调用Alert.accept()或者Alert.dismiss()方法接受或者拒绝,同样的操作也适用于Confirm窗口。我为了防止意外情况下WebDriver内置Alert处理的代码会崩溃,所以特意设计了一个超时机制,一旦超时就转而使用原始键盘模拟输入回车来消除窗口,使用的也是java.awt.Robot类来模拟操作系统的键盘输入动作。
演示代码如下:
[java]
view plain
copy
print
?
- public T handleAlert(){
- KeyboardUtils.enter();
- long clockTime = System.currentTimeMillis();
- long timeout = 3 *1000;
- while(true){
- if((System.currentTimeMillis()-clockTime) > timeout){
- KeyboardUtils.enter();
- break;
- }else if(WebDriverUtils.isAlertExist(driver)){
- try{
- Alert alert = driver.switchTo().alert();
- alert.getText();
- alert.accept();
- }catch(Exception ex){
- KeyboardUtils.enter();
- }
- }
-
- waiting(300);
- }
- return instance;
- }
-
-
-
- public staticboolean isAlertExist(WebDriver driver){
- try{
- driver.switchTo().alert();
- return true;
- }catch(NoAlertPresentException ex){
- return false;
- }
- }
public T handleAlert(){
KeyboardUtils.enter();
long clockTime = System.currentTimeMillis();
long timeout = 3 * 1000;
while(true){
if((System.currentTimeMillis()-clockTime) > timeout){
KeyboardUtils.enter();
break;
}else if(WebDriverUtils.isAlertExist(driver)){
try{
Alert alert = driver.switchTo().alert();
alert.getText();
alert.accept();
}catch(Exception ex){
KeyboardUtils.enter();
}
}
waiting(300);
}
return instance;
}
//In WebDriverUtils.class
public static boolean isAlertExist(WebDriver driver){
try{
driver.switchTo().alert();
return true;
}catch(NoAlertPresentException ex){
return false;
}
}
(三) To Handle more than Alert
还有一种情况,当我用Firefox打开某些游戏网页的时候,网页本身可能是Flash展示,它会自动触发Firefox弹出收藏窗口,添加本页进浏览器的Bookmark,这种窗口一旦出现,driver直接停止响应,代码本身都不会再继续往下走了。所以更别提你下面还有处理Alert窗口的代码,那都是浮云,本线程直接就阻塞了,这一度让我非常的火大。后来实在没办法,想了一种折中的方式,设计了另外一个线程进行心跳检测。主线程如果一直都持续运行,定期汇报心跳。一旦超过一段时间没有收到心跳信号,那么独立的心跳检测线程就会试图去模拟回车输入,或者其他动作,来进一步确保浏览器不会被各种奇怪的现象阻塞不能继续。
(四) Other Tips
1. 加载IEDriver的时候,通常会因为兼容模式的设置问题,而无法启动,尝试在创建IEDriver对象的时候,加入合适的参数设置:
[java]
view plain
copy
print
?
- DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
- ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS,true);
- return new InternetExplorerDriver(ieCapabilities);
DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
return new InternetExplorerDriver(ieCapabilities);
2. 关闭浏览器的时候,例如新浪主页,本身会弹出一个独立的广告窗口,如果调用driver.close的时候句柄焦点在主窗口上,那么广告窗口是无法关闭的。所以在关闭driver之前,试图检查一下还有无别的独立窗口句柄存在:
[java]
view plain
copy
print
?
- public staticvoid closeWebDriver(WebDriver driver){
- if(driver == null)
- return;
-
- try{
- String current = driver.getWindowHandle();
- Set<String> otherWins = driver.getWindowHandles();
- for(String winId : otherWins)
- if(winId.equals(current))
- continue;
- else
- driver.switchTo().window(winId).close();
- }catch(Exception ex){
-
- }finally{
- try{
- driver.quit();
- }catch(Exception ex){}
- }
- }
public static void closeWebDriver(WebDriver driver){
if(driver == null)
return;
try{
String current = driver.getWindowHandle();
Set<String> otherWins = driver.getWindowHandles();
for(String winId : otherWins)
if(winId.equals(current))
continue;
else
driver.switchTo().window(winId).close();
}catch(Exception ex){
//ignore
}finally{
try{
driver.quit();
}catch(Exception ex){}
}
}
3. 如何使用WebDriver进行网页截图,提示一点在Flash页面中截图可能会得到一片黑色的屏幕,另外WebDriver截图会把当时整个网页哪怕是需要滚动才显示出来的部分,都会包含在截图里面。
[java]
view plain
copy
print
?
- public static String takeScreenshot(WebDriver driver, String savePath,long delay){
- try {
- CommonUtils.waiting(delay * 1000);
- File screenShotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
- String path = "screenshot/"+savePath;
- if(!savePath.endsWith("/.png"))
- path = path + ".png";
-
- FileUtils.copyFile(screenShotFile, new File(path));
- LOG.info("Take screenshot at "+savePath);
- return path;
- } catch (IOException ex) {
- LOG.warn("failed to take screenshot for current page, caused by "+ex.getMessage());
- return null;
- }
- }