Web的UI自动化基础知识

目录

1 Web自动化入门基础

1.1 自动化知识以及工具

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

优点:

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

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

应用场景

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

1.2 主流web自动化测试工具

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

1.3 入门案例

# 导包
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,关闭浏览器

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选择器

四种定位方式

  1. 路径
  2. 元素属性
  3. 属性与逻辑结合
  4. 层级与属性结合

方法

element = driver。find_element_by_xpath(xpath)

路径

  • 绝对路径:
    1. 从外层元素到指定元素之间所有经过元素层级的路径
    2. 绝对路径以/html根节点开始,使用/来分割元素层级,如:/html/body/div/fieldset/p[1]/input
    3. 绝对路径对页面要求严格,不建议使用
  • 相对路径
    1. 匹配任意层级的元素,不限制元素的位置
    2. 相对路径//开始
    3. 格式://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")

time.sleep(5)
driver.close()

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

  1. 元素上右键 -> 检查

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

使用函数

不使用函数时:
//*[@id='xxx']

使用函数后
//*[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")
# driver.find_element_by_xpath("//*[@id='userA']").send_keys("admin")
# driver.find_element_by_xpath("//*[@placeholder='请输入用户名']").send_keys("admin")
driver.find_element_by_xpath("//*[@type='text']").send_keys("admin")

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")

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")
driver.find_element_by_css_selector("div[class='zc'] input").send_keys("admin")

time.sleep(5)
driver.close()

CSS扩展

  1. input[type^=‘p’] type属性以p字母开头的元素
  2. input[type$=‘d’] type属性以d字母结束的元素
  3. 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']
		CSS
			#userA
	通过class属性定位
		XPath
			//*[@class='telA']
		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 元素定位分类

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

2.1.11 元素定位的另一种写法

方法

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

  1. 需要2个参数, 第1个参数为定位的类型(由By提供), 第2个参数传入具体的值
  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")

time.sleep(5)
driver.close()

2.2 元素操作

方法

click() 单击元素
send_keys() 模拟输入
clear() 清除文本

案例

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

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

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

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

  4. 间隔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)

方法:

  1. context_click(element) 右击
  2. double_click(element) 双击
  3. move_to_element(element) 悬停
  4. drag_and_drop(source, target) 拖拽
  5. 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 鼠标拖动

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

  1. 源元素 source = driver.find_element_by_xxx("xxx")
  2. 目标元素 target = driver.find_element_by_xxx("xxx")
  3. 调用方法 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 键盘操作

说明:

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

2.6.1 常用操作

导包

  1. send_keys(Keys.BACK_SPACE) 删除键(Backspace)
  2. send_keys(Keys.SPACE) 空格键(Space)
  3. send_keys(Keys.TAB) 制表键(Tab)
  4. send_keys(Keys.ESCAPE) 回退键(ESC)
  5. send_keys(Keys.ENTER) 回车键(Enter)
  6. send_keys(Keys.CONTROL, 'a') 全选(Ctrl + A)
  7. send_keys(Keys.CONTROL, 'c') 复制(Ctrl + C)

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

2.6.2 键盘操作

需求

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

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

代码

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 元素等待

概念

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

分类

  • 隐式等待
  • 显式等待

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

  1. 网络速度慢
  2. 服务器计算慢
  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")

time.sleep(3)
driver.quit()


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

注意点

单个元素定位超时会报 NoSuchElementException

2.6.5 显式等待

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

方法 :

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

  1. 导包

  2. WebDriverWait(driver, timeout, poll_frequency=0.5)

    1. driver: 浏览器驱动对象
    2. timeout: 超时时长, 单位: 秒
    3. poll_frequency: 检测的间隔时间, 默认为0.5s
  3. 调用 until(method)

    1. method: 函数名称, 该函数用来实现元素定位
    2. 一般使用匿名来实现: 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='延时加载的输入框']"))
element.send_keys("admin")

time.sleep(3)
driver.quit()

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

注意点

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

2.6.6 隐式和显式区别

  1. 作用域: 隐式等待为全局有效, 显式等待为单个元素有效
  2. 使用方法: 隐式等待直接通过驱动对象调用, 而显式等待方法封装在 WebDriverWait 类中
  3. 达到最大超时时长后抛出异常不同: 隐式等待为 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()
# 2
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[2]").click()
# 3
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[1]").click()

time.sleep(3)
driver.quit()

案例

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

实例化对象:
select = Select(element)
element: <select>标签对应的元素, 通过元素定位方式获取
例如: driver.find_element_by_id("selectA")

操作方法:

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

步骤分析

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

