写点什么

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

作者:测试人
  • 2023-05-28
    北京
  • 本文字数:9225 字

    阅读完需:约 30 分钟

获取更多相关知识

本文为霍格沃兹测试开发学社学员学习笔记分享,文末附原文链接

一、Web 自动化测试复用浏览器

1、复用浏览器简介



2、为什么要复用浏览器的

1)自动化测试过程中,存在人为介入场景 2)提高调试 UI 自动化测试脚本效率

3、复用已有浏览器-配置步骤

1)需要退出当前所有的谷歌浏览器(特别注意)2)输入启动命令,通过命令启动谷歌浏览器    ①找到 chrome 的启动路径    ②配置环境变量3)验证是否启动成功    windows:chrome --remote-debugging-port=9222    mac:Google Chrome --remote-debugging-port=9222    (执行后,打开浏览器,输入localhost:9222)验证是否启动成功
复制代码

4、windows 环境变量配置

1)获取启动路径


2)配置环境变量


3)重启命令行


4)windows 关闭谷歌浏览器进程(打开任务管理器)


5、Mac 环境变量配置

1)获取启动路径(注意:使用 tab 键,不要手动输入)2)将启动路径配置到环境变量中
举例:export PATH=$PATH:/Applications/Google\ Chrome.app/Contents/MacOS
复制代码

6、复用已有浏览器-代码设置(Python)

from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.common.by import By
# 定义配置的实例对象optionoption = Options()# 修改实例属性为 debug 模式启动的 ip + 窗口option.debugger_address = "localhost:9222"# 实例化 driver的时候,添加option配置driver = webdriver.Chrome(options=option)# driver.get("https://work.weixin.qq.com/wework_admin/frame") # ①# 扫码登录之后,点击添加学员操作# driver.find_element(By.XPATH, "//*[text()='添加成员']").click() # ②driver.find_element(By.ID, "username").send_keys("username001") # 输入姓名driver.find_element(By.ID, "memberAdd_english_name").send_keys("othername") # 输入别名
复制代码

二、Web 自动化测试之 Cookie 登录

1、cookie 是什么

1)Cookie 是一些认证数据信息,存储在电脑的浏览器上2)当 web 服务器向浏览器发送 web 页面时,在连接关闭后,服务端不会记录用户的信息
复制代码

2、为什么要使用 Cookie 自动化登录

1)复用浏览器仍然在每次用例开始都需要人为介入2)若用例需要经常执行,复用浏览器则不是一个好的选择3)大部分cookie的时效性都很长,扫一次可以使用多次
复制代码



3、常见问题,企业微信为例:

1)企业微信cookie有互踢机制。在获取cookie成功之后。不需要再进行扫码操作2)获取cookie的时候,即执行代码获取cookie时,一定要确保已经登录3)植入cookie之后需要进入登录页面,刷新验证是否自动登录成功。
复制代码

4、python 语言

获取 cookie driver.get_cookies()添加 cookie driver.add_cookie(cookie)
复制代码


import timeimport yamlfrom selenium import webdriver
class TestCookieLogin:
def setup_class(self): self.driver = webdriver.Chrome()
# 获取cookie def test_get_cookies(self): # 登录企业微信页面,先登录再获取cookie self.driver.get("https://work.weixin.qq.com/wework_admin/frame#contacts") time.sleep(20) # 等待20秒,人工扫码登录 # 登录成功后获取cookie cookie = self.driver.get_cookies() # 将cookie写入一个可持久存储的地方,如数据库、文件 # 打开文件时添加写权限 with open("cookie.yaml", "w") as f: # 第一个参数是需要写入的数据 yaml.safe_dump(cookie, f)
# cookie植入 def test_add_cookies(self): # 访问企业微信主页面 self.driver.get("https://work.weixin.qq.com/wework_admin/frame#contacts") # 定义cookie,cookie信息从已经写入的cookie文件中获取 cookies = yaml.safe_load(open("cookie.yaml")) # 植入cookie for c in cookies: self.driver.add_cookie(c) time.sleep(3) # 强制等待 # 再次访问企业微信页面 self.driver.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
复制代码

三、pageobject 设计模式

1、page object 模式简介

