写点什么

软件测试 / 测试开发丨用户端 Web 自动化测试学习笔记

作者:测试人
  • 2023-06-20
    北京
  • 本文字数:19824 字

    阅读完需:约 65 分钟

点此获取更多相关资料

本文为霍格沃兹测试开发学社学员学习笔记分享

原文链接:https://ceshiren.com/t/topic/24826

一、SeleniumIDE 用例录制

1、SeleniumIDE 的下载以及安装

  1. 官网:www.selenium.dev/

  2. Chrome 插件:https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd

  3. Firefox 插件:https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/

  4. github release:https://github.com/SeleniumHQ/selenium-ide/releases

  5. 其它版本:https://addons.mozilla.org/en-GB/firefox/addon/selenium-ide/versions/

  • 注意:Chrome 插件在国内无法下载,Firefox 可以直接下载。

2、启动

  1. 安装完成后,通过在浏览器的菜单栏中点击它的图标来启动它:

  2. 如果没看到图标,首先确保是否安装了 Selenium IDE 扩展插件

  3. 通过以下链接访问所有插件

  • Chrome: chrome://extensions

  • Firefox: about:addons

3、SeleniumIDE 常用功能

  1. 新建、保存、打开

  2. 开始和停止录制

  3. 运行 8 中的所有的实例

  4. 运行单个实例

  5. 调试模式

  6. 调整案例的运行速度

  7. 要录制的网址

  8. 实例列表

  9. 动作、目标、值

  10. 对单条命令的解释

  11. 运行日志


4、SeleniumIDE 脚本导出:Java 和 Python



二、自动化测试用例结构分析

1、标准的用例结构(功能)

  1. 用例标题

  2. 前提条件

  3. 用例步骤

  4. 预期结果

  5. 实际结果

2、自动化用例结构及作用

