Web 自动化测试 WorkShop

霍格沃兹测试开发学社

testing-studio.com

Web 自动化测试 WorkShop

  • 本次 WorkShop 将在 10:00 开始
  • 线上的同学可以先扫码进群,我们等线下同学陆续入场
  • 转发直播间到朋友圈,凭截图扫码进群找课程顾问领取学习资料

Alt text

霍格沃兹测试开发学社

  • 教育品牌:霍格沃兹测试开发学社
  • 人才推荐:企业用人推荐
  • 技术服务:自动化解决方案、自动化工具、自动化平台、测试开发技术咨询

荣誉

实力

ChatGPT 智能小助手

chatgpt

Web 自动化测试 WorkShop

Alt text

讲师介绍-飞儿老师

  • 霍格沃兹测试开发学社讲师
  • 高级测试开发工程师 10 年+ 从业经验
  • 曾就职于知名网约车平台、地图平台等公司
  • 是《测试开发实战宝典》与《软件测试开发理论与项目实战教程》作者之一
  • 为多家企业客户提供测试技术支持,包括信通院、八爪云等

环境准备

  • Python 安装配置:
    • 推荐 Python 版本:3.8 以上
    • 官方下载地址
    • 操作步骤:
      • 下载系统对应的安装程序。
      • 运行安装包。
      • 检查是否安装成功。
  • PyCharm 安装:
  • Pytest 安装:
    • pip install pytest
    • PyCharm 直接安装

学习价值

  • 熟悉 Selenium 框架与常用操作
  • 掌握 Web 自动化测试用例编写能力
  • 掌握 Web 自动测试实战能力
  • 生成自动化测试报告

知识点梳理

成果展示

Web 自动化测试

什么时候做 Web 自动化测试

  • 需要频繁回归的场景
  • 稳定的用户界面
  • 多浏览器和多平台兼容性
  • 复杂的用户交互

Web 自动化测试相关技术

  • Web 自动化框架:Selenium,支持多语言,行业内最火最主流
  • 测试框架:Pytest/JUnit5
  • 测试报告:Allure

Selenium 的简介

  • 用于 Web 浏览器测试的工具
  • 支持的浏览器包括 IE,Firefox,Safari,Chrome,Edge 等
  • 使用简单,可使用 Java,Python 等多种语言编写用例脚本
  • 主要由三个工具构成:WebDriver、IDE、Grid

Selenium 原理

环境安装与验证

pip install selenium

被测产品

需求说明

  • 完成智联招聘简历投递功能的 Web 自动化测试。
    • 使用 page object 模式编写测试用例
    • 输出测试报告

Pytest 编写规范

  • 三部分构成:
    • 用例名称
    • 用例步骤
    • 用例断言
类型 规则
文件 test_开头 或者 _test 结尾
Test 开头
方法/函数 test_开头
注意:测试类中不可以添加__init__构造函数

浏览器操作总结

元素定位总结

元素常用操作总结

Cookie 登录

  • Cookie 是一些认证数据信息,存储在电脑的浏览器上
  • 大部分 Cookie 的时效性都很长,扫一次可以使用多次

常见问题

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

登录智联招聘网站

class TestHandinResume:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        # 设置全局隐式等待
        self.driver.implicitly_wait(3)

    def test_login(self):
        url = "https://passport.zhaopin.com/login?bkUrl=%2F%2Fi.zhaopin.com%2Fblank%3Fhttps%3A%2F%2Fwww.zhaopin.com%3FvalidateCampus%3D"
        self.driver.get(url)
        self.driver.find_element(By.CLASS_NAME, "zppp-panel-normal-bar__img").click()
        # 等待扫码
        time.sleep(20)
        # 登陆成功后,获取登陆 cookies
        cookie = self.driver.get_cookies()
        print(cookie)
        # 将 cookie 写入文件
        with open("cookie.yaml", "w") as f:
            yaml.safe_dump(cookie, f)

简历投递自动化测试脚本

def change_handle(driver):
    '''
    切换窗口句柄
    :return:
    '''
    handles = driver.window_handles
    print(f"当前存在的窗口句柄为 {handles}")
    driver.switch_to.window(handles[-1])
    print("窗口切换成功")