PO设计模式|selenium:https://www.selenium.dev/zh-cn/documentation/test_practices/encouraged/page_object_models/
PageObject:https://martinfowler.com/bliki/PageObject.html
复制代码

2、传统 UI 自动化的问题

1)无法适应 UI 频繁变化2)无法清晰表达业务用例场景3)大量的样板代码 driver/find/click
复制代码

3、POM 模式的优势

1)降低 UI 变化导致的测试用例脆弱性问题2)让用例清晰明朗,与具体实现无关
复制代码

4、POM 建模原则

1)字段意义    ①不要暴露页面内部的元素给外部    ②不需要建模 UI 内的所有元素2)方法意义    ①用公共方法代表 UI 所提供的功能    ②方法应该返回其他的 PageObject 或者返回用于断言的数据    ③同样的行为不同的结果可以建模为不同的方法    ④不要在方法内加断言

复制代码

5、POM 使用方法

1)把元素信息和操作细节封装到 PageObject 类中2)根据业务逻辑,在测试用例中链式调用
复制代码


"""代码示例"""# test_search.py# 传统线性脚本,传统测试用例from selenium import webdriverfrom selenium.webdriver.common.by import By
class TestSearch:
def test_search(self):
# 初始化浏览器 self.driver = webdriver.Chrome() self.driver.get("https://xueqiu.com/") self.driver.implicitly_wait(3)
# 输入搜索关键词 self.driver.find_element(By.NAME, "q").send_keys("阿里巴巴-SW") # 点击搜索按钮 self.driver.find_element(By.CSS_SELECTOR, "i.search").click() # 获取搜索结果 name = self.driver.find_element(By.XPATH, "//table//strong").text # 断言 assert name == "阿里巴巴-SW"

# 脚本优化——>POM 脚本search_page.pyfrom selenium import webdriverfrom selenium.webdriver.common.by import By
class SearchPage:
__INPUT_SEARCH = (By.NAME, "q") __BUTTON_SEARCH = (By.CSS_SELECTOR, "i.search") __SPAN_STOCK = (By.XPATH, "//table//strong")
def __init__(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(3) self.driver.get("https://xueqiu.com/")
def search_stock(self, stock_name: str): self.driver.find_element(*self.__INPUT_SEARCH).send_keys(stock_name) self.driver.find_element(*self.__BUTTON_SEARCH).click() name = self.driver.find_element(By.XPATH, "//table//strong").text return name
# PO 模式测试用例 test_search_pom.pyfrom webui.pageobject.search_page import SearchPage
class TestSearchPom:
def test_search_pom(self):
text = SearchPage().search_stock("阿里巴巴-SW")
# 断言 assert "阿里巴巴-SW" == text
复制代码

四、异常自动截图

1、异常截图的目的(使用场景)

1)增加自动化测试代码的可测性2)丰富报告
复制代码

2、异常截图实现原理

1)装饰器2)自动化关键数据记录:截图、日志、page_source
复制代码


import timeimport allurefrom selenium import webdriverfrom selenium.webdriver.common.by import By

"""问题:异常捕获处理代码和业务代码无关,不能耦合,如有多条用例就要写多个异常捕获 解决:使用装饰器装饰用例或者相关方法,就不会体现在源码中了"""

# 装饰器的外函数需要一个形参代表函数对象# 问题:需要通过driver实例截图和打印page_source,装饰器需要先去获取driver实例对象# 解决:def ui_exception_record(func): def inner(*args, **kwargs): # 内函数 # 获取被装饰方法的self,如def test_baidu(self)方法中的self,即实例对象 # 通过self就可以拿到声明的实例变量 # 前提条件,被装饰的方法是一个实例方法,实例需要有实例变量self.driver # 注意:要保证使用装饰器时,driver已经声明,在用例类里面的前置操作对self.driver声明,也可以将driver = args[0].driver放在except Exception里面的第一行 # driver = args[0].driver try: # 当被装饰方法/函数发生异常时就捕获并做数据记录 return func(*args, **kwargs) # 这里会执行被装饰函数 except Exception: driver = args[0].driver # 出现异常时的处理,截图操作 timestamp = int(time.time()) # 获取当前时间的时间戳 # 提前创建好image和pagesource路径 image_path = f"./image/image_{timestamp}.PNG" page_source_path = f"./page_source/page_source_{timestamp}.html " # 截图 driver.save_screenshot(image_path) # 记录page_source,将获取的page_source写入到record.html with open(page_source_path, "w", encoding="utf-8") as f: f.write( driver.page_source) # 将截图放到报告中 allure.attach.file(image_path, name="picture", attachment_type=allure.attachment_type.PNG) # 将pagesource记录放到报告中 # 想要html的源码可以将attachment_type=allure.attachment_type.TEXT allure.attach.file(page_source_path, name="pagesource", attachment_type=allure.attachment_type.TEXT) # 问题1:异常处理会影响用例本身的结果,如本来就是有异常的用例,却通过了 # 解决:在exception之后再把异常抛出 raise Exception return inner # 返回内函数,不需要加括号