【结构:自动化测试用例:作用】

  1. 用例标题:测试包、文件、类、方法名称:【用例的唯一标识】

  2. 前提条件:setup、setup_class(Pytest;BeforeEach、BeforeAll(JUnit):【测试用例前的准备动作,比如读取数据或者 driver 的初始化】

  3. 用例步骤:测试方法内的代码逻辑:【测试用例具体的步骤行为】

  4. 预期结果:assert 实际结果 = 预期结果:【断言,印证用例是否执行成功】

  5. 实际结果:assert 实际结果 = 预期结果:【断言,印证用例是否执行成功】

  6. 后置动作:teardown、teardown_class(Pytest);@AfterEach、@AfterAll(JUnit):【脏数据清理、关闭 driver 进程】

3、IDE 录制脚本

  1. 脚本步骤:

  • 访问百度网站

  • 搜索框输入“霍格沃兹测试开发”

  • 点击搜索按钮

# Generated by Selenium IDEimport pytestimport timeimport jsonfrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.common.action_chains import ActionChainsfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.common.desired_capabilities import DesiredCapabilities
class Test(): def setup_method(self, method): self.driver = webdriver.Chrome() self.vars = {} def teardown_method(self, method): self.driver.quit() def test_sougou(self): # 打开网页,设置窗口 self.driver.get("https://www.sogou.com/") self.driver.set_window_size(1235, 693) # 输入搜索信息 self.driver.find_element(By.ID, "query").click() self.driver.find_element(By.ID, "query").send_keys("霍格沃兹测试开发") # 点击搜索 self.driver.find_element(By.ID, "stb").click() element = self.driver.find_element(By.ID, "stb") actions = ActionChains(self.driver) actions.move_to_element(element).perform()
复制代码


#优化后的代码,添加断言判断用例是否成功# Generated by Selenium IDEimport pytestimport timeimport jsonfrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.common.action_chains import ActionChainsfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.common.desired_capabilities import DesiredCapabilities

class Test(object): # 前提条件,webdriver的初始化 def setup_method(self, method): self.driver = webdriver.Chrome() self.vars = {}
# 后置操作,关闭网站 def teardown_method(self, method): self.driver.quit()
# 测试用例步骤 def test_sougou(self): # 打开网页,设置窗口 self.driver.get("https://www.sogou.com/") self.driver.set_window_size(1235, 693) # 输入搜索信息 self.driver.find_element(By.ID, "query").click() self.driver.find_element(By.ID, "query").send_keys("霍格沃兹测试开发") # 点击搜索 self.driver.find_element(By.ID, "stb").click() element = self.driver.find_element(By.ID, "stb") actions = ActionChains(self.driver) actions.move_to_element(element).perform() # 问题:无法确定用例执行成功或失败 # 解决方案:添加断言信息,判断搜索列表中,是否会有"霍格沃兹测试开发" res_element = self.driver.find_element(By.CSS_SELECTOR, "#sogou_vr_30000000_0 > em") # 获取到定位的文本信息 # 判断实际获取到的搜索展示的列表和预期是否一致 assert res_element.text == "霍格沃兹测试开发"
复制代码

三、web 浏览器控制

  • 模拟功能测试中对浏览器的操作


import timefrom selenium import webdriver# 打开网页def open_browser():    # 实例化chromedriver    driver = webdriver.Chrome()    # 调用get方法时需要传递浏览器的URL    driver.get("https://ceshiren.com/")    # 添加等待1秒    time.sleep(2)    # # 刷新浏览器    # driver.refresh()    # # 通过get跳转到百度    # driver.get("https://www.baidu.com/")    # # 回退操作,退回到测试人网页    # driver.back()    # 最大化浏览器    driver.maximize_window()    time.sleep(2)    # 最小化浏览器    driver.minimize_window()    time.sleep(2)
if __name__ == '__main__': open_browser()
复制代码

四、常见控件定位方法

1、HTML 铺垫

  1. 标签:尖括号,如括起来的<a>、<html>、<title>等

  2. 属性:a=b,如 href

  3. 类属性: class

<!DOCTYPE html><html><head><meta charset="utf-8"><title>测试人论坛</title></head><body><a href="https://ceshiren.com/" class="link">链接</a></body></html>
复制代码

2、Selenium 八大定位方式

  1. selenium 常用定位方式

  • 格式:

driver.find_element_by_定位方式(定位元素)

driver.find_element(By.定位方式, 定位元素)

  • 示例,两种方式作用一模一样

  • 官方建议使用下面的方式

driver.find_element_by_id("su")

driver.find_element(By.ID, "su")


  1. 【方式:描述】

  • class name:class 属性对应的值

  • css selector(重点):css 表达式 格式: driver.find_element(By.CSS_SELECTOR, "css 表达式") 复制绝对定位 编写 css selector 表达式

  • id(重点):id 属性对应的值 格式: driver.find_element(By.ID, "ID 属性对应的值")

  • name(重点):name 属性对应的值 格式: driver.find_element(By.NAME, "Name 属性对应的值")

  • link text:查找其可见文本与搜索值匹配的锚元素 格式:driver.find_element(By.LINK_TEXT,"文本信息")

  • partial link text:查找其可见文本包含搜索值的锚元素。如果多个元素匹配,则只会选择第一个元素。

  • tag name:标签名称

  • xpath(重点):xpath 表达式 格式: driver.find_element(By.XPATH, "xpath 表达式") 复制绝对定位 编写 xpath 表达式


from selenium import webdriverfrom selenium.webdriver.common.by import Bydef web_locate():    # 首先需要实例化driver对象,Chrome一定要加括号    driver = webdriver.Chrome()    # 打开一个网页    driver.get("https://vip.ceshiren.com/#/ui_study/frame")    # 1.ID定位,第一个参数传递定位方式,第二个参数传递定位元素,调用这个方法的返回值为WebElement    web_element = driver.find_element(By.ID, "locate_id")    print(web_element)    # 2.NAME定位,如果没有报错,证明元素找到了    # 如果报错no such element,代表元素定位可能出现错误    # driver.find_element(By.NAME, "locate123")   # 错误示例    driver.find_element(By.NAME, "locate")    # 3.CSS选择器定位    driver.find_element(By.CSS_SELECTOR, "#locate_id > a > span")    # 4.xpath表达式定位    driver.find_element(By.XPATH, '//*[@id="locate_id"]/a/span')  # 表达式里有双引号,建议用单引号    # 5.Link text,通过链接文本的方式,(1)元素一定是a标签;(2)输入的元素为标签内的文本    driver.find_element(By.LINK_TEXT, "元素定位")   # 通常会带一个点击的操作,在最后面添加.click(),可加可不加

if __name__ == '__main__': web_locate()
复制代码

五、强制等待与隐式等待

1、为什么要添加等待

避免页面未渲染完成后操作,导致的报错

from selenium import webdriverfrom selenium.webdriver.common.by import By
def wait_sleep(): """ 如果直接执行,不添加任何等待,可能会报错 """ driver = webdriver.Chrome() driver.get("https://vip.ceshiren.com/") # 不加等待,可能会因为网速等原因产生报错 # 报错:no such element: Unable to locate element # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来 driver.find_element(By.XPATH, "//*[text()='个人中心']")
if __name__ == '__main__': wait_sleep()
复制代码

2、强制(直接)等待

  1. 解决方案:在报错的元素操作之前添加等待

  2. 原理:强制等待,线程休眠一定时间。time.sleep(3)

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWait
def wait_sleep(): """ 如果直接执行,不添加任何等待,可能会报错 """ driver = webdriver.Chrome() driver.get("https://vip.ceshiren.com/") # 不加等待,可能会因为网速等原因产生报错 # 报错:no such element: Unable to locate element # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来 # 1.强制等待,让页面渲染完成,在报错的元素操作之前添加等待,没有报错,就证明是页面渲染速度导致,有报错则是其他问题,如定位错误等 time.sleep(3) driver.find_element(By.XPATH, "//*[text()='个人中心']")
if __name__ == '__main__': wait_sleep()
复制代码

3、隐式等待

  1. 问题:难以确定元素加载的具体等待时间。

  2. 解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置。

  3. 原理:设置一个等待时间,轮询查找(默认 0.5 秒)元素是否出现,如果没出现就抛出异常

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWait
def wait_sleep(): """ 如果直接执行,不添加任何等待,可能会报错 """ driver = webdriver.Chrome() driver.get("https://vip.ceshiren.com/") # 不加等待,可能会因为网速等原因产生报错 # 报错:no such element: Unable to locate element # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来 # 1.强制等待,让页面渲染完成,在报错的元素操作之前添加等待,没有报错,就证明是页面渲染速度导致,有报错则是其他问题,如定位错误等 # time.sleep(3) # 强制等待的问题:(1)不确定页面加载时间,可能会因为等待时间过长,而影响用例的执行效率;(2)不确定页面加载时间,可能会因为等待时间过短,而导致代码依然报错 # 2.隐式等待 # 设置一个最长的等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常 # 注意:(1)在代码一开始运行时就添加隐式等待的配置,隐式等待是全局生效,即在所有find_element动作之前添加该配置即可;(2)隐式等待只能解决元素查找问题,不能解决元素交互问题 driver.implicitly_wait(3) driver.find_element(By.XPATH, "//*[text()='个人中心']") # driver.implicitly_wait(5) # 修改隐式等待的配置 driver.find_element(By.XPATH, "//*[text()='题库']")
复制代码

4、隐式等待无法解决的问题

  1. 问题:元素可以找到,使用点击等操作,出现报错

  2. 原因:

  • 页面元素加载是异步加载过程,通常 html 会先加载完成,js、css 其后

  • 元素存在与否是由 HTML 决定,元素的交互是由 css 或者 js 决定

  • 隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互

  1. 解决方案:使用显式等待

5、显式等待基本使用(初级)

  1. 示例: WebDriverWait(driver 实例, 最长等待时间, 轮询时间).until(结束条件)

  2. 原理:在最长等待时间内,轮询,是否满足结束条件

  • 在初级时期,先关注使用

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWait

"""显示等待"""

def wait_show(): driver = webdriver.Chrome() driver.get("https://vip.ceshiren.com/#/ui_study/frame") # driver.implicitly_wait(3) # 问题:元素可以找到,但是点击效果没有触发 # 显示等待,第一个参数是driver,第二个参数是最长等待时间,轮询时间可加可不加,util方法内需要结合expected_conditions或者自己封装的方法进行使用 # expected_conditions的参数传入都是一个元组,即多一层小括号 WebDriverWait(driver, 10).until(expected_conditions.element_to_be_clickable((By.ID, "success_btn"))) driver.find_element(By.ID, "success_btn").click() # 添加点击操作.click(),点击"消息提示" time.sleep(5)

if __name__ == '__main__': wait_show()
复制代码

6、总结

  1. 直接等待:

  • 使用方式:time.sleep(等待时间))

  • 原理:强制线程等待

  • 适用场景:调试代码,临时性添加

  1. 隐式等待:

  • 使用方式:driver.implicitly_wait(等待时间)

  • 原理:在时间范围内,轮询查找元素

  • 适用场景:解决找不到元素问题,无法解决交互问题

  1. 显式等待:

  • 使用方式:WebDriverWait(driver 实例, 最长等待时间, 轮询时间).until(结束条件)

  • 原理:设定特定的等待条件,轮询操作

  • 适用场景:解决特定条件下的等待问题,比如点击等交互性行为