class TestHandinResume:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        # 设置全局隐式等待
        self.driver.implicitly_wait(3)
        url = "https://passport.zhaopin.com/login?bkUrl=%2F%2Fi.zhaopin.com%2Fblank%3Fhttps%3A%2F%2Fwww.zhaopin.com%3FvalidateCampus%3D"
        self.driver.get(url)
        self.driver.find_element(By.CLASS_NAME, "zppp-panel-normal-bar__img").click()
        time.sleep(1)
        # 从文件中获取 cookie 信息登陆
        with open("cookie.yaml", "r", encoding="utf-8") as f:
            cookies = yaml.safe_load(f)
        print(f"读取出来的cookie:{cookies}")
        for cookie in cookies:
            try:
                # 添加 cookie
                self.driver.add_cookie(cookie)
            except Exception as e:
                print(e)
        time.sleep(3)


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


    def test_handin_resume(self):
        # ----首页----
        self.driver.get("https://i.zhaopin.com/")
        print(self.driver.title, self.driver.current_url)
        # 点击搜索框,输入搜索关键词
        self.driver.find_element(By.XPATH, "//*[@class='a-input__native']").send_keys("测试开发")
        # 点击搜索按钮
        self.driver.find_element(By.XPATH, "//*[@class='a-button search-box__button zp-iconfont  zp-search a--bordered a--filled']").click()
        # ----搜索结果页----
        # 打开新页面,切换句柄
        change_handle(self.driver)
        print(self.driver.title, self.driver.current_url)
        # 点击职位
        eles = self.driver.find_elements(By.XPATH, "//*[@class='iteminfo__line1__jobname__name']")
        eles[0].click()
        # ----职位详情页----
        # 切换页面,进入职位详情页
        change_handle(self.driver)
        print(self.driver.title, self.driver.current_url)
        # 点击职位申请按钮
        self.driver.find_element(By.XPATH, "//*[@class='a-button a--bordered a--filled']").click()
        # ----投递结果页----
        # 切换页面
        time.sleep(3)
        change_handle(self.driver)
        print(self.driver.title, self.driver.current_url)
        result = self.driver.find_element(By.XPATH, "//*[@class='deliver-dialog__box__title']").text
        print(result)
        # 断言投递结果
        assert result == "申请成功!"

学习路线

传统 UI 自动化的问题

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

page object 模式简介

POM 建模原则

  • 字段意义
    • 不要暴露页面内部的元素给外部
    • 不需要建模 UI 内的所有元素
  • 方法意义
    • 用公共方法代表 UI 所提供的功能
    • 方法应该返回其他的 PageObject 或者返回用于断言的数据
    • 同样的行为不同的结果可以建模为不同的方法
    • 不要在方法内加断言
  • 使用方法
    • 把元素信息和操作细节封装到 PageObject 类中
    • 根据业务逻辑,在测试用例中链式调用

Web 自动化测试 WorkShop

  • 下午 WorkShop 将在 2:00 开始
  • 线上的同学可以先扫码进群,我们等线下同学陆续入场
  • 转发直播间到朋友圈,凭截图扫码进群找课程顾问领取学习资料

Alt text

项目结构

Hogwarts $ tree
.
├── __init__.py
├── base
│ ├── __init__.py
│ └── base_driver.py
├── tests
│ ├── __init__.py
│ └── test_xxx.py
├── log
│ ├── test.log
├── datas
│ └── xxx.yaml
├── page
│ ├── __init__.py
│ ├── main_page.py
│ ├── xxx_page.py
│ └── xxx_page.py
├── pytest.ini
└── utils
    ├── __init__.py
    └── log_utils.py

基类建模

# base/base_driver.py