# 问题2:异常捕获处理代码和业务代码无关,不能耦合,如有多条用例就要写多个异常捕获# 解决:使用装饰器装饰用例或者相关方法,就不会体现在源码中了class TestBaidu: # 前置操作 def setup(self): self.driver = webdriver.Chrome() # driver声明
# 后置操作 def teardown(self): self.driver.quit()
# 如果装饰器中的try没有return,被装饰方法有返回值时,会丢失返回值,其他方法调用就没有返回值 # @ui_exception_record # def find(self): # return self.driver.find_element(By.ID, "su") # 返回find_element对象
@ui_exception_record # 调用上面的装饰器 def test_baidu(self): self.driver.get("https://www.baidu.com/") # self.find().click() # 调用find()方法的返回对象,并点击 self.driver.find_element(By.ID, "su").click() # 定位"百度一下"按钮
复制代码





五、测试用例流程设计

1、自动化测试用例流程


2、web 自动化测试-用例设计

【类型:框架对应:作用】前置:setup_class/BeforeAll:准备测试数据、实例的初始化setup/BeforeEach:恢复用例初始状态、数据清理(也可以在用例级别完成)后置:teardown_class/AfterAll:driver进程退出teardown/AfterEach:恢复用例初始状态、数据清理(也可以在用例级别完成)
复制代码

3、恢复用例初始状态

1)场景:单条用例执行完成之后如果不恢复下一条用例的开始状态(回复用例初始页面),则会影响下一条用例的执行。2)解决方案:    ①每条用例执行完成都quit()(影响执行效率)    ②封装一个方法,用例执行完成之后回到首页
例子:1)用例1 执行过程经过A->B->C 三个页面2)用例2 执行过程经过A->B->C 三个页面3)用例1 执行完成之后执行用例2
"""title 不恢复初始状态"""participant 页面A as p1participant 页面B as p2participant 页面C as p3
p1 -> p2: [用例1]步骤1p2 -> p3: [用例1]步骤2p3 x-> p2: [用例2]步骤1
复制代码



"""title 恢复初始状态"""participant 页面A as p1participant 页面B as p2participant 页面C as p3
p1 -> p2: [用例1]步骤1p2 -> p3: [用例1]步骤2p3 -> p1: 前置或后置步骤恢复到初始页面p1 -> p2: [用例2]步骤1p2 -> p3: [用例2]步骤2
复制代码



4、数据清理

1)清理策略    ①在前置处理中执行    ②在后置处理中执行2)清理方式    ①调用业务接口    ②通过UI自动化方式操作    ③连接数据库执行SQL(不推荐)

复制代码

六、selenium 多浏览器处理

1、多浏览器测试概述

1)是跨不同浏览器组合验证网站或 web 应用程序功能的过程2)是兼容性测试的一个分支,用于保持功能和质量的一致性3)适用于面向客户的网站和组织内部使用的站点
复制代码

2、多浏览器的实现方案


3、pytest hook 函数

1)pytest_addoption 添加命令行参数组/命令行参数2)pytest_configure 解析命令行选项,每个插件都会用到这个hook函数
复制代码

4、pytest_addoption 与 pytest_configure

(1)pytest_addoption:    1)parser.getgroup 创建/获取组名    2)addoption 添加一个命令行选项(2)pytest_configure:    1)通过config 对象的getoption()方法获取命令行参数    2)将命令行获取到的内容赋值给变量
复制代码


conftest.py文件代码from _pytest.config import Configfrom _pytest.config.argparsing import Parser