六、常见控件交互方法

1、元素操作

点击,输入,清空

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import By
def element_interaction(): """ 元素操作:点击、输入、清空 :return: """ # 1.实例化driver对象 driver = webdriver.Chrome() # 2.打开一个网页 driver.get("https://www.sogou.com/") # 3.定位到输入框进行输入操作,.send_keys() driver.find_element(By.ID, "query").send_keys("霍格沃滋测试开发") # 强制等待2秒 time.sleep(2) # 4.对输入框进行清空操作.clear() driver.find_element(By.ID, "query").clear() time.sleep(2) # 5.再次输入 driver.find_element(By.ID, "query").send_keys("霍格沃滋测试开发学社") time.sleep(2) # 6.点击搜索.click() driver.find_element(By.ID, "stb").click() time.sleep(2)
if __name__ == '__main__': element_interaction()
复制代码

2、获取元素属性信息

  1. 原因:定位到元素后,获取元素的文本信息,属性信息等

  2. 目的:根据这些信息进行断言或者调试

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import By
# 获取元素属性def element_get_attr(): # 1.实例化driver对象 driver = webdriver.Chrome() # 2.打开网页 driver.get("https://vip.ceshiren.com/#/ui_study/iframe") # 3.定位一个元素 web_element = driver.find_element(By.ID, "locate_id") # 4.打印这个元素信息 # 使用debug方式查看,断点打在想看的对象的下一行 # print(web_element) # 5.获取元素的文本信息,不是每个元素都有文本信息的 print(web_element.text) # 6.获取元素的属性信息,如 id="locate_id", title="xxx" res = web_element.get_attribute("class") print(res)
if __name__ == '__main__': # 获取元素属性 element_get_attr()
复制代码



3、获取元素属性信息的方法

  1. 获取元素文本

  2. 获取元素的属性(html 的属性值)

# 获取元素文本driver.find_element(By.ID, "id").text# 获取这个元素的name属性的值driver.find_element(By.ID, "id").get_attribute("name")
复制代码

七、自动化测试定位策略

1、定位方式


2、通用 Web 定位方式


3、选择定位器通用原则

  1. 与研发约定的属性优先(class 属性: [name='locate'])

  2. 身份属性 id,name(web 定位)

  3. 复杂场景使用组合定位:

  • xpath,css

  • 属性动态变化(id,text)

  • 重复元素属性(id,text,class)

  • 父子定位(子定位父)

  1. js 定位

4、Web 弹框定位

  1. 场景:web 页面 alert 弹框

  2. 解决:web 需要使用 driver.switchTo().alert() 处理

5、下拉框/日期控件定位

  1. 场景:

  • <input>标签组合的下拉框无法定位

  • <input>标签组合的日期控件无法定位

  1. 解决:面对这些元素,我们可以引入 JS 注入技术来解决问题。

6、文件上传定位

  1. 场景:input 标签文件上传

  2. 解决:input 标签直接使用 send_keys()方法


L1 实战–测试人论坛搜索功能自动化测试

from selenium import webdriverfrom selenium.webdriver.common.by import By
class TestCeshiren01:
def setup(self): """ 前提条件:进入测试人论坛的搜索页面 :return: """ # 实例化driver对象 self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) # 隐式等待 # 打开被测地址 self.driver.get("https://ceshiren.com/search?expanded=true")
def teardown(self): # 每一次用例结束之后都会关闭chromedriver进程,也会关闭浏览器 self.driver.quit()
def test_search01(self): """ 测试步骤:1.输入搜索关键词;2.点击搜索按钮 :return: """ # 定位搜索框,并输入搜索内容,如果是动态id,使用css self.driver.find_element(By.CSS_SELECTOR, "[placeholder=搜索]").send_keys("appium") # 定位到搜索按钮,并点击搜索 self.driver.find_element(By.CSS_SELECTOR, ".search-cta").click() # 断言=预期结果与实际结果对比的结果 # 定位实际结果,即为获取搜索结果列表的标题内容 web_element = self.driver.find_element(By.CSS_SELECTOR, ".topic-title") # 获取文本类的实际结果断言,appium关键字是否在获取的实际结果文本之中 assert "appium" in web_element.text
复制代码

