Python Web自动化测试
一、基础准备
1. 环境搭建
工欲善其事必先利其器,废话不多说。我们先开始搭建环境。
# 创建项目目录
mkdir WebTesting
# 切换到项目目录下
cd WebTesting
# 安装虚拟环境创建工具
pip install virtualenv
# 创建虚拟环境,env代表虚拟环境的名称,可自行定义
virtualenv env
# 启动虚拟环境,执行下面命令后会发现路径上有 (env) 字样的标识
source env/Scripts/activate
# 查看 (env) 环境下使用的 Python 和 pip 工具版本
ls env/Scripts/
# *** 安装 Selenium ***
pip install selenium
# 退出虚拟环境,退出后路径上的 (env) 字样的标识消失
cd env/Scripts/
deactivate
# 导出环境所需要的模块的清单
pip freeze >> requirements.txt
# 上传 GitHub 时,将下面项忽略上传
echo env/ >> .gitignore
echo WebTesting.iml >> .gitignore
echo __pycache__/ >> .gitignore
# 将代码传至 GitHub
# 本地仓初始化
git init
# 创建本地仓与 GitHub 仓的远程链接
git remote add github 你的github仓的地址
# 将代码添加到暂存区
git add .
# 将代码提交到
git commit -m "init environment"
# 将代码上传到GitHub仓中
git push github master
初始化环境的项目结构示例如下:
2. Selenium 原理
Selenium 是一套完整的 web 应用程序测试系统 ,它包含了测试录制(Selenium IDE)、编写及运行(Selenium Remote Control) 和测试的并行处理(Selenium Grid)。Selenium的核心 Selenium Core基于 JsUnit,完全由 JavaScript 编写,因此可以运行于任何支持 JavaScript 的浏览器上。其基本原理如下:
2.1 设置浏览器驱动
from selenium import webdriver
driver = webdriver.Firefox() # Firefox浏览器
driver = webdriver.Chrome() # Chrome浏览器
driver = webdriver.Ie() # Ie浏览器
driver = webdriver.Edge() # Edge浏览器
driver = webdriver.PhantomJS() # PhantomJS()
2.2 Selenium元素定位
<html>
<head>
<body link="#0000cc">
<a id="result_logo" href="/" onmousedown="return c({'fm':'tab','tab':'logo'})">
<form id="form" class="fm" name="f" action="/s">
<span class="soutu-btn"></span>
<input id="kw" class="s_ipt" name="wd" value="" maxlength="255" autocomplete="off">
# 通过 id 定位
dr.find_element_by_id("kw")
# 通过name定位:
dr.find_element_by_name("wd")
# 通过class name定位:
dr.find_element_by_class_name("s_ipt")
# 通过tag name定位:
dr.find_element_by_tag_name("input")
# 通过 xpath 定位的几种写法
dr.find_element_by_xpath("//*[@id='kw']")
dr.find_element_by_xpath("//*[@name='wd']")
dr.find_element_by_xpath("//input[@class='s_ipt']")
dr.find_element_by_xpath("/html/body/form/span/input")
dr.find_element_by_xpath("//span[@class='soutu-btn']/input")
dr.find_element_by_xpath("//form[@id='form']/span/input")
dr.find_element_by_xpath("//input[@id='kw' and @name='wd']")
# 通过 css 定位的几种写法
dr.find_element_by_css_selector("#kw")
dr.find_element_by_css_selector("[name=wd]")
dr.find_element_by_css_selector(".s_ipt")
dr.find_element_by_css_selector("html > body > form > span > input")
dr.find_element_by_css_selector("span.soutu-btn> input#kw")
dr.find_element_by_css_selector("form#form > span > input")
# 通过 link_text 定位
dr.find_element_by_link_text("新闻")
dr.find_element_by_link_text("hao123")
dr.find_element_by_partial_link_text("新")
dr.find_element_by_partial_link_text("hao")
dr.find_element_by_partial_link_text("123")
# 如果是定位一组元素,用下面
find_elements_by_id()
find_elements_by_name()
find_elements_by_class_name()
find_elements_by_tag_name()
find_elements_by_link_text()
find_elements_by_partial_link_text()
find_elements_by_xpath()
find_elements_by_css_selector()
2.3 控制浏览器操作
(1) 控制浏览器窗口大小
WebDriver中 set_window_size() 方法来设置浏览器窗口的大小;maximize_window() 使打开的浏览器全屏显示。
【GitHub示例】
from selenium import Webdriver
driver = Webdriver.Chrome('../tools/chromedriver.exe')
driver.get_url('http://www.5itest.cn/register')
# 设置浏览器窗口大小
print("设置浏览器宽500,高600")
driver.set_window_size()
driver.quit()
(2) 控制浏览器后退、前进
webdriver 提供了对应的 back() 和 forward() 方法来模拟后退和前进按钮。【GitHub示例】
from selenium import webdriver
import time
# 2. 控制浏览器的前进、后退
browser_links = webdriver.Chrome('../tools/chromedriver.exe')
first_url = 'https://www.baidu.com/'
second_url = 'https://news.baidu.com/'
print("访问第一个链接:%s" % first_url)
browser_links.get(first_url)
time.sleep(1)
print("访问第二个链接:%s" % second_url)
browser_links.get(second_url)
time.sleep(1)
print("回退到第一个链接:%s" % first_url)
browser_links.back()
time.sleep(1)
print("前进到第二个链接:%s", second_url)
browser_links.forward()
time.sleep(1)
browser_links.quit()
(3) 刷新页面 F5
webdriver中可以用 refresh 方法进行页面刷新。【GitHub代码】
from selenium import webdriver
import time
refresh_url = 'http://www.baidu.com/'
browser_refresh = webdriver.Chrome('../tools/chromedriver.exe')
browser_refresh.get(refresh_url)
time.sleep(2)
browser_refresh.refresh()
browser_refresh.quit()
2.4 webdriver常用方法
webdriver常用方法【GitHub示例】
(1) 点击、输入和清除
定位元素后我们还需要对元素进行操作,常用的元素操作方法有:clear()、send_keys(value)、click()
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 03_commonMethod.py
@Time : 2019/8/20 12:12
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
import time
base_url = 'https://www.baidu.com'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# 1. 清除、输入、点击
browser.find_element_by_id('kw').clear()
browser.find_element_by_id('kw').send_keys('python')
browser.find_element_by_id('su').click()
time.sleep(2)
browser.quit()
(2) 提交
submit()方法用于提交表单,在搜索框后输入关键字后,可用于“回车”模拟。
from selenium import webdriver
import time
base_url = 'https://www.baidu.com'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# 2.提交
search_text = browser.find_element_by_id('kw')
search_text.send_keys('selenium')
search_text.submit()
time.sleep(3)
(3) 其他常用的方法
size: 返回元素的尺寸。
text: 获取元素的文本。
get_attribute(name): 获得属性值。
is_displayed(): 设置该元素是否用户可见。
from selenium import webdriver
import time
base_url = 'https://www.baidu.com'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# 3. 其他常用方法
size = browser.find_element_by_id('kw').size
print("返回元素的尺寸:%s" % size)
text = browser.find_element_by_id('cp').text
print("返回元素的文本:%s" % text)
attribute = browser.find_element_by_id('kw').get_attribute('type')
print("返回元素的属性:%s" % attribute)
result = browser.find_element_by_id('kw').is_displayed()
print("返回元素是否可见:%s" % result)
browser.quit()
2.4 鼠标事件
在webdriver中,鼠标操作的方法封装在 ActionChains 类提供。ActionChains类提供了鼠标操作的常用方法:【GitHub示例】
ActionChains(driver),将浏览器驱动 driver 作为参数传入。
(1) perform(): 执行所有 ActionChains 中存储的行为,是对整个操作的提交动作;
(2) context_click(): 右击
(3) double_click(): 双击
(4) drag_and_drop(): 拖动
(5) move_to_element(): 鼠标悬停, 在调用时需要指定元素定位
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 04_mouseEvent.py
@Time : 2019/8/20 16:59
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time
base_url = 'http://www.baidu.com/'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# 定位到悬停元素处
above = browser.find_element_by_link_text('设置')
# 对元素执行鼠标悬停操作
ActionChains(browser).move_to_element(above).perform()
time.sleep(5)
# 右击
ActionChains(browser).context_click().perform()
time.sleep(5)
# 定位到要双击的元素处
# double_click_element = browser.find_element_by_link_text('新闻')
# print(double_click_element)
# ActionChains(browser).move_to_element(double_click_element).double_click().perform()
# time.sleep(5)
# 拖动元素
drag_and_drop_element = browser.find_element_by_link_text('地图')
ActionChains(browser).move_to_element(drag_and_drop_element).drag_and_drop().perform()
time.sleep(5)
browser.quit()
2.5 键盘事件
前面的 send_keys() 方法用来模拟键盘输入;keys() 类提供了键盘上几乎所有按键的方法,组合键也是可以的。【GitHub示例】
常用的键盘操作如下:
send_keys(Keys.BACK_SPACE) 删除键(BackSpace)
send_keys(Keys.SPACE) 空格键(Space)
send_keys(Keys.TAB) 制表键(Tab)
send_keys(Keys.ESCAPE) 回退键(Esc)
send_keys(Keys.ENTER) 回车键(Enter)
send_keys(Keys.CONTROL,'a') 全选(Ctrl+A)
send_keys(Keys.CONTROL,'c') 复制(Ctrl+C)
send_keys(Keys.CONTROL,'x') 剪切(Ctrl+X)
send_keys(Keys.CONTROL,'v') 粘贴(Ctrl+V)
send_keys(Keys.F1) 键盘 F1
……
send_keys(Keys.F12) 键盘 F12
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 05_keyboardEvent.py
@Time : 2019/8/20 22:22
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
base_url = 'https://www.baidu.com/'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(base_url)
# 先输入百度
driver.find_element_by_id('kw').send_keys('百度')
# 1.删除度
driver.find_element_by_id('kw').send_keys(Keys.BACK_SPACE)
time.sleep(3)
# 2.键入空格
driver.find_element_by_id('kw').send_keys(Keys.SPACE)
driver.find_element_by_id('kw').send_keys('加入空格')
time.sleep(5)
# 3.ctrl+a 全选输入框里的内容
driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'a')
time.sleep(3)
# 4.ctrl+x 剪切输入框里的内容
driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'x')
time.sleep(3)
# 5. ctrl+v 粘贴剪切的内容
driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'v')
time.sleep(3)
# 6. 回车
driver.find_element_by_id('su').send_keys(Keys.ENTER)
time.sleep(3)
driver.quit()
2.6 获取断言信息
测试时需要拿实际结果与预期结果进行比较,这个比较称为 断言,通常可以获取断言元素有:【GitHub示例】
(1)title:当前页面的标题
(2)current_url:当前页面的URL
(3)text:获取元素的文本信息
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 06_asseert.py
@Time : 2019/8/20 23:23
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
import time
base_url = 'https://www.baidu.com/'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(base_url)
time.sleep(1)
print("直接访问链接后页面元素获取")
title = driver.title
print('first title: %s' % title)
current_url = driver.current_url
print('first current_url: %s' % current_url)
driver.find_element_by_id('kw').send_keys('python')
driver.find_element_by_id('su').click()
time.sleep(1)
print('搜索关键词后页面元素获取')
title2 = driver.title
print('second title: %s' % title2)
current_url2 = driver.current_url
print('first current_url2: %s ' % current_url2)
kw_text = driver.find_element_by_id('kw').text
print('nums text: %s' % kw_text)
driver.quit()
2.7 设置元素等待
webdriver提供了两种等待方式:显示等待和隐式等待。
(1) 显示等待使webdrver 等待某条件成立时继续执行,否则在达到最大时长时抛出超时异常(TimeoutException)。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 07_wait.py
@Time : 2019/8/21 14:01
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
base_url = 'http://www.baidu.com/'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(base_url)
# 1.显示等待
# WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
# driver :浏览器驱动。
# timeout :最长超时时间,默认以秒为单位。
# poll_frequency :检测的间隔(步长)时间,默认为0.5S。
# ignored_exceptions :超时后的异常信息,默认情况下抛NoSuchElementException异常
# until(method, message=‘’)-----调用该方法提供的驱动程序作为一个参数,直到返回值为True。
# until_not(method, message=‘’)---调用该方法提供的驱动程序作为一个参数,直到返回值为False。
# presence_of_element_located()方法判断元素是否存在。
element = WebDriverWait(driver, 5, 0.5).until(
EC.presence_of_element_located((By.ID, 'kw'))
)
element.send_keys('要搜索的内容')
time.sleep(3)
driver.quit()
(2) 隐式等待:WebDriver提供了implicitly_wait()方法实现隐式等待,默认设置为0。假设在第6秒定位到了元素则继续执行,若直到超出设置时长(10秒)还没有定位到元素,则抛出异常。它的设置并不影响程序的执行速度。【GitHub示例】
# 2. 隐式等待
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from time import ctime
import time
base_url2 = 'https://www.baidu.com/'
browser = webdriver.Chrome('../tools/chromedriver.exe')
# 设置隐式等待为10s
browser.implicitly_wait(10)
browser.get(base_url2)
try:
print(ctime())
browser.find_element_by_id('kw').send_keys('se')
time.sleep(3)
except NoSuchElementException as e:
print(e)
finally:
print(ctime())
browser.quit()
2.8 多窗口切换
在页面操作过程中有时候点击某个链接会弹出新的窗口,这时就需要主机切换到新打开的窗口上进行操作。webdriver 中的 switch_to.window() 方法,可以实现在不同窗口之间切换。【GitHub示例】
current_window_handle:获得当前窗口句柄
window_handles:返回所有窗口的句柄到当前会话
switc_to.window():用于切换到相应的窗口
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 08_switchWindow.py
@Time : 2019/8/22 21:48
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
import time
base_url = 'https://www.baidu.com/'
browser = webdriver.Chrome('../tools/chromedriver.exe')
# 隐式等待10秒
browser.implicitly_wait(10)
browser.get(base_url)
# 获得搜索窗口的句柄
search_windows = browser.current_window_handle
browser.find_element_by_link_text('登录').click()
browser.find_element_by_link_text('立即注册').click()
# 活得当前打开窗口的句柄
all_handles = browser.window_handles
# 进入注册窗口
for handle in all_handles:
if handle != search_windows:
browser.switch_to.window(handle)
print('now register window!')
browser.find_element_by_name('account').send_keys('username')
browser.find_element_by_name('password').send_keys('password')
time.sleep(2)
browser.quit()
2.9 警告框处理
在 webdriver 中处理 JavaScript 所生成的 alert、confirm 以及 prompt 十分简单,具体做法是使用 switch_to.alert 方法定位到 alert/confirm/prompt,然后使用 text/accept/dismiss/ send_keys 等方法进行操作。【GitHub示例】
text:返回 alert/confirm/prompt 中的文字信息。
accept():接受现有警告框。
dismiss():解散现有警告框。
send_keys(keysToSend):发送文本至警告框。keysToSend:将文本发送至警告框。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 09_alert.py
@Time : 2019/8/22 22:30
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time
base_url = 'https://www.baidu.com/'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.implicitly_wait(10)
driver.get(base_url)
# 鼠标悬停至 “设置” 链接
link = driver.find_element_by_link_text('设置')
ActionChains(driver).move_to_element(link).perform()
# 打开搜索设置
driver.find_element_by_link_text('搜索设置').click()
time.sleep(3)
# 点击 “搜索设置”
driver.find_element_by_class_name('prefpanelgo').click()
time.sleep(3)
# 接受警告框prefpanelgo
driver.switch_to.alert.accept()
time.sleep(3)
driver.quit()
2.10 下拉框选择
webdriver 提供了 Select 类来处理下拉框。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 10_select.py
@Time : 2019/8/22 22:49
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.support.select import Select
from time import sleep
base_url = 'https://www.baidu.com/'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.implicitly_wait(10)
driver.get(base_url)
# 鼠标悬停至“设置”链接
driver.find_element_by_name('设置').click()
sleep(2)
# 打开 “搜索设置”
driver.find_element_by_name('搜索设置').click()
sleep(2)
# 搜索结果显示条数
# Select类用于定位select标签。
sel = driver.find_element_by_xpath("//select[@id='nr']")
# select_by_value() 方法用于定位下接选项中的value值。
Select(sel).select_by_value('50')
driver.quit()
2.11 文件上传
通过input标签实现的上传功能,可以将其看作是一个输入框,即通过send_keys()指定本地文件路径的方式实现文件上传。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 11_upfile.py
@Time : 2019/8/23 0:12
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
import os
driver = webdriver.Chrome('../tools/chromedriver.exe')
file_path = "file:///" + os.path.abspath('upfile.html')
driver.get(file_path)
# 定位上传按钮的位置
driver.find_element_by_name('file').send_keys(os.path.abspath('upfile.txt'))
driver.quit()
2.12 cookie操作
网站为了辨别用户身份、进行 session 跟踪而存储在用户本地终端上的数据,也可以叫做浏览器缓存。webdriver 对 cookie 的常用操作有添加、删除、读取。【GitHub示例】
(1) get_cookies()-----获得所有的 cookie 信息
(2) get_cookie(name)-----活得 key 值为 name 的 cookie 的信息
(3) add_cookie(cookie_dict)----添加 cookie。"cookie_dict" 指字典对象,必须有 name 和 value 值
(4) delete_cookie(name,optionsString):删除cookie信息。“name”是要删除的cookie的名称,“optionsString”是该cookie的选项,目前支持的选项包括“路径”,“域”
(5) delete_all_cookies()----删除所有 cookie 信息
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 12_cookie.py
@Time : 2019/8/23 10:11
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from time import sleep
base_url = 'https://www.baidu.com/'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# 1. 获取 cookie 信息
cookies = browser.get_cookies()
print(cookies)
sleep(2)
browser.quit()
# 2. cookie 写入
browser.add_cookie(
{
'name': 'add-cookie',
'value': 'add-cookie-value'
}
)
# 遍历cookies打印cookie信息
for cookie in browser.get_cookies():
print("%s ---> %s" % (cookie['name'], cookie['value']))
sleep(2)
browser.quit()
2.13 调用 JavaScript
对于 webdriver 中无法操作的动作(例如:滚动浏览器的侧边栏),可以调用 webdriver 进行浏览器的控制。webdriver 提供了execute_script()方法来执行 JavaScript 代码。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 13_callJavaScript.py
@Time : 2019/8/24 12:46
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from time import sleep
base_url = 'https://www.baidu.com'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
# window.scrollTo()方法用于设置浏览器窗口滚动条的水平和垂直位置。方法的第一个参数表示水平的左间距,第二个参数表示垂直的上边距。
browser.set_window_size(500, 500)
browser.find_element_by_id('kw').send_keys('百度')
browser.find_element_by_id('su').click()
sleep(2)
# 通过javascript设置浏览器窗口的滚动条位置
js = "window.scrollTo(100, 450);"
browser.execute_script(js)
sleep(2)
browser.quit()
2.14 窗口截图
自动化用例是由程序去执行的,因此有时候打印的错误信息并不十分明确。如果在脚本执行出错的时候能对当前窗口截图保存,那么通过图片就可以非常直观地看出出错的原因。webdriver 提供了截图函数 get_screenshot_as_file() 来截取当前窗口。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : 14_screenShoot.py
@Time : 2019/8/24 13:00
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from time import sleep
base_url = 'http://www.baidu.com/'
browser = webdriver.Chrome('../tools/chromedriver.exe')
browser.get(base_url)
browser.find_element_by_id('kw').send_keys('python selenium')
browser.find_element_by_id('su').click()
sleep(2)
# 截取当前窗口并指定报错截图的位置
# browser.get_screenshot_as_file('ScreenShot/14_screenShot.jpg')
browser.get_screenshot_as_file('ScreenShot/14_screenShot.png')
browser.quit()
2.15 关闭浏览器
关于浏览器的关闭:
close() 关闭单个窗口
quit() 关闭所有窗口
二、通用模型
为了便于自动化测试的学习,使用网站 乐学 http://www.5itest.cn/ 作为测试对象。
1. 启动测试页面
全局定义测试页面地址 ---> 创建浏览器驱动对象 ---> 浏览器对象打开测试页面 ---> 页面加载等待 ---> 判断测试页面是否被打开 ---> 关闭测试页面,退出浏览器【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register.py
@Time : 2019/8/25 9:42
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
# 1.启动要测试页面
# 全局定义注册页面地址
register_url = 'http://www.5itest.cn/register'
# 创建 driver 对象
driver = webdriver.Chrome('../tools/chromedriver.exe')
# 打开测试页面
driver.get(register_url)
# 等待页面加载
sleep(3)
# 通过title是否加载成功,来判断注册页面是否加载成功了,下面的打印结果说明了该对象存在于内存对象中也就是title加载成功了
print(EC.title_contains('注册'))
# 关闭浏览器(close关闭单个页面)
driver.close()
2. 页面元素查找
通过 id(class/xpath等) 查找注册页面元素。【GitHub示例】
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
# 2.查找页面元素并传值给对应的元素处
# 邮箱地址
register_email = driver.find_element_by_id('register_email')
print("邮箱地址的提示语:", register_email.get_attribute('placeholder'))
register_email.send_keys('register_mail1@163.com')
# 用户名
register_nickname = driver.find_element_by_id('register_nickname')
print("用户名的提示语:", register_nickname.get_attribute('placeholder'))
register_nickname.send_keys('register_mail1')
# 密码
register_password = driver.find_element_by_id('register_password')
print("密码的提示语:", register_password.get_attribute('placeholder'))
register_password.send_keys('test@password')
# 验证码
captcha_code = driver.find_element_by_xpath('//*[@id="captcha_code"]')
print('验证码的提示语:', captcha_code.get_attribute('placeholder'))
captcha_code.send_keys('x7xx4')
# 检查《用户协议》是否加载出来了来判断
user_terms = driver.find_element_by_id('user_terms')
locator = (By.ID, 'user_terms')
print(WebDriverWait(driver, 1).until(EC.visibility_of_element_located(locator)))
# 关闭浏览器(close关闭单个页面)
driver.close()
3. 生成测试数据
注册时我们通常需要大量的测试数据(例如注册页面的测试),这时候我们就需要想办法用代码生成大量的测试数据(如邮箱、用户名等)。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : generate_testdata.py
@Time : 2019/8/25 21:38
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import random
# 1. 生成测试邮箱数据
for i in range(5):
register_test_email = ''.join(random.sample('abcdefgh1234567890', 8)) + '@163.com'
print("生成的五组测试邮箱账号为:", register_test_email)
# 2. 同理,生成测试昵称数据
for i in range(5):
register_nickname = ''.join(random.sample('abcdefghijk', 5))
print("生成的五组测试用户昵称为:", register_nickname)
4. 图片验证码破解
针对注册页面的图片验证码,我们怎么解决呢?最简单的就是在代码中注释掉验证码的代码,但是这种不太友好,不符合普通用户的使用场景。
那么怎么识别图片验证码中的文字呢?
(1)将整个注册页面保存下来
(2)定位图片验证码图片的坐标
(3)计算图片四个定点的位置
(4)将图片验证截取
(5)利用第三方库或者专门的图片验证码识别接口进行图片验证码文字的识别
4.1 定位验证码图片
先进行验证码的图片的定位【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : position_captchacode.py
@Time : 2019/8/26 12:28
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from PIL import Image
from selenium import webdriver
from time import sleep
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
sleep(3)
# (1)将含有验证码的页面截图保存下来(这里指的是注册页面)
driver.save_screenshot('image/register_screenshot.png')
# (2)定位图片验证码图片的坐标
code_element = driver.find_element_by_id('getcode_num')
print("验证码的图片左上角顶点的坐标为:", code_element.location)
print("验证码的图片高宽的大小为:", code_element.size)
# (3)计算图片四个定点的位置
left = code_element.location['x']
top = code_element.location['y']
right = code_element.size['width'] + left
height = code_element.size['height'] + top
image = Image.open('image/register_screenshot.png')
# (4)将图片验证截取
code_image = image.crop((left, top, right, height))
code_image.save('image/captchcode_image.png')
driver.close()
4.2 识别图片验证码
获取到了图片验证码后,可以调用第三方接口(https://www.showapi.com/api/lookPoint/184)来识别,当然 Python 也有第三方库(pytesseract)可以识别图片验证码,但是只能识别简单的数字图片,对于组合复杂的图片就力不从心了。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : discern_codeimagepy
@Time : 2019/8/26 18:17
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : ShowapiRequest库从https://www.showapi.com/api/lookPoint/184下载
"""
from selenium import webdriver
from time import sleep
from api import ShowapiRequest
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
sleep(5)
captcha_code = driver.find_element_by_xpath('//*[@id="captcha_code"]')
print('验证码的提示语:', captcha_code.get_attribute('placeholder'))
sleep(5)
# 解析验证码图片中的文字(用第三方的图片验证码识别接口 ShowApiRequest)
# 这里my_appId需要替换成你自己的my_appId,my_appSecret需要替换成你自己的my_appSecret
r = ShowapiRequest("http://route.showapi.com/184-4", "my_appId", "my_appSecret")
r.addBodyPara("img_base64", "")
r.addBodyPara("typeId", "35")
r.addBodyPara("convert_to_jpg", "0")
r.addBodyPara("needMorePrecise", "0")
r.addFilePara("image", r"image/captchcode_image.png") # 文件上传时设置
res = r.post()
text = res.json()["showapi_res_body"]["Result"]
captcha_code.send_keys(text)
sleep(3)
driver.close()
5. 读取元素配置文件
经过前面的学习,有没有感觉到我们写了大量的重复代码。例如:页面中关键元素的查找与传值。对于一个自动化测试页面来说页面的元素是相对于比较稳定的,那么我们就可以把这些重复性的代码封装起来,直接去读取页面中相对于稳定的元素就可以了,这些元素信息我们可以卸载配置文件中,当页面元素发生变动时,只需要修改相应的页面元素信息即可,不用每次去修改代码,从而尽可能的避免引入bug。
5.1 创建配置文件
读取配置文件前,按页面元素的特点写进配置文件中。【GitHub示例】
[RegisterElement]
register_email = id>register_email
register_nickname = id>register_nickname
register_password = id>register_password
getcode_num = id>getcode_num
captcha_code = id>captcha_code
5.2 读取配置文件
【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : read_ini.py
@Time : 2019/8/27 12:48
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import configparser
class ReadIni(object):
# 初始化配置文件路径及节点加载
def __init__(self, file_name=None, node=None):
if file_name is None:
self.file_name = '../data/RegisterElement.ini'
if node is None:
self.node = 'RegisterElement'
else:
self.node = node
self.cf = self.load_ini()
# 加载配置文件
def load_ini(self):
cf = configparser.ConfigParser()
cf.read(self.file_name)
return cf
# 获取各个配置项的值
def get_value(self, key):
data = self.cf.get(self.node, key)
return data
if __name__ == "__main__":
ri = ReadIni()
print(ri.get_value('register_nickname'))
6. 查找元素封装
查找页面元素代码封装。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : find_element.py
@Time : 2019/8/27 14:45
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : 将页面查找元素的功能封装
"""
from util.read_ini import ReadIni
class FindElement(object):
def __init__(self, driver):
self.driver = driver
def get_element(self, key):
ri = ReadIni()
data = ri.get_value(key=key)
by = data.split('>')[0]
value = data.split('>')[1]
try:
if by == 'id':
return self.driver.find_element_by_id(value)
elif by == 'name':
return self.driver.find_element_by_name(value)
elif by == 'className':
return self.driver.find_element_by_className(value)
else:
return self.driver.find_element_by_xpath(value)
except:
file_path = '../image/no_element.png'
self.driver.save_screenshot(file_path)
if __name__ == "__main__":
fe = FindElement()
fe.get_element('register_nickname')
7. 代码封装
写到这里是不是感觉代码虽然写出来了,但是很没有条理性。是的,现在就把代码模块化,让其每个模块做一部分事情。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : package.py
@Time : 2019/8/27 14:13
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from time import sleep
from util.read_ini import ReadIni
from basic.find_element import FindElement
import random
from PIL import Image
from api import ShowapiRequest
class Register(object):
def __init__(self, url):
self.driver = self.get_driver(url=url)
# 启动浏览器,打开目标测试页面url
def get_driver(self, url):
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(url=url)
driver.maximize_window()
return driver
# 定位用户信息,获取元素element
def get_user_element(self, key):
find_element = FindElement(self.driver)
user_element = find_element.get_element(key=key)
return user_element
# 输入用户信息
def send_user_info(self, key, data):
self.get_user_element(key=key).send_keys(data)
# 获取随机数
def get_range(self):
number = ''.join(random.sample('abcdefg123456', 8))
return number
# 获取验证码图片
def get_captcha_image(self, file_name):
self.driver.save_screenshot(filename=file_name)
captcha_element = self.get_user_element('getcode_num')
left = captcha_element.location['x']
top = captcha_element.location['y']
right = captcha_element.size['width'] + left
height = captcha_element.size['height'] + top
image = Image.open(file_name)
img = image.crop((left, top, right, height))
img.save(file_name)
# 识别图片验证码
def discern_captcha_image(self, file_name):
self.get_captcha_image(file_name=file_name)
# 解析验证码图片中的文字(用第三方的图片验证码识别接口 ShowApiRequest)
r = ShowapiRequest("http://route.showapi.com/184-4", "48120", "12c017278c0845c2bcda177212d2d2ac")
r.addBodyPara("img_base64", "")
r.addBodyPara("typeId", "35")
r.addBodyPara("convert_to_jpg", "0")
r.addBodyPara("needMorePrecise", "0")
r.addFilePara("image", file_name) # 文件上传时设置
res = r.post()
text = res.json()["showapi_res_body"]["Result"]
return text
# 主函数
def main(self):
register_nickname = self.get_range()
register_email = self.get_range() + '@163.com'
register_password = self.get_range() + '@123'
file_name = '../image/code_image.png'
captcha_code = self.discern_captcha_image(file_name=file_name)
self.send_user_info('register_nickname', register_nickname)
self.send_user_info('register_email', register_email)
self.send_user_info('register_password', register_password)
self.send_user_info('captcha_code', captcha_code)
self.get_user_element('register-btn').click()
sleep(5)
self.driver.close()
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
r = Register(register_url)
r.main()
7.1 异常处理
测试中我们要让程序畅通运行的同时,也要注意对程序的异常进行处理。例如:由于调用的第三方接口来是被验证码图片,难免会出现识别不了的情况,为了不阻塞程序的正常运行,当识别出错的时候,可以对错误识别进行异常的处理。【GitHub示例】
# 在 [RegisterElement.ini] 文件添加验证码错误的元素id
captcha_code_error = id>captcha_code-error
# 在 package.py 代码的基础上加上异常处理
# 异常处理:注册失败进行截图,方便问题排查
captcha_code_error = self.get_user_element('captcha_code_error')
if captcha_code_error is None:
print("......恭喜你注册成功了......")
else:
self.driver.save_screenshot('../image/captcha_code_error.png')
sleep(5)
7.2 兼容多浏览器执行
在实际的测试中,由于不同浏览器的技术标准的差异,导致同一份代码在不同浏览器中的表现形式不一致,所以避免不了多浏览器执行测试用例。【GitHub示例】
注意:FireFox 驱动与 Chrome 的驱动配置不同
下载地址:https://github.com/mozilla/geckodriver/releases
下载后(根据系统版本选择):
(1)解压取出geckodriver.exe(以64x为例);
(2)将geckodriver.exe放到Firefox的安装目录下,如:(D:\火狐\Mozilla Firefox);
(3)将火狐安装目录(D:\火狐\Mozilla Firefox)添加到环境变量path中;
(4)重启 IDEA,记得一定要
# 兼容多浏览器执行测试
def get_more_driver(self, url, browser):
if browser == 'chrome':
# 版本 76.0.3809.100(64位)对应的驱动
driver = webdriver.Chrome('../tools/chromedriver.exe')
elif browser == 'firefox':
# FireFox 68.0.2(64位) 对应的驱动,和 chrome 驱动使用有差异
driver = webdriver.Firefox()
driver.get(url=url)
driver.maximize_window()
return driver
8. 日志记录
到这里已经搞了好多,但是在排查问题的时候,不是很方便,我们需要对程序的执行中错误的地方进行记录。
8.1 在 console 输出log
可以将日志信息输出的console中,但是这种方式不常用。【GitHub示例】日常更多使用的是8.2的方法,将日志信息输出到log文件中。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : record_log.py
@Time : 2019/8/28 19:15
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import logging
class RecordLog(object):
def __init__(self):
self.logger = logging.getLogger()
self.logger.setLevel(logging.DEBUG)
# 1. 在 console 中输出日志文件
# 能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象
# 日志信息会输出到指定的stream中,如果stream为空则默认输出到sys.stderr。
console = logging.StreamHandler(stream=None)
# 将sys.stderr中的信息添加到logger中
self.logger.addHandler(console)
# 输出调试信息
self.logger.debug("这是一条在控制台线上的log")
# 关闭流
console.close()
# 移除
self.logger.removeHandler(console)
if __name__ == "__main__":
rl = RecordLog()
8.2 输出log到文件
【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : record_log.py
@Time : 2019/8/28 19:15
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import logging
import os
from datetime import datetime
class RecordLog(object):
def __init__(self):
self.logger = logging.getLogger()
self.logger.setLevel(logging.DEBUG)
# 2.将log信息输出到log文件中
# 2.1 先定位看将log文件输出到哪里去
current_dir = os.path.dirname(os.path.abspath(__file__))
print(current_dir) # D:\MySpace\Python\WebTesting\util
log_dir = os.path.join('../logs')
# 日志名称构建
log_file_name = datetime.now().strftime("%Y-%m-%d") + '.log'
log_file_path = log_dir + '/' + log_file_name
print(log_file_path)
# 2.2 好的,将日志写进log文件中
self.file_handle = logging.FileHandler(log_file_path, 'a', encoding='utf-8')
formatter = logging.Formatter(
'%(asctime)s %(filename)s %(funcName)s %(levelno)s: [%(levelname)s] ---> %(message)s')
self.file_handle.setFormatter(formatter)
self.logger.addHandler(self.file_handle)
def get_log(self):
return self.logger
def close_handle(self):
self.logger.removeHandler(self.file_handle)
self.file_handle.close()
if __name__ == "__main__":
rl = RecordLog()
log_info = rl.get_log()
log_info.debug('输出到文件中去')
rl.close_handle()
9.识别验证码
作为一个通用模块,我们应该把识别验证码模块封装起来,可以方便其他部分的调用。【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : discern_captcha.py
@Time : 2019/8/29 23:22
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : 图片验证码识别模块
"""
from api.ShowapiRequest import ShowapiRequest
from PIL import Image
from time import sleep
from selenium import webdriver
class DiscernCaptcha(object):
def __init__(self, driver):
self.driver = driver
# 获取照片
def get_captcha_code_image(self, file_name):
self.driver.save_screenshot(file_name)
captcha_image = self.driver.find_element_by_id('getcode_num')
left = captcha_image.location['x']
top = captcha_image.location['y']
right = captcha_image.size['width'] + left
height = captcha_image.size['height'] + top
im = Image.open(file_name)
img = im.crop((left, top, right, height))
img.save(file_name)
sleep(3)
# 识别图片
def discern_image(self, file_name):
self.get_captcha_code_image(file_name)
# 解析验证码图片中的文字(用第三方的图片验证码识别接口 ShowApiRequest)
r = ShowapiRequest("http://route.showapi.com/184-4", "48120", "12c017278c0845c2bcda177212d2d2ac")
r.addBodyPara("img_base64", "")
r.addBodyPara("typeId", "35")
r.addBodyPara("convert_to_jpg", "0")
r.addBodyPara("needMorePrecise", "0")
r.addFilePara("image", file_name) # 文件上传时设置
res = r.post()
text = res.json()["showapi_res_body"]["Result"]
return text
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
driver.maximize_window()
dc = DiscernCaptcha(driver)
file_name = '../image/discern_captcha/code_image.png'
dc.get_captcha_code_image(file_name)
dc.discern_image(file_name)
driver.close()
三、PO模型
PO 模型:将测试的每个页面看作一个对象,将这些对象抽象成类,完成页面元素和业务操作;将测试类和 page 类区分开来,需要调用什么类去取即可,降低耦合。当页面元素发生变化时,只需修改对应页面类部分,其他部分极可能做到最小修改。
PO 模型的分层结构(以注册页面作为page对象):
(1)register_page(页面元素查找类) --->
(2)register_handle(操作层:将查找到的元素上传递数据) --->
(3)register_business(业务层:调用操作层,根据操作层传递的数据进行测试业务场景判断,如验证码输入错误场景等) --->
(4)register_cases(测试模块:封装业务层,进行测试用例业务组装)。
1. 元素查找
此页面主要是查找注册页面中正常的元素和异常的元素(错误的提示信息)。【GitHub示例】
register_email_error = id>register_email-error
register_nickname_error = id>register_nickname-error
register_password_error = id>register_password-error
captcha_code_error = id>captcha_code-error
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : regitser_page.py
@Time : 2019/8/29 11:38
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from basic.find_element import FindElement
from selenium import webdriver
class RegisterPage(object):
# 初始化元素查找类,执行该类的时候就会加载
def __init__(self, driver):
self.fe = FindElement(driver)
# 注册邮箱
def get_register_email(self):
return self.fe.get_element('register_email')
# 用户昵称
def get_register_nickname(self):
return self.fe.get_element('register_nickname')
# 密码
def get_register_password(self):
return self.fe.get_element('register_password')
# 验证码输入框
def get_getcode_num(self):
return self.fe.get_element('getcode_num')
# 验证码图片
def get_captcha_code(self):
return self.fe.get_element('captcha_code')
# 注册邮箱框文本提示语
def get_register_email_placeholder(self):
print(self.fe.get_element('register_email').get_attribute('placeholder'))
return self.fe.get_element('register_email').get_attribute('placeholder')
# 用户昵称框文本提示语
def get_register_nickname_placeholder(self):
print(self.fe.get_element('register_nickname').get_attribute('placeholder'))
return self.fe.get_element('register_nickname').get_attribute('placeholder')
# 密码框文本提示语
def get_register_password_placeholder(self):
print(self.fe.get_element('register_password').get_attribute('placeholder'))
return self.fe.get_element('register_password').get_attribute('placeholder')
# 验证码框文本提示语
def get_captcha_code_placeholder(self):
print(self.fe.get_element('captcha_code').get_attribute('placeholder'))
return self.fe.get_element('captcha_code').get_attribute('placeholder')
# 不合法注册邮箱错误提示语
def get_register_email_error(self):
return self.fe.get_element('register_email_error')
# 不合法注册用户错误提示语
def get_register_nickname_error(self):
return self.fe.get_element('register_nickname_error')
# 不合法密码错误提示语
def get_register_password_error(self):
return self.fe.get_element('register_password_error')
# 不合法验证码错误提示语
def get_captcha_code_error(self):
return self.fe.get_element('captcha_code_error')
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
rp = RegisterPage(driver)
rp.get_register_email_placeholder()
rp.get_register_nickname_placeholder()
rp.get_register_password_placeholder()
rp.get_captcha_code_placeholder()
driver.close()
2. 操作层
上一层我们获取到注册页面中主要元素信息,接下来就该给这些元素进行数据上的操作处理(赋值)。【(GitHub示例)[https://github.com/Crisimple/WebTesting/commit/3fd4bfedc52f9c384356dbc723226c4db3c5eae8]】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_handle.py
@Time : 2019/8/29 15:07
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : 给register_page中找到的元素赋值
"""
from page_object_model.regitser_page import RegisterPage
from selenium import webdriver
from time import sleep
class RegisterHandle(object):
def __init__(self, driver):
self.rp = RegisterPage(driver)
# 输入注册邮箱
def send_register_email(self, email):
self.rp.get_register_email().send_keys(email)
# 输入用户昵称
def send_register_nickname(self, nickname):
self.rp.get_register_nickname().send_keys(nickname)
# 输入注册密码
def send_register_password(self, password):
self.rp.get_register_password().send_keys(password)
# 输入验证码
def send_register_captcha(self, captcha):
self.rp.get_getcode_num().send_keys(captcha)
# 获取错误信息
def get_user_text(self, error_info, error_value):
text = None
if error_info == "register_email_error":
text = self.rp.get_register_email_error().send_keys(error_value)
elif error_info == 'register_nickname_error':
text = self.rp.get_register_nickname_error().send_keys(error_value)
elif error_info == 'register_password_error':
text = self.rp.register_password_error().send_keys(error_value)
elif error_info == 'captcha_code_error':
text = self.rp.captcha_code_error().send_keys(error_value)
else:
print("error element not found")
return text
# 点击注册按钮
def click_register_btn(self):
self.rp.get_register_btn().send_keys()
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
rh = RegisterHandle(driver)
rh.send_register_email('jjij@163.com')
rh.send_register_nickname('MiFan')
rh.send_register_password('123@123abc')
rh.send_register_captcha('qwer')
rh.click_register_btn()
sleep(5)
driver.close()
3. 业务层
业务层,也就是我们要做些什么,做事的逻辑是什么?对于自动化测试来说,就是自动化的测试场景,也就是我们的测试点逻辑。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_business.py
@Time : 2019/8/29 17:35
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from page_object_model.register_handle import RegisterHandle
from selenium import webdriver
from time import sleep
class RegisterBusiness(object):
def __init__(self, driver):
self.rh = RegisterHandle(driver)
# 正常注册
def common_register(self, register_email, nickname, password, captcha):
self.rh.send_register_email(register_email)
self.rh.send_register_nickname(nickname)
self.rh.send_register_password(password)
self.rh.send_register_captcha(captcha)
# 判断是否注册成功
def success_or_fail(self):
if self.rh.get_register_btn_text() is None:
return True
else:
return False
# 邮箱错误
def register_email_error(self, register_email, nickname, password, captcha):
self.common_register(register_email, nickname, password, captcha)
if self.rh.get_user_text('register_email_error', "请输入有效的电子邮件地址") is None:
print("注册邮箱输入错误")
return True
else:
return False
# 用户昵称错误
def register_nickname_error(self, register_email, nickname, password, captcha):
self.common_register(register_email, nickname, password, captcha)
if self.rh.get_user_text('register_nickname_error', "字符长度必须大于等于4,一个中文字算2个字符") is None:
print("用户昵称错误")
return True
else:
return False
# 用户密码错误
def register_password_error(self, register_email, nickname, password, captcha):
self.common_register(register_email, nickname, password, captcha)
if self.rh.get_user_text('register_password_error', "最少需要输入 5 个字符") is None:
print("用户密码错误")
return True
else:
return False
# 验证码错误
def captcha_code_error(self, register_email, nickname, password, captcha):
self.common_register(register_email, nickname, password, captcha)
if self.rh.get_user_text('captcha_code_error', "验证码错误") is None:
print("验证码错误")
return True
else:
return False
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
rb = RegisterBusiness(driver)
rb.captcha_code_error('1243589@163.com', 'pass123', 'test@123', 'sds')
sleep(3)
driver.close()
4. 测试层
经过一些列分层计划,虽然在每一层我们都在进行测试,但是我们最终还是要回归到本质进行测试,将各层模块一同调用起来。基于业务层编写测试用例。【GitHub示例】
(这里留了个问题,就是两条测试数据之间页面没有刷新重新输入,导致数据在一个页面重复输入)【】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_testcases.py
@Time : 2019/8/29 21:20
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from page_object_model.register_business import RegisterBusiness
from selenium import webdriver
import unittest
class RegisterTestcase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.register_url = 'http://www.5itest.cn/register'
cls.driver = webdriver.Chrome('../tools/chromedriver.exe')
cls.driver.get(cls.register_url)
cls.driver.maximize_window()
cls.rb = RegisterBusiness(cls.driver)
@classmethod
def tearDownClass(cls) -> None:
cls.driver.close()
# 注册邮箱错误,但用例执行成功
def test_register_email_error(self):
register_email_error = self.rb.register_email_error('23', 'test01', 'test01abc', 'abc4')
if register_email_error is True:
print("账号注册失败,该用例执行成功")
else:
print("账号注册成功,该用例执行失败")
# 验证码错误,但用例执行成功‘
def test_captcha_code_error(self):
captcha_code_error = self.rb.captcha_code_error('test02@163.com', 'test02', 'test02abc', 'height')
if captcha_code_error is True:
print("账号注册失败,该用例执行成功")
else:
print("账号注册成功,该用例执行失败")
if __name__ == "__main__":
unittest.main()
四、数据驱动框架
那么问题来了,什么是数据驱动呢?就是,数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变,也就是参数的应用化。
这里对于数据驱动测试,《虫师》的两篇博文写的很到位【《使用“数据驱动测试”之前应该知道的》、《使用“数据驱动测试”之前你应该知道的》】。总结起来就是,数据驱动绝非读取文件(excel、csv、xml)中数据进行参数的赋值测试,因为采用的这种方式的测试,工作重心反而变成了如何读写文件,而对于自动化测试中关心的执行结果统计、断言结果反而不是那么容易去实现。尤其是测试页面结构发生大的调整时,文件类的字段调整获取也要发生较大的修改,所以文件数据驱动测试也是可以的,但是并不是最优解。
那么什么才是最优的数据驱动测试呢?是的,用单元测试 unittest 结合 ddt 库。使用单元测试可以很方便的解决两个问题:
(1)断言。利用单元测试的断言机制,我们可以方便的进行预期结果和实际结果的对比;
(2)数据统计。执行完测试用例后,一共执行了多少条用例,执行成功多少,失败多少,失败的用例错误在哪里?单元测试框架会帮我们统计展示。
1. DDT 入门
Python 的 unittest 没有自带数据驱动功能,如果使用 unittest, 同时又想使用数据驱动,就用 DDT 吧。奉上 ddt 官方文档 《DDT 官方文档》。
DDT 的使用方法:
(1) ddt.ddt --- 装饰类,也就是继承自 TestCase 的类;
(2) ddt.data --- 装饰测试方法,参数是一系列的值。
(3) ddt.file_data --- 装饰测试方法,参数是文件名。文件可以是 json 或 yaml 类型,除.yaml结尾的文件,其他文件均会作为json文件处理。
(4) ddt.unpack --- 传递的是复杂的数据结构是使用。如:元组或列表。添加到 unpack 上之后,ddt 会自动把元组或者列表对应到多个参数上。
(5) 测试用例方法名生成规则 ---
示例用法【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : ddt_introduction.py
@Time : 2019/8/30 10:35
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import unittest
import ddt
@ddt.ddt
class DDTExample(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print(cls.__name__)
@classmethod
def tearDownClass(cls) -> None:
print('...end...')
@ddt.data(
[1, 2],
[3, 4],
[5, 6]
)
@ddt.unpack
def test_add(self, a, b):
print(a + b)
if __name__ == "__main__":
unittest.main()
2. 实践应用
将 ddt 引入到自动化测试中。【GitHub示例】
但是这里同样埋着一个BUG,稍后解决。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_ddt_cases.py
@Time : 2019/8/30 14:19
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
import ddt
from page_object_model.register_business import RegisterBusiness
from selenium import webdriver
import unittest
from time import sleep
@ddt.ddt
class RegisterDdtCases(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.register_url = 'http://www.5itest.cn/register'
cls.driver = webdriver.Chrome('../tools/chromedriver.exe')
cls.driver.maximize_window()
cls.driver.get(cls.register_url)
sleep(3)
cls.rb = RegisterBusiness(cls.driver)
@classmethod
def tearDownClass(cls) -> None:
sleep(2)
cls.driver.close()
# 邮箱错误测试的测试用例
@ddt.data(
# 顺序分别是:注册邮箱、用户昵称、注册密码、验证码、错误信息定位元素、错误提示信息
['123', 'test01', 'test01abc', 'tyu9'],
['@163.com', 'test01', 'test01abc', 'tyu9'],
['@163', 'test01', 'test01abc', 'tyu9']
)
@ddt.unpack
def test_ddt_email_error(self, register_email, nickname, password, captcha):
register_email_error = self.rb.register_email_error(register_email, nickname, password, captcha)
print("register_email_error: ", register_email_error)
self.assertFalse(register_email_error, '你输入的邮箱错误,但此条测试用例执行成功')
if __name__ == "__main__":
unittest.main()
五、关键字模型
关键字简单来说就是,把我们的执行操作每一个关键步骤当成一个关键字来对待,用来驱动程序的设计开发。例如:进行web自动化我们的首要是打开浏览器,是的 “打开浏览器” 我们就可以作为一个关键字来对待它,关键字就是来驱动我们程序设计的关键步骤。通过关键字的改变从而驱动自动化测试的执行,最终引起测试结果的改变。
对于测试一个注册页面,我们来梳理下看有哪些关键词,更深层次了解下关键词模型:
(1) 打开浏览器 ---> 打开浏览器
(2) 输入注册页面的url ---> 输入测试地址
(3) 页面加载等待 ---> 页面加载等待
(4) 输入(注册邮箱、用户名、密码、验证码)---> 输入元素
(5) 点击注册按钮 ---> 点击元素
(6) 退出浏览器
1. 构建关键词类
【GitHub示例】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_keyword.py
@Time : 2019/8/30 23:59
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from selenium import webdriver
from basic.find_element import FindElement
from time import sleep
class RegisterKeyword(object):
def __init__(self, driver):
self.fe = FindElement(driver)
# 打开浏览器
def open_browser(self, browser):
if browser == 'chrome':
self.driver = webdriver.Chrome('../tools/chromedriver.exe')
elif browser == 'firefox':
self.driver = webdriver.Firefox()
else:
self.driver = webdriver.Edge()
# 输入测试地址
def get_url(self, url):
self.driver.get(url)
# 定位元素
def get_element(self, key):
return self.fe.get_element(key)
# 输入元素
def send_element_key(self, key, value):
get_element = self.get_element(key)
get_element.send_keys(value)
# 点击元素
def click_element(self, key):
self.fe.get_element(key).click()
# 页面等待
@staticmethod
def wait_loading():
sleep(3)
# 关闭浏览器
def close_browser(self):
self.driver.close()
if __name__ == "__main__":
register_url = 'http://www.5itest.cn/register'
driver = webdriver.Chrome('../tools/chromedriver.exe')
driver.get(register_url)
rk = RegisterKeyword(driver)
print(rk.get_element('register_email'))
driver.close()
2. 关键词模型测试用例
我们关键词方法直接从 register_keyword 中读取即可,但是测试数据从哪获取到从而传给相应的关键词方法呢?为了方便测试数据方便管理,我们可以将其存储到 Excel中去并获取。
2.1 读取Excel
【(GitHub示例)[https://github.com/Crisimple/WebTesting/commit/89aa1c61d712b272ca56db18f6cc00ca411b1720]】
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : read_excel.py
@Time : 2019/9/1 0:25
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : 读写 excel 文件
"""
import xlrd
from xlutils.copy import copy
class ReadExcel(object):
def __init__(self, excel_path=None, index=None):
if excel_path is None:
self.excel_path = '../data/register_keyword_testdata.xls'
self.index = 0
else:
self.excel_path = excel_path
self.index = index
# 打开 excel 文件,获取数据列表
self.data = xlrd.open_workbook(self.excel_path)
# 读取第一 sheet 页的数据
self.table = self.data.sheets()[0]
def get_data(self):
result = []
rows = self.get_lines()
if rows != '':
for i in range(rows):
col = self.table.row_values(i)
result.append(col)
return result
return None
# 获取 excel 行数
def get_lines(self):
rows = self.table.nrows
if rows >= 1:
return rows
return None
# 获取单元格的值
def get_cell(self, row, col):
if self.get_lines() > row:
data = self.table.cell(row, col).value
return data
return None
def write_data(self, row, col, value):
read_data = xlrd.open_workbook(self.excel_path)
write_data = copy(read_data)
write_data.get_sheet(self.index).write(row, col, value)
write_data.save("../data/register_keyword_testdata.xls")
write_data.save(self.excel_path)
if __name__ == "__main__":
re = ReadExcel()
print(re.get_data())
print(re.get_lines())
print(re.get_cell(0, 0))
re.write_data(11, 0, 123456)
2.2 测试用例实践
前面将测试测数据存在到excel中了,接下来怎么写关键字对应的测试用例。【GitHub示例】
(1) 拿到操作值,是否执行
(2) 拿到执行方法
(3) 拿到输入数据
(4) 是否有输入数据
执行方法(输入数据,操作元素)
没有输入数据
执行方法(操作元素)
(5) 对比预期结果和实际结果的值
对比结果一样,测试结论为pass;否则为fail
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : register_keyword_cases.py
@Time : 2019/9/1 0:03
@Author : Crisimple
@Github : https://crisimple.github.io/
@Contact : Crisimple@foxmail.com
@License : (C)Copyright 2017-2019, Micro-Circle
@Desc : None
"""
from util.read_excel import ReadExcel
from keyword_model.register_keyword import RegisterKeyword
from selenium import webdriver
class RegisterKeywordCases(object):
def __init__(self):
self.rk = RegisterKeyword()
self.excel_path = '../data/register_keyword_testdata.xls'
# 执行关键字测试方法
def run_keyword_method(self, keyword_method, operator_element='', send_value=''):
print('keyword_method ---> ', keyword_method)
print("operator_element ---> ", operator_element)
print("send_value ---> ", send_value)
execute_method = getattr(self.rk, keyword_method)
print(execute_method)
if operator_element is '' and send_value is not '':
result = execute_method(send_value)
elif operator_element is not '' and send_value is '':
result = execute_method(operator_element)
elif operator_element is '' and send_value is '':
result = execute_method()
else:
result = execute_method(operator_element, send_value)
return result
# 执行关键词测试用例
def run_keyword_excel_cases(self):
handle_excel = ReadExcel(self.excel_path)
# 获取 excel 关键词测试用例的条数
cases_numbers = handle_excel.get_lines()
print("注册页获取到的关键词测试的测试用例条数为:%s" % cases_numbers)
# 循环遍历测试用例
if cases_numbers:
# 第 0 行是标题行不作为用例执行
for i in range(1, cases_numbers):
# 获取测试用例的名称
testcase_name = handle_excel.get_cell(i, 0)
# 获取用例是否执行
is_run = handle_excel.get_cell(i, 1)
if is_run == 'yes':
keyword_method = handle_excel.get_cell(i, 2)
operator_element = handle_excel.get_cell(i, 3)
send_value = handle_excel.get_cell(i, 4)
except_result = handle_excel.get_cell(i, 5)
actual_result = handle_excel.get_cell(i, 6)
# 反射
self.run_keyword_method(keyword_method, operator_element, send_value)
# if except_result is not '':
# except_value = self.run_keyword_method(keyword_method)
else:
print('第 %s 条用例不执行,用例名称是: [%s],无预期结果' % (i, testcase_name))
else:
print("略略略~,请检查你是否有写测试用例!")
if __name__ == "__main__":
rkc = RegisterKeywordCases()
# rkc.run_keyword_method('open_browser', '', 'chrome')
# rkc.run_keyword_method('get_url', '', 'http://www.5itest.cn/register')
rkc.run_keyword_excel_cases()
六、行为驱动模型
什么是行为驱动测试呢?行为驱动(Behave Driven Development)测试,是一种敏捷的开发方法,通常应用在自动化测试中,通过使用自然描述语言确定自动化脚本。
【GitHub示例】
不懂不懂,什么乱七八糟的,不喜欢这种方式用在自动化脚本里。