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

2.3 Selenium IDE

正如2.2节中介绍的,Selenium IDE是一个Firefox浏览器的附加组件,不是一个独立的工具。它安装简单且易学,可以将Web页面上的操作录制下来,转换为脚本文件,是Selenium家族中最容易上手的工具。本节先详细介绍Selenium IDE的功能,再结合场景演练展示Selenium IDE的实践过程。

2.3.1 安装Selenium IDE

笔者编写本章时,Selenium IDE的最新版本为2.9.1,支持Firefox 17.0及以上版本。

打开Firefox浏览器,前往https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/下载IDE插件。如图2-2所示,单击Add to Firefox,进入安装页面后,单击“安装”即可。

图2-2 在FireFox中安装Selenium IDE

安装完成后,在Firefox的“工具”菜单中可看到Selenium IDE选项,到此安装完毕,如图2-3所示。

图2-3 Selenium IDE出现在工具菜单栏

2.3.2 Selenium IDE的使用

打开Selenium IDE,其界面如图2-4所示。

图2-4 Selenium IDE界面

● Base URL

指目标测试站点的根路径,脚本运行时会默认打开这个地址。该站点下的页面可以在此URL基础上使用相对URL。比如,我们要测试的Web站点是https://www.pingxx.com,待测试页面的完整URL是https://www.pingxx.com/productshttps://www.pingxx.com/customers之类,可以将Base URL写为https://www.pingxx.com,如图2-5所示。若要测试其他页面,则可以在测试脚本中使用2.3.3小节Selenese中介绍的open命令,打开/products、/customers等页面。

图2-5 Base URL

● Tool Bar

控制回放速度,即控制每个Command之间执行的时间间隔。

回放Test Suite,即执行Test Case Pane中显示的所有Test Case。

回放选中的Test Case。

暂停/继续

暂停后按步执行,用于对录制脚本进行调试。

添加Rollup,将在2.3.3小节中对Rollup的用法进行详细介绍。

收藏的Test Suite,单击菜单栏中的Favorites→Add favorite收藏,前提是该Test Suite已经保存了。

开始/停止录制。

制定执行计划,支持脚本定时运行,如图2-6所示。

图2-6 脚本定时计划

● Test Case Pane

当前Test Suite中的Test Case列表。运行之后,绿色表示通过,红色表示失败。

如图2-7所示,执行结果表明:执行了两个Test Case,其中一个失败了。

图2-7 Test Case Pane(测试范例面板)

● Editor Pane

Editor Pane用于显示界面操作,也就是我们在测试过程中需要对页面上的元素执行操作。当Selenium IDE处于录制状态时,我们在浏览器上的操作将被记录到这里。

如图2-8所示,Editor Pane分为Table和Source两种视图。

图2-8 Editor Pane(编辑面板)

默认显示Table视图。其中,分为三大元素。

● Command:操作命令,如open、click、type,会在2.3.3小节Selenese中进行详细说明。

● Target:操作目标。根据操作命令不同,操作目标的类型也不尽相同。它可能是某个元素的属性值,也有可能是一个表达式。

● Value:操作值,如图2-8中在文本框输入的用户名、密码,若要更新Table视图中的内容,选中一行后即可编辑。

● Select按钮:单击Select,再将鼠标移至浏览器页面,鼠标划过的元素会高亮显示,单击即可修改Target为该目标。

● Find按钮:选中Table视图中的一行,单击Find,页面中该对象将高亮显示。

● Source视图:顾名思义,即显示Table视图相应的源码。如图2-9所示,Source视图默认为HTML代码。

图2-9 Editor Pane的Source视图

