web自动化测试 - 可乔

什么时候可以做UI自动化测试

  • 业务流程不频繁改动
  • UI 元素不频繁改动
  • 需要频繁回归的场景
  • 核心场景等

Driver的下载与配置步骤

  1. 下载浏览器对应的driver,谷歌浏览器和火狐浏览器选一个即可。
  1. 配置 driver 的环境变量
  2. 重启命令行工具,验证是否配置成功

1. chromedriver的下载-Windows

  • chromedriver的下载
    • 确定与浏览器匹配的driver版本
    • 根据使用的操作系统下载相应的 chromedriver
  • 淘宝镜像:

2. chromedriver 环境变量配置-Windows

3. 验证配置chromedriver是否成功-Windows

  • 重启命令行,输入:chromedriver --version
  • 出现chromedriver版本信息则配置成功

geckodriver 配置-Windows

  • 下载geckodriver。
    • 没有chromedriver类似的对应关系,默认下载最新的driver版本
    • 根据使用的操作系统下载相应的 geckodriver
    • 淘宝镜像:CNPM Binaries Mirror
  • 配置步骤与谷歌浏览器的相同。
  • 输入geckodriver --version,验证是否配置成功。

在 python 中的使用

# 导入selenium 包
from selenium import webdriver

# 创建一个 Chromdriver 的实例。Chrome()会从环境变量中寻找浏览器驱动
driver = webdriver.Chrome()
# 打开网址
driver.get("https://www.baidu.com/")
# 关闭driver
driver.quit()
  • 火狐浏览器演示
# 导入selenium 包
from selenium import webdriver

# 创建一个 Geckodriver 的实例。Firefox()会从环境变量中寻找浏览器驱动
driver = webdriver.Firefox()
# 打开网址
driver.get("https://www.baidu.com/")
# 关闭driver
driver.quit()

用例结构对比

自动化测试用例	作用

用例标题 测试包、文件、类、方法名称 用例的唯一标识
前提条件 setup、setup_class(Pytest);
BeforeEach、BeforeAll(JUnit) 测试用例前的准备动作,比如读取数据或者driver的初始化
用例步骤 测试方法内的代码逻辑 测试用例具体的步骤行为
预期结果 assert 实际结果 = 预期结果 断言,印证用例是否执行成功
实际结果 assert 实际结果 = 预期结果 断言,印证用例是否执行成功
后置动作 teardown、teardown_class(Pytest);
@AfterEach@AfterAll(JUnit) 脏数据清理、关闭driver进程

脚本优化

import time
from selenium import webdriver
from selenium.webdriver.common.by import By


class TestDemo01():
    def setup_method(self, method):
        # 实例化chromedriver
        self.driver = webdriver.Chrome()
        # 添加全局隐式等待
        self.driver.implicitly_wait(5)

    def teardown_method(self, method):
        # 关闭driver
        self.driver.quit()

    def test_demo01(self):
        # 访问网站
        self.driver.get("https://www.baidu.com/")
        # 设置窗口
        self.driver.set_window_size(1330, 718)
        # 点击输入框
        self.driver.find_element(By.ID, "kw").click()
        # 输入框输入信息
        self.driver.find_element(By.ID, "kw").send_keys("霍格沃兹测试开发")
        # 点击搜索按钮
        self.driver.find_element(By.ID, "su").click()
        # 等待界面加载
        time.sleep(5)
        # 元素定位后获取文本信息
        res = self.driver.find_element(By.XPATH, "//*[@id='1']/h3/a").get_attribute("text")
        # 打印文本信息
        print(res)
        # 添加断言
        assert "霍格沃兹测试开发" in res
        # 查看界面展示
        time.sleep(5)

web 浏览器控制

浏览器控制

  • 模拟功能测试中对浏览器的操作
    ||操作|使用场景|
    | — | — | — |
    |get|打开浏览器|web自动化测试第一步|
    |refresh|浏览器刷新|模拟浏览器刷新|
    |back|浏览器退回|模拟退回步骤|
    |maximize_window|最大化浏览器|模拟浏览器最大化|
    |minimize_window|最小化浏览器|模拟浏览器最小化|

打开网页

  • get方法打开浏览器
from selenium import webdriver
import time

def window_start():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)

if __name__ == '__main__':
    window_start()

刷新

  • refresh方法刷新页面
from selenium import webdriver
import time

def window_refresh():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)
    # 刷新网页
    driver.refresh()
    # 等待一秒
    time.sleep(1)

if __name__ == '__main__':
    window_refresh()

回退

from selenium import webdriver
import time
from selenium.webdriver.common.by import By

def window_back():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)
    driver.find_element(By.XPATH,"//*[@id='ember35']").click()
    # 等待一秒
    time.sleep(1)
    # 返回上一个界面
    driver.back()
    # 等待一秒
    time.sleep(1)