八、高级定位-css

1、css 选择器概念

  1. css 选择器有自己的语法规则和表达式

  2. css 定位通常分为绝对定位和相对定位

  3. 和 Xpath 一起常用于 UI 自动化测试中的元素定位

2、css 相对定位使用场景

  1. 支持 web 产品

  2. 支持 app 端的 webview

3、css 相对定位的优点

  1. 可维护性更强

  2. 语法更加简洁

  3. 解决各种复杂的定位场景

4、css 定位的调试方法

  1. 进入浏览器的 console

  2. 输入:$("css 表达式")或者 $$("css 表达式"),如果表达式里有双引号,外面要单引号,如果表达式用的是单引号,外面就要用双引号,外双内单,外单内双

5、css 基础语法


【类型:表达式】

  1. 标签:标签名

  • $("div") 获取所有的 div 标签

  1. 类:.表示为 class 属性值,如果类里面有多个值(单词),就不能直接复制,将空格改为.即可

  • $(".logo-big")等同于 $("[class='logo-big']")

  1. ID:#表示为 id 属性值

  • $("#site-logo")等同于 $("[id='site-logo']")

  1. 属性:[属性名='属性值']

  • $("[alt='测试人社区']")


6、css 关系定位


【类型:格式】

  1. 并集:元素,元素 $("#main,#ember4")



  1. 邻近兄弟(了解即可):元素+元素 $("#ember39+#ember40")

  2. 兄弟(了解即可):元素 1~元素 2 $("#ember39~#ember41")

  3. 父子:元素>元素 $("#main>#ember4")

  4. 后代:元素 元素 $("#main #skip-link")


7、css 顺序关系


【类型:格式】

  1. 父子关系+顺序:元素 元素(父亲有多个孩子,找到第一个孩子) 表达式://:nth-child(n) $("#ember15>:nth-child(3)") ( #ember15 为父亲,:nth-child(3)为第三个孩子)

  2. 父子关系+标签类型+顺序:元素 元素(父亲的孩子有多个类型,想找其中一个类型里面的第一个孩子) 表达式://:nth-of-type(n) $("#ember15>div:nth-of-type(1)")( #ember15 为父亲,:nth-of-type(1)为第三个类型 div 的第一个孩子)


css 表达式定位


九、高级定位-xpath

1、xpath 基本概念

  1. XPath 是一门在 XML 文档中查找信息的语言

  2. XPath 使用路径表达式在 XML 文档中进行导航

  3. XPath 的应用非常广泛

  4. XPath 可以应用在 UI 自动化测试

2、xpath 使用场景

  1. web 自动化测试

  2. app 自动化测试

3、xpath 相对定位的优点

  1. 可维护性更强

  2. 语法更加简洁

  3. 相比于 css 可以支持更多的方式

4、xpath 定位的调试方法

  1. 浏览器-console

  • $x("xpath 表达式")

  1. 浏览器-elements

  • ctrl+f 输入 xpath 或者 css

5、xpath 基础语法(包含关系)


【表达式:结果】

  1. / :从该节点的子元素选取

  2. // :从该节点的子孙元素选取

  3. :通配符 $x("//*[@id='ember61']"),匹配所有的[@id='ember61'] $x("//tr[@id='ember61']"),匹配 tr 标签下面的[@id='ember61']

  4. nodename :选取此节点的所有子节点,相当于标签名

  5. .. :选取当前节点的父节点, 如 $x("//*[@id='ember61']/.."),寻找 ember61 的父节点

写法二:$x("//tr[@id='ember61']"),在某个标签下寻找

  1. @ :选取属性

  2. 整个页面 $x("/")


页面中的所有的子元素,匹配/下面的所有节点,相当于是 html $x("/*")


整个页面中的所有元素 $x("//*")


查找页面上面所有的 div 标签节点,标签不等于属性 $x("//div")


查找 id 属性为 ember61 的节点,*先匹配所有节点,再匹配某个属性 $x('//*[@id="ember61"]')


查找 ember61 节点的父节点

$x('//*[@id="ember61"]/..')

$x("//*[@id='ember61']/../..") 再往上寻找父节点

6、xpath 顺序关系(索引)

xpath通过索引直接获取对应元素
# 查找tbody下的所有tr$x("//tbody//tr")# 查找tbody下的第一个tr,下标从1开始$x("//tbody//tr[1]")# 获取所有的tr,注意这里的tr并不是都是同一个父节点$x("//tr")# 获取每个不同父节点下面的第一个tr$x("//tr[1]")
复制代码



7、xpath 高级用法

①[last()]: 选取最后一个$x("//tr[last()]")   # 获取每一个父节点下的最后一个tr$x("//tbody//tr[last()]")   # 获取tbody下面的最后一个tr
②[@属性名='属性值' and @属性名='属性值']: 与关系$x("//*[@class='topic-list-item category-bu-161-category unseen-topic ember-view' and @id='ember44']")
③[@属性名='属性值' or @属性名='属性值']: 或关系$x("//*[@class='topic-list-item category-bu-161-category unseen-topic ember-view' or @id='ember44']")
④[text()='文本信息']: 根据文本信息定位$x("//*[text()='赏金任务']") # text不是属性,不需要加@,是一个方法,加括号
⑤[contains(text(),'文本信息')]: 根据文本信息包含定位;也可以contains(@id或@name等)$x("//*[contains(text(),'赏金')]")$x("//*[contains(@id,'site')]")
⑥注意:所有的表达式需要和[]结合
复制代码

