在软件生命周期各个阶段都逐渐开始重视交付速度的情况下,全球化测试同样也面临了敏捷的挑战。因此自动化测试也开始在全球化测试领域变得流行起来,但是由于受限于翻译验证性测试中"一次编写,多个语言环境下运行"的特点,在利用 Selenium 进行 Web 自动化测试时对元素的一致定位成了编写自动化脚本的瓶颈。本文将针对现阶段遇到的问题结合实例详细阐述在利用 Selenium 对 Web 应用进行自动化测试时定位元素的方法,并针对全球化测试中遇到的难点给予重点解答。
翻译验证性测试中定位 Web 元素遇到的挑战
翻译验证性测试是全球化测试中的一部分,旨在验证软件用户界面中需要翻译的字符串是否已经正确翻译。为了提高效率,目前一般采用先由熟悉产品的测试支持工程师书写测试用例,再由熟悉各国语言的测试人员执行用例或者先由测试支持工程师将复杂用例的执行结果在各个语言环境下截图,然后再发送给测试人员进行验证两种模式。目前随着持续交付模式的出现,无论是测试人员多语言环境下重复执行用例还是测试支持工程师多语言环境下手动截图都难以满足持续交付对速度的要求,因此测试支持工程师编写自动化脚本完成多语言环境下的自动截图变成了提高效率的必然选择。
在传统的 Web 自动化测试应用场景中,由于程序编码的不规范,元素通常很难直接通过静态 ID 或 name 定位,幸好有 CSS 选择器和 XPath 定位为我们提供了新的可能。然而由于 DHTML 和 Ajax 的盛行,新的挑战又摆在了测试人员面前,由 Dojo 等客户端脚本生成的 DOM 结构异常复杂:层次繁多且一个 DOM 树上可能存在多个页面的 DOM 子节点,而且其中元素的 ID 是动态的(该 ID 一般由一个前缀和一个动态的数字组成,前缀表明了元素对应的部件类型,数字则由运行时该小部件在 DOM 树上的位置确定)。这导致了有些元素甚至需要依靠 CSS 伪类再运用组合选择器才能精确定位的到。而对于在翻译验证性测试中的自动化,这种问题尤其突出,由于翻译验证性测试中的自动化脚本“一次编写,多个语言环境下”运行的要求,元素定位的后“救命稻草”-CSS 伪类中的内文本匹配也无法开箱即用了。
下面笔者将通过实例详细阐述元素定位的方法,以期读者能灵活运用以应对上述的各种挑战。
Selenium IDE 与 WebDriver 定位元素概览
Selenium 是一款强大的浏览器自动化工具,利用它可以实现 Web 界面测试的自动化。根据实现原理和使用方式的不同,其又分为 Selenium IDE 和 WebDriver,前者以浏览器插件的方式赋予用户简单快速地创建自动化脚本的能力,后者则通过 API 库的形式向用户提供更加灵活和健壮的 Web 自动化能力。
Selenium IDE 在其命令中采用 locatorType=location 的格式进行定位,该方法将定位到条件匹配的第一个元素,一般情况下 locatorType 可以省略;WebDriver 利用 findElement(By.locatorType("value") 或 findElements(By.locatorType("value") 函数查找元素,前者将返回一个的元素,后者将返回一个元素列表。具体的定位由 By.locatorType("value") 实现。
除去定位使用的方法而言,二者本质上都是通过 locatorType 进行定位的,常用的 locatorType 包括 id,name,link text,dom/JavaScript,xpath,css 等,示例见清单 2(行末括号中的数字显示当前行定位器将定位到清单 1 HTML 文档中的第几行元素):
清单 1. HTML 示例 1
(01)<html>
(02) <body>
(03) <form id="loginForm" name="loginFrom">
(04) <input class="required" name="username" type="text" />
(05) <input class="required passfield" name="password" type="password" />
(06) <input name="continue" type="submit" value="Login" />
(07) <input name="continue" type="button" value="Clear" />
(08) </form>
(09) <p>Are you sure you want to do this?</p>
(10) <a href="continue.html">Continue</a>
(11) <a href="cancel.html">Cancel</a>
(12)</body>
(13)<html>
清单 2. Selenium 和 WebDriver 中通用的定位方式
//by ID
id=loginForm(03)//in IDE
WebElement element = driver.findElement(By.id("loginForm"));(03)//in WebDriver
//by Name
name=continue type=button(07)//in IDE
WebElement element = driver.findElement(By.name("loginForm"));(03)//in WebDriver
//by link text
link=Continue(10)//in IDE
WebElement element = driver.findElement(By.linkText("Continue"));(10)//in WebDriver
//by DOM or JavaScript
dom=document.forms[0].elements['username'](04)//use DOM in IDE
WebElement element = (WebElement) ((JavascriptExecutor)driver).
executeScript("return $('.required')[0]"); (04)//use JQuery in WebDriver
//by XPath
//form[@id='loginForm']/input[1](04)//by IDE
WebElement element = driver.findElement(By.xpath("//input[@name='username']"));(04)//by WebDriver
//by CSS
css=#loginForm input[type="password"](05)//by IDE
WebElement e = driver.findElement(By.cssSelector("input.passfield")(05)//by WebDriver
通过 ID 和 name 定位是高效也是的定位方式,不过由于 name 不一定,在定位时匹配条件的元素可能有多个,因此这种情况下只会定位到匹配条件的第一个元素。针对多个元素具有相同 name(或链接文本)属性的情况还需额外增加其他的过滤器才能进行精确定位,如清单 2 中 IDE 利用 name 进行定位的示例。
DOM 代表了整个 HTML 文档的结构,使用 JavaScript 可以访问 DOM 中的节点。Selenium IDE 基于 DOM 结构可以使用 JavaScript 的点操作符进行层次定位以简化定位操作,由于只有 DOM 定位器以“document”开头,因此“dom=”也可以省略;此外在 WebDriver 中甚至可以通过执行任意的返回值为 DOM 对象的 JavaScript 语句来查找元素,如清单 2 中 WebDriver 使用 JQuery(一种 JavaScript 框架)的元素查找函数$进行定位。
XPath 初是用来在 XML 文档中定位 DOM 节点的语言,由于 HTML 也可以算作 XML 的一种实现,所以 Selenium 也可以利用 XPath 这一强大的语言来定位 Web 元素。XPath 在传统属性定位之外扩展了诸如“定位第三个多选框”等定位能力,以便应对没有 ID 或 name 属性的情况。利用 Xpath 可以通过路径,或者相对于一个可精确定位的元素的相对路径来定位。为了保证定位的健壮性,推荐使用相对路径和基于位置关系的定位。同样由于只有 XPath 定位器以“//”开头,所以“xpath=”也可以省略。
CSS (Cascading Style Sheets) 是一种用于渲染 HTML 或者 XML 文档的语言,CSS 利用其选择器可以将样式属性绑定到文档中的指定元素,即前端开发人员可以利用 CSS 设定页面上每一个元素的样式。所以理论上说无论一个元素定位有多复杂,既然开发人员能够定位到并设置样式,那么测试人员同样应该也能定位继而操作该元素。这也正是 Selenium 官方极力推荐使用 CSS 定位,而不是 XPath 定位的主要原因。CSS 定位被推崇的另一个原因是不同的浏览器 XPath 引擎不同甚至没有自己的 Xpath 引擎,这导致了 XPath 定位速度较慢,而采用 CSS 定位往往能用更简洁的语法快速定位到复杂的元素。因此后文将详细介绍 CSS 定位器的使用方法。
除了以上通用的定位方式之外,在 Selenium IDE 中还可通过 identifier 进行定位,这是通用也是默认的定位方式,其定位时首先将 identifier 的值视为 ID 继而查找匹配元素,没有匹配项之后再把其看作 name 进行查找,直到找到第一个匹配的元素为止。由于其是默认的定位方式,因此“identifier=”通常也会省略。对于 WebDriver 而言,特有的定位方式更多。首先它可以通过链接文本进行部分匹配,如清单 3 所示;然后它还可以通过标签名或者 CSS 类名的方式进行定位,利用 findElement 的话这两种方式均是定位到第一个匹配条件的元素,利用 findElements 则可获得一个所有满足条件的元素列表,如清单 3 所示。