selenium WEB UI自动化


1.技术浅谈

1.1 何为 selenium

Selenium广泛使用的开源Web UI(用户界面)自动化测试套件之一,支持跨不同浏览器,平台和编程语言的自动化

Selenium通过使用各种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ruby。目前,Selenium + python 简直就是黄金搭档。

1.2 功能特性

  • 开源的Web测试框架

  • Selenium IDE为创作测试提供了回放和录制功能,而无需学习测试脚本语言

  • Selenium支持各种操作系统,浏览器和编程语言

  • 支持并行测试执行,从而减少了时间并提高了测试效率

  • 与其他自动化测试工具相比,Selenium需要的资源更少。

  • WebDriver API已经尝试集于Selenium中,这是对Selenium进行的最重要的修改之一。

  • Selenium Web驱动程序不需要服务器安装,测试脚本直接与浏览器交互

  • Selenium命令根据不同的类进行分类,使其更易于理解和实现

1.3 局限性

  • Selenium不支持桌面应用程序的自动化测试

  • 无法使用Selenium对Web服务(如SOAP或REST)执行自动化测试。

  • 使用者应该至少知道或熟悉一种受支持的编程语言,以便在Selenium WebDriver中创建测试脚本。

  • Selenium没有任何内置的报告功能; 必须依赖JUnit和TestNG等插件来获取测试报告。

  • 无法对图像执行测试。需要将Selenium与Sikuli集成以进行基于图像的测试。

1.4 工具套件

Selenium不仅仅是一个工具,而是一套软件,每个软件都有不同的方法来支持自动化测试。 它由四个主要组成部分组成,包括:

  • Selenium集成开发环境(IDE)
  • Selenium RC远程控制器(现已弃用)
  • webdriver
  • Selenium Grid

Selenium IDE实现为Firefox扩展,在测试脚本上提供记录和回放功能,允许测试人员以多种语言导出录制的脚本,可以在Webdriver中使用这些导出的脚本

SeleniumWebDriver提供了一个编程接口来创建和执行测试用例。 编写测试脚本是为了识别网页上的Web元素,然后对这些元素执行所需的操作。

Selenium Grid允许在不同的机器上并行运行不同浏览器的测试。 简单来说,可以在运行不同浏览器和操作系统的不同机器上同时运行测试。

2.Selenium - webdriver的环境配置

想要Python通过selenium操作浏览器,有两个前提条件:

  1. 下载selenium模块
  2. 安装selenium的浏览器驱动 webdriver

selenium模块下载

1
2
pip install selenium
pip install -i https://pypi.doubanio.com/simple/ selenium

浏览器驱动下载

Chrome的驱动下载地址:

  1. https://registry.npmmirror.com/binary.html?path=chromedriver/
  2. http://chromedriver.storage.googleapis.com/index.html

选择和浏览器匹配的版本,压缩包内的chromedriver.exe可执行文件,将该文件移动到Python的安装目录中的Scripts目录(因为该目录已经添加到了系统Path中,你移动到别的目录也行,只要将目录添加到系统的Path中)

Firefox的驱动下载地址:https://github.com/mozilla/geckodriver/releases下载到本地是一个`geckodriver.exe`可执行文件,同样的,你将该文件移动到Python安装目录中的`Scripts`目录

3.Selenium - webdriver基本操作

3.1 常用类和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from selenium import webdriver   # 驱动浏览器
from selenium.webdriver import ActionChains # 鼠标的相关操作,比如滑动验证
from selenium.webdriver.common.by import By # 选择器,以什么方式选择标签元素
from selenium.webdriver.common.keys import Keys # 键盘相关
from selenium.webdriver.support import expected_conditions as EC # 各种判断,一般跟等待事件连用,比如说等待某个元素加载出来
from selenium.webdriver.support.wait import WebDriverWait # 等待事件,可以与EC连用

browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
browser.get('https://www.baidu.com')
browser.maximize_window() # 窗口最大化
print(browser.current_url) # 获取当前页URL
print(browser.title) # 获取页面的title
print(browser.name) # 获取driver对象:chrome
print(browser.current_window_handle) # 获取当前窗口
print(browser.get_cookies()) # 获取cookies
print(browser.page_source) # 获取当前页面内容
browser.refresh() # 刷新
browser.save_screenshot('error.png') # 保存截图
browser.close() # 关闭当前窗口
browser.quit() # 退出浏览器驱动,关闭所有关联窗口