十、显式等待高级使用

1、显式等待原理

  1. 在代码中定义等待一定条件发生后再进一步执行代码

  2. 在最长等待时间内循环执行结束条件的函数,结合③一起查看

  3. 源码:WebDriverWait(driver 实例, 最长等待时间, 轮询时间).until(结束条件函数)

2、常见 expected_conditions


3、封装等待条件

  1. 官方的 excepted_conditions 不可能覆盖所有场景,如有些按钮需要点击两次或多次才会有反应

  2. 定制封装条件会更加灵活、可控

import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.remote.webdriver import WebDriverfrom selenium.webdriver.support.wait import WebDriverWait

class TestWebdriverWait:
driver = webdriver.Chrome()
driver.maximize_window() driver.implicitly_wait(5) driver.get("https://vip.ceshiren.com/#/ui_study/iframe") def teardown(self): self.driver.quit() def test_webdriver_wait(self): # 解决的问题:有的按钮点击一次没有反应,可能要点击多次,比如企业微信的添加成员 # 解决的方案:一直点击按钮,直到下个页面出现,封装成显式等待的一个条件 def muliti_click(button_element,until_ele): # 函数封装 def inner(driver): # 封装点击方法 driver.find_element(By.XPATH,button_element).click() return driver.find_element(By.XPATH,until_ele) return inner time.sleep(5) # 在限制时间内会一直点击按钮,直到展示弹框 WebDriverWait(self.driver,10).until(muliti_click("//*[text()='点击两次响应']","//*[text()='该弹框点击两次后才会弹出']")) time.sleep(5)
复制代码

十一、高级控件交互方法

1、使用场景


2、ActionChains 解析

  1. 实例化类 ActionChains,参数为 driver 实例。

  2. 中间可以有多个操作。

  3. .perform()代表确定执行。

3、键盘事件

  1. 按下、释放键盘键位

  2. 结合 send_keys 回车

  3. 键盘事件-使用 shift 实现大写

  • ActionChains(self.driver): 实例化 ActionChains 类

  • key_down(Keys.SHIFT, ele): 按下 shift 键实现大写

  • send_keys("selenium"): 输入大写的 selenium

  • perform(): 确认执行

from selenium import webdriverfrom selenium.webdriver import ActionChains, Keysfrom selenium.webdriver.common.by import By
class TestKeyBoard:
def setup_class(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) # 隐式等待3秒
def teardown_class(self): self.driver.quit()
def test_shift(self): """ 键盘事件-使用shift实现大写 1.访问https://ceshiren.com/官方网站 2.点击搜索按钮 3.输入搜索的内容,输入的同时按着shift键 :return: """ self.driver.get("https://ceshiren.com/") # 点击搜索按钮 self.driver.find_element(By.ID, "search-button").click() # 目标元素,即为输入框 ele = self.driver.find_element(By.ID, "search-term") # key_down代表按下某个键位,.send_keys输入内容,.perform()确认执行以上操作 ActionChains(self.driver).key_down(Keys.SHIFT, ele).send_keys("selenium").perform()
复制代码
  1. 键盘事件-输入后回车

  • 直接输入回车: 元素.send_keys(Keys.ENTER)

  • 使用 ActionChains: key_down(Keys.ENTER)

from selenium import webdriverfrom selenium.webdriver import ActionChains, Keysfrom selenium.webdriver.common.by import By
class TestKeyBoard:
def setup_class(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) # 隐式等待3秒
def teardown_class(self): self.driver.quit()
def test_enter(self): """键盘事件-输入后回车""" self.driver.get("https://www.sogou.com/") self.driver.find_element(By.ID, "query").send_keys("python语言") # 第一种回车方式,直接输入回车 self.driver.find_element(By.ID, "query").send_keys(Keys.ENTER) # 第二种使用ActionChains,要记得加.perform() ActionChains(self.driver).key_down(Keys.ENTER).perform()
复制代码
  1. 键盘事件-复制粘贴

  • 多系统兼容 mac 的复制按钮为 COMMAND windows 的复制按钮为 CONTROL

  • 左箭头:Keys.ARROW_LEFT

  • 按下 COMMAND 或者 CONTROL: key_down(cmd_ctrl)

  • 按下剪切与粘贴按钮: send_keys("xvvvvv")

import sysfrom selenium import webdriverfrom selenium.webdriver import ActionChains, Keysfrom selenium.webdriver.common.by import By
class TestKeyBoard: def setup_class(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) # 隐式等待3秒
def teardown_class(self): self.driver.quit()
def test_copy_and_paste(self): """键盘事件-复制粘贴""" # 打开网页 self.driver.get("https://ceshiren.com/") # 点击搜索按钮 self.driver.find_element(By.ID, "search-button").click() # 目标元素,即为输入框 ele = self.driver.find_element(By.ID, "search-term") # 判断操作系统是否为Mac(darwin),是mac返回command键位,windows返回control键位 command_control = Keys.COMMAND if sys.platform == "darwin" else Keys.CONTROL # .key_down(Keys.SHIFT, ele)按下shift键,.send_keys("selenium")输入大写selenium, # .key_down(Keys.ARROW_LEFT)按下左箭头,.key_down(command_control)按下command或control键位, # .send_keys("xvv"),x表示为剪切,多少个v表示复制多少次,.key_up(command_control)表示松开command或control键位,.perform()执行 ActionChains(self.driver)\ .key_down(Keys.SHIFT, ele)\ .send_keys("selenium@")\ .key_down(Keys.ARROW_LEFT)\ .key_down(command_control).send_keys("xvvvvvv").key_up(command_control)\ .perform()
复制代码