案例

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 弹出框分类

  1. alert 警告框
  2. confirm 确认框
  3. 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脚本来操作滚动条

  1. 设置 JS 脚本控制滚动条
    js = "window.scrollTO(0,1000)"
    (0:左边距, 1000:上边距 单位:像素(px))
  2. 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%">
<frame src="a.html">
<frame src="b.html">
</frameset>
形式二:
<iframe name="iframe_a" src="demo.html" width="200" height="200"></iframe>

方法

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

步骤:
1.driver.switch_to.frame(frame_reference) 切换到指定frame
frame_reference: 可以传frame框架的id,name,定位的frame元素
2.driver.switch_to.default_content() 恢复默认页面
必须回到默认页面才能进一步操作

解决方案

  1. 在主页面输入用户名 admin
  2. 切换到A页面, 再输入用户名 adminA
  3. 恢复默认页面
  4. 切换到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页面

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

方法

说明: 在Selenium中封装了获取当前窗口句柄,获取所有窗口句柄和切换到指定句柄窗口的方法
句柄: 英文handle, 窗口的唯一识别码
方法:

1. `driver.current_window_handle`		获取当前窗口句柄
2. `driver.window_handles`				获取所有窗口句柄
3. `driver.switch_to.window(handle)`	切换到指定句柄的窗口

对于需求的解决方案:

  1. 打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)
  2. 在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄
  3. 根据句柄, 切换到新浪窗口, 对输入框输入 “新浪搜索”
  4. 切换回原本窗口(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

概念

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

应用场景

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

方法

说明: 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就等于拿到了账号的控制权,通行贴吧,知道,文库…主要产品

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

代码

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 配置文件

应用场景

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

用法

步骤:

  1. 新建 scripts 模块, 测试脚本放到模块中
  2. 新建 pytest.ini 文件, 名称为 pytest.ini, 第一行为 [pytest], 并且补全配置项
  3. 命令行运行 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

生成报告

步骤:

  1. 命令行输入 pytest 运行脚本
  2. 在项目目录下会有一个 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模式的分层思想对代码进行拆分, 分离page
  • v5: 对PO分层后的代码继续优化, 分离page中的元素和操作
  • v6: PO模式深入封装, 把共同操作提取封装

4.2 无模式

4.2.1 案例说明

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

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

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

4.2.2 选择测试用例

  • 账号不存在

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

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

    2. 输入用户名

    3. 输入一个错误的密码

    4. 输入验证码

    5. 点击登录按钮

    6. 获取错误提示信息

4.3 V1版本

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

示例代码

  1. 登录功能, 账号不存在
# 账号不存在
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()
  1. 登录功能, 密码错误
# 密码错误
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模式分层

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

分层方式

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

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

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

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:
    data1 = json.load(f)
    print(data1)

# 把字典写入json文件 "data2.json"
data2 = data1
with open("data2.json", "w", encoding="utf-8") as f:
    json.dump(data2, f)

# 把字典写入json文件 "data3.json"  ------解决写入中文的问题
data3 = data1
with open("data3.json", "w", encoding="utf-8") as f:
    json.dump(data2, f, ensure_ascii=False)

实现步骤

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

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:
        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

级别:

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

常见日志级别:

  1. DEBUG === 调试
  2. INFO === 信息
  3. WARNING === 警告
  4. ERROR === 错误
  5. CRITICAL === 严重错误

日志基本用法:

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

  • 使用

    1. 导入 logging 包
    2. 输出日志
    3. 默认的日志级别被设置为 warning
  • 设置日志级别

    • 方法
      • logging.basicConfig(level=logging.DEBUG)
  • 设置日志格式

    • 默认格式
      • 日志级别 : Logger名称 : 日志内容
    • 自定义格式
      • logging.basicConfig(format="xxxxxx")
  • 将日志信息输出到文件

    • 默认
      • python的logging模块将日志打印到了标准输出中(控制台)
    • 将日志输出到文件的方法
      • logging.basicConfig(filename=“xxx.log”)

7.2 日志高级用法

思考

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

7.3 四大组件

  1. 日志器(Logger):提供了程序使用日志的入口
  2. 处理器(Handler):将logger创建的日志记录发送到合适的输出
  3. 格式器(Formatter):决定日志的输出格式
  4. 过滤器(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.warning
    • logger.error
    • logger.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. 导包
  2. 创建日志器对象 / 设置日志级别
  3. 创建处理器对象: 输出到控制台 + 文件(按时间切割)
  4. 创建格式器对象
  5. 将格式器添加到处理器
  6. 将处理器添加到日志器
  7. 打印日志

代码

# 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)
# 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 面试题

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

①需求变动不频繁

②项目周期长

③项目需要回归测试

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

①手工测试结束后

  • 43
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值