class BaseDriver:

    def __init__(self, driver: WebDriver=None):
        print(f"当前页面的 driver 对象 {driver}")
        if driver == None:
            # 打开 Chrome 浏览器空白页面
            self.driver = webdriver.Chrome()
            # 设置隐式等待
            self.driver.implicitly_wait(10)
            # 窗口最大化
            self.driver.maximize_window()
        else:
            self.driver = driver

    def close(self):
        '''
        关闭 driver
        '''
        print("关闭 driver")
        self.driver.quit()

    def open_url(self, url=None):
        '''
        打开网页
        :param url: 要打开页面的 URL
        '''
        info = f"要打开的页面 url 为 {url}"
        print(info)
        with allure.step(info):
            self.driver.get(url)

    def find_ele(self, by, value):
        '''
        查找单个元素
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return: 找到的元素对象
        '''
        info = f"查找单个元素:{by},{value}"
        print(info)
        with allure.step(info):
            ele = self.driver.find_element(by, value)
        return ele

    def find_eles(self, by, value):
        '''
        查找多个元素
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return: 找到的元素对象列表
        '''
        info = f"查找多个元素的定位:{by},{value}"
        print(info)
        with allure.step(info):
            eles = self.driver.find_elements(by, value)
        return eles

    def find_and_get_text(self, by, value):
        '''
        获取单个元素的文本属性
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return: 获取到的文本属性
        '''
        text = self.find_ele(by, value).text
        print(f"获取元素 {by},{value} 的文本:{text}")
        return text

    def find_and_click(self, by, value):
        '''
        查找并点击元素
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return:
        '''
        print(f"要点击的元素:{by},{value}")
        ele = self.find_ele(by, value)
        ele.click()

    def click_ele(self, ele):
        '''
        点击元素
        :param ele: 要点击的元素对象
        '''
        print(f"要点击的元素:{ele}")
        ele.click()

    def find_and_sendkeys(self, by, value, text):
        '''
        查找并输入关键字
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :param text: 要输入的关键字
        '''
        print(f"要在元素:{by},{value} 中输入关键字 {text}")
        ele = self.find_ele(by, value)
        ele.clear()
        ele.send_keys(text)

    def wait_ele_click(self, by, value, timeout=8):
        '''
        显示等待单个元素可以被点击到
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :param timeout: 等待时间
        :return:
        '''
        print(f"要等待元素 {by},{value} 可以点击")
        WebDriverWait(self.driver, timeout, 0.1).until(
            expected_conditions.element_to_be_clickable((by, value))
        )

    def url(self):
        '''
        获取当前页面的 url
        :return: url
        '''
        url = self.driver.current_url
        print(f"当前页面 url 为 {url}")
        return url

    def title(self):
        '''
        获取当前页面的 title
        :return: title
        '''
        title_text = self.driver.title
        print(f"当前页面 title 为 {title_text}")
        return title_text

    def login_yaml(self, file_path):
        '''
        从 yaml 文件中获取 cookie 登录
        '''
        time.sleep(1)
        with open(file_path, "r", encoding="utf-8") as f:
            cookies = yaml.safe_load(f)
        print(f"读取出来的 cookie:{cookies}")
        for cookie in cookies:
            try:
                # 添加 cookie
                self.driver.add_cookie(cookie)
            except Exception as e:
                print(e)
        time.sleep(3)

    def mouse_suspension(self, by, value):
        '''
        鼠标悬浮操作
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return:
        '''
        print(f"要鼠标悬浮的元素为 {by},{value}")
        action = ActionChains(self.driver)
        ele = self.find_ele(by, value)
        action.move_to_element(ele).perform()

    def change_handle(self):
        '''
        切换窗口句柄
        :return:
        '''
        time.sleep(1)
        handles = self.driver.window_handles
        print(f"当前存在的窗口句柄为 {handles}")
        self.driver.switch_to.window(handles[-1])
        print("窗口切换成功")

首页建模

# page/main_page.py

class MainPage(BaseDriver):

    login_url = "https://passport.zhaopin.com/login?bkUrl=%2F%2Fi.zhaopin.com%2Fblank%3Fhttps%3A%2F%2Fwww.zhaopin.com%3FvalidateCampus%3D"
    main_url = "https://i.zhaopin.com/"

    def login(self):
        '''
        登录,进入首页
        :return:
        '''
        # 进入登录页面
        self.open_url(self.login_url)
        # 点击扫码登录
        self.find_and_click(By.CLASS_NAME, "zppp-panel-normal-bar__img")
        # 从 yaml 文件中获取 cookie 信息登陆
        self.login_yaml("../datas/cookie.yaml")
        # 进入首页
        self.open_url(self.main_url)
        # 打印当前页面标题与 url
        self.title()
        self.url()
        return self


    def input_search_key(self, search_key):
        '''
        搜索职位
        :param search_key: 搜索关键词
        :return:
        '''
        # 1 搜索框中输入搜索关键词
        self.find_and_sendkeys(By.XPATH, "//*[@class='a-input__native']", search_key)
        # 2 点击搜索按钮
        self.find_and_click(
            By.XPATH,
            "//*[@class='a-button search-box__button zp-iconfont  zp-search a--bordered a--filled']"
        )
        # 3 跳转搜索结果页
        return SearchResultPage(self.driver)

搜索结果页建模

# page/search_result_page.py

class SearchResultPage(BaseDriver):


    def click_job(self):
        '''
        点击职位
        :return:
        '''
        # ----搜索结果页----
        # 打开新页面,切换句柄
        self.change_handle()
        # 打印当前页面 title 与 url
        self.title()
        self.url()
        # 1 点击第一个职位
        eles = self.find_eles(By.XPATH, "//*[@class='iteminfo__line1__jobname__name']")
        # eles[0].click()
        self.click_ele(eles[0])
        # 2 跳转职位详情页
        return JobInfoPage(self.driver)

职位详情页建模

# page/job_info_page.py