4、鼠标事件

  1. 双击 double_click(元素对象): 双击元素

  2. 拖动元素 drag_and_drop(起始元素对象, 结束元素对象): 拖动并放开元素

  3. 指定位置(悬浮) move_to_element(元素对象): 移动到某个元素

import timefrom selenium import webdriverfrom selenium.webdriver import ActionChains, Keysfrom selenium.webdriver.common.by import By
class TestKeyBoard:
def setup_class(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) # 隐式等待3秒
def teardown_class(self): self.driver.quit()
def test_double_click(self): """鼠标事件-双击""" # 打开网页 self.driver.get("https://vip.ceshiren.com/#/ui_study/iframe") # 获取点击按钮 ele = self.driver.find_element(By.ID, "primary_btn") # .double_click(ele)调用双击方法,传入双击元素,.perform()执行 ActionChains(self.driver).double_click(ele).perform()
def test_drag_and_drop(self): """鼠标事件-拖动元素""" # 打开网页 self.driver.get("https://vip.ceshiren.com/#/ui_study/action_chains") # 获取需要拖动的元素,即起始元素的位置 item_left = self.driver.find_element(By.CSS_SELECTOR, '#item1') # #表示id标签 # 获取目标元素的位置 item_right = self.driver.find_element(By.CSS_SELECTOR, '#item3') # #表示id标签 # 实现拖拽操作,.drag_and_drop(item_left, item_right) ActionChains(self.driver).drag_and_drop(item_left, item_right).perform()
def test_hover(self): """鼠标事件-悬浮""" self.driver.get("https://vip.ceshiren.com/#/ui_study/action_chains2") # 获取下拉框位置 ele = self.driver.find_element(By.CSS_SELECTOR, ".menu") # 鼠标悬浮在下拉框 ActionChains(self.driver).move_to_element(ele).perform() # 选择下拉选项 self.driver.find_element(By.XPATH, "//*[contains(text(),'测开班')]").click() time.sleep(3)
复制代码

5、滚轮/滚动操作

  1. 滚动到元素 scroll_to_element(WebElement 对象):滚动到某个元素

  2. 根据坐标滚动 scroll_by_amount(横坐标, 纵坐标)

  3. 注意: selenium 版本需要在 4.2 之后

import timefrom selenium import webdriverfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import By
class TestScroll:
def setup_class(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(10) # 隐式等待
def teardown_class(self): self.driver.quit()
def test_scoll_to_element(self): """滚轮/滚动操作-滚动到元素""" # 打开网页 self.driver.get("https://ceshiren.com/") # 获取页面底部某个标题 ele = self.driver.find_element(By.XPATH, "//*[text()='接口测试上线标准']") # 页面滚动操作,找到"接口测试上线标准",selenium版本需要在 4.2 之后 ActionChains(self.driver).scroll_to_element(ele).perform() time.sleep(5)
def test_scroll_to_xy(self): """滚轮/滚动操作-根据坐标滚动""" self.driver.get("https://ceshiren.com/") # 坐标滚动方式,.scroll_by_amount(0, 10000)纵向滚动 ActionChains(self.driver).scroll_by_amount(0, 3000).perform() time.sleep(3)
复制代码

十二、网页 frame 与多窗口处理

1、selenium⾥⾯如何处理多窗⼜场景

  1. 多个窗口识别

  2. 多个窗口之间切换

2、selenium⾥⾯如何处理 frame

  1. 多个 frame 识别

  2. 多个 frame 之间切换

3、多窗口处理

  1. 点击某些链接,会重新打开⼀个窗口,对于这种情况,想在新页⾯上操作,就得先切换窗口了。

  2. 获取窗口的唯⼀标识⽤句柄表⽰,所以只需要切换句柄,就可以在多个页⾯灵活操作了。

4、多窗口处理流程

  1. 先获取到当前的窗口句柄(driver.current_window_handle)

  2. 再获取到所有的窗口句柄(driver.window_handles)

  3. 判断是否是想要操作的窗口,如果是,就可以对窗口进⾏操作,如果不是,跳转到另外⼀个窗口,对另⼀个窗口进⾏操作 (driver.switch_to_window)

"""base.py文件,Base类有前置和后置操作"""from selenium import webdriver
# 前置和后置操作,提供测试用例调用class Base(object): # 前置操作 def setup(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(5) # 隐式等待
# 后置操作 def teardown(self): self.driver.quit()

"""测试用例文件test_window.py"""import timefrom selenium.webdriver.common.by import Byfrom webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作class TestWindows(Base): def test_window(self): self.driver.get("https://www.baidu.com/") # 找到登录按钮,并点击 self.driver.find_element(By.XPATH, "//*[@id='s-top-loginbtn']").click() print(self.driver.current_window_handle) # 点击登录之后,打印当前窗口 # 点击弹窗里的立即注册按钮 self.driver.find_element(By.XPATH, "//*[text()='立即注册']").click() # # 点击立即注册之后,打印当前窗口 # print(self.driver.current_window_handle) # # 打印所有窗口的名字,列表形式,会有注册页面的窗口, # print(self.driver.window_handles) # 获取所有窗口列表,赋给window window = self.driver.window_handles # window[-1]获取注册页面的窗口,并切换到注册页面的窗口 self.driver.switch_to.window(window[-1]) # 注册页面,输入用户名username self.driver.find_element(By.ID, "TANGRAM__PSP_4__userName").send_keys("username") time.sleep(3) # 切换回最初登录页面的窗口 self.driver.switch_to.window(window[0]) # # 登录页面,点击短信登录 # self.driver.find_element(By.XPATH, "//*[text()='短信登录']").click() # 获取用户名输入框,并输入用户名 self.driver.find_element(By.ID, "TANGRAM__PSP_11__userName").send_keys("login_username") # 获取密码输入框,并输入密码 self.driver.find_element(By.ID, "TANGRAM__PSP_11__password").send_keys("login_password") # 点击登录 self.driver.find_element(By.ID, "TANGRAM__PSP_11__submit").click() time.sleep(6)
复制代码

5、frame 处理

1)frame介绍    ①在web⾃动化中,如果⼀个元素定位不到,那么很⼤可能是在iframe中。    ②什么是frame?        a. frame是html中的框架,在html中,所谓的框架就是可以在同⼀个浏览器中显⽰不⽌⼀个页⾯。        b. 基于html的框架,又分为垂直框架和⽔平框架(cols,rows)    ③Frame 分类        a. frame标签包含frameset、frame、iframe三种,        b. frameset和普通的标签⼀样,不会影响正常的定位,可以使⽤index、id、name 、webelement任意种⽅式定位frame。        c. ⽽frame与iframe对selenium定位⽽⾔是⼀样的。selenium有⼀组⽅法对frame进⾏操作
2)多frame切换 ①frame存在两种:⼀种是嵌套的,⼀种是未嵌套的 ②切换frame ❖ driver.switch_to.frame() # 根据元素id或者index切换切换frame ❖ driver.switch_to.default_content() # 切换到默认frame ❖ driver.switch_to.parent_frame() # 切换到⽗级frame
3)frame未嵌套 ①处理未嵌套的iframe ❖ driver.switch_to_frame(“frame 的 id”) ❖ driver.switch_to_frame(“frame - index”) frame⽆ID的时候依据索引来处理,索引从0开始 driver.switch_to_frame(0)
4)frame嵌套 ②处理嵌套的iframe ❖ 对于嵌套的先进⼊到iframe的⽗节点,再进到⼦节点,然后可以对⼦节点⾥⾯的对象进⾏处理和操作 ❖ driver.switch_to.frame(“⽗节点”) ❖ driver.switch_to.frame(“⼦节点”)
复制代码