web_env = {}

# 实现命令行注册,解决自定义参数报错的问题# 形参parser,想要使用parser对象的方法和属性,需要添加类型提示parser:Parser,指向Parser,需要导入def pytest_addoption(parser: Parser): # 注册一个命令行组 hello = parser.getgroup("hello") # 注册一个命令行参数,第一个参数为指定的命令行的参数形式 # 如 pytest ./test_demo.py --browser=chrome # pytest ./test_demo.py --driver=chrome # default默认的浏览器,dest重命名,将--browser更改为browser,help hello.addoption("--browser", default="firefox", dest="browser", help="指定执行到浏览器")

def pytest_configure(config: Config): # config是一个对象 browser = config.getoption("browser") # 获取的是pytest_addoption方法中hello.addoption中的"--browser" print(f"通过命令行获取到浏览器为{browser}") web_env["browser"] = browser # 将命令行获取到的内容赋值给变量web_env

#测试用例文件test_browser.pyfrom selenium import webdriverfrom webui.test_muliti_browser.conftest import web_env

class TestBrowser:
def setup_class(self): # self.browser = web_env.get("browser")
def test_browser(self): print(f"获取到的浏览器信息为{self.browser}") if self.browser == "firefox": self.driver = webdriver.Firefox() else: self.driver = webdriver.Chrome() self.driver.get("https://ceshiren.com/") self.driver.quit()
复制代码



5、命令行参数处理

1)通过 pytest_addoption hook 函数,配置命令行参数2)通过 pytest_configure 函数,接收命令行参数信息3)将参数保存到变量中
复制代码

七、执行 javascript 脚本

1、JavaScript 简介

1)JavaScript 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言2)可以嵌入到HTML页面对浏览器事件做出响应3)也可以基于Node.js技术进行服务器端编程
复制代码

2、自动化测试中使用 JavaScript 脚本

使用场景:部分场景使用selenium原生方法无法解决,如修改时间控件、滚动到某个元素、其他场景等
复制代码

3、JavaScript 调试方法

1)进入 console 调试2)js 脚本如果有返回值则会在浏览器返回
复制代码

4、JS 脚本-元素操作

1)通过 css 查找元素    ①点击元素(对应click)    ②input标签对应的值(对应send_keys)    ③元素的类属性    ④元素的文本属性
获取属性的值.value,如获取百度输入框的值,元素获取采用css表达式document.querySelector("#kw").value
修改属性的值,如修改输入框内的值为"python语言",修改的值必须是input标签对应的值document.querySelector("#kw").value="python语言"
# 修改时间控件的值document.querySelector("#train_date").value="2023-05-23"
点击按钮document.querySelector("#su").click()
输入内容并点击搜索按钮,两个表达式使用分号隔开document.querySelector("#kw").value="go语言";document.querySelector("#su").click()
定位“我的淘宝”元素document.querySelector("#J_SiteNavMytaobao")
获取“我的淘宝”下拉框的悬停类属性值document.querySelector("#J_SiteNavMytaobao").className
修改类属性的值,达到悬停效果document.querySelector("#J_SiteNavMytaobao").className="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover"
获取元素内的文本信息document.querySelector("#ember90").innerText
复制代码






5、JS 脚本滚动操作

1)页面滚动到底部document.documentElement.scrollTop="10000"
2)指定到滚动的位置document.querySelector('[data-topic-id="22037"]').scrollIntoView()
复制代码

6、Selenium 执行 js

1)Selenium执行js    ①调用执行js方法    ②在 js 语句中添加 return:代码可以获取js的执行结果    ③结合 find_element 方法
复制代码


import timefrom selenium import webdriverfrom selenium.webdriver.common.by import By
"""案例一:通过属性修改展示下拉框打开淘宝首页https://www.taobao.com/修改下拉框属性点击悬浮框内的选项"""

def test_taobao_js(): driver = webdriver.Chrome() # 打开淘宝地址 driver.get("https://www.taobao.com/") driver.implicitly_wait(3) # 隐式等待
# 修改类属性值,使得"我的淘宝"下拉框悬停 driver.execute_script('document.querySelector("#J_SiteNavMytaobao").className="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover"') # 定位到下拉选项"已买到的宝贝",并点击 driver.find_element(By.XPATH, "//*[text()='已买到的宝贝']").click() time.sleep(5) driver.quit()

