Selenium 自动化框架

5.17'22

Selenium 是流行的浏览器自动化框架,包含了很多库和工具。常被用于端到端的测试,可以配合 mochachai 等测试库使用。

Selenium 使用 WebDriver 让一套代码能运行于多个主要浏览器。

Example

const {Builder, By, Key, until} = require('selenium-webdriver')

async function example() {
  const driver = await new Builder().forBrowser('chrome').build()
  try {
    await driver.get('https://cn.bing.com')
    await driver.findElement(By.name('q')).sendKeys('selenium', Key.ENTER)
    const firstResult = await driver.wait(until.elementLocated(By.css('h2')), 10000)
    console.log(await firstResult.getAttribute('textContent'))
  } finally {
    driver.quit()
  }
}

example()

保存为文件 test.js,运行

node test.js

Install

Selenium webdriver

用于控制浏览器的驱动,提供多种语言的接口。以 JavaScript 为例

npm i selenium-webdriver

Driver binaries

下载对应浏览器的 driver 二进制文件,添加到系统路径。版本需要随着浏览器版本一起更新。

以 Chrome 为例:https://chromedriver.storage.googleapis.com/index.html

Getting started with WebDriver

Locating elements

WebDriver 基本功能之一是定位页面元素

使用driver.findElementBy等接口

const {By} = require('selenium-webdriver')
const result = driver.findElement(By.css('h2'))

判断元素是否存在

const elements = await driver.findElements(By.css('#id'))
const hasElement = elements.length

By.[css|xpath|name|...]()

LocatorDescription
class nameLocates elements whose class name contains the search value (compound class names are not permitted)
css selectorLocates elements matching a CSS selector
idLocates elements whose ID attribute matches the search value
nameLocates elements whose NAME attribute matches the search value
link textLocates anchor elements whose visible text matches the search value
partial link textLocates anchor elements whose visible text contains the search value. If multiple elements are matching, only the first one will be selected.
tag nameLocates elements whose tag name matches the search value
xpathLocates elements matching an XPath expression

Perform actions

获取元素后可以执行相关的操作

发送按键来模拟用户输入

await driver.findElement(By.name('q')).sendKeys('selenium', Key.ENTER)

拖放元素(drag and drop)

const actions = driver.actions({ bridge: true })
const source = driver.findElement(By.id('source'))
const target = driver.findElement(By.id('target'))
await actions.dragAndDrop(source, target).perform()

点击元素

driver.findElement(By.css('input[type="submit"]')).click()

WebDriver

WebDriver 负责通过各种方式与浏览器交互。

术语(terminology)

  • API: 命令。The commands to manipulate WebDriver.
  • Libary:代码模块。A code module which contains the APIs and the code.
  • Driver: 浏览器驱动。Responsible for controlling the actual browser.
  • Framework: 整体环境。A support for WebDriver suites

各组件关系(component)

最简单的情况,WebDriver 直接调用 driver 来控制对应的浏览器。

也可以借助 Remote WebDriver, selenium Server or Grid 等间接调用 driver。

比较复杂的情况, 测试框架(Test Framework)负责运行 WebDriver 相关的测试步骤。

调用关系为 The Test Framework <-> WebDriver <-> ... <-> Driver <-> Browser

Browser manipulation

Browser manipulation

通过 WebDriver 启动浏览器后,可以模拟用户进行各种操作。

  • Browser navigation

    await driver.get('https://selenium.dev')
    
    await driver.navigate().back()
    await driver.navigate().forward()
    await driver.navigate().refresh()
    
    await driver.getCurrentUrl()
    await driver.getTitle()
    
  • Windows and tabs

  • Frames and Iframes

  • Window management

  • Set window position

Waits

Waits

浏览器有很多异步操作修改 DOM 的状态。为了确保 WebDriver 获取到正确的 DOM 状态,需要在一定程度上控制异步的流程。Waits 可以让当前进程一直等待,条件满足后再执行后续流程。

Explicit wait

显示的等待,可用于 imperative, procedural language(命令式、过程式语言)。可以暂停或冻结进程,定时检查继续的条件是否满足。适合于同步浏览器和 DOM 的状态到脚本。

使用driver.wait,可以指定最大等待时间(timeout)

await driver.get('file///path/to/test.html')

const isInitialised = () => 
  driver.executeScript('return initialised')

await driver.wait(isInitialised, 2000)
const element = driver.findElement(By.css('p'))

配合until的方法可以写得更简洁

const {By, until} = require('selenium-webdriver')

const element = await driver.wait(
  until.elementLocated(By.css('p')), 
  2000,
) 

test.html 中的模拟脚本

let initialised = false
window.addEventListener('load', function () {
  setTimeout(() => {
    const newElement = document.createElement('p')
    newElement.textContent = 'Hello from JavaScript!'
    document.body.appendChild(newElement)
    initialised = true
  }, 500)
})

Implicit wait

隐式等待。设置间隔后,WebDriver 每次获取 DOM 元素时,如果不存在,等待一定间隔后会重新尝试。默认为0, 不启用。

driver.manage().setTimeouts({ implicit: 10000 })

await driver.get('file:///Users/apple/files/html/tmp.html')
const element = driver.findElement(By.css('p'))

FluentWait

弹性等待。同时设置最大等待时间和检查条件的间隔。

// Waiting 30 seconds for an element to be present on the page, 
// checking for its presence once every 5 seconds
const element = await driver.wait(
  until.elementLocated(By.css('p')), 
  30000, 
  'Timed out after 30 seconds', 
  5000
) 

Web element

WebElement 对象代表了一个 DOM element。可以通过 WebDriver 或其它 WebElement 来查找。条件包括 ID, Name, Class, XPath, CSS Selectors, link Text, etc.

主要组合使用findElement()By.[css|xpath|name|...]()

Find Element

const element = driver.findElement(By.name('q'))

Find Elements

const elements = await driver.findElements(By.name('q'))
for(let e of elements) {
  console.log(await e.getText())
}

Find Element/Elements from Element

调用同样的方法,查找已有 WebElement 的子元素

Get Active Element

获取当前焦点所在的元素

await driver.switchTo().activeElement().getAttribute('title')

Is Element Enabled

await driver.findElement(By.name('btn')).isEnabled()

Is Element Selected

This method is widely used on Check boxes, radio butotns, input elements, and option elements.

await driver.findElement(By.css("input[type='checkbox']:last-of-type")).isSelected()

Get Element TagName

await driver.findElement(By.css('h1')).getTagName()

Get Elment Rect

await driver.findElement(By.css('h1')).getRect()

Get Element CSS Value

await driver.findElement(By.css('h1')).getCssValue('color')

Keyboard

Keyboard 代表了键盘相关的事件,用来模拟输入。

sendKeys

模拟发送连续的按键

await driver.findElement(By.name('q')).sendKeys('selenium', Key.ENTER)

keyDown

模拟按下 modifier key(CONTROL, SHIFT, ALT, COMMAND, ...)

await driver.actions().keyDown(Key.CONTROL).sendKeys('a').perform()

keyUp

模拟释放 modifier key(CONTROL, SHIFT, ALT, COMMAND, ...)

const search = driver.findElement(By.name('q'))
await driver.actions().click(search)
  .keyDown(Key.SHIFT).sendKeys('qwerty')
  .keyUp(Key.SHIFT).sendKeys('qerty').perform()

clear

This is only applied for the elements which are editable and interactable

await search.clear()
📖