Web的UI自动化基础知识

Anthony_231 2024-07-20 11:33:01 阅读 60

目录

1 Web自动化入门基础1.1 自动化知识以及工具1.2 主流web自动化测试工具1.3 入门案例

2 使用工具的API2.1 元素定位2.1.1 id选择器2.1.2 name2.1.3 class_name选择器2.1.4 tag_name选择器2.1.5 link_text选择器2.1.6 partial_link_text选择器2.1.7 xpath选择器2.1.8 CSS选择器2.1.9 Xpath和CSS区别2.1.10 元素定位分类2.1.11 元素定位的另一种写法

2.2 元素操作2.3 浏览器操作2.4 获取元素信息2.5 鼠标操作2.5.1 常用方法2.5.2 执行的方法2.5.3 鼠标右击2.5.4 鼠标双击2.5.5 鼠标悬停2.5.6 鼠标拖动

2.6 键盘操作2.6.1 常用操作2.6.2 键盘操作2.6.3 元素等待2.6.4 隐式等待2.6.5 显式等待2.6.6 隐式和显式区别2.6.7 下拉框2.6.8 弹出框分类

2.7 滚动条实现方法2.8 frame切换2.8.1 多窗口切换2.8.2 窗口截图

2.9 验证码处理2.10 cookie

3 Pytest框架3.1 总体介绍3.2 断言方法3.3 setup和teardown3.4 配置文件3.5 测试报告插件3. 6 数据参数化3.6.1 单一参数3.6.2 多个参数3.6.3 推荐用法

4 PO模式4.1 递进学习路线4.2 无模式4.2.1 案例说明4.2.2 选择测试用例

4.3 V1版本4.4 V2版本4.4.1 方法封装

4.5 V3版本4.6 PO模式4.6.1 概念4.6.2 PO模式分层4.6.3 PO模式优点

4.7 V4版本4.8 v5版本4.9 v6版本4.9.1 示例代码

5 数据驱动5.1 JSON基本介绍5.2 字典与JSON转换5.3 JSON文件读写

6 项目实战6.1 项目结构6.2 base包6.3 data包6.4 page包6.5 scripts包6.6 utils包

7 日志收集7.1 日志收集7.2 日志高级用法7.3 四大组件7.3.1 Logger类7.3.2 Handler类7.3.3 Formatter类

8 面试题

1 Web自动化入门基础

1.1 自动化知识以及工具

自动化概念 :由机器设备代替人工自动完成指定目标的过程

优点:

减少人工劳动力提高工作效率产品规格统一标准规模化安全

自动化测试概念 :由程序代替人工去执行测试的过程

应用场景

解决回归测试

已实现的功能需要回归已解决的bug需要回归 解决压力测试:例如使用Jmeter做接口自动化解决兼容性测试:在不同浏览器上做兼容性测试解决操作重复性问题

1.2 主流web自动化测试工具

QTP :收费且支持web/桌面自动化测试selenium:开源web自动化测试工具(功能测试)【跨平台、支持多种浏览器、支持多种语言、稳定】robot framework :基于python的可扩展的关键字驱动的自动化测试框架

1.3 入门案例

<code># 导包

import time

from selenium import webdriver

# 创建浏览器驱动

driver = webdriver.Chrome()

# 打开百度首页

driver.get("http://www.taobao.com")

# 暂停3秒

time.sleep(10)

# 关闭浏览器

driver.quit()

在这里插入图片描述

2 使用工具的API

2.1 元素定位

八种定位方式

2.1.1 id选择器

案例:

打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作

最大化页面

使用ID定位,输入用户名:admin

使用ID定位,输入密码:12345

等待5s,关闭浏览器

<code>import time

from selenium import webdriver

# 获取驱动对象

driver = webdriver.Chrome()

# 获取地址

driver.get("https://parabank.parasoft.com/parabank/admin.htm")

# 页面最大化

driver.maximize_window()

# 根据id查找元素

driver.find_element_by_id('username').send_key("admin")

driver.find_element_by_id('password').send_key("123456")

# 等待五秒

time.sleep(5)

# 关闭浏览器

driver.quit()

2.1.2 name

案例:

打开https://parabank.parasoft.com/parabank/admin.htm 网站首页,完成以下操作

最大化页面

使用name定位,输入用户名:admin

使用name定位,输入密码:12345

等待5s,关闭浏览器

import time

from selenium import webdriver

# 获取驱动对象

driver = webdriver.Chrome()

# 获取地址

driver.get("https://parabank.parasoft.com/parabank/admin.htm")

# 页面最大化

driver.maximize_window()

# 根据id查找元素

driver.find_element_by_name('username').send_key("admin")

driver.find_element_by_name('password').send_key("123456")

# 等待五秒

time.sleep(5)

# 关闭浏览器

driver.quit()

2.1.3 class_name选择器

案例:

打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作

最大化页面

使用class_name定位,输入用户名:admin

使用class_name定位,输入密码:12345

等待5s,关闭浏览器

import time

from selenium import webdriver

# 获取驱动对象

driver = webdriver.Chrome()

# 获取地址

driver.get("https://parabank.parasoft.com/parabank/admin.htm")

# 页面最大化

driver.maximize_window()

# 根据id查找元素

driver.find_element_by_class_name('username').send_key("admin")

driver.find_element_by_class_name('password').send_key("123456")

# 等待五秒

time.sleep(5)

# 关闭浏览器

driver.quit()

2.1.4 tag_name选择器

# 1.导包

import time

from selenium import webdriver

# 2.创建浏览器驱动对象

driver = webdriver.Chrome()

# 3.业务操作

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_tag_name("input").send_keys("xxxxxx")

# 4.暂停5秒

time.sleep(5)

# 5.关闭驱动对象

driver.quit()

2.1.5 link_text选择器

# 1.导包

import time

from selenium import webdriver

# 2.创建浏览器驱动对象

driver = webdriver.Chrome()

# 3.业务操作

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_link_text("访问 新浪 网站").click()

# 4.暂停5秒

time.sleep(5)

# 5.关闭驱动对象

driver.quit()

2.1.6 partial_link_text选择器

# 1.导包

import time

from selenium import webdriver

# 2.创建浏览器驱动对象

driver = webdriver.Chrome()

# 3.业务操作

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# driver.find_element_by_partial_link_text("访问 新浪 网站").click() # 通过全部文本定位超链接

driver.find_element_by_partial_link_text("访问").click() # 通过局部文本定位超链接

# 4.暂停5秒

time.sleep(5)

# 5.关闭驱动对象

driver.quit()

定位一组元素

# 1.导包

import time

from selenium import webdriver

# 2.创建浏览器驱动对象

driver = webdriver.Chrome()

# 3.业务操作

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

elements = driver.find_elements_by_tag_name("input")

elements[1].send_keys("123456")

# 4.暂停5秒

time.sleep(5)

# 5.关闭驱动对象

driver.quit()

2.1.7 xpath选择器

四种定位方式

路径元素属性属性与逻辑结合层级与属性结合

方法

element = driver。find_element_by_xpath(xpath)

路径

绝对路径:

从外层元素到指定元素之间所有经过元素层级的路径绝对路径以/html根节点开始,使用/来分割元素层级,如:/html/body/div/fieldset/p[1]/input绝对路径对页面要求严格,不建议使用 相对路径

匹配任意层级的元素,不限制元素的位置相对路径//开始格式://input 或者 //*

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 定位用户名输入框, 输入 admin

driver.find_element_by_xpath("/html/body/div/fieldset/form/p[1]/input").send_keys("admin")

# 暂停3s

time.sleep(3)

# 定位密码输入框, 输入 123

driver.find_element_by_xpath("//*[@id='passwordA']").send_keys("123")code>

time.sleep(5)

driver.close()

使用谷歌浏览器获取 XPath 表达式的过程:

