Selenium自动化测试之道
上QQ阅读APP看书,第一时间看更新

2.4 Selenium WebDriver

如果Selenium IDE已经让你度过了自动化测试的破冰期,那么接下来要学习的Selenium WebDriver会让你开阔视野,帮助我们处理更加复杂的测试场景。Selenium WebDriver提供了一套友好的API,支持Java、Python、Ruby和C#等多种编程语言来创建测试脚本。为了突出Selenium WebDriver的设计思想,弱化编程语言本身对测试脚本的影响,本节将从工作原理入手,在场景演练环节分别介绍Python和Java两门语言的入门示例。

2.4.1 工作原理

Selenium WebDriver是调用浏览器的原生接口来操作浏览器的。也就是说,测试脚本操作浏览器的过程就是在测试脚本中创建WebDriver对象,再通过这个对象调用WebDriver API来访问浏览器接口,从而操作浏览器的过程。

如图2-38所示,我们在测试脚本中使用Selenium WebDriver,无论是哪种平台、哪种浏览器,处理逻辑都是通过一个ComandExecutor发送命令,实际上就是一条发送给Web Service的HTTP请求。Web Service是基于特定WebDriver Wire协议的RESTful接口,测试脚本通知浏览器要做的操作都包含于发送给Web Service的HTTP请求体中。

图2-38 Selenium WebDriver实现原理

不同浏览器的WebDriver子类(FirefoxDriver、InternetExplorerDriver、ChromeDriver、SafariDriver等)都需要依赖特定的浏览器原生组件,例如Firefox需要附加组件webdriver.xpi。而IE需要用到一个dll文件来转化Web Service的命令为浏览器调用。

以下是Selenium HttpCommandExecutor类的部分代码。里面维护了一个Map,它会将简单的命令转化为相应的请求URL。当RESTful Web Service接收到HTTP请求后,它便解析出需要执行的操作。同时,代码还表现出:请求是基于sessionId的,这意味着不同WebDriver对象在多线程并行的时候不会有冲突和干扰。

    nameToUrl = ImmutableMap.<String, CommandInfo>builder()
          .put(NEW_SESSION, post("/session"))
          .put(QUIT, delete("/session/:sessionId"))
          .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))
          .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))
          .put(GET, post("/session/:sessionId/url"))

            // The Alert API is still experimental and should not be used.
          .put(GET_ALERT, get("/session/:sessionId/alert"))
          .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))
          .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))
          .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))
          .put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text"))

Selenium WebDriver把这些逻辑都封装了起来,我们只需要关心driver对象的创建以及调用driver对象的哪个方法来操作页面元素。

2.4.2 元素定位

元素定位,即通过元素特有的属性唯一确定元素的过程。比如说,页面上有标签、文本框、按钮等多个元素,我们要编写脚本来实现按钮的点击操作。在编写Selenium WebDriver脚本之前,首先要了解如何让浏览器“知道”我们将要操作的是哪个元素。

1.常见的元素定位方法

页面中的元素定位是使用DOM元素属性进行实现的,使用Firefox浏览器的Firebug插件或Chrome浏览器的Inspector(检查器)可以很方便地来定位元素,并查看元素属性,如图2-39所示。

图2-39 使用Chrome Inspector查看元素属性

下面介绍常见的元素定位方法。注意,Selenium WebDriver脚本是基于WebDriver对象的,这里为了演示元素定位而跳过了WebDriver对象的创建,直接引用了WebDriver对象(driver)。2.4.3场景演练中有WebDriver对象的创建语句,第3章详细介绍了不同类型的WebDriver对象。

● ID:根据DOM元素的ID属性定位。例如以下元素及相应定位方法:

    <div id="pingxx">...</div>

    WebElement element = driver.findElement(By.id("pingxx "));

● Name:根据DOM元素的Name属性定位。

    <input name="pingxx" type="text"/>
    WebElement element = driver.findElement(By.name("pingxx"));

● ClassName:根据DOM元素的Class属性定位。例如以下元素及相应定位方法:

    <div class="test"><span>Pingxx</span></div><div class="test"><span>Gouda</span></div>

    List<WebElement> tests = driver.findElements(By.className("test"));

● TagName:根据DOM元素的TagName定位。例如以下元素及相应定位方法:

    <iframe src="..."></iframe>

    WebElement frame = driver.findElement(By.tagName("iframe"));

● LinkText:根据DOM元素(link)的文本内容定位。例如以下元素及相应定位方法:

    <a href="http://www.google.com/search? q=pingxx">searchPingxx</a>

    WebElement cheese = driver.findElement(By.linkText("searchPingxx"));