3.2 选择器

形式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from selenium import webdriver   # 驱动浏览器
from selenium.webdriver.support.wait import WebDriverWait # 等待事件
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

browser.get('https://www.baidu.com')
browser.find_element_by_id('id') # id选择器
browser.find_element_by_class_name('xx') # 类选择器,1个
browser.find_elements_by_class_name('xx') # 类选择器,多个
browser.find_element_by_link_text('xxx') # 链接文本选择器
browser.find_element_by_xpath('xxxx') # xpath选择器
browser.find_element_by_tag_name('h1') # 标签选择器,获取1个
browser.find_elements_by_tag_name('h1') # 标签选择器,获取多个
browser.find_element_by_css_selector('xxx') # 样式选择器

形式2:

1
2
3
4
5
6
7
8
9
from selenium.webdriver.common.by import By  # 选择器,以什么方式选择标签元素
browser.find_element(by=By.ID, value=None) # 选择器类型, value值
注:常用的选择器
By.ID
By.LINK_TEXT
By.CLASS_NAME
By.XPATH
By.CSS_SELECTOR
By.TAG_NAME

当获取了标签对象后,可以接着获取这个标签的属性、内容、css样式和子标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10)


try:
driver.get('https://zhantop.com/login/')

# 根据 class 属性获取 a 标签对象
a_obj = driver.find_element_by_class_name('navbar-brand')

# 获取 a 标签的href属性
print(a_obj.get_attribute('href'))
print(a_obj.text) # 获取标签的文本内容
print(a_obj.tag_name) # 获取标签的名称
# 获取 css 样式
print(a_obj.value_of_css_property('padding'))
print(a_obj.value_of_css_property('font-size'))
# 如果这个标签有 submit 属性,那么就可以点击它了,比如我们点击form表单中有submit属性的标签
driver.find_element_by_xpath('//*[@id="fm"]/div[5]/div/input').submit()

# 获取该标签下的子标签,或者子标签元素
div_obj = driver.find_element_by_class_name('col-xs-3')
print(div_obj.find_element_by_tag_name('input'))
print(div_obj.find_element_by_tag_name('input').get_attribute('value'))

# 获取验证码图片的大小
print(driver.find_element_by_id('image_code').size)
# # 保存验证码图片
driver.find_element_by_id('image_code').screenshot('./a.png')

except Exception as e:
print(e)

finally:
time.sleep(10)
driver.quit()

注意:按钮提交时,除了click之外,还有一个submit,区别是click就是单纯的点一下。而submit是完成了表单提交,需要携带表单信息进行提交的。如果是button按钮的话,只能使用click,而不能使用submit。

3.3 keys键盘事件

1
2
3
4
5
6
7
from selenium.webdriver.common.keys import Keys  # 键盘相关
print(dir(Keys))
'''
[
'ADD', 'ALT', 'ARROW_DOWN', 'ARROW_LEFT', 'ARROW_RIGHT', 'ARROW_UP', 'BACKSPACE', 'BACK_SPACE', 'CANCEL', 'CLEAR', 'COMMAND', 'CONTROL', 'DECIMAL', 'DELETE', 'DIVIDE', 'DOWN', 'END', 'ENTER', 'EQUALS', 'ESCAPE', 'F1', 'F10', 'F11', 'F12', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'HELP', 'HOME', 'INSERT', 'LEFT', 'LEFT_ALT', 'LEFT_CONTROL', 'LEFT_SHIFT', 'META', 'MULTIPLY', 'NULL', 'NUMPAD0', 'NUMPAD1', 'NUMPAD2', 'NUMPAD3', 'NUMPAD4', 'NUMPAD5', 'NUMPAD6', 'NUMPAD7', 'NUMPAD8', 'NUMPAD9', 'PAGE_DOWN', 'PAGE_UP', 'PAUSE', 'RETURN', 'RIGHT', 'SEMICOLON', 'SEPARATOR', 'SHIFT', 'SPACE', 'SUBTRACT', 'TAB', 'UP'
]
'''

3.4 鼠标事件

1
2
3
4
5
6
7
from selenium.webdriver import ActionChains  # 鼠标的相关操作,比如滑动验证
print(dir(ActionChains))
'''
[
'click', 'click_and_hold', 'context_click', 'double_click', 'drag_and_drop', 'drag_and_drop_by_offset', 'key_down', 'key_up', 'move_by_offset', 'move_to_element', 'move_to_element_with_offset', 'pause', 'perform', 'release', 'reset_actions', 'send_keys', 'send_keys_to_element'
]
'''