元素上右键 -> 检查

在F12对应的文档中的对应元素上 右键 -> Copy -> Copy XPath 或者 Copy full XPath

使用函数

不使用函数时:

//*[@id='xxx']code>

使用函数后

//*[text()='xxx'] 文本内容是 xxx 的元素

//*[contains(@attribute, 'xxx')] 属性中含有 xxx 值的元素

//*[starts-with(@attribute, 'xxx')] 属性以xxx开头的元素

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 利用元素属性通过XPath 定位用户名输入框, 并输入 admin

# driver.find_element_by_xpath("//*[@name='userA']").send_keys("admin")code>

# driver.find_element_by_xpath("//*[@id='userA']").send_keys("admin")code>

# driver.find_element_by_xpath("//*[@placeholder='请输入用户名']").send_keys("admin")code>

driver.find_element_by_xpath("//*[@type='text']").send_keys("admin")code>

time.sleep(5)

driver.close()

2.1.8 CSS选择器

常用的定位方式

id选择器class选择器元素选择器属性选择器层级选择器

方法

element = driver.find_element_by_css_selector(css表达式)

id

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位方式中的id选择器, 定位用户名输入框, 并输入 admin

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_css_selector("#userA").send_keys("admin")

time.sleep(5)

driver.close()

class

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位方式中的class选择器, 定位电话号码输入框, 并输入 13100000000

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_css_selector(".telA").send_keys("13100000000")

time.sleep(5)

driver.close()

元素选择器

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位方式中的元素选择器, 定位注册按钮, 并点击

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_css_selector("button").click()

time.sleep(5)

driver.close()

属性选择器

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位方式中的属性选择器, 定位密码输入框, 并输入 123456

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

driver.find_element_by_css_selector("[type='password']").send_keys("123456")code>

time.sleep(5)

driver.close()

层级选择器

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位方式中的层级选择器, 定位用户名输入框, 并输入 admin

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# driver.find_element_by_css_selector("p[id='pa']>input").send_keys("admin")code>

driver.find_element_by_css_selector("div[class='zc'] input").send_keys("admin")code>

time.sleep(5)

driver.close()

CSS扩展

input[type^=‘p’] type属性以p字母开头的元素input[type$=‘d’] type属性以d字母结束的元素input[type*=‘w’] type属性包含w字母的元素

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面, 使用CSS定位扩展的方式, 定位用户名输入框, 并输入 admin

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# driver.find_element_by_css_selector("input[type^='t']").send_keys("admin")

# driver.find_element_by_css_selector("input[name^='u']").send_keys("admin")

# driver.find_element_by_css_selector("input[type$='t']").send_keys("admin")

driver.find_element_by_css_selector("input[type*='ex']").send_keys("admin")

time.sleep(5)

driver.close()

2.1.9 Xpath和CSS区别

XPath和CSS对比

通过标签名定位

XPath

//input

CSS

input

通过id属性定位

XPath

//*[@id='userA']code>

CSS

#userA

通过class属性定位

XPath

//*[@class='telA']code>

CSS

.telA

通过其他属性定位

XPath

//*[starts-with(@type,'x')]

以x字母开头的type值的元素

//*[contains(@type, 'x')]

包含x字母的type值的元素

//*[text()='x']

文本内容为 x 的元素

CSS

[type^='x']

以x字母开头的type值的元素

[type*='x']

包含x字母的type值的元素

[type$='x']

以x字母结尾的type值的元素

2.1.10 元素定位分类

id, name, class_name: 元素属性定位tag_name: 元素标签名定位link_text, partial_link_text: 通过文本定位超链接XPath: 通过路径定位元素CSS: 使用CSS选择器定位

2.1.11 元素定位的另一种写法

方法

方法: driver.find_element(方式, 值)

备注:

需要2个参数, 第1个参数为定位的类型(由By提供), 第2个参数传入具体的值如果要使用By, 需要导包

示例

import time

from selenium import webdriver

from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 八中定位方法都适用"另一种方法"

# driver.find_element(By.ID, "userA").send_keys("admin")

driver.find_element(By.XPATH, "//*[@placeholder='请输入电子邮箱']").send_keys("123456@qq.com")code>

time.sleep(5)

driver.close()

2.2 元素操作

方法

click() 单击元素

send_keys() 模拟输入

clear() 清除文本

案例

需求:打开注册A页面,完成以下操作

通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com

间隔3秒,修改电话号码为:18600000000

间隔3秒,点击‘注册’按钮

间隔3秒,关闭浏览器

注意:元素定位方法不限

代码

import time

from selenium import webdriver

from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

# 打开注册A页面,完成以下操作

# 1.通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com

# 2.间隔3秒,修改电话号码为:18600000000

# 3.间隔3秒,点击‘注册’按钮

# 4.间隔3秒,关闭浏览器

# ps: 元素定位方法不限

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1

driver.find_element_by_id("userA").send_keys("admin")

driver.find_element_by_id("passwordA").send_keys("123456")

driver.find_element_by_id("telA").send_keys("18611111111")

driver.find_element_by_name("emailA").send_keys("123@qq.com")

# 2

time.sleep(3)

driver.find_element_by_id("telA").clear()

driver.find_element_by_id("telA").send_keys("18600000000")

# 3

time.sleep(3)

driver.find_element_by_css_selector("body > div > fieldset > form > p:nth-child(5) > button").click()

# 4

time.sleep(3)

driver.close()

2.3 浏览器操作

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

## maximize_window() 浏览器窗口最大化

driver.maximize_window()

## set_window_size() 设置窗口大小(单位:像素点) set_window_position() 设置窗口的位置

driver.set_window_size(300, 300)

driver.set_window_position(300, 300)

## back() 后退 forward() 前进 refresh() 刷新

driver.back()

driver.forward()

time.sleep(3)

driver.refresh()

## title 获取页面标题 current_url 获取当前页面url

print("页面标题:", driver.title)

print("当前页面地址:", driver.current_url)

## driver.close() 关闭当前浏览器窗口 ==> 执行结果, 留下了新浪网站, 关闭了注册A页面

time.sleep(3)

driver.find_element_by_link_text("访问 新浪 网站").click()

time.sleep(3)

driver.close()

### 序号 30~48 的脚本应该使用 driver.quit() 关闭浏览器驱动 而不是 driver.close()

## driver.quit() 关闭浏览器驱动对象(关闭浏览器) ==> 执行结果, 关闭所有窗口, 关闭浏览器驱动

time.sleep(3)

driver.find_element_by_link_text("访问 新浪 网站").click()

time.sleep(3)

driver.quit()

2.4 获取元素信息

应用场景

用于校验, 判断定位的元素是否准确

常用方法

size 返回元素大小

text 获取元素文本

get_attribute("xxx") 获取属性值, 参数是元素的属性名

is_displayed() 判断元素是否可见

is_enabled() 判断元素是否可用

is_selected() 判断元素是否选中, 用来检查复选框或单选按钮

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 需求: 打开A页面, 完成以下操作:

# 1.获取用户名输入框的大小

print(driver.find_element_by_id("userA").size)

# 2.获取页面上第一个超链接的文本内容

print(driver.find_element_by_tag_name("a").text)

# 3.获取页面上第一个超链接的地址

print(driver.find_element_by_tag_name("a").get_attribute("href"))

# 4.判断页面中的span标签是否可见

print(driver.find_element_by_tag_name("span").is_displayed())

# 5.判断页面中的取消按钮是否可用

print(driver.find_element_by_id("cancelA").is_enabled())

# 6.判断页面中的'旅游'对应的复选框是否为选中状态

print(driver.find_element_by_id("lyA").is_selected())

time.sleep(3)

driver.quit()

2.5 鼠标操作

什么是鼠标操作

单击, 右击, 双击, 悬停, 拖拽等

为什么要用到鼠标操作