● PartialLinkText:根据DOM元素(link)的部分文本内容定位。例如以下元素及相应定位方法:

    <a href="http://www.google.com/search? q=pingxx">search for Pingxx</a>

    WebElement cheese = driver.findElement(By.partialLinkText("Pingxx"));

● CssSelector:根据CSS选择器定位元素。

    <div id="food"> <span class="dairy">milk</span> <span class="dairy aged">cheese</span> </div>

    WebElement cheese = driver.findElement(By.cssSelector("#food span.dairy.aged"));

● XPath:使用XPath定位元素。在Firefox浏览器中可以下载firebug插件或firepath插件来直接获得元素的XPath。

    <input type="text" name="example" />
    <input type="text" name="other" />

    List<WebElement> inputs = driver.findElements(By.xpath("//input"));

2.如何选择定位方法

前面介绍了多种元素定位的方法,可谓“条条大道通罗马”。那么,在实际项目中,如何选择最适合的定位方法呢?这是测试脚本的编写者经常会面临的问题。

(1)当页面元素存在id属性时,一般使用id来定位,因为id具有唯一性,可以直接定位到这个元素。然而,在实际项目中,有时会出现缺少标准属性(例如没有id属性或者某些元素的id是动态的)、页面刷新会改变等情况,这时就只能选择其他属性来定位。

(2)如果这个元素不存在诸如id这类唯一值的属性,也就是说,我们不方便通过某个值直接定位到这个元素。那么,可以转变思路:先找到一类元素,再通过具体的顺序位置定位到某一个元素。一般在这种情况下,可以考虑用TagName或ClassName。当有链接需要定位时,可以考虑linkText或partialLinkText方式。代码里可以直接看到链接的文本内容,增强代码的可读性,便于维护。

(3)XPath定位很强大,但定位性能不是很好,且可读性也会变差。例如这一段代码:

    driver.findElement(By.id("btn_login")).click();

上述语句可以很明确地看出是在单击“登录”按钮进行登录操作,若使用XPath,这句则变成:

    driver.findElement(By.xpath(“/html/body/div[2]/div/div/div[3]/a[7]”)).click();

显然后者的表述形式不够简明。另一方面,如果页面上的元素位置做了调整,即元素路径变了,那么使用XPath的脚本也要调整,这说明大量使用XPath的测试脚本维护成本很高。因此建议只有在少数元素不好定位的情况下,选择XPath或cssSelector。

(4)上面提到的均是Selenium WebDriver自带的元素定位方法,在一些特殊情况下,它们或许都无法满足我们的需求。但是,我们可以另辟蹊径,用JavaScript来操作元素。Selenium WebDriver提供了执行JavaScript语句的方法,可以直接调用。由于这种方式需要一定的JavaScript功底,笔者这里不做详细介绍,7.2.2小节代码示例中有所涉及。

2.4.3 场景演练

下文将以“Bing搜索”为例介绍Selenium WebDriver编写测试脚本的整个过程。采用Java和Python两门语言作为示例代码。

1.环境准备

(1)确保编程语言的运行环境是可用的。

● Java:不论你是否要写Java代码,最好都先准备JRE环境,2.2小节提到过RemoteWebDriver,它依赖一个启动Jar包Selenium Server,而在2.4节中我们也会用到Jar包。访问http://www.oracle.com/technetwork/java/javase/downloads可下载最新版本的JDK。

● Python:如果你选择Python作为测试脚本的语言,可访问https://www.python.org/downloads/下载安装包。由于Python 2与Python 3不能完全兼容,请选择合适的Python版本下载。本书的示例代码均在2.7.8版本中执行成功。

(2)下载该编程语言对应的Selenium包,又称为Selenium Language Binding Package,如图2-40所示。下载地址:http://docs.seleniumhq.org/download/。如果你使用Python,并且安装了pip,可使用pip install selenium命令下载。

图2-40 WebDriver各个语言包的下载页面

(3)目标浏览器以及相应的Driver文件。因浏览器的不同,Driver文件的形式和用法也不同。各个Driver对象的介绍将在第3章中详细说明。下文示例中将使用FireFoxDrvier,无须额外下载Driver文件,只需要Firefox浏览器安装完成即可。

2. Python篇:Selenium WebDriver脚本

测试场景:在http://cn.bing.com/中搜索关键字WebDriver。

具体步骤:

步骤 01 在脚本中导入Selenium Python包。

步骤 02 创建Firefox的WebDriver对象。

步骤 03 调用get方法打开Bing页面。