3.5 浏览器的前进和后退

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用selenium自动的模拟前进(forward)后退(back)和刷新(refresh)三个动作
import time
from selenium import webdriver # 驱动浏览器
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.get('https://www.taobao.com')
browser.get('http://www.sina.com.cn/')
time.sleep(5)
browser.back() # 后退
time.sleep(3)
browser.forward() # 前进
time.sleep(2)
browser.refresh() # 刷新
browser.close() # 关闭当前窗口
time.sleep(3)
browser.quit()

3.6 文件操作

上传文件在send_keys中添加上文件路径即可。

1
2
driver.find_element_by_name('file_obj').send_keys(r'C:\Users\Desktop\abc.jpg')
driver.find_element_by_xpath('/html/body/div/form/input[0]').click()

3.7 元素定位

  • 见3.2

​ 补充

  • 根据link text 模糊定位

    • drier.find_element_by_partial_link_text,模糊定位,获取多个,返回一个drier.find_elements_by_partial_link_text,模糊定位,以列表的形式返回。
  • 根据xpath定位

    • drier.find_element_by_xpath,获取多个,返回一个
    • drier.find_elements_by_xpath,以列表的形式返回
  • css selector定位,推荐

    • drier.find_element_by_css_selector,获取多个,返回一个
    • drier.find_elements_by_css_selector,以列表的形式返回。

3.8 XPath定位

XPath中节点匹配的基本方法。

路径匹配