现在web产品中存在丰富的鼠标交互方式, 作为一个web自动化测试框架, 需要应对这些鼠标操作的场景

2.5.1 常用方法

说明: 在Selenium中将鼠标操作的方法封装在 ActionChains 类中

实例化对象: action = ActionChains(driver)

方法:

context_click(element) 右击double_click(element) 双击move_to_element(element) 悬停drag_and_drop(source, target) 拖拽perform() 执行

2.5.2 执行的方法

说明: 在 ActionChains 类中所有提供的鼠标事件方法, 在调用的时候, 所有行为都存储在 ActionChains 对象中, 而 perform() 方法就是真正去执行所有的鼠标事件

强调: 必须调用 perform() 方法才能执行鼠标事件

2.5.3 鼠标右击

说明: 对于点击鼠标右键, 如果弹出的是浏览器的默认菜单, Selenium并没有提供操作菜单的方法

如果是自定义的右键菜单, 则可以通过元素定位来操作菜单中的选项

需求: 打开A页面, 在用户名文本框上点击鼠标右键

import time

from selenium import webdriver

from selenium.webdriver import ActionChains

driver = webdriver.Chrome()

# 需求: 打开A页面, 在用户名文本框上点击鼠标右键

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 定位用户名输入框

element = driver.find_element_by_id("userA")

# 执行右键点击操作

action = ActionChains(driver)

action.context_click(element).perform()

time.sleep(3)

driver.quit()

2.5.4 鼠标双击

说明: 模拟鼠标双击左键的操作

需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)

import time

from selenium import webdriver

from selenium.webdriver import ActionChains

driver = webdriver.Chrome()

# 需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

element = driver.find_element_by_id("userA")

element.send_keys("admin")

time.sleep(3)

action = ActionChains(driver)

action.double_click(element).perform()

time.sleep(3)

driver.quit()

2.5.5 鼠标悬停

说明: 模拟鼠标悬停在指定元素上

需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上

import time

from selenium import webdriver

from selenium.webdriver import ActionChains

driver = webdriver.Chrome()

# 需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

element = driver.find_element_by_tag_name("button")

action = ActionChains(driver)

action.move_to_element(element).perform()

time.sleep(3)

driver.quit()

2.5.6 鼠标拖动

说明: 模拟鼠标拖动动作, 选定拖动源元素释放到目标元素

源元素 source = driver.find_element_by_xxx("xxx")目标元素 target = driver.find_element_by_xxx("xxx")调用方法 action.drag_and_drop(source, target).perform()

需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上

import time

from selenium import webdriver

from selenium.webdriver import ActionChains

driver = webdriver.Chrome()

# 需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上

driver.get("file:///C:/Users/57769/Desktop/pagetest/drag.html")

red = driver.find_element_by_id("div1")

blue = driver.find_element_by_id("div2")

ActionChains(driver).drag_and_drop(red, blue).perform()

time.sleep(3)

driver.quit()

2.6 键盘操作

说明:

模拟键盘上的一些按键或者组合键的输入, 如: 复制/粘贴Selenium中把键盘的按键都封装在 Keys 类中

2.6.1 常用操作

导包

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)

提示: 以上方法很多, 不会逐一讲解, 因为调用方法都一样

2.6.2 键盘操作

需求

打开 A 页面, 完成以下操作

输入用户名 admin1, 暂停2s, 删除1全选用户名 admin 暂停2s复制用户名 admin 暂停2s粘贴到电话输入框

代码

import time

from selenium import webdriver

from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1. 输入用户名 admin1, 暂停2s, 删除1

element = driver.find_element_by_id("userA")

element.send_keys("admin1")

time.sleep(2)

element.send_keys(Keys.BACK_SPACE)

# 2. 全选用户名 admin 暂停2s

element.send_keys(Keys.CONTROL, "a")

time.sleep(2)

# 3. 复制用户名 admin 暂停2s

element.send_keys(Keys.CONTROL, "c")

time.sleep(2)

# 4. 粘贴到电话输入框

driver.find_element_by_id("telA").send_keys(Keys.CONTROL, "v")

time.sleep(5)

driver.quit()

2.6.3 元素等待

概念

定位页面元素, 如果未找到, 在指定时间内一直等待的过程

分类

隐式等待显式等待

由于一些原因, 我们想找的元素并没有立刻出来, 此时直接定位会报错, 场景如下:

网络速度慢服务器计算慢硬件配置差

思考: 是否定位每个元素时, 都需要元素等待?

2.6.4 隐式等待

方法

隐式等待为全局设置 (只需要设置1次,会作用于所有元素)

参数:

timeout: 超时的时长, 单位: 秒

driver.implicitly_wait(timeout)

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 需求: 打开A页面, 使用隐式等待定位 "延时加载的输入框", 并输入 admin

driver.implicitly_wait(10)

driver.find_element_by_css_selector("input[placeholder='延时加载的输入框']").send_keys("admin")code>

time.sleep(3)

driver.quit()

# 不使用元素等待时, 如果找不到元素会报 NoSuchElementException 异常

# 使用隐式等待时, 如果找不到元素会报 NoSuchElementException 异常

注意点

单个元素定位超时会报 NoSuchElementException

2.6.5 显式等待

说明: 在Selenium中把显式等待的相关方法封装在 WebDriverWait 类中

方法 :

显式等待, 为定位不同的元素的超时时间设置不同的值

导包

WebDriverWait(driver, timeout, poll_frequency=0.5)

driver: 浏览器驱动对象timeout: 超时时长, 单位: 秒poll_frequency: 检测的间隔时间, 默认为0.5s

调用 until(method)

method: 函数名称, 该函数用来实现元素定位一般使用匿名来实现: lambda x: x.find_element_by_xxx("xxx")

如:element = WebDriverWait(driver,10,1).until(lambda x: x.find_element_by_xxx("xxx"))

案例

import time

from selenium import webdriver

from selenium.webdriver.support.wait import WebDriverWait

driver = webdriver.Chrome()

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 需求: 打开A页面, 使用显式等待定位 "延时加载的输入框", 并输入 admin

wait = WebDriverWait(driver, 10, 1)

element = wait.until(lambda x: x.find_element_by_css_selector("input[placeholder='延时加载的输入框']"))code>

element.send_keys("admin")

time.sleep(3)

driver.quit()

# 单个元素定位超时会报错 TimeoutException

注意点

单个元素定位超时会报错 TimeoutException

2.6.6 隐式和显式区别

作用域: 隐式等待为全局有效, 显式等待为单个元素有效使用方法: 隐式等待直接通过驱动对象调用, 而显式等待方法封装在 WebDriverWait 类中达到最大超时时长后抛出异常不同: 隐式等待为 NoSuchElementException, 显式等待为 TimeoutException

2.6.7 下拉框

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面,完成以下下拉框操作

# 1. 暂停2s, 选择广州

# 2. 暂停2s, 选择上海

# 3. 暂停2s, 选择北京

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1

time.sleep(2)

driver.find_element_by_xpath("//*[@id='selectA']/option[3]").click()code>

# 2

time.sleep(2)

driver.find_element_by_xpath("//*[@id='selectA']/option[2]").click()code>

# 3

time.sleep(2)

driver.find_element_by_xpath("//*[@id='selectA']/option[1]").click()code>

time.sleep(3)

driver.quit()

案例

说明: Select类是Selenium为操作select标签封装的

实例化对象:

select = Select(element)

element: <select>标签对应的元素, 通过元素定位方式获取

例如: driver.find_element_by_id("selectA")

操作方法:

select_by_index(index) 根据option索引来定位, 从0开始select_by_value(value) 根据option属性 value值来定位select_by_visible_text(text) 根据option显示文本内容来定位

步骤分析

导包实例化Select类 select = Select(driver.find_element_by_id("selectA"))调用方法

案例