if __name__ == '__main__':
    window_back()

最大化

  • maximize_window方法使窗口最大化
def max_window():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)
    # 屏幕最大化
    driver.maximize_window()
    # 等待一秒
    time.sleep(1)

if __name__ == '__main__':
    max_window()

最小化

  • minimize_window方法使窗口最小化
from selenium import webdriver
import time

def min_window():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)
    # 屏幕最小化
    driver.minimize_window()
    # 等待一秒
    time.sleep(1)

if __name__ == '__main__':
    min_window()

常见控件定位方法

HTML铺垫

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试人论坛</title>
</head>
<body>
<a href="https://ceshiren.com/" class="link">链接</a>
</body>
</html>
  • 标签:<a>
  • 属性:href
  • 类属性: class

Selenium定位方式

Selenium提供了八种定位方式:Locator strategies | Selenium

方式 描述
class name class 属性对应的值
css selector(重点) css 表达式
id(重点) id 属性对应的值
name(重点) name 属性对应的值
link text 查找其可见文本与搜索值匹配的锚元素
partial link text 查找其可见文本包含搜索值的锚元素。如果多个元素匹配,则只会选择第一个元素。
tag name 标签名称
xpath(重点) xpath表达式

selenium 常用定位方式

# 示例,两种方式作用一模一样
# 官方建议使用下面的方式
driver.find_element_by_id("su")
driver.find_element(By.ID, "su") 

通过 id 定位

  • 格式: driver.find_element(By.ID, "ID属性对应的值")
from selenium import webdriver
import time
from selenium.webdriver.common.by import By

def id_position_method():

    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.ceshiren.com')
    # 等待一秒
    time.sleep(1)
    # 点击“精华帖”
    driver.find_element(By.ID,"ember35").click()
    # 等待两秒
    time.sleep(2)

if __name__ == '__main__':
    id_position_method()

name 定位

  • 格式: driver.find_element(By.NAME, "Name属性对应的值")
from selenium import webdriver
import time
from selenium.webdriver.common.by import By

def name_position_method():

    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 打开网站
    driver.get('http://www.baidu.com')
    # 等待一秒
    time.sleep(1)
    # 点击更多
    driver.find_element(By.NAME,"tj_briicon").click()
    # 等待两秒
    time.sleep(2)

if __name__ == '__main__':
    name_position_method()

css selector 定位

  • 格式: driver.find_element(By.CSS_SELECTOR, "css表达式")
  • 复制绝对定位
  • 编写 css selector 表达式(后面章节详细讲解)

xpath 定位

  • 格式: driver.find_element(By.XPATH, "xpath表达式")
  • 复制绝对定位
  • 编写 xpath 表达式(后面章节详细讲解)

link 定位

  • 格式:
    • driver.find_element(By.LINK_TEXT,"文本信息")

强制等待与隐式等待

为什么要添加等待

  • 避免页面未渲染完成后操作,导致的报错
from selenium import webdriver
from selenium.webdriver.common.by import By

def wait_sleep():
    """
    如果直接执行,不添加任何等待,可能会报错
    """
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/")
    # 不加等待,可能会因为网速等原因产生报错
    driver.find_element(By.XPATH, "//*[text()='个人中心']")

if __name__ == '__main__':
    wait_sleep()

直接等待

  • 解决方案:在报错的元素操作之前添加等待
  • 原理:强制等待,线程休眠一定时间
  • 演练环境:https://vip.ceshiren.com/
  • time.sleep(3)

隐式等待

  • 问题:难以确定元素加载的具体等待时间。
  • 解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置。
  • 演练环境:https://vip.ceshiren.com/
  • 原理:设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常

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

隐式等待无法解决的问题

元素可以找到,使用点击等操作,出现报错
原因:
页面元素加载是异步加载过程,通常html会先加载完成,js、css其后
元素存在与否是由HTML决定,元素的交互是由css或者js决定
隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互
解决方案:使用显式等待

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

  • 示例: WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
  • 原理:在最长等待时间内,轮询,是否满足结束条件
  • 演练环境: 霍格沃兹测试开发
  • 注意:在初级时期,先关注使用
def wait_until():
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/#/ui_study")
    WebDriverWait(driver, 10).until(
        expected_conditions.element_to_be_clickable(
            (By.CSS_SELECTOR, '#success_btn')))
    driver.find_element(By.CSS_SELECTOR, "#success_btn").click()

总结

类型 使用方式 原理 适用场景
直接等待 time.sleep(等待时间)) 强制线程等待 调试代码,临时性添加
隐式等待 driver.implicitly_wait(等待时间) 在时间范围内,轮询查找元素 解决找不到元素问题,无法解决交互问题
显式等待 WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件) 设定特定的等待条件,轮询操作 解决特定条件下的等待问题,比如点击等交互性行为