import timefrom selenium.webdriver.common.by import Byfrom webui.window_frame.base import Base
# 继承Base类,Base类里有前置和后置操作class TestFrame(Base): def test_frame(self): self.driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable") # 切换到frame中,根据frame的元素id self.driver.switch_to.frame("iframeResult") # 写法一,常用 # self.driver.switch_to_frame("iframeResult") # 写法二 # 获取"请拖拽我!"的属性,获取文本内容 print(self.driver.find_element(By.ID, "draggable").text)
# 写法一:切回网页,即不在frame页面,即切回frame父节点 # self.driver.switch_to.parent_frame() # 写法二:切换到默认frame节点,即刚打开的地址节点 self.driver.switch_to.default_content() # 获取"点击运行"按钮,并点击 self.driver.find_element(By.ID, "submitBTN").click() time.sleep(5)
复制代码



十三、文件上传弹框处理

1、文件上传

  1. input 标签可以直接使⽤send_keys(⽂件地址)上传⽂件

  2. ⽤法: el = driver.find_element_by_id('上传按钮 id') el.send_keys(”⽂件路径+⽂件名")

import timefrom selenium.webdriver.common.by import Byfrom webui.window_frame.base import Base
# 继承Base类,Base类里有前置和后置操作class TestFile(Base): def test_file_upload(self): self.driver.get("https://www.baidu.com/") # 找到百度的相机图标并点击 self.driver.find_element(By.XPATH, "//*[@class='soutu-btn']").click() # 定位到选择文件,并选择文件 self.driver.find_element(By.XPATH, "//input[@class='upload-pic']").send_keys("/Users/jiyu/PycharmProjects/pythonProject/webui/img/12.png") time.sleep(5)
复制代码

2、chrome 开启 debug 模式

  1. 有时候登录⽅式⽐较繁琐,需要动态⼿机密码,⼆维码登录之类的。⾃动话实现⽐较⿇烦。⼿⼯登录后,不想让 selenium 启动⼀个新浏览器。可以使⽤chrome 的 debug⽅式来执⾏测试。

  2. 启动 chrome 的时候需要先退出所有 chrome 进程。使⽤ps aux|grep chrome|grep -v 'grep'查看是否有 chrome 进程存在。确保没有 chrome 进程被启动过。

  3. 正常启动 chrome 的 debug 模式 默认 macOS 系统 /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debuggingport=9222 Windows 下找到 chrome.exe 位置执⾏下⾯的命令 chrome.exe --remote-debugging-port=9222

  4. 启动后的提⽰信息,代表 chrome 运⾏正常,不要关闭⾃动打开的 chrome 窗口。

3、弹框处理机制

  1. 在页⾯操作中有时会遇到 JavaScript 所⽣成的 alert、confirm 以及 prompt 弹框,可以使⽤switch_to.alert()⽅法定位到。然后使⽤text/accept/dismiss/send_keys 等⽅法进⾏操作。

  2. 操作 alert 常⽤的⽅法: switch_to.alert():获取当前页⾯上的警告框。 text:返回 alert/confirm/prompt 中的⽂字信息。 accept():接受现有警告框。 dismiss():解散现有警告框。 send_keys(keysToSend):发送⽂本⾄警告框。keysToSend:将⽂本发送⾄警告框。


import timefrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom webui.window_frame.base import Base
class TestAlert(Base): def test_alert(self): """ ❖ 打开⽹页 https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable ❖ 操作窗⼜右侧页⾯, 将元素1拖拽到元素2 ❖ 这时候会有⼀个alert弹框,点击弹框中的’确定’ ❖ 然后再按’点击运⾏’ ❖ 关闭⽹页 :return: """ self.driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable") # 切换到frame中,根据frame的元素id self.driver.switch_to.frame("iframeResult") # 获取需要拖动的元素,即起始元素的位置 a1 = self.driver.find_element(By.CSS_SELECTOR, '#draggable') # 获取目标元素的位置 a2 = self.driver.find_element(By.CSS_SELECTOR, '#droppable') # 实现拖拽操作,.drag_and_drop(a1, a2):将a1拖拽到a2上,.perform()执行 action = ActionChains(self.driver) action.drag_and_drop(a1, a2).perform() time.sleep(2) # 强制等待2秒,查看效果 # 切换到alert页面,点击alert的确认按钮.accept() self.driver.switch_to.alert.accept() # 退出alert页面,返回到默认frame节点 self.driver.switch_to.default_content() # 获取"点击运行"按钮,并点击 self.driver.find_element(By.ID, 'submitBTN').click() time.sleep(3)
复制代码

十四、自动化关键数据记录

1、什么是关键数据

  1. 代码的执行日志

  2. 代码执行的截图

  3. page source(页面源代码)

2、记录关键数据的作用


3、行为日志记录

  1. 日志配置

  2. 脚本日志级别 debug 记录步骤信息 info 记录关键信息,比如断言等

#日志配置文件log_utils.py# 日志配置import logging# 创建logger实例logger = logging.getLogger('simple_example')# 设置日志级别logger.setLevel(logging.DEBUG)# 流处理器ch = logging.StreamHandler()ch.setLevel(logging.DEBUG)# 日志打印格式formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')# 添加格式配置ch.setFormatter(formatter)# 添加日志配置logger.addHandler(ch)
复制代码


# 测试用例脚本文件test_data_record.py# 日志与脚本结合from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom webui.data_record.log_utils import loggerfrom webui.window_frame.base import Base
# 继承Base类,Base类里有前置和后置操作class TestDataRecord(Base): """""" def test_data_record(self): search_content = "python语言" # 打开网页 self.driver.get("https://www.sogou.com/") # 定位输入框,输入内容 self.driver.find_element(By.ID, "query").send_keys(search_content) # 加入日志,步骤信息较多且琐碎,使用debug logger.debug(f"搜索的信息为:{search_content}") # 点击搜索 self.driver.find_element(By.ID, "stb").click() # 获取搜索结果列表的标题,对应测试结果的实际结果 search_result = self.driver.find_element(By.CSS_SELECTOR, "em") # 多个em的情况下,默认拿第一个 # 添加日志,断言使用info,级别高于debug,断言实际结果与预期结果是否一致 logger.info(f"实际结果为:{search_result.text},预期结果为:{search_content}") # 断言,search_result.text 获取搜索结果的文本信息,预期结果search_content assert search_result.text == search_content
复制代码

4、步骤截图记录

  1. save_screenshot(截图路径+名称)

  2. 记录关键页面 断言页面 重要的业务场景页面 容易出错的页面

from selenium.webdriver.common.by import Byfrom webui.data_record.log_utils import loggerfrom webui.window_frame.base import Base
# 继承Base类,Base类里有前置和后置操作class TestDataRecord(Base): def test_screen_shot_data_record(self): search_content01 = "python语言" # 打开网页 self.driver.get("https://www.sogou.com/") # 定位输入框,输入内容 self.driver.find_element(By.ID, "query").send_keys(search_content01) # 加入日志,步骤信息较多且琐碎,使用debug logger.debug(f"搜索的信息为:{search_content01}") # 点击搜索 self.driver.find_element(By.ID, "stb").click() # 获取搜索结果列表的标题,对应测试结果的实际结果 search_result01 = self.driver.find_element(By.CSS_SELECTOR, "em") # 多个em的情况下,默认拿第一个 # 添加日志,断言使用info,级别高于debug,断言实际结果与预期结果是否一致 logger.info(f"实际结果为:{search_result01.text},预期结果为:{search_content01}") # 截图记录,双重保障 self.driver.save_screenshot("search_result01.png") # 断言,search_result01.text 获取搜索结果的文本信息,预期结果search_content01 assert search_result01.text == search_content01
复制代码

5、page source 记录

  1. 使用 page_source 属性获取页面源码

  2. 在调试过程中,如果有找不到元素的错误可以保存当时的 page_source 调试代码

from selenium.webdriver.common.by import Byfrom webui.window_frame.base import Base
# 继承Base类,Base类里有前置和后置操作class TestDataRecord(Base): def test_page_source_data_record(self): """获取page_source主要用于调试,可以不需要断言等其余信息""" # 现象:产生了no such element的错误 # 方案:在报错的代码行之前打印page_source,确认定位的元素没有问题 content = "python语言" # 打开网页 self.driver.get("https://www.sogou.com/") # # 定位输入框,输入内容 # self.driver.find_element(By.ID, "query").send_keys(content) # 获取page_source,即获取的是这个网页的源代码 # logger.debug(self.driver.page_source) # 可以使用debug查看输出的内容 # 将获取的page_source写入到record.html with open("record.html", "w", encoding="utf-8") as f: f.write(self.driver.page_source) # 错误定位输入框 self.driver.find_element(By.ID, "query1").send_keys(content)
复制代码




点此获取更多相关资料

发布于: 刚刚阅读数: 4
用户头像

测试人

关注

专注于软件测试开发 2022-08-29 加入

霍格沃兹测试开发学社,测试人社区:https://ceshiren.com/t/topic/22284

评论

发布
暂无评论
软件测试/测试开发丨用户端Web自动化测试学习笔记_Python_测试人_InfoQ写作社区