import time

from selenium import webdriver

from selenium.webdriver.support.select import Select

driver = webdriver.Chrome()

# 需求: 打开A页面,完成以下下拉框操作

# 1. 暂停2s, 选择广州

# 2. 暂停2s, 选择上海

# 3. 暂停2s, 选择北京

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

select = Select(driver.find_element_by_id("selectA"))

# 1

time.sleep(2)

select.select_by_index(2)

# 2

time.sleep(2)

select.select_by_value("sh")

# 3

time.sleep(2)

select.select_by_visible_text("北京")

time.sleep(3)

driver.quit()

2.6.8 弹出框分类

alert 警告框confirm 确认框prompt 提示框

弹出框的错误示范

错误代码

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面,完成以下弹出框操作

# 1.点击 alert 按钮

# 2.暂停2s, 输入用户名 admin

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1

driver.find_element_by_id("alerta").click()

# 2

time.sleep(2)

driver.find_element_by_id("userA").send_keys("admin")

time.sleep(3)

driver.quit()

# 思考

# 1.什么问题导致的?

# driver的焦点在弹出框页面, 并不在A页面, 无法为你输入admin(找不到用户名输入框)

# 2.如何处理弹出框?

弹出框方法

说明: Selenium中对弹出框的处理, 有专用的方法, 且处理的方法都一样(alert/confirm/prompt)

1.获取弹出框对象

alert = driver.switch_to.alert

2.调用

alert.text 返回alert/confirm/prompt文字信息

alert.accept() 接受对话框选项(确认)

alert.dismiss() 取消对话框选项(取消)

案例

正确代码

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面,完成以下弹出框操作

# 1.点击 alert 按钮

# 2.暂停2s, 输入用户名 admin

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1

driver.find_element_by_id("alerta").click()

time.sleep(2)

alert = driver.switch_to.alert

print(alert.text)

time.sleep(2)

alert.accept()

# 2

time.sleep(2)

driver.find_element_by_id("userA").send_keys("admin")

time.sleep(3)

driver.quit()

2.7 滚动条实现方法

方法

说明: Selenium中没有提供滚动条的操作方法, 但是它提供了执行 JS 的方法, 所有我们可以通过 JS脚本来操作滚动条

设置 JS 脚本控制滚动条

js = "window.scrollTO(0,1000)"

(0:左边距, 1000:上边距 单位:像素(px))Selenium 调用执行 JS 脚本的方法

driver.execute_script(js)

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开A页面

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# js1 滚动到最底部

js1 = "window.scrollTo(0, 10000)"

# js2 滚动到最顶部

js2 = "window.scrollTo(0, 0)"

# 执行第一个脚本

time.sleep(2)

driver.execute_script(js1)

# 执行第二个脚本

time.sleep(2)

driver.execute_script(js2)

time.sleep(3)

driver.quit()

2.8 frame切换

概念

frame : html页面中的一种框架, 主要作用是在当前页面指定区域显示另一个页面元素

形式一:

<frameset cols="25%,75%">code>

<frame src="a.html">code>

<frame src="b.html">code>

</frameset>

形式二:

<iframe name="iframe_a" src="demo.html" width="200" height="200"></iframe>code>

方法

说明: 在Selenium中封装了如何切换frame框架的方法

步骤:

1.driver.switch_to.frame(frame_reference) 切换到指定frame

frame_reference: 可以传frame框架的id,name,定位的frame元素

2.driver.switch_to.default_content() 恢复默认页面

必须回到默认页面才能进一步操作

解决方案

在主页面输入用户名 admin切换到A页面, 再输入用户名 adminA恢复默认页面切换到B页面, 再输入用户名 adminB

正确代码

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开"注册实例"页面

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8C%E5%AE%9E%E4%BE%8B.html")

# 1.填写主页面的用户名 admin

time.sleep(2)

driver.find_element_by_id("userA").send_keys("admin")

# 2.填写注册页面A中的用户名 adminA

time.sleep(2)

# driver.switch_to.frame("idframe1") # 从主页面, 切换到了A页面, 通过 id

# driver.switch_to.frame("myframe1") # 从主页面, 切换到了A页面, 通过 name

driver.switch_to.frame(driver.find_element_by_id("idframe1")) # 从主页面, 切换到了A页面, 通过 定位到的元素

driver.find_element_by_id("userA").send_keys("adminA")

# 3.回到主页面

time.sleep(1)

driver.switch_to.default_content()

# 4.填写注册页面B中的用户名 adminB

time.sleep(1)

driver.switch_to.frame("idframe2") # 从主页面, 切换到B页面

driver.find_element_by_id("userA").send_keys("adminB")

time.sleep(3)

driver.quit()

2.8.1 多窗口切换

概念

什么是窗口? 窗口类似于浏览器中的标签页, 每个窗口就对应了一个标签页

为什么要切换窗口? 在html页面中, 当点击按钮或超链接时, 有的会在新窗口打开页面

如果点击按钮或超链接在当前窗口打开新页面, 就不需要切换窗口

需求

打开A页面

在新窗口打开新浪页面在新浪的搜索框输入"新浪搜索"在A页面输入用户名 admin

方法

说明: 在Selenium中封装了获取当前窗口句柄,获取所有窗口句柄和切换到指定句柄窗口的方法

句柄: 英文handle, 窗口的唯一识别码

方法:

1. `driver.current_window_handle`获取当前窗口句柄

2. `driver.window_handles`获取所有窗口句柄

3. `driver.switch_to.window(handle)`切换到指定句柄的窗口

对于需求的解决方案:

打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄根据句柄, 切换到新浪窗口, 对输入框输入 “新浪搜索”切换回原本窗口(A页面), 输入用户名 admin

注意: 新浪页面需要访问网络, 可能加载慢, 可能需要用到元素等待

代码

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 隐式等待10秒, 以防新浪窗口加载慢, 定位不到输入框

driver.implicitly_wait(10)

# 1. 打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

print("当前A页面窗口句柄:", driver.current_window_handle)

# 2. 在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄

driver.find_element_by_id("fw").click()

handles = driver.window_handles

print("所有窗口句柄:", handles)

# 3. 根据句柄, 切换到新浪窗口, 对输入框输入 "新浪搜索"

driver.switch_to.window(handles[1])

time.sleep(1)

driver.find_element_by_class_name("inp-txt").clear()

time.sleep(1)

driver.find_element_by_class_name("inp-txt").send_keys("新浪搜索")

time.sleep(2)

# 4. 切换回原本窗口(A页面), 输入用户名 admin

driver.switch_to.window(handles[0])

driver.find_element_by_id("userA").send_keys("admin")

time.sleep(3)

driver.quit()

2.8.2 窗口截图

概念

什么是窗口截图?

把当前操作的页面, 截图保存到指定的位置

为什么要窗口截图?

有时候打印的错误信息不十分准确, 需要窗口截图辅助定位错误

方法

说明: 在Selenium中提供了截图方法, 我们只需要调用即可

方法:

driver.get_screenshot_as_file(imgpath)

imgpath: 图片保存路径 + 图片名

案例

import time

from selenium import webdriver

driver = webdriver.Chrome()

# 需求: 打开 A 页面, 完成以下操作

# 1.输入用户名 admin

# 2.截图保存

driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")

# 1

driver.find_element_by_id("userA").send_keys("admin")

# 2

time.sleep(1)

# 每次都是用固定文件名, 会股改上一次生成的图片文件

# driver.get_screenshot_as_file("./png/123.png") # 需要提前创建 png 目录

# 使用时间去格式化文件名, 可以使每次截图保存的文件名都不同, 不会覆盖之前保存的文件, 更有效

imgpath = "./png/test_{}.png".format(time.strftime("%Y%m%d%H%M%S"))

driver.get_screenshot_as_file(imgpath)

time.sleep(3)