常见控件交互方法

点击,输入,清空

  • 点击百度搜索框
  • 输入”霍格沃兹测试开发”
  • 清空搜索框中信息
  • 演练地址: https://www.baidu.com/
# 点击百度搜索框
driver.find_element(By.ID,"kw").click()
# 输入"霍格沃兹测试开发"
driver.find_element(By.ID,"kw").send_keys("霍格沃兹测试开发")
# 清空搜索框中信息
driver.find_element(By.ID,"kw").clear()

获取元素属性信息

  • 原因:
    • 定位到元素后,获取元素的文本信息,属性信息等
  • 目的:
    • 根据这些信息进行断言或者调试
  • 演练地址: 霍格沃兹测试开发

获取元素属性信息的方法

  • 获取元素文本
  • 获取元素的属性(html的属性值)

获取元素文本

driver.find_element(By.ID, “id”).text

获取这个元素的name属性的值

driver.find_element(By.ID, “id”).get_attribute(“name”)

自动化测试定位策略

  • 不知道应该使用哪种定位方式?
  • 元素定位不到无法解决?

选择定位器通用原则

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

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

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

  • xpath,css
  • 属性动态变化(id,text)
  • 重复元素属性(id,text,class)
  • 父子定位(子定位父)
  1. js定位

Web 弹框定位

  • 场景
    • web 页面 alert 弹框
  • 解决:
    • web 需要使用 driver.switchTo().alert() 处理

下拉框/日期控件定位

  • 场景:
    • <input>标签组合的下拉框无法定位
    • <input>标签组合的日期控件无法定位
  • 解决:
    • 面对这些元素,我们可以引入 JS 注入技术来解决问题。

文件上传定位

  • 场景:
    • input 标签文件上传
  • 解决:
    • input 标签直接使用 send_keys()方法

高级定位-css

css 定位场景

  • 支持web产品
  • 支持app端的webview

css 相对定位的优点

  • 可维护性更强
  • 语法更加简洁
  • 解决各种复杂的定位场景
# 绝对定位
$("#ember63 > td.main-link.clearfix.topic-list-data > span > span > a")
# 相对定位
$("#ember63 [title='新话题']")

css 定位的调试方法

  • 进入浏览器的console
  • 输入:
    • $("css表达式")
    • 或者$("css表达式")

css基础语法

类型 表达式
标签 标签名
.class属性值
ID #id属性值
属性 [属性名=‘属性值’]
//在console中的写法
// https://www.baidu.com/
//标签名
$('input')
//.类属性值
$('.s_ipt')
//#id属性值
$('#kw')
//[属性名='属性值']
$('[name="wd"]')

css关系定位

类型 格式
并集 元素,元素
邻近兄弟(了解即可) 元素+元素
兄弟(了解即可) 元素1~元素2
父子 元素>元素
后代 元素 元素
//在console中的写法
//元素,元素
$('.bg,.s_ipt_wr,.new-pmd,.quickdelete-wrap')
//元素>元素
$('#s_kw_wrap>input')
//元素 元素
$('#form input')
//元素+元素,了解即可
$('.soutu-btn+input')
//元素1~元素2,了解即可
$('.soutu-btn~i')

css 顺序关系

类型 格式
父子关系+顺序 元素 元素
父子关系+标签类型+顺序 元素 元素
//:nth-child(n)
$('#form>input:nth-child(2)')
//:nth-of-type(n)
$('#form>input:nth-of-type(1)')

高级定位-xpath

xpath 定位场景

  • web自动化测试
  • app自动化测试

xpath 相对定位的优点

  • 可维护性更强
  • 语法更加简洁
  • 相比于css可以支持更多的方式
# 复制的绝对定位
$x('//*[@id="ember75"]/td[1]/span/a')
# 编写的相对行为
$x("//*[text()='技术分享 | SeleniumIDE用例录制']")

xpath 定位的调试方法

  • 浏览器-console
    • $x("xpath表达式")
  • 浏览器-elements
    • ctrl+f 输入xpath或者css

xpath 基础语法(包含关系)

表达式 结果
/ 从该节点的子元素选取
// 从该节点的子孙元素选取
* 通配符
nodename 选取此节点的所有子节点
选取当前节点的父节点
@ 选取属性

整个页面

$x(“/”)

页面中的所有的子元素