Source视图还支持其他语言的转换,但是并不提倡这种做法,进入Options→Format中的链接(http://blog.reallysimplethoughts.com/2011/06/10/does-selenium-ide-v1-0-11-support-changing-formats/)说明了原因:目前Selenium IDE只有在HTML格式下才可以稳定工作,其他语言格式还在实验阶段,仍需要做很多工作来保证其稳定性。如果一定要转换,可进入Options→Options…→General,勾选Enable experimental features,再进入Options→Format,如图2-10所示。

图2-10 支持多种语言的转换

当你选择其他语言时,IDE也会给出不推荐转换语言的提示,如图2-11所示。

图2-11 不推荐转换语言的提示

转换为其他语言后,执行按钮、Table视图都被置灰,如图2-12所示。在2.3.3小节中介绍了导出脚本的方式,可转换为各种编程语言。

图2-12 Table视图不可用

● Log

如图2-13所示,每一个步骤的执行结果都将记录为Log。默认显示所有log,可以根据不同类型(Debug、Info、Warn、Error)过滤,便于调试。

图2-13 Log

● Reference

显示被选中Command的功能说明,如图2-14所示,包括所需要的参数,便于快速了解Command的用法。

图2-14 Reference

● UI-Element

Selenium的高级用法,使用JavaScript定义对象。本节不做介绍,可通过帮助→UI-Element Documentation了解详情。

● Rollup

将多个操作合并到一个操作步骤中,将步骤内容写入JavaScript文件后,作为用户扩展导入。导入完成后,便可以在Rollup标签页中看到JavaScript文件中定义的步骤说明,如图2-15所示。2.3.3小节将演示Rollup的用法。

图2-15 Rollup

提示

遗憾的是,在Mozilla发布了Firefox 55.0版本的第二天,即2017年8月9日,Selenium官方在博客上进行了正式说明,Selenium IDE无法在Firefox 55中使用。博文中提到了两个原因,一是浏览器是不断发展的复杂软件。Mozilla一直在努力将Firefox提升得更快更稳定。Firefox浏览器扩展正在从原来的“XPI”格式转换为新的更广泛采用的“Web扩展”机制。二是Selenium项目缺少人力,没有足够的时间和精力将新的技术应用到IDE迭代中。

当然,博文中也提到了Selenium IDE在重构,还号召更多的开发者加入到Selenium项目中来。作为具有广泛影响力和群众基础的项目,相信Selenium IDE在重构之后,将以崭新的面貌出现在世人面前。

官方博文地址:https://seleniumhq.wordpress.com/2017/08/09/firefox-55-and-selenium-ide/

2.3.3 场景演练

前文已经对Selenium IDE做了详细的介绍,接下来,我们将测试一个具体的场景,来掌握Selenium IDE的使用。

业务背景:企业用户在登录系统之后,可在Ping++管理平台的企业账户中进行充值,用于身份实名认证和银行卡认证接口费用。可以简单理解为一个电子钱包,支持充值、消费、查看交易明细。

场景说明:登录Ping++管理平台并查看企业账户及余额、明细,如图2-16所示。

图2-16 待测页面截图

Selenium IDE使用过程如下:

步骤 01 打开Firefox,确保Selenium IDE在录制状态。

步骤 02 在Firefox浏览器上进行测试操作,例如访问待测系统、进入页面、点击按钮等操作。

步骤 03 在操作过程中观察Selenium IDE界面上的命令变化。在Firefox页面上每做一次操作,都会录制形成一条命令。

步骤 04 停止录制,查看结果。

使用过程很简单,录制结果如图2-17所示。

图2-17 Selenium IDE录制结果

录制完成后,单击File→Save Test Case,保存脚本到本地,文件格式为HTML。

如图2-17所示为由录制生成的Test Case,没有经过任何的改动。单击按钮进行回放,你会发现运行过程中抛出了异常,无法通过,如图2-18所示。

图2-18 执行测试失败

如果你动手实践了上述操作,或许你已经基于Log面板中的信息找到了脚本异常的原因。如果你还没有想明白,别着急,让我们带着这个疑问先对Selenium IDE提供的命令进行系统性的学习。

我们不仅要了解如何解决脚本中的异常,创建一个可以正常运行的脚本,更重要的是,利用Selenium IDE的高级功能(例如模糊匹配,Roll up)使脚本更加完善,易于维护。

1. Selenese

Selenese是命令集合的统称,分为以下几种类型。

● Action:直接作用在页面元素上的操作,如点击、输入等。

常用的有:


➢ open打开页面。

➢ type输入内容。

➢ click点击。

➢ sendKeys键盘输入。


● Accessor:将某个值保存到变量中,方便复用,提高可维护性。

使用store命令来储存变量,如图2-19所示,将变量值作为Target的内容,自定义的变量名作为Value的内容。当我们要使用变量的时候,用${变量名}格式引用变量。图2-19将用户名、密码分别存储到变量username与password中,如果测试脚本的多个步骤都用到了用户名和密码,使用上述变量的方式会让脚本易于维护。如果用户名密码改变了,我们无须逐个修改步骤中的值,只要更新变量值即可,这就是所谓的“脚本参数化”。否则,我们需要确认每个使用用户名、密码的步骤都做了更新。

图2-19 脚本参数化

除了上文提到的store命令之外,常用的Accessor类型的命令还有:


➢ storeTitle储存当前页面的标题(title)。

➢ storeText储存元素的文本(text)属性值。

➢ storeElementPresent记录元素是否存在,返回true或false。


● Assertions:Assertions即检查点,Selenium IDE的“检查点”支持两种类型:

Assert与Verify。Assert又译作“断言”,它不是某个测试工具独有的概念,各种测试框架中都能见到它的身影。Assert用于检查指定条件是否满足,如果不满足,整个Test Case运行终止。举个例子,我们可以在Test Case开始时用Assert相关命令检查页面的title属性是不是等于期望值,如图2-20所示,如果不等于,可能是页面跳转有误,没有必要继续执行后续操作,脚本会即刻终止。

图2-20 使用assertTitle

常用的Assert命令有:


➢ assertLocation检查当前是否在正确的页面。

➢ assertTitle检查当前页面的title。

➢ assertValue检查输入的值。

➢ assertSelected检查select的下拉菜单中选中是否正确。

➢ assertText检查指定文本是否正确。

➢ assertTextPresent /assertTextNotPresent检查指定文本存在/不存在。

➢ assertEditabl/assertNotEditable检查指定文本框可编辑/不可编辑。


Verify也是用于验证指定条件是否等于期望值,如图2-21所示。与Assert不同的是,如果不等于期望值,那么当前步骤失败,继续执行下一步。它不会影响后续步骤的执行。

图2-21 使用verifyVisible

常用的Vertify命令有:


➢ verifyTitle验证页面title是否正确。

➢ verifyTextPresent/ verifyTextNotPresent验证指定文本存在/不存在。

➢ verifyElementPresent/ verifyElementNotPresent验证指定元素存在/不存在。

➢ verifyVisible验证指定元素是否可见。


● Wait:手工测试的过程中,页面毫秒级的加载速度肉眼是感觉不到的,而对于自动化测试而言,页面的加载问题是测试脚本中一个重要的考虑因素。况且由于网络、服务器等问题可能会导致页面加载慢。在手工测试过程中,测试人员往往是等待元素加载完成后才会进行操作;同样地,测试脚本也要学会“等待”。否则就会遇到2.3.3场景演练开篇脚本中出现的异常。

Selenium IDE提供的Wait命令分为两种形式。一种形式简写为“***AndWait”,即执行操作后,要等待页面刷新完成才进行下一步,常见的有:clickAndWait(点击并等待)、typeAndWait(输入并等待)、selectAndWait(选择并等待);另一种形式简写为“waitFor***”,即等待直到符合某一特定条件才进行下一步,常见的有:waitForElementPresent(等待直到指定的元素出现在页面上)、waitForVisible(等待直到元素可见。它与waitForElementPresent非常类似,两者之间的微妙差别是:Present是以HTML元素的形式存在于页面上的,而Visibility则是CSS设置的)、waitForTitle(等待直到页面title为指定的内容)。

Selenium IDE默认的最大等待时间为30000毫秒,即若在30秒内没有找到元素,则测试步骤失败,脚本抛出异常。可进入Selenium IDE菜单栏的Options→Options…→General,设置Default timeout value,单位为毫秒。

如图2-22所示,余额明细是在页面加载完成之后出现的,可以用它作为加载完成的标志。

图2-22 待测页面的截图

如图2-23所示,使用waitForElementPresent命令,使脚本在出现余额明细之后才会执行后续操作。

图2-23 使用wait命令

除了图2-23的waitForElementPresent方法,本例还可以使用其他的waitFor***方法。图2-24演示了多种wait命令的使用。这里需要特别说明的是,为了演示clickAndWait的用法,在图2-24录制脚本中第4步waitForTitle之前,我们使用了clickAndWait命令。而事实上,脚本中clickAndWait有画蛇添足之嫌。通常的做法是:“先click再waitForTitle”或者“先clickAndWait再assertTitle”。

图2-24 多种wait命令的使用

你或许注意到,示例中还使用了另一种等待方式:pause,暂停2秒。它与waitFor***的区别在于,waitFor ***是隐式等待,如果条件满足,就立即执行下一步,否则就一直等到Selenium IDE设置的最大等待时间,因此具体的等待时间是不固定的;而pause是显示等待,明确了等待时间,一定要等到时间结束为止。二者相比,waitFor***更高效。

到此,我们已经了解了Selenese命令的类型及其常见用法。如图2-25所示,运用Selenese命令更新Selenium IDE录制脚本之后,脚本立刻“旧貌换新颜”,不再因为加载问题导致执行失败,还做了参数化,设置了基本的检查点。

图2-25 运用Selenese命令更新Selenium IDE录制脚本

2.保存脚本

可以将一系列Test Case保存为一个Test Suite,作为一个脚本文件来管理。

如图2-26所示,单击File→Save Test Suite,选择位置并保存。

图2-26 保存Test Suite

打开保存的文件,文件为HTML格式,每个Test Case为一个链接,如图2-27所示。

图2-27 保存的Test Suite文件

点开一个Test Case链接,是一个HTML表格,如图2-28所示。

图2-28 Test Suite文件中的Test Case

3.编辑脚本的技巧

编辑和调试脚本的过程中,有如下小技巧:

● 在页面中右击,在打开的快捷菜单中选择Insert New Command直接插入步骤。

● 使用暂停,点击即可逐步执行,方便观察脚本的执行状态。

● 在页面中右击,在打开的快捷菜单中添加Set/Clear Start Point,或者选中目标行,使用快捷键S添加Start Point,只能设置一个,每次执行从此开始,不必从头开始执行脚本。

● 从右键菜单添加Toggle Breakpoint,或者选中目标行,使用快捷键B设置Breakpoint(断点),脚本执行至此会暂停。

● 可在右键菜单Insert New Comment中添加Comment,增强脚本可读性。

● 确保打开Selenium IDE后,在浏览器中对目标元素右击,打开如图2-29所示的菜单,可快速添加assert、verify、store、waitFor等命令。

图2-29 浏览器中有关Selenium IDE的菜单

4.模糊匹配在检查点中的使用

有一些页面元素的属性值不是固定的,且符合一定的规则,比如带有Test字符串前缀的订单号。当我们要为这些动态值设置检查点的时候,可以使用模糊匹配的功能。接下来,介绍以下几种模糊匹配的类型。

Globbing

前缀:glob:在selenium中只支持两种特殊匹配字符。

● *:可以被理解为anything,可以匹配无、单个字符、若干字符,如图2-30所示。

图2-30 glob的用法

● []:匹配[]中包含的任一单个字符,如[abcdef],代表匹配a到f的小写字符(注意区分大小写),可以使用“-”来代表一定范围内的字符集(在ACCII中相连的),如[a-f]。

regular expression(正则表达式)

前缀:regexp:或者regexpi:,前者区分大小写,后者不区分大小写。

对于正则表达式,你一定不陌生,作为匹配模式,它是最强大、最灵活的。在Selenese中,正则表达式可以完成其他匹配模式比较难以完成的任务。例如,regexp: [0-9]+ 表明只允许数字,如图2-31所示。

图2-31 regexp的用法

exact patterns

前缀:exact:当你需要找到*、[]、{}这样的特殊字符时,就需要用到exact匹配模式。

5. Rollup

在实际工作中,往往需要在多个Test Cases中加入多个相同的步骤,比如作为前置条件的登录等。那么,是不是要复制相同的步骤到每个Test Case中?Selenium IDE的Rollup功能为我们提供了更好的解决方案。

Rollup的做法是,将Test Cases中的相同步骤抽离出来,把它们在JavaScript文件中定义好,之后将这个js文件作为user-extension导入Selenium IDE中。导入之后,在js文件中定义的多个步骤看起来就像是一个步骤似的被Test Case调用。

下面是一个简单的Rollup范例,将登录步骤合并为一步,具体步骤如下。

步骤 01 将登录操作的源码保存为loginCommandsRollup.js文件。

    var manager = new RollupManager);
    manager.addRollupRule{
        //name,description,pre,post中描述了Rollup的基本属性,在IDE中,选中Rollup行,在下
方的Rollup面板中可以看到,便于用户了解该Rollup
        name:'loginCommandsRollup',   //Rollup的名字,在引入该Rollup时使用
        description:'Combine login commands.',   //描述,介绍该Rollup的功能
        pre: 'The Dashboard works.The username and password is correct.', //执行该Rollup的前提条件
        post:'The user login Dashboard successfully.',  //执行该Rollup的结果
        args: [],
        commandMatchers: [],
        getExpandedCommands: functionargs) {
          var commands=[];  //定义一个Rollup包含的Step列表
          //如下为Rollup包含的Step,按照顺序,依次被执行,每个Step包含3个字段,command、
target、value,在此不再赘述
          commands.push{
              command: 'open',
              target: '/',
              value: ''
          });
          commands.push{
              command: 'clickAndWait',
              target: 'link=登入',
              value: ''
          });
          commands.push{
              command: 'type',
              target: 'id=username',
              value: '*********'
          });
          commands.push{
              command: 'type',
              target: 'id=password',
              value: '**********'
          });

          commands.push{
              command: 'click',
              target: 'id=loginBtn',
              value: ''
          });
          return commands;
        }
    });