driver.quit()

2.9 验证码处理

概念

什么是验证码?

一种随机生成的信息 (数字, 字母, 汉字, 图片, 算术题…) 等为了防止恶意的请求行为, 增加应用的安全性

为什么要学习验证码?

在web应用中, 大部分系统在用户登录注册的时候都需要输入验证码, 而我们自动化脚本也要面临处理验证码的问题

常用方法

说明: Selenium中并没有对验证码处理的方法, 在这里我们介绍几种常用的处理方式

方法:

1.去掉验证码

(测试环境下采用)

2.设置万能验证码

(生产和测试环境下采用)

3.验证码识别技术

(通过 python-tesseract 来识别图片类型的验证码: 识别率很难达到100%)

4.记录 cookie

(通过记录 cookie 进行跳过登录)

注意

1 和 2, 都是开发人员来完成

3 验证码识别技术成功率不高, 不太合适

4 记录cookie 比较实用, 推荐

2.10 cookie

概念

cookie是由web服务器生成的, 并且保存在用户浏览器上的小文本文件, 它可以包含用户信息cookie数据格式: 键值对 (python中的字典)cookie产生: 客户端请求服务器, 如果服务器需要记录该用户状态, 就向客户端浏览器颁发一个cookie数据cookie使用: 当浏览器再次请求该网站时, 浏览器把请求的数据和cookie数据一同提交给服务器, 服务器检查该cookie, 以此来辨认用户

应用场景

实现会话跟踪, 记录用户登录状态实现记住密码和自动登录的功能用户未登录状态下, 记录购物车中的商品

方法

说明: Selenium中对cookie操作提供相应的方法

方法:

1.driver.get_cookies() 获取本网站所有本地cookies

2.driver.get_cookie(name) 获取指定cookie

name: 为cookie中键值对数据的 键名

3.driver.add_cookie(cookie_dict) 添加cookie

cookie_dict: 一个字典对象, 必选的内容包括: “name” 和 “value”

案例需求

使用cookie 实现跳过百度登录

1.手动登录百度, 获取cookie

2.请求百度, 并且带上cookie

步骤分析

BDUSS是登录百度后的唯一身份凭证, 拿到BDUSS就等于拿到了账号的控制权,通行贴吧,知道,文库…主要产品

登录百度, 抓取BDUSS添加 BDUSS 的键值对调用刷新的方法

代码

import time

from selenium import webdriver

driver = webdriver.Chrome()

driver.maximize_window()

# 需求: 使用cookie 实现跳过百度登录

# 1.手动登录百度, 获取cookie

# 2.请求百度, 并且带上cookie

# 没有cookie的时候

driver.get("http://www.baidu.com")

# 添加cookie操作

driver.add_cookie({ "name": "BDUSS", "value": "VZMUEl0WFJQYkxNSXk0c0VMUk5ZNGYteWVYNG01aVJtZXFCV056alk5M3V3SUZlSVFBQUFBJCQAAAAAAAAAAAEAAAC2KUFmTFhKX0pheQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO4zWl7uM1peQ"})

time.sleep(3)

# 刷新, 再次请求百度首页, 验证是否带上身份信息

driver.refresh()

time.sleep(3)

driver.quit()

3 Pytest框架

3.1 总体介绍

什么是断言

让程序代替人工去判断测试程序的执行结果是否符合预期的过程

为什么学习断言

自动化脚本在执行的时候一般都是无人值守的状态, 我们不知道执行结果是否符合预期, 所以我们需要让程序代替人工去检测程序的执行结果是否符合预期, 这就需要断言

3.2 断言方法

assert xx 判断 xx 为真

assert not xx 判断 xx 不为真

assert a in b 判断 b 包含 a

assert a == b 判断 a 等于 b

assert a != b 判断 a 不等于 b

代码案例

def add(x, y):

return x + y

class TestPlus:

# 判断 1+1 的结果等于 2

def test_a(self):

assert 2 == add(1, 1)

# 调换表达式两个值的位置, 判断 1+1 的结果等于 2

def test_b(self):

assert add(1, 1) == 2

# 判断 1+2 的结果不等于4

def test_c(self):

assert 4 != add(1, 2)

# 误判: 1+2 等于 4 了

def test_d(self):

assert 4 == add(1, 2)

3.3 setup和teardown

应用场景

pytest 在运行自动化脚本的前后会执行两个特殊的方法, 分别是"前置"和"后置"方法

在脚本执行前会执行"前置"方法,在脚本执行后会执行"后置"方法

概念和方法

1.初始化(前置处理方法):

def setup(self)

2.销毁(后置处理方法):

def teardown(self)

3.运行于测试方法的始末, 即:运行一次测试方法就会运行一次 setup 和 teardown

案例

import time

def add(x, y):

return x + y

class TestPlus:

# 获取并打印开始时间, 每个测试函数执行前都打印一次

def setup(self):

print("start-time:", time.time())

# 获取并打印结束时间, 每个测试函数执行后都打印一次

def teardown(self):

print("end-time:", time.time())

def test_a(self):

assert 2 == add(1, 1)

def test_b(self):

assert add(1, 1) == 2

3.4 配置文件

应用场景

使用配置文件, 可以通过配置项来选择执行哪些目录下的哪些测试模块

用法

步骤:

新建 scripts 模块, 测试脚本放到模块中新建 pytest.ini 文件, 名称为 pytest.ini, 第一行为 [pytest], 并且补全配置项命令行运行 pytest 即可

示例

pytest.ini

[pytest]

addopts = -s

testpaths = ./scripts

python_files = test_*.py

python_classes = Test*

python_functions = test_*

3.5 测试报告插件

应用场景

需要测试报告来体现自动化脚本测试是否通过

安装

pip install pytest-html==1.21.1

使用

在配置文件中的命令行参数中, 增加 --html=用户路径/report.html

生成报告

步骤:

命令行输入 pytest 运行脚本在项目目录下会有一个 report文件夹, 里面有个 report.html 就是测试报告

3. 6 数据参数化

应用场景

需要测试多组值得时候, 使用数据参数化可以使代码更简洁, 可读性更好

方法

数据参数化, 装饰器需要放在要传多组值的函数上

@pytest.mark.parametrize(argnames, argvalues)

参数:

argnames: 参数名

argvalues: 参数对应值, 类型必须是可迭代类型, 一般使用 list

3.6.1 单一参数

代码

import pytest

class TestDemo:

# 需求: 不使用数据参数化, 分别打印用户名 "zhangsan" 和 "lisi"

def test_a(self):

print("zhangsan")

def test_b(self):

print("lisi")

# 需求: 使用数据参数化 (单一参数), 修改上面的代码

@pytest.mark.parametrize("name", ["zhangsan", "lisi"])

def test_c(self, name):

print(name)

3.6.2 多个参数

代码

import pytest

class TestDemo:

# 需求: 使用数据参数化 (多个参数), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222

@pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])

def test_c(self, username, password):

print(username + "-----" + password)

# 使用元组可以传多个值 ("zhangsan", "111111"), 列表行不行?

3.6.3 推荐用法

代码

import pytest

class TestDemo:

# 需求: 使用数据参数化 (推荐用法), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222

# @pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])

# def test_c(self, username, password):

# print(username + "-----" + password)

@pytest.mark.parametrize("dict", [{ "username": "zhangsan", "password": "111111"}, { "username": "lisi", "password": "222222"}])

def test_d(self, dict):

print(dict)

print(dict["username"])

print(dict["password"])

#("zhangsan", "111111", "13000000000", "1", "1", "30", "......")

#("lisi", "222222", "13100000000", ??????)

# 推荐的用法是用字典表示参数值

# {"username": "zhangsan", "password": "111111"}

4 PO模式

4.1 递进学习路线