"""案例二:通过属性修改时间控件的值打开12306网站修改时间控件值打印出发日期"""

def test_12306_js(): driver = webdriver.Chrome() # 打开淘宝地址 driver.get("https://www.12306.cn/index/") driver.implicitly_wait(3) # 隐式等待 # 修改时间控件的信息 driver.execute_script('document.querySelector("#train_date").value="2023-05-23"') # 获取时间控件的信息并返回所选日期的值给到date_data date_data = driver.execute_script('return document.querySelector("#train_date").value') # 打印出发日期 print(f"获取的时间信息为{date_data}")
复制代码

八、headless 无头浏览器使用

1、Options 概述

1)是一个配置浏览器启动的选项类,用于自定义和配置Driver会话2)常见使用场景:    ①设置无头模式:不会显示调用浏览器,避免人为干扰的问题。    ②设置调试模式:调试自动化测试代码(浏览器复用)

复制代码

2、添加启动配置(arguments)

1)无头模式: --headless2)窗体最大化 start-maximized3)指定浏览器分辨率 window-size=1920x3000
复制代码


from selenium import webdriverfrom selenium.webdriver.common.by import By
def test_options(): # 在实例化driver对象之前,需要先定义好配置信息 options = webdriver.ChromeOptions() # 在浏览器启动之前,就配置完成,窗口最大化的配置 options.add_argument("start-maximized") # 指定浏览器分辨率 options.add_argument("window-size=1920x3000") # 无头模式,浏览器不会显示的启动在机器上 options.add_argument("--headless") # 实例化一个driver对象,注意:配置对象option 要通过chrome_options参数添加 driver = webdriver.Chrome(chrome_options=options) driver.get("https://ceshiren.com/") # 获取登录按钮的文本信息 login_text = driver.find_element(By.CSS_SELECTOR, ".login-button").text print(login_text) driver.quit()
复制代码

九、capability 配置参数解析与分布式运行

1、capability 概述

1)Capabilities是WebDriver支持的标准命令之外的扩展命令(配置信息)2)配置web驱动的属性,如浏览器名称、浏览器平台等。3)结合Selenium Grid完成分布式、兼容性等测试4)官网地址: https://www.selenium.dev/zh-cn/documentation/webdriver/capabilities/shared/
复制代码


from selenium import webdriverfrom selenium.webdriver.common.by import By
def test_ceshiren(): # 本机是Mac,切换成 windows 就会报错 capabilities = {"browserName": "chrome", "platformName": "mac"} # 通过 desired_capabilities 添加配置信息 driver = webdriver.Chrome(desired_capabilities=capabilities) driver.implicitly_wait(5) # 隐式等待 driver.get("https://ceshiren.com/") driver.quit()
复制代码

2、Selenium Grid

1)Selenium Grid 允许我们在多台机器上并行运行测试,并集中管理不同的浏览器版本和浏览器配置(而不是在每个单独的测试中)。2)官网地址:https://www.selenium.dev/documentation/grid/
复制代码



3)保证本地可以正常调通4)实例化Remote()类并添加相应的配置:远程地址、设备配置
复制代码


from selenium import webdriverfrom selenium.webdriver.common.by import By
def test_ceshiren_grid(): # 1.本地可以调通 # 2.切换为webdriver.Remote # driver = webdriver.Chrome() # 主控hub的地址:域名+/wd/hub executor_url = "https://selenium-node.hogwarts.ceshiren.com/wd/hub" # capabilities = {"browserName": "chrome", "browserVersion": "100.0"} capabilities = {"browserName": "firefox"} # 不需要版本 # 实例化Remote,获取可以远程控制的driver实例对象 # 通过 command_executor 配置selenium hub地址 # 通过 desired_capabilities 添加配置信息 driver = webdriver.Remote(command_executor=executor_url, desired_capabilities=capabilities) driver.implicitly_wait(5) # 隐式等待 driver.get("https://ceshiren.com/") login_text = driver.find_element(By.CSS_SELECTOR, ".login-button").textprint(login_text)
复制代码


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

发布于: 22 小时前阅读数: 2
用户头像

测试人

关注

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

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

评论

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