
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/products、https://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的大门。