$x(“/*”)

整个页面中的所有元素

$x(“//*”)

查找页面上面所有的div标签节点

$x(“//div”)

查找id属性为site-logo的节点

$x(‘//*[@id=“site-logo”]’)

查找节点的父节点

$x(‘//*[@id=“site-logo”]/…’)

xpath 顺序关系(索引)

  • xpath通过索引直接获取对应元素

获取此节点下的所有的li元素

$x(“//*[@id=‘ember21’]//li”)

获取此节点下【所有的节点的】第一个li元素

$x(“//*[@id=‘ember21’]//li[1]”)

xpath 高级用法

  • [last()]: 选取最后一个
  • [@属性名='属性值' and @属性名='属性值']: 与关系
  • [@属性名='属性值' or @属性名='属性值']: 或关系
  • [text()='文本信息']: 根据文本信息定位
  • [contains(text(),'文本信息')]: 根据文本信息包含定位
  • 注意:所有的表达式需要和[]结合
# 选取最后一个input标签
//input[last()]
# 选取属性name的值为passward并且属性pwd的值为123456的input标签
//input[@name='passward' and @pwd='123456']
# 选取属性name的值为passward或属性pwd的值为123456的input标签
//input[@name='passward' or @pwd='123456']
# 选取所有文本信息为'霍格沃兹测试开发'的元素
//*[text()='霍格沃兹测试开发']
# 选取所有文本信息包'霍格沃兹'的元素
//*[contains(text(),'霍格沃兹')]

显式等待高级使用

显示等待原理

  • 在代码中定义等待一定条件发生后再进一步执行代码
  • 最长等待时间循环执行结束条件的函数
  • WebDriverWait(driver 实例, 最长等待时间, 轮询时间).until(结束条件函数)

image

显示等待-expected_conditions

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait


def wait_until():
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/#/ui_study")
    WebDriverWait(driver, 10).until(
        expected_conditions.element_to_be_clickable(
            (By.CSS_SELECTOR, '#success_btn')))
    driver.find_element(By.CSS_SELECTOR, "#success_btn").click()

常见 expected_conditions

类型 示例方法 说明
element element_to_be_clickable()
visibility_of_element_located() 针对于元素,比如判断元素是否可以点击,或者元素是否可见
url url_contains() 针对于 url
title title_is() 针对于标题
frame frame_to_be_available_and_switch_to_it(locator) 针对于 frame
alert alert_is_present() 针对于弹窗

显式等待-封装等待条件

  • 官方的 excepted_conditions 不可能覆盖所有场景
  • 定制封装条件会更加灵活、可控
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from 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")
    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)

高级控件交互方法

使用场景

使用场景 对应事件
复制粘贴 键盘事件
拖动元素到某个位置 鼠标事件
鼠标悬停 鼠标事件
滚动到某个元素 滚动事件
使用触控笔点击 触控笔事件(了解即可)

ActionChains解析

  • 实例化类ActionChains,参数为driver实例。
  • 中间可以有多个操作。
  • .perform()代表确定执行。
ActionChains(self.driver).操作.perform()

键盘事件

  • 按下、释放键盘键位
  • 结合send_keys回车

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

  • ActionChains(self.driver): 实例化ActionChains类
  • key_down(Keys.SHIFT, ele): 按下shift键实现大写
  • send_keys("selenium"): 输入大写的selenium
  • perform(): 确认执行
class TestKeyBoardDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    def test_key_down_up(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")
        ActionChains(self.driver).key_down(Keys.SHIFT, ele).\
        send_keys("selenium").perform()

键盘事件-输入后回车

  • 直接输入回车: 元素.send_keys(Keys.ENTER)
  • 使用ActionChains: key_down(Keys.ENTER)
class TestKeyBoardDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    def test_enter(self):
        # 演练环境
        self.driver.get("https://www.sogou.com/")
        ele = self.driver.find_element(By.ID, "query")
        ele.send_keys("selenium")
        # 第一种方式
        ele.send_keys(Keys.ENTER)
        # 第二种方式
        # ActionChains(self.driver).\
        #     key_down(Keys.ENTER).\
        #     perform()
        time.sleep(3)

键盘事件-复制粘贴

  • 多系统兼容
    • mac 的复制按钮为 COMMAND
    • windows 的复制按钮为 CONTROL
  • 左箭头:Keys.ARROW_LEFT
  • 按下COMMAND或者CONTROL: key_down(cmd_ctrl)
  • 按下剪切与粘贴按钮: send_keys("xvvvvv")
class TestKeyBoardDemo:
    # driver 初始化
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)
    # 关闭driver进程
    def teardown_class(self):
        self.driver.quit()
    # 复制粘贴逻辑
    def test_copy_and_paste(self):
        # 演练环境
        self.driver.get("https://ceshiren.com/")
        cmd_ctrl = Keys.COMMAND if sys.platform == 'darwin' else Keys.CONTROL
        self.driver.find_element(By.ID, "search-button").click()
        ele = self.driver.find_element(By.ID, "search-term")
        # 打开搜索,选择搜索框,输入selenium,剪切后复制,几个v就代表复制几次
        ActionChains(self.driver)\
            .key_down(Keys.SHIFT, ele)\
            .send_keys("Selenium!")\
            .send_keys(Keys.ARROW_LEFT)\
            .key_down(cmd_ctrl)\
            .send_keys("xvvvvv")\
            .key_up(cmd_ctrl)\
            .perform()
        time.sleep(5)

鼠标事件

  • 双击
  • 拖动元素
  • 指定位置(悬浮)

鼠标事件-双击

  • double_click(元素对象): 双击元素
class TestMouseDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    def test_double_click(self):
        # 演练环境
        self.driver.get("https://vip.ceshiren.com/#/ui_study")
        ele = self.driver.find_element(By.ID, "primary_btn")
        ActionChains(self.driver).double_click(ele).perform()
        time.sleep(2)

鼠标事件-拖动元素

  • drag_and_drop(起始元素对象, 结束元素对象): 拖动并放开元素
class TestMouseDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    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')
        item_right = self.driver.find_element(By.CSS_SELECTOR,'#item3')
        ActionChains(self.driver).drag_and_drop(item_left, item_right).perform()
        time.sleep(5)

鼠标事件-悬浮

  • move_to_element(元素对象): 移动到某个元素
class TestMouseDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    def test_hover(self):
        # 演练环境
        self.driver.get("https://vip.ceshiren.com/#/ui_study/action_chains2")
        time.sleep(2)
        title = self.driver.find_element(By.CSS_SELECTOR, '.title')
        ActionChains(self.driver).move_to_element(title).perform()
        options = self.driver.find_element(By.CSS_SELECTOR,'.options>div:nth-child(1)')
        ActionChains(self.driver).click(options).perform()
        time.sleep(5)

滚轮/滚动操作

  • 滚动到元素
  • 根据坐标滚动
  • 注意: selenium 版本需要在 4.2 之后

滚轮/滚动操作-滚动到元素

  • scroll_to_element(WebElement对象):滚动到某个元素
class TestScrollDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()
    
    def test_scoll_to_element(self):
        # 演练环境  
        self.driver.get("https://ceshiren.com/")
        # 4.2 之后才提供这个方法
        ele = self.driver.find_element\
        (By.XPATH, "//*[text()='怎么写高可用集群部署的测试方案?']")
        ActionChains(self.driver).scroll_to_element(ele).perform()
        time.sleep(5)

滚轮/滚动操作-根据坐标滚动

  • scroll_by_amount(横坐标, 纵坐标)
class TestScrollDemo:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)

    def teardown_class(self):
        self.driver.quit()

    def test_scroll_to_amount(self):
        # 演练环境
        self.driver.get("https://ceshiren.com/")
        # 4.2 之后才提供这个方法
        ActionChains(self.driver).scroll_by_amount(0, 10000).perform()
        time.sleep(5)

#多窗⼜处理与⽹页frame

自动化关键数据记录

什么是关键数据

  • 代码的执行日志
  • 代码执行的截图
  • page source(页面源代码)

记录关键数据的作用

内容 作用
日志 1. 记录代码的执行记录,方便复现场景
  1. 可以作为bug依据|
    |截图|1. 断言失败或成功截图
    2.异常截图达到丰富报告的作用
  2. 可以作为bug依据|
    |page source|1. 协助排查报错时元素当时是否存在页面上|

行为日志记录

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

行为日志记录

# 日志配置
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)
# 日志与脚本结合
class TestDataRecord:
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)
    
    def teardown_class(self):
        self.driver.quit()

    def test_log_data_record(self):
        # 实例化self.driver
        search_content = "霍格沃兹测试开发"
        # 打开百度首页
        self.driver.get("https://www.sogou.com/")
        logger.debug("打开搜狗首页")
        # 输入霍格沃兹测试学院
        self.driver.find_element(By.CSS_SELECTOR, "#query").\
        send_keys(search_content)
        logger.debug(f"搜索的内容为{search_content}")
        # 点击搜索
        self.driver.find_element(By.CSS_SELECTOR, "#stb").click()
        # 搜索结果
        search_res = self.driver.find_element(By.CSS_SELECTOR, "em")
        logger.info(f"搜索结果为{search_res.text}")
        assert search_res.text == search_content

步骤截图记录

  • save_screenshot(截图路径+名称)
  • 记录关键页面
    • 断言页面
    • 重要的业务场景页面
    • 容易出错的页面
# 调用save方法截图并保存保存在当前路径下的images文件夹下
driver.save_screenshot('./images/search1.png')

page_source记录

  • 使用page_source属性获取页面源码
  • 在调试过程中,如果有找不到元素的错误可以保存当时的page_source调试代码
# 在报错行前面添加保存page_source的操作
with open("record.html", "w", encoding="u8") as f:
    f.write(self.driver.page_source)

Web自动化测试之复用浏览器

复用浏览器简介

为什么要学习复用浏览器

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

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

  1. 需要退出当前所有的谷歌浏览器(特别注意)
  2. 输入启动命令,通过命令启动谷歌浏览器
  3. 找到 chrome 的启动路径(下一页 ppt)
  4. 配置环境变量(下一页 ppt)
  5. 验证是否启动成功
  • windows:chrome –remote-debugging-port=9222
  • mac:Google Chrome –remote-debugging-port=9222

windows 环境变量配置:1. 获取启动路径

image

windows 环境变量配置:2. 配置环境变量

image

windows 环境变量配置:3. 重启命令行

image

windows关闭谷歌浏览器进程

image

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

from selenium.webdriver.chrome.options import Options
option = Options()
option.debugger_address = "localhost:9222"
self._driver = webdriver.Chrome(options=option)
self._driver.get("https://work.weixin.qq.com/wework_admin/frame")

复用浏览器实战演示1

  • 问题1:企业微信登录无法通过输入用户名密码的方式解决登录问题,后面所有的脚本都无法执行。
  • 解决方案: 通过remote复用浏览器人工登录企业微信。后面的脚本自动执行

复用浏览器实战演示2

  • 问题2:编写自动化测试脚本过程中,用例的某一步骤出现问题。调试时需要将此步骤前面所有的步骤执行完成之后再复现调试。
  • 解决方案:使用复用浏览器的方式跳过前面的自动化执行步骤,轻松复现出问题的场景,只对问题行代码进行调试。

Web自动化测试之Cookie登录

cookie 是什么

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

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

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

思路

image

常见问题

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

代码(Python)

  • 获取 cookie driver.get_cookies()
  • 添加 cookie driver.add_cookie(cookie)
class TestCookieLogin:
    def setup_class(self):
        self.drvier = webdriver.Chrome()

    def test_get_cookies(self):
        # 1. 访问企业微信主页/登录页面
        self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
        # 2. 等待20s,人工扫码操作
        time.sleep(20)
        # 3. 等成功登陆之后,再去获取cookie信息
        cookie = self.drvier.get_cookies()
        # 4. 将cookie存入一个可持久存储的地方,文件
        # 打开文件的时候添加写入权限
        with open("cookie.yaml", "w") as f:
            # 第一个参数是要写入的数据
            yaml.safe_dump(cookie, f)

    def test_add_cookie(self):
        # 1. 访问企业微信主页面
        self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
        # 2. 定义cookie,cookie信息从已经写入的cookie文件中获取
        cookie = yaml.safe_load(open("cookie.yaml"))
        # 3. 植入cookie
        for c in cookie:
            self.drvier.add_cookie(c)
        time.sleep(3)
        # 4.再次访问企业微信页面,发现无需扫码自动登录,而且可以多次使用
        self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")

yaml相关依赖

<properties>
  <!--对应解析-->
  <jackson.version>2.13.1</jackson.version>
</properties>

<dependencies>
  <!--        yaml文件解析-->
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-yaml</artifactId>
      <version>${jackson.version}</version>
  </dependency>
</dependencies>

selenium 多浏览器处理

多浏览器测试概述

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

多浏览器的实现方案

pytest hook 函数

  • pytest_addoption 添加命令行参数组/命令行参数
  • pytest_configure 解析命令行选项,每个插件都会用到这个hook函数

pytest_addoption 与 pytest_configure

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

pytest_addoption 与 pytest_configure代码示例

def pytest_addoption(parser):
    # group 将下面所有的 option都展示在这个group下。
    mygroup = parser.getgroup("hogwarts")  
    # 注册一个命令行选项
    mygroup.addoption("--browser",
     # 参数的默认值  
    default='Chrome',
    # 存储的变量 
    dest='browser',  
    # 参数的描述信息
    help='set your browser, \
    such as:Chrome, Firefox, Headless...'  
     )
def pytest_configure(config):
    browser = config.getoption("--browser", default='Chrome')
    tmp = {"browser": browser}
    global_env.append(tmp)

命令行参数处理

  1. 通过 pytest_addoption hook 函数,配置命令行参数
  2. 通过 pytest_configure 函数,接收命令行参数信息
  3. 将参数保存到变量中
global_env = {}
def pytest_addoption(parser):
    # group 将下面所有的 option都展示在这个group下。
    mygroup = parser.getgroup("hogwarts")
    # 注册一个命令行选项
    mygroup.addoption("--browser",
     # 参数的默认值
    default='Chrome',
    # 存储的变量
    dest='browser',
    # 参数的描述信息
    help='set your browser, \
    such as:Chrome, Firefox, Headless...'
     )

def pytest_configure(config):
    browser = config.getoption("browser", default='Chrome')
    browser_conf = {"browser": browser}
    global_env.update(browser_conf)

测试代码

  • 通过命令行执行测试用例: pytest 测试用例文件.py --browser="浏览器名称"
  • 后续结合 jenkins 实现切换浏览器+持续集成
# test_args.py
from selenium import webdriver
from conftest import global_env
class TestArgs():
    def setup(self):
        self.browser = global_env.get("browser")
        print(self.browser)
        if self.browser == 'Firefox':
            self.driver = webdriver.Firefox(executable_path="/path/to/geckodriver")
        elif self.browser == 'Remote':
            executor_url = "https://selenium-node.hogwarts.ceshiren.com/wd/hub"
            capabilities = {"browserName": "chrome", "browserVersion": "99.0"}
            self.driver = webdriver.Remote(command_executor=executor_url, desired_capabilities=capabilities)
        else:
            self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(5)
        self.driver.maximize_window()

    def test_option(self):
        print("用例")

执行JavaScript脚本

自动化测试中使用JavaScript脚本

使用场景:部分场景使用selenium原生方法无法解决

  • 修改时间控件
  • 滚动到某个元素
  • 其他场景

JavaScript 使用思路

  1. 页面调试 js 脚本
  2. Selenium执行js

JavaScript调试方法

  1. 进入 console 调试
  2. js 脚本如果有返回值则会在浏览器返回

JS 脚本-元素操作

  • 通过 css 查找元素
    • 点击元素(对应click)
    • input标签对应的值(对应send_keys)
    • 元素的类属性
    • 元素的文本属性
// 百度首页:https://www.baidu.com/
// 修改属性值
document.querySelector("#kw").value = "霍格沃兹测试学院"
// 点击操作
document.querySelector("#su").click()
// 淘宝首页: https://www.taobao.com/ 
// 修改元素的类属性
document.querySelector("#J_SiteNavMytaobao").className\
="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover"
// 测试人首页:https://ceshiren.com/
// 获取元素内的文本信息
document.querySelector("#ember63").innerText

JS脚本滚动操作

  • 页面滚动到底部
  • 指定到滚动的位置

document.documentElement.scrollTop=10000
document.querySelector(‘css表达式’).scrollIntoView();

Selenium执行js

  • Selenium执行js
    • 调用执行js方法
    • 在 js 语句中添加 return:代码可以获取js的执行结果
    • 结合 find_element 方法

示例代码

直接执行

execute_script(“js脚本”)

获取js执行结果

execute_script(“return js脚本”)

js处理-案例1-展示下拉框

  • 案例一:通过属性修改展示下拉框
def test_select_down():
    driver = webdriver.Chrome()
    driver.implicitly_wait(3)    # 打开网址
    driver.get("https://www.taobao.com/")
    # 修改下拉框属性
    sleep(1)
    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()
    sleep(5)
    driver.quit()

js处理-案例2-时间控件

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

示例代码-Python

def test_data_time():
    # 打开网址
    driver = webdriver.Chrome()
    driver.implicitly_wait(3)
    driver.get("https://www.12306.cn/index/")
    sleep(1)
    # 修改出发日期
    driver.execute_script('document.querySelector("#train_date").value="2022-12-22"')
    sleep(1)
    # 打印出发日期, 返回值的使用
    train_data = driver.execute_script('return document.querySelector("#train_date").value')
    print(train_data)
    sleep(3)

selenium option 常用操作

headless无头浏览器使用

Options概述

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

添加启动配置(arguments)-Python版本

  • 无头模式: --headless
  • 窗体最大化 start-maximized
  • 指定浏览器分辨率 window-size=1920x3000
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_chrome_pref():
    options = webdriver.ChromeOptions()
    # 无头模式
    options.add_argument('--headless')
    # 窗体最大化
    options.add_argument('start-maximized')
    # 指定浏览器分辨率
    options.add_argument('window-size=1920x3000')
    driver = webdriver.Chrome(chrome_options=options)
    # 打开测试人页面
    driver.get("https://ceshiren.com/")
    # 点击登录
    login_button_text = driver.find_element(By.CSS_SELECTOR, ".login-button").text
    print(login_button_text)  #打印:登录
    driver.quit()

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

capability概述

capability 使用示例-Python

import time
from selenium import webdriver
from selenium.webdriver.common.by import By


def test_ceshiren():
    # 切换成 windows 就会报错
    capabilities = {"browserName":"chrome","platformName":"mac"}
    # 通过 desired_capabilities 添加配置信息
    driver = webdriver.Chrome(desired_capabilities=capabilities)
    driver.implicitly_wait(5)
    driver.get("https://ceshiren.com/")
    text = driver.find_element(By.CSS_SELECTOR, ".login-button").text
    print(text) #打印:登录
    time.sleep(30)
    driver.quit()

Selenium Grid

  • elenium Grid 允许我们在多台机器上并行运行测试,并集中管理不同的浏览器版本和浏览器配置(而不是在每个单独的测试中)。
  • 官网地址:Grid | Selenium

演示环境

  • 学院搭建的演示环境:Selenium Grid
  • (搭建教程会在 docker 章节讲解)
  1. 保证本地可以正常调通
  2. 实例化Remote()类并添加相应的配置
  • 远程地址
  • 设备配置

代码示例-Python

def test_ceshiren2():
    hogwarts_grid_url = "https://selenium-node.hogwarts.ceshiren.com/wd/hub"
    capabilities = {"browserName":"chrome","browserVersion":"101.0"}
    # 配置信息
    # 实例化Remote,获取可以远程控制的driver实例对象
    # 通过 command_executor 配置selenium hub地址
    # 通过 desired_capabilities 添加配置信息
    driver = webdriver.Remote(
        command_executor=hogwarts_grid_url,
        desired_capabilities=capabilities)
    driver.implicitly_wait(5)
    driver.get("https://ceshiren.com/")
    text = driver.find_element(By.CSS_SELECTOR, ".login-button").text
    print(text)
    time.sleep(3)
    driver.quit()

Playwright 自动化测试框架

Playwright 简介

  • Web 自动化测试框架。
  • 跨平台多语言支持。
  • 支持 Chromium、Firefox、WebKit 等主流浏览器自动化操作。

Playwright 的优点

  • 支持所有流行的浏览器。
  • 速度更快,更可靠的执行。
  • 更强大的自动化测试配置。
  • 强大的工具库:Codegen、Playwright inspector、Trace Viewer。

Playwright 与 selenium 对比

项目 Playwright Selenium
是否需要驱动 需要对应浏览器 webdriver
支持语言 Java, Python, Javascript Java, Python, Javascript, Ruby, C#等
支持浏览器 Chrome、Firefox 等 Chrome、Firefox 等
通讯方式 websocket 双向通讯协议 http 单向通讯协议
使用的测试框架 无限制(pytest,unittest) 无限制(pytest,unittest)
测试速度
录制测试视频、快照 支持 支持
社区支持 微软 thoughtworks 公司

Playwright 核心工具

  • Codegen:通过记录你的操作来生成测试。 将它们保存为任何语言。
  • Playwright inspector: 检查页面、生成选择器、逐步执行测试、查看点击点、探索执行日志。
  • Trace Viewer:捕获所有信息以调查测试失败。 Playwright 跟踪包含测试执行截屏、实时 DOM 快照、动作资源管理器、测试源等等。

Playwright 环境安装

  • 安装 playwright 插件
    • pip install pytest-playwright
  • 安装所需的浏览器
    • playwright install

Codegen

设定展示窗口大小

playwright codegen --viewport-size=800,600 地址

指定设备

playwright codegen --device=“iPhone 11” 地址

Codegen-保存登录状态

  • 场景:单点登录、验证码问题

保存登录状态

playwright codegen --save-storage=auth.json

加载认证信息

playwright codegen --load-storage=auth.json 地址

Playwright 常用API

常用API 含义
start() 实例化playwright
chromium().launch() 打开chrome浏览器
new_page() 打开一个窗口页面
page.goto() 跳转到某个地址
page.locator(““) 定位某个元素
click() 点击元素
fill() 输入内容
keyboard().down() 键盘事件
screenshot() 截图操作

Playwright 使用实例

from playwright.sync_api import sync_playwright, expect
def test_playwright():
    # 实例化playwright
    playwright = sync_playwright().start()
    # 打开chrome浏览器,headless默认是True,无头模式,这里设置为False方便查看效果
    browser = playwright.chromium.launch(headless=False)
    # 打开一个窗口页面
    page = browser.new_page()
    # 在当前窗口页面打开测试人网站
    page.goto("https://ceshiren.com/")
    # 定位搜索按钮并点击
    page.locator("#search-button").click()
    # 定位搜索框并输入web自动化
    page.locator("#search-term").fill("web自动化")
    # 使用keyboard.down模拟键盘的enter事件
    page.keyboard.down("Enter")
    # 断言搜索结果
    result = page.locator(".list>li:nth-child(1) .topic-title>span")
    expect(result).to_contain_text("自动化测试")
    # 截图
    page.screenshot(path='screenshot.png')
    # 用例完成后先关闭浏览器
    browser.close()
    # 然后关闭playwright服务
    playwright.stop()

Trace Viewer 简介

Trace Viewer 使用

# trace 在代码中的配置
browser = chromium.launch()
# 实例化一个新的context
context = browser.new_context()
# 设置trace的参数信息
context.tracing.start(screenshots=True, snapshots=True, sources=True)
# 使用context生成的page实例进行对应的操作
page = context.new_page()
# 结束追踪
context.tracing.stop(path = "trace.zip")

打开trace

playwright show-trace trace.zip