class JobInfoPage(BaseDriver):

    def click_deliver(self):
        '''
        点击投递简历按钮
        :return:
        '''
        # ----职位详情页----
        # 切换页面,进入职位详情页
        self.change_handle()
        self.title()
        self.url()
        # 点击职位申请按钮
        self.find_and_click(By.XPATH, "//*[@class='a-button a--bordered a--filled']")
        # 跳转投递结果页
        return DeliverResultPage(self.driver)

投递结果页建模

# page/deliver_result_page.py

class DeliverResultPage(BaseDriver):

    def get_deliver_result(self):
        '''
        获取投递结果
        :return:
        '''
        # 切换页面
        self.change_handle()
        self.title()
        self.url()
        # 获取投递结果文本
        result = self.find_and_get_text(By.XPATH, "//*[@class='deliver-dialog__box__title']")
        print(result)
        return result

投递简历测试用例

# test/test_deliver_resume.py

class TestDeliverResume:

    def setup(self):
        self.main = MainPage()

    def teardown(self):
        self.main.close()

    def test_job_info_deliver(self):
        '''
        测试步骤
        1 登录进入首页
        2 搜索职位
        3 点击搜索结果页的第一个职位
        4 在职位详情页点击投递简历
        5 断言投递结果页展示投递结果信息
        :return:
        '''
        result = self.main.login().input_search_key("测试开发").click_job().click_deliver().get_deliver_result()
        assert result == '申请成功!'

Allure2 介绍

  • Allure 是由 Java 语⾔开发的⼀个轻量级,灵活的测试报告⼯具。
  • Allure 多平台的 Report 框架。
  • Allure ⽀持多语⾔,包括 python、JaveScript、PHP、Ruby 等。
  • 可以为开发/测试/管理等人员提供详尽的的测试报告,包括测试类别、测试步骤、日志、图片、视频等。
  • 可以为管理层提供高水准的统计报告。
  • 可以集成到 Jenkins 生成在线的趋势汇总报告。

Allure2 安装

  1. 安装 Java,需要配置环境变量。
  2. 安装 Allure ,需要配置环境变量。
    • 下载地址
      • mac/linux: 下载 tar
      • windows: 下载 zip
    • 配置环境变量:解压后将 bin 目录加入 PATH 环境变量。
    • 执行命令验证环境:allure --version
    • 详细安装步骤参考:https://ceshiren.com/t/topic/3386
  3. 安装插件:
    • Python:pip install allure-pytest

添加报告描述信息

# test/test_deliver_resume.py

@allure.feature("智联招聘网站测试")
class TestDeliverResume:

    def setup(self):
        self.main = MainPage()

    def teardown(self):
        self.main.close()

    @allure.story("简历投递")
    @allure.title("职位详情页投递简历")
    def test_job_info_deliver(self):
        '''
        测试步骤
        1 登录进入首页
        2 搜索职位
        3 点击搜索结果页的第一个职位
        4 在职位详情页点击投递简历
        5 断言投递结果页展示投递结果信息
        :return:
        '''
        result = self.main.login().input_search_key("测试开发").click_job().click_deliver().get_deliver_result()
        assert result == '申请成功!'
# base/base_driver.py

class BaseDriver:

    ...

    def open_url(self, url=None):
        '''
        打开网页
        :param url: 要打开页面的 URL
        '''
        info = f"要打开的页面 url 为 {url}"
        print(info)
        with allure.step(info):
            self.driver.get(url)

    def find_ele(self, by, value):
        '''
        查找单个元素
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return: 找到的元素对象
        '''
        info = f"查找单个元素:{by},{value}"
        print(info)
        with allure.step(info):
            ele = self.driver.find_element(by, value)
        return ele

    def find_eles(self, by, value):
        '''
        查找多个元素
        :param by: 元素定位方式
        :param value: 元素定位表达式
        :return: 找到的元素对象列表
        '''
        info = f"查找多个元素的定位:{by},{value}"
        print(info)
        with allure.step(info):
            eles = self.driver.find_elements(by, value)
        return eles

生成测试报告

# 在测试执行期间收集结果
pytest [测试用例/模块/包] --alluredir=./result/ --clean-alluredir

# 生成在线的测试报告
allure serve ./result

# 生成静态报告
allure generate --clean result/html result -o result/html

总结

  • Selenium 完成 Web 自动化测试
  • PO 模式封装 Web 自动化测试框架
  • Allure 报告

实战练习

  • 完成智联招聘投递简历线性脚本
  • 完成 PO 模式 Web 自动化测试框架封装
  • 生成 Allure 报告

圆桌会议

  • 常见面试题
    • 什么样的项目比较适合做自动化测试,什么样的不适合做自动化测试?
    • 实际工作中,你是如何开展自动化工作的?
    • 显式等待与隐式等待的区别?
    • 如何判断一个页面上元素是否存在?