v1: 不使用任何设计模式和单元测试框架v2: 使用 pytest 管理用例v3: 使用方法封装的思想, 对代码进行优化v4: 采用PO模式的分层思想对代码进行拆分, 分离pagev5: 对PO分层后的代码继续优化, 分离page中的元素和操作v6: PO模式深入封装, 把共同操作提取封装

4.2 无模式

4.2.1 案例说明

对 TPshop 项目的登录模块进行自动化测试

登录模块包含了很多测试用例, 如: 账号不存在, 密码错误, 验证码错误, 登录成功等等

为了节省时间, 我们只选取几个有代表性的用例来演示: 账号不存在, 密码错误

4.2.2 选择测试用例

账号不存在

点击首页的"登录"链接, 进入登录页面输入一个不存在的用户名输入密码输入验证码点击登录按钮获取错误提示信息

密码错误

点击首页的"登录"链接, 进入登录页面

输入用户名

输入一个错误的密码

输入验证码

点击登录按钮

获取错误提示信息

4.3 V1版本

不使用任何设计模式和单元测试框架每个文件对应编写一个测试用例, 完全的面向过程的编程方式

示例代码

登录功能, 账号不存在

# 账号不存在

import time

from selenium import webdriver

# 实例化浏览器驱动

driver = webdriver.Chrome()

driver.maximize_window()

driver.implicitly_wait(10)

driver.get("http://localhost/")

# 1. 点击首页的"登录"链接, 进入登录页面

driver.find_element_by_class_name("red").click()

# 2. 输入一个不存在的用户名

driver.find_element_by_id("username").send_keys("18800000000")

# 3. 输入密码

driver.find_element_by_id("password").send_keys("123456")

# 4. 输入验证码

driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 关闭浏览器驱动

time.sleep(5)

driver.quit()

登录功能, 密码错误

# 密码错误

import time

from selenium import webdriver

# 实例化浏览器驱动

driver = webdriver.Chrome()

driver.maximize_window()

driver.implicitly_wait(10)

driver.get("http://localhost/")

# 1. 点击首页的"登录"链接, 进入登录页面

driver.find_element_by_class_name("red").click()

# 2. 输入用户名

driver.find_element_by_id("username").send_keys("17150312012")

# 3. 输入一个错误密码

driver.find_element_by_id("password").send_keys("error")

# 4. 输入验证码

driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 关闭浏览器驱动

time.sleep(5)

driver.quit()

存在的问题

一条测试用例对应一个文件, 用例多时, 不方便维护管理代码高度冗余

4.4 V2版本

引入pytest管理测试用例, 并断言用例的执行结果

好处

方便组织和管理多个测试用例提供了丰富的断言方法方便生成测试报告减少了代码冗余

示例代码

# 导包

import time

from selenium import webdriver

# 定义测试类

class TestLogin:

def setup(self):

self.driver = webdriver.Chrome()

self.driver.maximize_window()

self.driver.implicitly_wait(10)

self.driver.get("http://localhost/")

def teardown(self):

time.sleep(5)

self.driver.quit()

# 定义用户不存在的测试方法

def test_login_account_not_exist(self):

# 1. 点击首页的"登录"链接, 进入登录页面

self.driver.find_element_by_class_name("red").click()

# 2. 输入一个不存在的用户名

self.driver.find_element_by_id("username").send_keys("18800000000")

# 3. 输入密码

self.driver.find_element_by_id("password").send_keys("123456")

# 4. 输入验证码

self.driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

self.driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 断言

assert "账号不存在!" == msg

# 定义密码错误的测试方法

def test_login_password_error(self):

# 1. 点击首页的"登录"链接, 进入登录页面

self.driver.find_element_by_class_name("red").click()

# 2. 输入用户名

self.driver.find_element_by_id("username").send_keys("17150312012")

# 3. 输入一个错误密码

self.driver.find_element_by_id("password").send_keys("error")

# 4. 输入验证码

self.driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

self.driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 断言

assert "密码错误!" == msg

存在问题

依然是代码冗余

4.4.1 方法封装

概念

是将一些有共性的或多次被使用的代码提取到一个方法中, 供其他地方调用

好处

避免代码冗余容易维护隐藏代码实现的细节

目的

用最少的代码实现最多的功能

4.5 V3版本

驱动工具类

# 获取/关闭浏览器驱动的类

from selenium import webdriver

class DriverUtils:

__driver = None

# 获取浏览器驱动

@classmethod

def get_driver(cls):

if cls.__driver is None:

cls.__driver = webdriver.Chrome()

cls.__driver.maximize_window()

cls.__driver.implicitly_wait(10)

return cls.__driver

# 关闭浏览器驱动

@classmethod

def quit_driver(cls):

if cls.__driver is not None:

cls.__driver.quit()

cls.__driver = None

测试类

# 导包

import time

from v3.driver_utils_121 import DriverUtils

# 定义测试类

class TestLogin:

def setup(self):

self.driver = DriverUtils.get_driver()

self.driver.get("http://localhost/")

def teardown(self):

time.sleep(5)

DriverUtils.quit_driver()

# 定义用户不存在的测试方法

def test_login_account_not_exist(self):

# 1. 点击首页的"登录"链接, 进入登录页面

self.driver.find_element_by_class_name("red").click()

# 2. 输入一个不存在的用户名

self.driver.find_element_by_id("username").send_keys("18800000000")

# 3. 输入密码

self.driver.find_element_by_id("password").send_keys("123456")

# 4. 输入验证码

self.driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

self.driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 断言

assert "账号不存在!" == msg

# 定义密码错误的测试方法

def test_login_password_error(self):

# 1. 点击首页的"登录"链接, 进入登录页面

self.driver.find_element_by_class_name("red").click()

# 2. 输入用户名

self.driver.find_element_by_id("username").send_keys("17150312012")

# 3. 输入一个错误密码

self.driver.find_element_by_id("password").send_keys("error")

# 4. 输入验证码

self.driver.find_element_by_id("verify_code").send_keys("8888")

# 5. 点击登录按钮

self.driver.find_element_by_name("sbtbutton").click()

# 6. 获取错误提示信息

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

print(msg)

# 断言

assert "密码错误!" == msg

注意: 如果想要引用其他类, 那么被引用的类的文件名要符合要求, 比如不能出现 []

存在的问题

代码冗余

4.6 PO模式

在做UI自动化时, 元素定位特别依赖页面, 如果页面变更, 自动化脚本就需要被修改

存在的问题

如果前端工程师改了某个元素, 你就得修改所有对应的代码存在大量的冗余

如果解决?

答案就是 PO模式

4.6.1 概念

PO是Page Object的缩写, PO模式是自动化测试开发的最佳设计模式之一

核心思想:

通过对页面元素的封装减少冗余代码, 同时在后期维护中, 若元素发生变化, 只需要调整页面元素封装的代码即可, 提高了测试用例的可维护性, 可读性页面和测试脚本分离

4.6.2 PO模式分层

分层机制, 让不同层去做不同类型的事情, 让代码结构清晰, 增加复用性

分层方式

两层: 对象操作层 + 业务数据层

对象操作层: 封装页面信息, 包括元素以及元素的操作业务数据层: 封装多种操作组合的业务以及测试数据

三层: 对象库 + 操作层 + 业务数据层 / 对象操作层 + 业务层 + 数据层

四层: 对象库 + 操作层 + 业务层 + 数据层

4.6.3 PO模式优点

引入PO模式前

存在大量冗余代码业务流程不清晰后期维护成大 引入PO模式后

减少冗余代码业务代码和测试数据被分开, 降低耦合性维护成本低

4.7 V4版本

介绍

采用PO模式的分层思想对代码进行拆分

PO封装

对登录页面进行封装: 封装到类 LoginPage

对测试用例进行封装: 封装到类 TestLogin

代码结构

utils包

driver_utils.py

page包

login_page.py