步骤 02 打开Selenium IDE,进入options-options…-General,如图2-32所示。单击Selenium Core extensions.js(user-extensions.js) 一栏的Browser按钮,选中本地的js文件,即可导入Selenium Core extentions中。

图2-32 导入js文件,以供Rollup使用

步骤 03 回到Selenium IDE面板,添加Step, Commands填写rollup, Target填写上述.js文件中的name,即loginCommandsRollup,这样便完成了Rollup的添加。

步骤 04 选中Rollup行,切换至Rollup面板,即可看到详情,一目了然,如图2-33所示。

图2-33 Rollup的引用效果

另外,如图2-34所示,你也可以单击工具栏中的Apply rollup rules,直接添加Rollup。

图2-34 添加Rollup的对话框

可以看到,原本包含多个步骤的登录操作被合并成一个步骤了。这样做不但简化了脚本结构,还可以在不同的Test Case中引入这个Rollup,提高了重用性,降低了维护成本。

提示

添加Rollup扩展之后,需要重启Selenium IDE方可生效。

Rollup只适用于Selenium IDE界面,导出的各个语言的脚本均不支持Rollup,比如Python报错如下:

    # ERROR: Caught exception [ERROR: Unsupported command [rollup | loginCommandsRollup | ]]

6.导出脚本