路径匹配与文件路径的表示相仿,比较好理解,有以下几个符号:

  • /表示节点路径,如/A/B/C表示节点A的子节点B的子节点C/表示根节点。
  • //表示所有路径以//后指定的子路径结尾的元素,如//D表示所有的D元素;如果是//C/D表示所有父节点为CD元素。
  • *表示路径的通配符,如/A/B/C/*表示A元素下的B元素下的C元素下的所有子元素。

位置匹配
对于每一个元素,它的各个子元素都是有序的:

  • /A/B/C[1]表示A元素下的B元素下的C元素下的第一个子元素。
  • /A/B/C[last()]表示A元素下的B元素下的C元素下最后一个子元素。
  • /A/B/C[position()>2]表示A元素下的B元素下的C元素下的位置号大于2的元素。

属性及属性值
在XPath中可以利用属性及属性值来匹配元素,需要注意的是:元素的属性名前要有@前缀,例如:

  • //B[@id]表示所有具有属性id的B元素。
  • //B[@*]表示所有具有属性的B元素。
  • //B[not(@*)]表示所有不具有属性的B元素。
  • //B[@id="b1"]表示id值为b1的B元素。

3.9 Css Selector定位

推荐,因为前端人员一般使用其精准定位

  • 根据tag name定位

    1
    find_element_by_css_selector("form")
  • 根据id定位

    1
    2
    3
    4
    # 直接根据id定位
    find_element_by_css_selector("#kw").send_keys(text)
    # 通过标签加id的形式定位
    find_element_by_css_selector('input#su').click()
  • 根据class定位

    1
    2
    3
    4
    5
    6
    7
    8
    # 直接根据clas定位
    find_element_by_css_selector(".s_ipt").send_keys(text)
    # 标签加class
    find_element_by_css_selector('input.s_btn').click()
    # 多class属性定位
    find_element_by_css_selector('.bg.s_btn').click()
    # input加多属性
    find_element_by_css_selector('input.bg.s_btn').click()
  • 根据元素属性定位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 精准匹配
    find_element_by_css_selector('input[name=wd]').send_keys(text) # 属性名=属性值
    # 多属性
    drier.find_element_by_css_selector('input[type="submit"][value="百度一下"]').click()
    # 模糊匹配(正则表达式匹配)
    find_element_by_css_selector('input[id ^="k"]').send_keys(text) # ^= 匹配以 k 开头的id

    find_element_by_css_selector('input[id $="w"]').send_keys(text) # $= 匹配以 w 结尾的id

    find_element_by_css_selector('input[value *="度一"]').click() # *= 匹配 value 值的中间部分

    常用正则比配

    • E[attr]:只使用属性名,但没有确定任何属性值;

    • E[attr=”value”]:指定属性名,并指定了该属性的属性值;

    • E[attr~=”value”]:指定属性名,并且具有属性值,此属性值是一个词列表,并且以空格隔开,其中词列表中包含了一个value词,而且等号前面的“〜”不能不写;

    • E[attr^=”value”]:指定了属性名,并且有属性值,属性值是以value开头的;

    • E[attr$=”value”]:指定了属性名,并且有属性值,而且属性值是以value结束的;

    • E[attr*=”value”]:指定了属性名,并且有属性值,而且属值中包含了value;

    • E[attr|=”value”]:指定了属性名,并且属性值是value或者以“value-”开头的值(比如说zh-cn);


  • 子标签定位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 匹配子元素, A>B
    find_element_by_css_selector('form>span>input').send_keys(text)

    # 匹配后代元素, A空格B
    find_element_by_css_selector('form span input').send_keys(text)

    # 匹配第一个后代元素
    find_element_by_css_selector('form :first-child').get_attribute('name') # 返回form表单中隐藏的第一个input标签
    find_element_by_css_selector('form span:first-child').get_attribute('class') # 返回form表单中第一个span标签中的第一个子元素

    # 根据索引匹配元素
    find_element_by_css_selector('form :nth-child(2)').get_attribute('name') # 返回form表单中第二个input标签

    # 匹配兄弟标签
    find_element_by_css_selector('form :nth-child(2)+input').get_attribute('name') # 先定位到a,根据a匹配临近的b

    4.Selenium - webdriver滚动条

4.1 移动到底部或顶部

1
2
3
4
5
6
7
8
9
10
11
12
13
driver.implicitly_wait(time_to_wait=10)

driver.get("xxxx")
# 获取当前的窗口对象
element = driver.find_element_by_tag_name('body')
time.sleep(3)
#从顶部下拉到底部
driver.execute_script("arguments[0].scrollIntoView(false);", element) # 默认为true
time.sleep(3)
# driver.execute_script("window.scrollTo(0, document.body.scrollHeight)") # 使用window对象

# 从底部拉到顶部
driver.execute_script("arguments[0].scrollIntoView(true);", element)

4.2 相对移动

1
2
3
driver.execute_script("window.scrollBy(0, 500)")  # 相对移动,从当前位置移动500像素
time.sleep(3)
driver.execute_script("window.scrollBy(0, 500)") # 相对移动,从当前位置移动500像素,即 上次移动的500 + 本次的500

4.3 绝对移动

1
2
3
driver.execute_script("window.scrollTo(0, 2200)")  # 绝对移动,直接移动到2200像素位置
time.sleep(3)
driver.execute_script("window.scrollTo(0, 200)") # 绝对移动,从上次的2200像素移

5.Selenium - webdriver鼠标操作

关于鼠标相关操作的方法都封装在ActionChains类中

5.1 基本操作

Method Description
click(on_element=None) 鼠标左键单击
click_and_hold(on_element=None) 鼠标左键单击,但不松开
context_click(on_element=None) 鼠标右键单击
double_click(on_element=None) 鼠标左键双击
drag_and_drop(source, target) 鼠标左键单击不松开,移动到指定元素后松开(即拖拽 )
drag_and_drop_by_offset(source, xoffset, yoffset) 鼠标左键单击不松开,移动到指定坐标后松开
move_by_offset(xoffset, yoffset) 鼠标移动到某个坐标
move_to_element(to_element) 鼠标移动到某个元素
move_to_element_with_offset(to_element, xoffset, yoffset) 鼠标移动到距离某个元素的某个距离
pause(seconds) 暂停输入
release(on_element=None) 在某个元素松开鼠标左键
send_keys(*keys_to_send) 在当前元素中输入值
send_keys_to_element(element, *keys_to_send) 给某个元素输入值
perform() 相应存储的动作
reset_actions() 清除所有已存储的动作

5.2 拖动

1
2
3
4
5
6
7
8
9
10
11
12
# 页面中,有iframe标签,要先切换
frame = driver.find_element_by_tag_name('iframe')
driver.switch_to.frame(frame)

# 获取第一个元素
div1 = driver.find_element_by_id('draggable')

# 获取第二个元素
div2 = driver.find_element_by_id('droppable')

# 拖拽
ActionChains(driver).drag_and_drop(div1, div2).perform()

6.窗口切换

窗口切换一般两种情形

1.是浏览器打开多个窗口,使用selenium在多个窗口中相互切换

2.遇到iframe窗口,相当于嵌套,解决之道 就是切入与切出

6.1 窗口切换

  • driver.switch_to系列,切换窗口,包括三个常用的方法:
    • switch_to.window,切换窗口,替换switch_to_window
    • switch_to.frame,进入iframe,替换switch_to_frame
    • switch_to.default_content,退出iframe,替换原方法switch_to_default_content
  • window_handles,窗口数组
1
2
3
4
5
6
7
8
9
10
# 所有打开的窗口都存在这个数组中
# print(driver.window_handles) # ['CDwindow-11', 'CDwindow-22', 'CDwindow-33']

# 根据数组下标索引切换窗口
time.sleep(3)
driver.switch_to.window(driver.window_handles[1])
time.sleep(3)
driver.switch_to.window(driver.window_handles[0])
time.sleep(3)
driver.switch_to.window(driver.window_handles[2])

6.2 iframe窗口切换

  • switch_to.frame(iframe),进入窗口
  • switch_to.default_content(),退出窗口

7.等待机制

我们可以使用selenium提供的两种等待机制:

  • 显式等待

    selenium提供了 WebDriverWait类实现等待机制。该类接收4个参数来指定等待机制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    driver = webdriver.Chrome()
    WebDriverWait(driver=driver, timeout=10, poll_frequency=0.5, ignored_exceptions=None)

    # driver,浏览器驱动
    # timeout,最长超时时间,单位(秒)
    # poll_frequency,轮询检测时间,也就是每隔多少时间检测一次,默认是0.5秒
    # ignored_exceptions,超时后的异常信息,默认抛出NoSuchElementException

    WebDriverWait类提供了两个方法来完成等待机制:

    • until(self, method, message=’’),method为需要提供的驱动程序,直到返回True,用的较多。
    • until_not(self, method, message=’’),method为需要提供的驱动程序,直到返回False
  • 隐式等待

    直接通过浏览器驱动对象调用

    driver.implicitly_wait(time_to_wait=10) # 只需要一个等待超时时间参数

    implicitly_wait等待时间单位为秒,如上例所示,我们指定了10秒。需要注意的是,隐式与显式等待有明显的区别,隐式等待应用于全局,每当使用driver驱动找某个元素时,隐式等待机制就会被触发(导致测试速度变慢),如果元素存在,则继续执行,否则,它将以轮询的方式判断元素是否定位成功,直至等待超时,抛出错误NoSuchElementException。而显式等待则只是指定某(些)个元素是否存在时执行

实际使用:

  • 普通(静态页面较多)网页,休眠机制和显式等待机制可以相互搭配,提高效率。
  • 动态页面较多的时候,推荐使用隐式等待

8.处理提示框

JavaScript中,关于消息提示框的方法有三个(虽然都跟alert差不多):

  • alert(message)方法用于显示带有一条指定消息和一个 OK 按钮的警告框。
  • confirm(message)方法用于显示一个带有指定消息和 OK 及取消按钮的对话框。如果用户点击确定按钮,则 confirm() 返回 true。如果点击取消按钮,则 confirm() 返回 false。
  • prompt(text,defaultText)方法用于显示可提示用户进行输入的对话框。如果用户单击提示框的取消按钮,则返回 null。如果用户单击确认按钮,则返回输入字段当前显示的文本。

selenium操作上面三种提示框有以下几种方法:

  • alertObject.text:获取提示的文本值。
  • alertObject.accept():点击『确认』按钮。
  • alertObject.dismiss():点击『取消』或者叉掉对话框。
  • alertObject.send_keys(message):输入文本,仅适用于prompt方法

处理alert类型与confirm提示框

switch_to.alert方法将webdriver作用域切换到alert提示框上。同时可以使用text获取提示文本信息、accept()点击确认按钮、dismiss()点击取消或者叉掉提示框。

处理prompt提示框

基本步骤不变,但最后可以使用alertObject.send_keys(message)方法

9.规避网站监测

对采取了监测机制的web,只需要设置Chromedriver的启动参数即可解决问题。在启动Chromedriver之前,为Chrome开启实验性功能参数excludeSwitches,它的值为['enable-automation']

1
2
3
4
5
6
7
8
from selenium.webdriver import ChromeOptions  # 需要导入的类

# 创建 option 对象
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

# 创建浏览器对象
driver = webdriver.Chrome(options=option)

10.遇到链接非私密

添加options参数:

1
2
3
4
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--ignore-certificate-errors")
driver = webdriver.Chrome(options=options)
都看到这里了,不赏点银子吗^v^