scripts包

test_login.py

pytest.ini

PO封装

login_page.py

class LoginPage:

def __init__(self, driver):

self.driver = driver

# 点击首页的"登录"链接, 进入登录页面

def click_login_link(self):

return self.driver.find_element_by_class_name("red").click()

# 输入用户名

def input_username(self, username):

return self.driver.find_element_by_id("username").send_keys(username)

# 输入密码

def input_password(self, password):

return self.driver.find_element_by_id("password").send_keys(password)

# 输入验证码

def input_verify_code(self, code):

return self.driver.find_element_by_id("verify_code").send_keys(code)

# 点击登录按钮

def click_login_btn(self):

return self.driver.find_element_by_name("sbtbutton").click()

# 获取提示信息

def get_msg(self):

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

return msg

test_login.py

class LoginPage:

def __init__(self, driver):

self.driver = driver

# 点击首页的"登录"链接, 进入登录页面

def click_login_link(self):

return self.driver.find_element_by_class_name("red").click()

# 输入用户名

def input_username(self, username):

return self.driver.find_element_by_id("username").send_keys(username)

# 输入密码

def input_password(self, password):

return self.driver.find_element_by_id("password").send_keys(password)

# 输入验证码

def input_verify_code(self, code):

return self.driver.find_element_by_id("verify_code").send_keys(code)

# 点击登录按钮

def click_login_btn(self):

return self.driver.find_element_by_name("sbtbutton").click()

# 获取提示信息

def get_msg(self):

msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

return msg

4.8 v5版本

介绍

对PO分层后的代码继续优化

优化内容

分离page页面中的元素和操作优化元素定位方式

示例代码

login_page.py

from selenium.webdriver.common.by import By

class LoginPage:

# 登录链接 按钮

login_link_btn = By.CLASS_NAME, "red"

# 用户名 输入框

username_input = By.ID, "username"

# 密码 输入框

password_input = By.ID, "password"

# 验证码 输入框

verify_code_input = By.ID, "verify_code"

# 登录 按钮

login_btn = By.NAME, "sbtbutton"

# 提示信息

msg_info = By.CSS_SELECTOR, ".layui-layer-content"

def __init__(self, driver):

self.driver = driver

def find_el(self, feature):

return self.driver.find_element(*feature)

# return self.driver.find_elment(feature[0], feature[1])

# 点击首页的"登录"链接, 进入登录页面

def click_login_link(self):

return self.find_el(self.login_link_btn).click()

# return self.driver.find_elment(self.login_link_btn[0], self.login_link_btn[1]).click()

# return self.driver.find_element_by_class_name("red").click()

# 输入用户名

def input_username(self, username):

return self.find_el(self.username_input).send_keys(username)

# return self.driver.find_element_by_id("username").send_keys(username)

# 输入密码

def input_password(self, password):

return self.find_el(self.password_input).send_keys(password)

# return self.driver.find_element_by_id("password").send_keys(password)

# 输入验证码

def input_verify_code(self, code):

return self.find_el(self.verify_code_input).send_keys(code)

# return self.driver.find_element_by_id("verify_code").send_keys(code)

# 点击登录按钮

def click_login_btn(self):

return self.find_el(self.login_btn).click()

# return self.driver.find_element_by_name("sbtbutton").click()

# 获取提示信息

def get_msg(self):

return self.find_el(self.msg_info).text

# msg = self.driver.find_element_by_css_selector(".layui-layer-content").text

# return msg

4.9 v6版本

介绍

把共同的方法进行封装

优化内容

封装操作基类

封装查找元素的方法封装基本操作方法: 点击/ 清空/ 输入等等 page继承操作基类

结构

utils包

driver_utils.py

page包

login_page.py

scripts包

test_login.py

pytest.ini

base包

base_action.py

4.9.1 示例代码

base_action.py

class BaseAction:

def __init__(self, driver):

self.driver = driver

def find_el(self, feature):

return self.driver.find_element(*feature)

def find_els(self, feature):

return self.driver.find_elements(*feature)

def click(self, feature):

return self.find_el(feature).click()

def input(self, feature, content):

return self.find_el(feature).send_keys(content)

def clear(self, feature):

return self.find_el(feature).clear()

注意: page页面要继承 BaseAction

5 数据驱动

概念

是以数据来驱动整个测试用例的执行, 也就是测试数据决定测试结果

特点

可以把数据驱动理解为一种模式或者一种思想数据驱动技术可以让用户把关注点放在测试数据的构建和维护上, 而不是直接维护脚本, 可以利用同样的过程, 对不同的输入数据进行测试数据驱动要依赖参数化技术

数据来源

直接定义在测试脚本中 (简单直观, 但测试方法和测试数据未分离, 不方便后期维护)从文件中读取数据, 如 txt, excel, xml, JSON等格式文件从数据库读取数据

5.1 JSON基本介绍

概念

JSON全称是" JavaScript Object Notation", 是JavaScript 对象表示法, 它是一种基于文本, 独立于语言的轻量级数据交换格式

特点

JSON是纯文本JSON具有良好的自我描述性, 便于阅读和编写JSON具有清晰的层级结构有效的提升网络传输效率

对比XML

XML指可扩展标记语言, 被设计用来传输数据如果使用XML, 需要读取XML, 然后通过标签结点来遍历文档, 并读取对应的值, 然后传输使用JSON, 只需要读取JSON字符串

JSON语法规格

大括号保存对象中括号保存数组对象和数组可以相互嵌套数据采用键值对来表示多个数据用逗号分隔

JSON值

数字 (整数或者浮点数)字符串 (在双引号中)逻辑值 (true 或者 false)数组 (在中括号中)对象 (在大括号中)null

JSON中空值用 null 表示python中对应的用 None 表示

JSON基本操作

操作内容

python字典与JSON之间的转换

JSON文件读写

在python中想要操作 JSON, 需要先导入依赖包

import json

5.2 字典与JSON转换

代码

import json

# 把python字典类型转换为JSON字符串

dict1 = {

"name": "zhangsan",

"age": 18,

"is_man": True,

"school": None

}

# 使用 dumps 方法, 得到的结果是 json 字符串

json_str1 = json.dumps(dict1)

print(json_str1)

# 把JSON字符串转换为python字典

json_str2 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'

# 使用 loads 方法, 得到的结果是 python字典

dict2 = json.loads(json_str2)

print(dict2)

把python字典类型转换为JSON字符串: 使用 dumps 方法

把JSON字符串转换为python字典: 使用 loads 方法

5.3 JSON文件读写

代码

import json

# 读取 data.json 文件

with open("data.json", "r", encoding="utf-8") as f:code>

data1 = json.load(f)

print(data1)

# 把字典写入json文件 "data2.json"

data2 = data1

with open("data2.json", "w", encoding="utf-8") as f:code>

json.dump(data2, f)

# 把字典写入json文件 "data3.json" ------解决写入中文的问题

data3 = data1

with open("data3.json", "w", encoding="utf-8") as f:code>

json.dump(data2, f, ensure_ascii=False)

实现步骤

编写测试用例敲代码

采用PO模式的分层思想对页面进行封装编写测试脚本定义数据文件, 实现参数化

6 项目实战

在线计算器项目 http://cal.apple886.com/

6.1 项目结构

base ----> 存储页面对象的父类(便于子类调用)data ----> 存储测试用例数据page ----> 存储页面对象scripts ----> 存储测试脚本utils ----> 存储经常使用的工具类pytest.ini ----> 运行项目的配置

6.2 base包

新建base_action.py

class BaseAction:

# 初始化驱动

def __init__(self, driver):

self.driver = driver

# 查找单个元素

def find_el(self, feature):

return self.driver.find_element(*feature)

# 查找多个元素

def find_els(self, feature):

return self.driver.find_elements(*feature)

# 查找按钮元素