Selenium IDE支持将Test Case导出为C#、Java、Python 2、Ruby脚本(其中不支持将Test Suite导出为Python脚本)。下面以导出基于Python的unitest框架及WebDriver的脚本为例来介绍具体的操作步骤。

步骤 01 如图2-35所示,录制脚本已经准备好,该脚本实现登录Ping++管理平台并进入企业账户的测试场景。

图2-35 录制脚本已准备好

步骤 02 如图2-36所示,进入File,选择Export Test Case As... →Python 2/unittest/WebDriver,扩展名为.py,保存至本地。

图2-36 File菜单

步骤 03 查看导出的脚本,代码如下

    # -*- coding: utf-8-*-
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.support.ui import Select
    from selenium.common.exceptions import NoSuchElementException
    from selenium.common.exceptions import NoAlertPresentException
    import unittest, time, re

    class LoginDashboardAndCheckBalance(unittest.TestCase):
      def setUp(self):
          self.driver = webdriver.Firefox()
          self.driver.implicitly_wait(30)
          self.base_url = "https://www.pingxx.com/"
          self.verificationErrors = []
          self.accept_next_alert = True

        def test_login_dashboard_and_check_balance(self):
          driver = self.driver
          # open | / |
          driver.get(self.base_url + "/")
          #assertTitle|Ping++ 聚合支付系统 | 支付宝 微信支付 分期Apple Pay|
          self.assertEqual(u"Ping++ 聚合支付系统 | 支付宝 微信支付 分期Apple Pay",
driver.title)
          #click|link=登入 |
          driver.find_element_by_link_text(u"登入").click()
          #waitForTitle| 管理平台 |Ping++|
          for i in range(60):
              try:
                  if u"管理平台 |Ping++"==driver.title: break
              except: pass
              time.sleep(1)
          else: self.fail("time out")
          # store | ********** | username
          username = "**********"
          # store | ******** | password
          password = "********"
          # type | id=username | ${username}
          driver.find_element_by_id("username").clear()
          driver.find_element_by_id("username").send_keys(username)
          # type | id=password | ${password}
          driver.find_element_by_id("password").clear()
          driver.find_element_by_id("password").send_keys(password)
          # click | id=loginBtn |
          driver.find_element_by_id("loginBtn").click()
          # 等待账户入口出现
          # waitForElementPresent | id=accountMenu |
          for i in range(60):
              try:
                if self.is_element_present(By.ID, "accountMenu"): break
              except: pass
              time.sleep(1)
          else: self.fail("time out")
          # verifyVisible | id=accountMenu |
          try: self.assertTrue(driver.find_element_by_id("accountMenu").is_displayed())
          except AssertionError as e: self.verificationErrors.append(str(e))
          # click | //div[@id='accountMenu']/span |
          driver.find_element_by_xpath("//div[@id='accountMenu']/span").click()
          #click|link=企业账户 |
          driver.find_element_by_link_text(u"企业账户").click()
          # waitForVisible | class=text-blue balance_charge |
          # ERROR: Caught exception [Error: unknown strategy [class] for locator [class=text-blue
