Selenium Python Web 自动化测试

菜鸟测试 · 2019年09月09日 · 最后由 第十天 回复于 2023年07月19日 · 6817 次阅读

一、基础准备

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 的浏览器上。其基本原理如下:
selenium工作原理图

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. 实践应用
&emsp;&emsp;将 ddt 引入到自动化测试中。【**[GitHub示例](https://github.com/Crisimple/WebTesting/commit/2333108bf4ca20e5df2a7e14ca4e02cf8f314e54)**】

&emsp;&emsp;但是这里同样埋着一个BUG,稍后解决。
```python
#!/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 示例

不懂不懂,什么乱七八糟的,不喜欢这种方式用在自动化脚本里。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 5 条回复 时间 点赞
1楼 已删除

我想请教一下大牛,使用 move_to_element 这个进行鼠标悬停时,在同一个测试用例中,我使用第二次鼠标悬停就失效。请问下楼主遇到过这种问题吗?

这个暂时还没遇到过,你说的失效,是第二次悬停没有代码执行后,没有悬停成嘛?代码没报错的话,清一下浏览器的缓存试试;或检查下元素是否定位正确!

菜鸟测试 回复

报错的话是报 第二次悬停 后一步操作,我看这个测试用例的第二次悬停页面根本没有悬停的效果显示。 两次悬停都是一样的语句,应该不存在语句错误 悬停代码 ActionChains(driver).move_to_element(driver.find_element_by_xpath("元素定位")).perform()

睡觉或者刷新

每执行一次都只能设定某个浏览器吗?如果想同时开启多个浏览器一起测试有什么好主意吗?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册