def click(self, feature):

return self.find_el(feature).click()

# 查找输入元素

def input(self, feature, content):

return self.find_el(feature).send_keys(content)

# 清空

def clear(self, feature):

return self.find_el(feature).clear()

# 定位数字按钮

def find_el_num(self, feature, num):

# 将num格式化为字符串

return self.driver.find_element(feature[0], feature[1].format(str(num)))

6.3 data包

新建cal_data.json

{

"cal_001": {

"data": [1, 3],

"result": 4

},

"cal_002": {

"data": [1, 2],

"result": 3

},

"cal_002": {

"data": [1, 2, 3],

"result": 6

}

}

6.4 page包

新建cal_page.py

from selenium.webdriver.common.by import By

from base.base_action import BaseAction

class CalPage(BaseAction):

# 数字按钮

number_btn = By.ID, "simple{}"

# 加号按钮

add_btn = By.ID, "simpleAdd"

# 等号按钮

equal_btn = By.ID, "simpleEqual"

# 结果

result = By.ID, "resultIpt"

# 点击数字

def click_number_btn(self, num):

return self.find_el_num(self.number_btn, num).click()

# 点击加号

def click_add_btn(self):

return self.click(self.add_btn)

# 点击等于号

def click_equal_btn(self):

return self.click(self.equal_btn)

# 显示结果

def get_result(self):

return self.find_el(self.result).get_attribute("value")

6.5 scripts包

新建tesst_cal.py

# 导包

import time

import pytest

from page.cal_page import CalPage

from utils.driver_utils import DriverUtils

from utils.read_data import read_data

# 定义测试类

class TestCal:

def setup_method(self):

self.driver = DriverUtils.get_driver()

self.cal_page = CalPage(self.driver)

self.driver.get("http://cal.apple886.com/")

def teardown_method(self):

time.sleep(5)

DriverUtils.quit_driver()

# 加法算数

@pytest.mark.parametrize("params", read_data("cal_data.json"))

def test_2_add(self, params):

for i in params["data"]:

self.cal_page.click_number_btn(i)

self.cal_page.click_add_btn()

time.sleep(5)

# 4. 点击等于号按钮

self.cal_page.click_equal_btn()

time.sleep(5)

# 断言

assert str(params["result"]) == self.cal_page.get_result()

6.6 utils包

新建driver_utils.py

# 获取/关闭浏览器驱动的类

from selenium import webdriver

class DriverUtils:

__driver = None

# 获取浏览器驱动

@classmethod

def get_driver(cls):

if cls.__driver is None:

cls.__driver = webdriver.Chrome()

cls.__driver.maximize_window()

cls.__driver.implicitly_wait(10)

return cls.__driver

# 关闭浏览器驱动

@classmethod

def quit_driver(cls):

if cls.__driver is not None:

cls.__driver.quit()

cls.__driver = None

新建read_data.py用于读取data包的数据

# 读取data数据文件

import json

def read_data(filename):

with open("./data/" + filename, "r", encoding="utf-8") as f:code>

list_data = []

dict_list = json.load(f)

for value in dict_list.values():

list_data.append(value)

return list_data

在这里插入图片描述

7 日志收集

7.1 日志收集

概念 : 日志就是用于记录系统运行时的信息, 也称为Log

作用:

调试程序

旧的方式: print(“xxxx”)

low 新的方式: 通过日志 了解程序运行的情况, 是否正常程序运行故障分析与问题定位用来做用户行为分析和数据统计

需要学好 sql

级别:

思考

是否记录的所有日志信息重要性都一样? 日志级别, 指日志信息的重要性

常见日志级别:

DEBUG === 调试INFO === 信息WARNING === 警告ERROR === 错误CRITICAL === 严重错误

日志基本用法:

logging:python中有一个标准库, logging模块可以直接记录日志

使用

导入 logging 包输出日志默认的日志级别被设置为 warning

设置日志级别

方法

<code>logging.basicConfig(level=logging.DEBUG)

设置日志格式

默认格式

日志级别 : Logger名称 : 日志内容 自定义格式

logging.basicConfig(format="xxxxxx")code>

将日志信息输出到文件

默认

python的logging模块将日志打印到了标准输出中(控制台) 将日志输出到文件的方法

logging.basicConfig(filename=“xxx.log”)

7.2 日志高级用法

思考

如何将日志信息同时输出到控制台和日志文件中?如何将不同级别的日志输出到不同的日志文件?如何解决日志文件过大的问题?

7.3 四大组件

日志器(Logger):提供了程序使用日志的入口处理器(Handler):将logger创建的日志记录发送到合适的输出格式器(Formatter):决定日志的输出格式过滤器(Filter): 提供了更细粒度的控制工具来决定输出哪条日志记录, 丢弃哪条日志记录

组件之间的关系

日志器 (Logger) 是入口,真正干活的是处理器 (Handler),处理器还可以通过格式器 (Formatter)过滤器 (Filter) 对输出的日志内容做格式化和过滤

7.3.1 Logger类

如何创建Logger对象

logger = logging.getLogger(name)

可选参数 name

如果不写name, 日志器名称默认为 root

如果写了name, 如, logger = logging.getLogger(“myLogger”) 那么日志器的名称为 myLogger

Logger常用方法

打印日志

logger.debug()logger.info()logger.warninglogger.errorlogger.critical()

设置日志级别

logger.setLevel()为logger对象添加一个handler对象

logger.addHandler()

为logger对象添加一个filter对象

logger.addFilter

7.3.2 Handler类

如何创建Handler对象

在程序中不应该直接实例化和使用Handler实例, 因为Handler是一个基类, 它只定义了Handler应该有的接口, 应该使用Handler实现类来创建对象创建方式

输出日志到控制台

logging.StreamHandler 输出到磁盘文件, 默认文件大小会无限增长输出到文件, 按文件大小切割输出到文件, 按时间切割

logging.hanlders.TimedRotatingFileHandler 将日志消息以get或post的方式发送给http服务器将日志消息发送给一个指定的email地址 常用方法

为handler设置格式器对象

handler.setFormatter()

7.3.3 Formatter类

**作用:**Formatter对象用于配置日志信息的格式

如何创建Formatter对象

logging.Formatter(fmt=None, datefmt=None) fmt: 消息格式化字符串, 如果不指定该参数则默认使用message的原始值 datefmt: 日期格式化字符串, 如果不指定该参数则默认使用 “%Y-%m-%d %H:%M:%S”

案例

说明

可读性好的日志需要具备一些特征

在控制台和文件都能输出文件输出能够按时间切割

步骤

导包创建日志器对象 / 设置日志级别创建处理器对象: 输出到控制台 + 文件(按时间切割)创建格式器对象将格式器添加到处理器将处理器添加到日志器打印日志

代码

# 1. 导包

import logging

import logging.handlers

# 2. 创建日志器对象 / 设置日志级别

logger = logging.getLogger() # 默认日志器名称为 root

# logger = logging.getLogger("An") # 自定义日志器名称为 An

logger.setLevel(level=logging.DEBUG)

# 3. 创建处理器对象: 输出到控制台 + 文件(按时间切割)

ls = logging.StreamHandler()

lf = logging.handlers.TimedRotatingFileHandler(filename="172.log", when="s", backupCount=3)code>

# 4. 创建格式器对象

fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] - %(message)s"

formatter = logging.Formatter(fmt=fmt)

# 5. 将格式器添加到处理器

ls.setFormatter(formatter)

lf.setFormatter(formatter)

# 6. 将处理器添加到日志器

logger.addHandler(ls)

logger.addHandler(lf)

# 7. 打印日志

while 1:

logger.debug("===================================================================")

8 面试题

说明样的项目适合做web自动化?

①需求变动不频繁

②项目周期长

③项目需要回归测试

web自动化一个什么时候开始?

①手工测试结束后



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。