balance_charge]]
          # waitForElementPresent | css=a.active |
          for i in range(60):
              try:
                if self.is_element_present(By.CSS_SELECTOR, "a.active"): break
              except: pass
              time.sleep(1)
          else: self.fail("time out")
          # click | css=a.active |
          driver.find_element_by_css_selector("a.active").click()
          # waitForVisible | css=span.text-blue.balance_details |
          for i in range(60):
              try:
                  if driver.find_element_by_css_selector ("span.text-blue.balance_details").is_displayed():
break
              except: pass
              time.sleep(1)
          else: self.fail("time out")
          # click | css=span.text-blue.balance_details |
          driver.find_element_by_css_selector("span.text-blue.balance_details").click()
          # 等待余额明细可见
          # waitForElementPresent | id=balance_details |
          for i in range(60):
              try:
                  if self.is_element_present(By.ID, "balance_details"): break
              except: pass
              time.sleep(1)
          else: self.fail("time out")

        def is_element_present(self, how, what):
          try: self.driver.find_element(by=how, value=what)
          except NoSuchElementException as e: return False
          return True

        def is_alert_present(self):
          try: self.driver.switch_to_alert()
          except NoAlertPresentException as e: return False
          return True

        def close_alert_and_get_its_text(self):
          try:
              alert = self.driver.switch_to_alert()
              alert_text = alert.text
              if self.accept_next_alert:
                  alert.accept()
              else:
                  alert.dismiss()
              return alert_text
          finally: self.accept_next_alert = True
        def tearDown(self):
            self.driver.quit()
            self.assertEqual([], self.verificationErrors)
    if__name__=="__main__":
        unittest.main()

上述脚本中,除了实际的操作步骤之外,末尾还有一些附加的代码,这些代码是可在IDE中设置的。具体的设置方法是,进入Option→Option...→Formats,选择Python 2/unittest/WebDriver,如图2-37所示,可自定义Header、Footer、Indent、Show Selenese等,以及关于Selenium RC的配置。

图2-37 配置导出脚本的格式

在此介绍的Selenium IDE应用只是Selenium家族的冰山一角。

你会发现,Selenium IDE可快速上手,方便新手入门,但也恰恰因为它的简易,只能录制、管理比较简单的测试用例,不能作为开发和维护复杂测试集合的解决方案。后续我们将了解Selenium家族的其他成员,希望本小节已经开启你学习Selenium的大门。