步骤 04 找到搜索文本框,输入WebDriver。

步骤 05 单击“搜索”按钮。

步骤 06 调用quit方法关闭页面,结束测试。

    from selenium import webdriver
    driver =webdriver.Firefox()
    driver.get("http://cn.bing.com/")
    driver.find_element_by_id("sb_form_q").send_keys("WebDriver")
    driver.find_element_by_id("sb_form_go").click()
    driver.quit()

将上述代码保存为Python文件即可执行。这是一个最简单的示例,既没有考虑页面加载所需的等待时间,也没有考虑输出测试结果。希望通过这个示例能让你感受到Selenium WebDriver的入门非常简单。至于下面的Java篇,是希望让你了解,对于不同的编程语言,Selenium WebDriver的使用也非常类似。无论你在实际工作中使用的是Python还是Java,抑或是其他语言,本书各个示例都有一定的参考价值。

3. Java篇:Selenium WebDriver脚本

(1)创建测试用例

使用Selenium WebDriver原本与单元测试框架并没有直接的关系,本节引入JUnit单元测试框架,可以方便生成报告、进行代码注解等。

在Eclipse的测试项目中新建一个JUnit Test Case,选择New JUnit 4 test类型创建,如图2-41所示。Eclipse一般会自带JUnit4的Jar包,只需在单击Finish后弹出的窗口中单击“确定”按钮即可。如图2-42所示,JUnit包已经添加到项目中了。

图2-41 新建Test Case

图2-42 测试项目的文件结构

(2)编写测试脚本

创建成功后就可以在TestBing.java中写入测试代码了,代码如下。这里为了方便,使用了Thread.sleep(2000);作为等待处理,关于Selenium WebDriver的Wait写法,参见2.4.4小节。

    package com.selenium.test;
    import org.junit.*;
    import org.openqa.selenium.*;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.firefox.FirefoxDriver;
      public class TestBing{
        private WebDriver driver;
        private String baseUrl;

    @Before
      public void setUp()throws Exception{
          driver=new FirefoxDriver();
          baseUrl="http://cn.bing.com/";
      }

    @Test
      public void testBing()throws Exception{
          driver.get(baseUrl);
          Thread.sleep(2000);

          driver.findElement(By.id("sb_form_q")).sendKeys("WebDriver");
          driver.findElement(By.id("sb_form_go")).click();
      }

    @After
      public void tearDown()throws Exception{
          driver.quit();
      }
    }

(3)运行测试脚本

运行此程序时,需要在TestBing.java上右击,然后在弹出的快捷菜单中依次单击Run As→JUnit Test。

以上述代码为例,若执行成功,则会弹出火狐浏览器,自动打开必应首页并输入关键字进行搜索,之后浏览器自动退出,测试结束,如图2-43所示。

图2-43 脚本的执行结果

2.4.4 Wait

在2.3.3小节Selenese中,我们已经了解了Selenium IDE提供的Wait方法。接下来,将学习Wait在Selenium WebDriver中的写法。

1.显式等待

显式等待(Explicit Wait)就是提供了明确的等待条件,若条件满足,则不再等待,继续执行后续代码。例如,我们需要清空id属性为sb_form_q的文本框内容,用Java代码可以写为:

    driver.findElement(By.id(“sb_form_q”)).clear();

为了避免在页面加载过程中因sb_form_q元素未出现导致代码执行失败,我们可以增加Wait条件,等待sb_form_q元素出现之后,再做清空操作。其代码如下:

    WebElement searchElement = (new WebDriverWait(driver,10)).until(new ExpectedCondition
<WebElement>(){
    public WebElement apply (WebDriver wd){
            return wd.findElement(By.id("sb_form_q"));
        }
        });
    searchElement.clear();

在上述代码中,时间设置为最多等待10秒,超时会抛出异常TimeoutException。在这10秒内,WebDriverWait默认每隔500毫秒调用一次ExpectedCondition来确认是否找到元素。

2.隐式等待

隐式等待(Implicit Wait)没有明确设定在何时开始等待,它相当于设置了全局范围的等待,对所有元素设置等待时间,在WebDriver对象的整个生命周期中生效。若不设置,则默认为0。

代码如下,在初始化方法中,设置了全局30秒等待时间:

    @Before
      public void setUp() throws Exception {
        driver = new FirefoxDriver();
        baseUrl = "https://www.xxx.com";
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    }

2.4.5 常用的断言

表2-1罗列了Selenium WebDriver常用的断言方法,并附上Selenium IDE中对应的Selenese命令作为对照。

表2-1 Selenium WebDriver常用的断言方法

(续表)