Selenium WebDriver使用经验杂记

http://blog.csdn.net/ant_ren/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 ?
  1. FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA"));    
  2. fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS);    
  3. fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS);    
  4. fluentWait.until(new Predicate<By>() {    
  5.     public boolean apply(By by) {    
  6.         try {    
  7.             return browser.findElement(by).isDisplayed();    
  8.         } catch (NoSuchElementException ex) {    
  9.             return false;    
  10.         }    
  11.     }    
  12. });    
  13.   
  14. browser.findElement(By.tagName("TEXTAREA")).sendKeys("text to enter");   

或者也可以直接使用Wait接口的until方法,传入一个ExpectedCondition
[java] view plain copy print ?
  1. public ExpectedCondition<WebElement> visibilityOfElementLocated(final By by) {    
  2.     return new ExpectedCondition<WebElement>() {    
  3.           public WebElement apply(WebDriver driver) {    
  4.             WebElement element = driver.findElement(by);    
  5.             return element.isDisplayed() ? element : null;    
  6.           }    
  7.     };    
  8. }    
  9.       
  10. public void performSomeAction() {    
  11.     ..    
  12.     ..    
  13.     Wait<WebDriver> wait = new WebDriverWait(driver, 20);    
  14.     WebElement element = wait.until(visibilityOfElementLocated(By.tagName("a")));    
  15.     ..            
  16. }    

2. 利用Javascript注入,来读取不同Ajax调用框架的Ajax request status,一直等到Ajax调用全部返回才开始分析操作Dom元素
演示代码如下:

[java] view plain copy print ?
  1. protected void syncAjaxByJQuery(String timeout) {    
  2.     boolean isSucceed = false;    
  3.     try {    
  4.         selenium.waitForCondition(    
  5.                 "selenium.browserbot.getCurrentWindow().jQuery.active == 0", timeout);    
  6.         isSucceed = true;    
  7.     } catch (SeleniumException se) {    
  8.         LOG.error(se);    
  9.     } catch (Exception re) {    
  10.         throw new RuntimeException(re.getMessage());    
  11.     }    
  12.     operationCheck(isSucceed);    
  13. }  
  14.   
  15.   
  16. protected void syncAjaxByPrototype(String timeout) {    
  17.     boolean isSucceed = false;    
  18.     try {    
  19.         selenium.waitForCondition(    
  20.                 "selenium.browserbot.getCurrentWindow().Ajax.activeRequestCount == 0",    
  21.                 timeout);    
  22.         isSucceed = true;    
  23.     } catch (SeleniumException se) {    
  24.         LOG.error(se);    
  25.     } catch (Exception re) {    
  26.         throw new RuntimeException(re.getMessage());    
  27.     }    
  28.     operationCheck(isSucceed);    
  29. }  
  30.   
  31.   
  32. protected void syncAjaxByDojo(String timeout) {    
  33.     boolean isSucceed = false;    
  34.     try {    
  35.         selenium.waitForCondition(    
  36.                 "selenium.browserbot.getCurrentWindow().dojo.io.XMLHTTPTransport.inFlight.length == 0",    
  37.                 timeout);    
  38.         isSucceed = true;    
  39.     } catch (SeleniumException se) {    
  40.         LOG.error(se);    
  41.     } catch (Exception re) {    
  42.         throw new RuntimeException(re.getMessage());    
  43.     }    
  44.     operationCheck(isSucceed);    
  45. }  


(二) 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 ?
  1. public T handleAlert(){  
  2.     KeyboardUtils.enter();  
  3.     long clockTime = System.currentTimeMillis();  
  4.     long timeout = 3 * 1000;  
  5.     while(true){  
  6.         if((System.currentTimeMillis()-clockTime) > timeout){  
  7.             KeyboardUtils.enter();  
  8.             break;  
  9.         }else if(WebDriverUtils.isAlertExist(driver)){  
  10.             try{  
  11.                 Alert alert = driver.switchTo().alert();  
  12.                 alert.getText();  
  13.                 alert.accept();  
  14.             }catch(Exception ex){  
  15.                 KeyboardUtils.enter();  
  16.             }  
  17.         }  
  18.           
  19.         waiting(300);  
  20.     }     
  21.     return instance;  
  22. }  
  23.   
  24.   
  25. //In WebDriverUtils.class  
  26. public static boolean isAlertExist(WebDriver driver){  
  27.     try{  
  28.         driver.switchTo().alert();  
  29.         return true;  
  30.     }catch(NoAlertPresentException  ex){  
  31.         return false;  
  32.     }  
  33. }  


(三) To Handle more than Alert
还有一种情况,当我用Firefox打开某些游戏网页的时候,网页本身可能是Flash展示,它会自动触发Firefox弹出收藏窗口,添加本页进浏览器的Bookmark,这种窗口一旦出现,driver直接停止响应,代码本身都不会再继续往下走了。所以更别提你下面还有处理Alert窗口的代码,那都是浮云,本线程直接就阻塞了,这一度让我非常的火大。后来实在没办法,想了一种折中的方式,设计了另外一个线程进行心跳检测。主线程如果一直都持续运行,定期汇报心跳。一旦超过一段时间没有收到心跳信号,那么独立的心跳检测线程就会试图去模拟回车输入,或者其他动作,来进一步确保浏览器不会被各种奇怪的现象阻塞不能继续。


(四) Other Tips
1. 加载IEDriver的时候,通常会因为兼容模式的设置问题,而无法启动,尝试在创建IEDriver对象的时候,加入合适的参数设置:

[java] view plain copy print ?
  1. DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();   
  2. ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);  
  3. return new InternetExplorerDriver(ieCapabilities);  

2. 关闭浏览器的时候,例如新浪主页,本身会弹出一个独立的广告窗口,如果调用driver.close的时候句柄焦点在主窗口上,那么广告窗口是无法关闭的。所以在关闭driver之前,试图检查一下还有无别的独立窗口句柄存在:
[java] view plain copy print ?
  1. public static void closeWebDriver(WebDriver driver){  
  2.     if(driver == null)  
  3.         return;  
  4.       
  5.     try{  
  6.         String current = driver.getWindowHandle();  
  7.         Set<String> otherWins = driver.getWindowHandles();  
  8.         for(String winId : otherWins)  
  9.             if(winId.equals(current))  
  10.                 continue;  
  11.             else  
  12.                 driver.switchTo().window(winId).close();  
  13.     }catch(Exception ex){  
  14.         //ignore  
  15.     }finally{  
  16.         try{  
  17.             driver.quit();  
  18.         }catch(Exception ex){}  
  19.     }  
  20. }  

3. 如何使用WebDriver进行网页截图,提示一点在Flash页面中截图可能会得到一片黑色的屏幕,另外WebDriver截图会把当时整个网页哪怕是需要滚动才显示出来的部分,都会包含在截图里面。
[java] view plain copy print ?
  1. public static String takeScreenshot(WebDriver driver, String savePath, long delay){  
  2.      try {  
  3.         CommonUtils.waiting(delay * 1000);  
  4.         File screenShotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);  
  5.         String path = "screenshot/"+savePath;  
  6.         if(!savePath.endsWith("/.png"))  
  7.             path = path + ".png";  
  8.           
  9.         FileUtils.copyFile(screenShotFile, new File(path));  
  10.         LOG.info("Take screenshot at "+savePath);  
  11.         return path;  
  12.     } catch (IOException ex) {  
  13.         LOG.warn("failed to take screenshot for current page, caused by "+ex.getMessage());  
  14.         return null;  
  15.     }  


你可能感兴趣的:(Selenium WebDriver使用经验杂记)