Selenium UI自动化测试实战:从环境搭建到CI/CD集成

📅 2026/7/7 20:51:30 👁️ 阅读次数 📝 编程学习
Selenium UI自动化测试实战:从环境搭建到CI/CD集成

1. 项目概述:为什么我们需要UI自动化测试?

如果你是一名测试工程师或者开发,最近几年肯定没少听到“自动化测试”这个词。尤其是在Web应用开发领域,UI自动化测试已经从“锦上添花”变成了保障产品质量、提升迭代效率的“必需品”。我干了十多年测试,亲眼看着团队从纯手工点点点,到引入Selenium写脚本,再到如今构建起一整套自动化测试流水线。这个过程里,最大的感受就是:UI自动化测试的核心价值,不是替代人,而是解放人。

想象一下这个场景:你的产品每周都要发布一个新版本,每次上线前,测试团队都需要把几十个核心业务流程(比如用户登录、下单、支付、查看订单)手动跑一遍。这个过程枯燥、重复,还容易因为人为疲劳而出错。更头疼的是,一旦某个底层接口被无意改动,可能引发一连串的页面问题,而手工测试很难做到全覆盖。这时候,一套稳定、可靠的UI自动化测试脚本,就像一位不知疲倦的“数字员工”,可以在每次代码提交后、版本发布前,自动、快速、准确地执行这些预设的检查,第一时间把问题暴露出来。

Selenium正是扮演这个“数字员工”角色的绝佳工具。它不是一个有界面的软件,而是一个浏览器自动化库。你可以用Python、Java、C#等熟悉的编程语言,编写代码来“指挥”浏览器(如Chrome、Firefox)完成点击、输入、跳转等所有用户能做的操作,并能检查页面元素、文本内容是否符合预期。这就是Selenium Web UI自动化测试的本质:用代码模拟用户行为,对Web应用进行端到端的验证。

那么,谁适合学习并实践它呢?首先是测试工程师,这是提升个人技术栈、从手工测试转向测试开发的必经之路。其次是后端或前端开发,自己写的代码自己验,通过自动化测试能更早发现集成问题。甚至产品经理或项目经理,了解其原理也能更好地规划测试资源和评估项目风险。接下来,我将带你从零开始,拆解如何用Selenium搭建一套可实战的Web UI自动化测试框架,其中会包含我趟过的坑、总结的技巧,以及如何让它真正在项目中发挥价值。

2. 环境搭建与核心工具链选型

万事开头难,搭建一个稳定可用的自动化测试环境是第一步。这一步如果没做好,后续的脚本编写和调试会举步维艰。很多人卡在环境配置上就放弃了,其实只要理清脉络,整个过程非常清晰。

2.1 Python与Selenium库安装:为什么是Python?

虽然Selenium支持多种语言,但我强烈推荐从Python入手。原因有三:一是语法简洁,上手快,能让测试人员更专注于测试逻辑而非语言细节;二是生态丰富,Pytest测试框架、Allure报告生成等周边工具链成熟;三是与CI/CD(持续集成/持续部署)工具集成方便。

安装Python很简单,去官网下载最新稳定版即可。安装时务必勾选“Add Python to PATH”,这样就能在命令行全局使用pythonpip命令。安装完成后,打开终端(CMD或PowerShell),用一行命令安装Selenium库:

pip install selenium

这里有个关键点:建议使用虚拟环境。为你的自动化项目创建一个独立的虚拟环境,可以避免不同项目间的库版本冲突。可以使用venv模块:

# 在项目目录下 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (Mac/Linux) source venv/bin/activate # 然后在激活的环境下安装selenium pip install selenium

2.2 浏览器驱动管理:从手动到自动的进化

这是新手最容易踩坑的地方。Selenium需要通过一个叫“WebDriver”的驱动程序来与真实的浏览器对话。每个浏览器(Chrome, Firefox, Edge)都需要其对应的驱动,且驱动版本必须与浏览器版本严格匹配。

传统做法(手动管理,不推荐):去浏览器驱动的官网(如ChromeDriver的谷歌官方存储库)下载对应版本的exe文件,放在某个目录,并将该目录添加到系统环境变量PATH中,或者在代码里指定驱动路径。这种方式极其繁琐,每次浏览器自动升级后,你都得重新下载匹配的驱动。

现代最佳实践(自动管理,强烈推荐):使用webdriver-manager这个Python库。它会自动检测你本地安装的浏览器版本,并下载、配置匹配的WebDriver。这彻底解决了版本匹配的噩梦。

pip install webdriver-manager

在代码中,你可以这样使用它来启动Chrome浏览器:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 自动下载并使用匹配的ChromeDriver service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)

对于Firefox(geckodriver)和Edge(msedgedriver),webdriver-manager同样支持。这行代码是你自动化脚本稳定性的第一道保障。

2.3 IDE与辅助工具选择:提升编码与调试效率

工欲善其事,必先利其器。一个好的集成开发环境(IDE)能极大提升写测试脚本的体验。

  • PyCharm (社区版免费):对Python支持最好的IDE,没有之一。它的代码提示、调试功能、对虚拟环境和Pytest的集成都非常完善。特别适合中大型自动化测试项目。
  • VS Code + Python插件:轻量级,启动快,插件生态丰富。如果你习惯用VS Code,配置好Python环境后也是一个绝佳选择。

除了IDE,还有两个浏览器开发者工具技巧你必须掌握:

  1. 元素选择器:在浏览器中按F12打开开发者工具,使用左上角的箭头图标(或Ctrl+Shift+C)点击页面元素,可以直接在Elements面板看到其HTML结构和可能的定位信息(如ID、Class、CSS Selector)。这是编写元素定位代码的基础。
  2. Selenium IDE (浏览器插件):这是一个录制/回放工具。你可以用它快速录制一段操作,生成Selenium脚本。但请注意,我仅推荐用它来学习定位语法和快速生成脚本草稿,绝不建议直接使用其生成的脚本用于正式项目。因为录制的脚本通常缺乏健壮性(比如使用不稳定的XPath)、没有等待逻辑、结构混乱,难以维护。

注意:环境搭建阶段,务必确保你的网络环境能够正常访问ChromeDriver等资源的下载服务器。如果遇到下载慢或失败,可以尝试为pipwebdriver-manager配置国内镜像源。

3. Selenium核心操作与元素定位实战

环境准备好后,我们进入核心环节:如何用代码“告诉”浏览器做什么。这分为两步:一是找到页面上的元素(定位),二是对该元素进行操作(点击、输入等)。

3.1 八大元素定位策略详解与选用原则

Selenium提供了多种定位元素的方法,理解每种方法的适用场景和优缺点至关重要。

from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get("https://www.example.com/login") # 1. 通过ID定位 (最优先) # 前提:元素有唯一且稳定的id属性。这是最快、最可靠的定位方式。 username_input = driver.find_element(By.ID, "username") # 2. 通过Name定位 # 常用于表单元素,如input、select。但Name不一定唯一。 search_box = driver.find_element(By.NAME, "q") # 3. 通过Class Name定位 # 用于通过CSS类名定位。一个元素可能有多个类,一个类也可能用于多个元素,需谨慎。 submit_button = driver.find_element(By.CLASS_NAME, "btn-primary") # 4. 通过Tag Name定位 # 通过HTML标签名定位,如`<input>`, `<a>`。通常用于查找一组同类元素。 all_links = driver.find_elements(By.TAG_NAME, "a") # 注意是find_elements,返回列表 # 5. 通过Link Text / Partial Link Text定位 # 专门用于定位超链接(<a>标签),通过其完整的或部分的可见文本。 full_link = driver.find_element(By.LINK_TEXT, "忘记密码?") partial_link = driver.find_element(By.PARTIAL_LINK_TEXT, "忘记") # 6. 通过CSS Selector定位 (强烈推荐) # 功能强大,语法灵活,性能优于XPath。可以组合ID、Class、属性等进行定位。 # 通过ID: #username # 通过Class: .btn-primary # 通过属性: input[type='email'] # 组合: form#loginForm input[name='user'] email_input = driver.find_element(By.CSS_SELECTOR, "input[type='email']") # 7. 通过XPath定位 (功能强大但需谨慎) # 可以在整个HTML文档中导航,功能最强大,但写不好会非常脆弱且性能较差。 # 绝对路径 (脆弱,不推荐): /html/body/div[1]/form/input[1] # 相对路径 (稍好): //form[@id='loginForm']//input[@name='user'] # 使用文本: //button[text()='登录'] # 包含文本: //a[contains(text(),'欢迎')] login_button = driver.find_element(By.XPATH, "//button[@type='submit']")

定位策略选用原则(我的经验之谈)

  1. 优先级从高到低ID > Name > CSS Selector > XPath > 其他。ID是首选,因为它天生具有唯一性。
  2. 避免使用绝对XPath:绝对路径(如/html/body/div[3]/div[2])极度脆弱,页面结构稍有变动(比如中间多了一个<div>)就会定位失败。尽量使用相对XPath或CSS Selector。
  3. CSS Selector vs XPath:对于简单的属性定位,CSS Selector更简洁、性能更好。但对于需要根据文本内容、父子兄弟关系等复杂逻辑定位的场景,XPath更胜一筹。现代浏览器对CSS Selector的解析优化得更好。
  4. 使用find_elements进行验证:当你不能确定元素是否存在时,使用find_elements。它返回一个列表,如果找不到元素,返回空列表[],而不会像find_element那样抛出NoSuchElementException异常。这在判断元素是否存在时非常有用。

3.2 常用浏览器操作与模拟用户交互

定位到元素后,就可以模拟用户操作了。这些操作是构建测试用例的基石。

from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains import time # 1. 输入文本 username_input.send_keys("testuser@example.com") # 2. 清除输入框 username_input.clear() username_input.send_keys("new_user@example.com") # 3. 点击元素 login_button.click() # 4. 提交表单 # 如果元素在一个<form>里,可以调用submit(),效果等同于按回车。 password_input.send_keys("password123") password_input.submit() # 假设密码输入框在表单内 # 5. 获取元素属性、文本、状态 print(username_input.get_attribute("value")) # 获取输入框的值 print(login_button.text) # 获取按钮上的文字 print(checkbox.is_selected()) # 判断复选框是否被选中 print(button.is_enabled()) # 判断按钮是否可点击 print(link.is_displayed()) # 判断元素是否可见 # 6. 模拟键盘操作 from selenium.webdriver.common.keys import Keys search_box.send_keys("Selenium自动化") search_box.send_keys(Keys.ENTER) # 模拟按下回车键 # 组合键,如Ctrl+A全选 search_box.send_keys(Keys.CONTROL, 'a') # 7. 模拟鼠标复杂操作 (使用ActionChains) # 悬停、拖拽、右键等复杂交互需要ActionChains。 actions = ActionChains(driver) menu = driver.find_element(By.ID, "dropdownMenu") hidden_submenu = driver.find_element(By.ID, "submenu1") # 将鼠标移动到菜单上,暂停,再点击子菜单 actions.move_to_element(menu).pause(1).click(hidden_submenu).perform() # 8. 执行JavaScript # 对于Selenium API无法直接处理的特殊操作,可以执行JS。 driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 滚动到页面底部 driver.execute_script("arguments[0].click();", element) # 用JS点击元素,有时能解决普通click失效的问题

3.3 等待机制:自动化脚本稳定的关键

这是新手写出的脚本运行不稳定的最主要原因。页面加载和元素渲染需要时间,如果你的脚本在元素还没出现时就尝试去操作它,必然会抛出NoSuchElementException

1. 强制等待 (time.sleep) —— 知其然,知其所以不推荐

import time time.sleep(5) # 无条件等待5秒

缺点:无论元素是否已就绪,都必须等待固定时间。时间设短了会失败,设长了浪费执行时间。仅在调试或不得已时使用。

2. 隐式等待 (implicitly_wait) —— 设置全局超时

driver.implicitly_wait(10) # 在抛出NoSuchElementException之前,最多等待10秒

原理:设置一个全局的等待时间,在查找任何一个元素时,如果元素没有立即找到,WebDriver会轮询DOM直到找到它或超时。优点:设置一次,对整个driver生命周期有效。缺点:不够灵活,无法等待特定条件(如元素可点击、包含特定文本)。并且,它只对find_element这类查找操作有效。

3. 显式等待 (WebDriverWait + expected_conditions) —— 最佳实践这是最推荐的方式。它允许你为某个特定的操作或条件设置等待,更加精确和高效。

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待最多10秒,直到ID为‘username’的元素出现在DOM中并可见 wait = WebDriverWait(driver, 10) username_field = wait.until(EC.visibility_of_element_located((By.ID, "username"))) username_field.send_keys("user") # 等待元素可被点击 submit_btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']"))) submit_btn.click() # 等待元素包含特定文本 success_msg = wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, "alert"), "登录成功")) # 等待页面标题包含特定文字 wait.until(EC.title_contains("控制台"))

常用expected_conditions

  • presence_of_element_located: 元素存在于DOM(不一定可见)。
  • visibility_of_element_located: 元素存在且可见。
  • element_to_be_clickable: 元素存在、可见且可点击。
  • text_to_be_present_in_element: 元素包含特定文本。

我的经验:在项目中,我通常会混合使用隐式等待和显式等待。设置一个较短的全局隐式等待(如5秒)作为兜底,然后在关键操作点(如点击登录按钮后等待页面跳转、等待动态加载的数据表格)使用显式等待,指定更精确的条件。这能在保证稳定性的同时,最大化执行效率。

4. 构建可维护的自动化测试框架

当你会写单个测试脚本后,下一个挑战是如何组织几十、上百个测试用例,让它们易于编写、维护和运行。这就是测试框架要解决的问题。一个良好的框架能让你像搭积木一样构建测试。

4.1 测试框架选型:Pytest vs Unittest

Python有两个主流的测试框架:unittest(标准库自带)和pytest(第三方)。我毫无保留地推荐pytest

为什么选择Pytest?

  • 更简洁:不需要像unittest那样强制继承TestCase类,写普通的函数加上assert语句就是测试用例。
  • 更强大:丰富的Fixture机制(用于测试前置和后置条件)、参数化测试、插件生态(如生成Allure报告、并行执行)都非常成熟。
  • 失败信息更清晰:断言失败时,pytest会给出非常详细的差异对比。
  • 社区活跃:是目前Python测试领域的事实标准。

安装pytest:pip install pytest

4.2 使用Page Object Model (POM) 设计模式

这是UI自动化测试中最重要的设计模式,没有之一。POM的核心思想是将页面抽象成一个,将页面上的元素定义为类的属性,将页面上的操作定义为类的方法。测试用例则通过调用这些页面对象的方法来完成。

没有POM的代码(难以维护)

def test_login(): driver.find_element(By.ID, "username").send_keys("user") driver.find_element(By.ID, "password").send_keys("pass") driver.find_element(By.ID, "loginBtn").click() assert "Welcome" in driver.page_source

使用POM后的代码(清晰易维护)

# pages/login_page.py class LoginPage: def __init__(self, driver): self.driver = driver self.username_input = (By.ID, "username") self.password_input = (By.ID, "password") self.login_button = (By.ID, "loginBtn") self.welcome_msg = (By.ID, "welcome") def open(self): self.driver.get("https://example.com/login") return self def enter_username(self, username): self.driver.find_element(*self.username_input).send_keys(username) return self # 支持链式调用 def enter_password(self, password): self.driver.find_element(*self.password_input).send_keys(password) return self def click_login(self): self.driver.find_element(*self.login_button).click() return HomePage(self.driver) # 返回下一个页面对象 def get_welcome_text(self): return self.driver.find_element(*self.welcome_msg).text # tests/test_login.py from pages.login_page import LoginPage def test_login_success(driver): # driver通过pytest fixture注入 home_page = LoginPage(driver).open() \ .enter_username("testuser") \ .enter_password("secret") \ .click_login() assert home_page.get_welcome_text() == "Welcome, testuser!"

POM的优势

  1. 高可维护性:当页面元素定位符(如ID)发生变化时,你只需要在一个地方(Page类中)修改,所有用到该元素的测试用例都会自动生效。
  2. 高可读性:测试用例读起来就像自然语言,业务逻辑清晰。
  3. 低冗余:页面操作被封装成方法,避免了重复代码。
  4. 团队协作:测试开发负责维护Page类,手工测试或业务测试人员可以基于清晰的页面方法来编写或理解测试用例。

4.3 数据驱动测试:将测试数据与脚本分离

测试同一个功能,往往需要多组不同的数据(如正确用户名/密码、错误密码、空用户名等)。硬编码在脚本里会让脚本臃肿且难以管理。数据驱动测试(DDT)将测试数据(如JSON、YAML、Excel、CSV)外部化。

使用Pytest的参数化功能实现数据驱动

import pytest from pages.login_page import LoginPage # 将测试数据定义在装饰器里 @pytest.mark.parametrize("username, password, expected_result", [ ("correct_user", "correct_pass", "登录成功"), ("wrong_user", "some_pass", "用户名或密码错误"), ("", "some_pass", "用户名不能为空"), ("correct_user", "", "密码不能为空"), ]) def test_login_with_different_data(driver, username, password, expected_result): login_page = LoginPage(driver).open() login_page.enter_username(username) login_page.enter_password(password) login_page.click_login() # 假设页面会根据结果弹出不同的提示信息 actual_msg = login_page.get_message() assert actual_msg == expected_result

更复杂的数据可以从外部文件读取,比如使用pytest@pytest.mark.parametrize结合jsoncsv模块。

4.4 测试用例的组织与Fixture的使用

Pytest的Fixture是一个强大的机制,用于提供测试运行所需的环境(如初始化浏览器、登录用户、准备测试数据)和清理工作(如关闭浏览器、清理数据库)。

conftest.py文件:这是Pytest的一个特殊文件,用于存放被多个测试文件共享的Fixture。通常放在项目根目录或测试目录下。

# conftest.py import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options @pytest.fixture(scope="function") # scope可以是function, class, module, session def driver(): """为每个测试函数提供一个全新的浏览器实例""" chrome_options = Options() chrome_options.add_argument("--headless") # 无头模式,不显示浏览器UI,适合CI环境 chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") service = Service(ChromeDriverManager().install()) driver_instance = webdriver.Chrome(service=service, options=chrome_options) driver_instance.implicitly_wait(5) driver_instance.maximize_window() yield driver_instance # 测试函数执行时使用这个driver # 测试函数执行完毕后,执行清理工作 driver_instance.quit() @pytest.fixture(scope="session") def login_user(driver): """会话级别的Fixture,在整个测试会话中只登录一次,获取登录态""" from pages.login_page import LoginPage home_page = LoginPage(driver).open() \ .enter_username("admin") \ .enter_password("admin123") \ .click_login() yield home_page # 将登录后的首页对象传递给测试用例 # 如果需要登出,可以在这里操作

在测试用例中,你只需要将Fixture的名字作为参数,Pytest会自动注入:

# test_dashboard.py def test_view_dashboard(driver, login_user): # 自动注入driver和登录后的用户 dashboard_page = login_user.navigate_to_dashboard() assert dashboard_page.get_title() == "数据概览"

通过合理的Fixture设计,你可以构建出层次清晰、依赖关系明确的测试环境,让每个测试用例都专注于自己的业务逻辑验证。

5. 高级技巧、问题排查与持续集成

掌握了基础和框架,你已经能应对大部分场景。但要写出真正稳定、高效、能在团队中落地的自动化脚本,还需要一些“高级装备”和“避坑指南”。

5.1 处理弹窗、iframe与多窗口

1. 处理JavaScript弹窗 (Alert, Confirm, Prompt)

from selenium.webdriver.common.alert import Alert # 触发一个alert driver.find_element(By.ID, "triggerAlert").click() # 切换到alert alert = Alert(driver) print(alert.text) # 获取弹窗文本 alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消” # 对于Prompt弹窗,还可以send_keys输入文本 # alert.send_keys("输入内容")

2. 处理iframe/框架如果元素位于一个<iframe>内部,你必须先切换到该iframe上下文,才能定位其中的元素。

# 通过ID或Name切换 driver.switch_to.frame("iframe_id_or_name") # 通过索引切换 (从0开始) driver.switch_to.frame(0) # 通过WebElement切换 iframe_element = driver.find_element(By.TAG_NAME, "iframe") driver.switch_to.frame(iframe_element) # 操作iframe内的元素 driver.find_element(By.ID, "inner_element").click() # 操作完成后,切换回主文档 driver.switch_to.default_content() # 或者切换到父级iframe driver.switch_to.parent_frame()

3. 处理多窗口/标签页

# 获取当前窗口句柄 main_window = driver.current_window_handle # 点击一个打开新窗口的链接 driver.find_element(By.LINK_TEXT, "在新窗口打开").click() # 获取所有窗口句柄 all_windows = driver.window_handles # 切换到新窗口 for window in all_windows: if window != main_window: driver.switch_to.window(window) break # 在新窗口操作 print(driver.title) # 关闭新窗口,切换回主窗口 driver.close() driver.switch_to.window(main_window)

5.2 文件上传、下载与Cookie管理

文件上传:对于<input type="file">元素,直接使用send_keys传入文件绝对路径即可。千万不要尝试用ActionChains去模拟点击“浏览”按钮,那会打开系统文件选择对话框,Selenium无法与之交互。

upload_element = driver.find_element(By.XPATH, "//input[@type='file']") upload_element.send_keys("/Users/yourname/Desktop/test_image.png")

文件下载:需要预先设置浏览器的下载偏好,使其不弹出下载对话框,并指定下载目录。

from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() prefs = { "download.default_directory": "/path/to/your/download/folder", "download.prompt_for_download": False, "plugins.always_open_pdf_externally": True # 对于PDF } chrome_options.add_experimental_option("prefs", prefs) driver = webdriver.Chrome(options=chrome_options)

Cookie管理:可用于实现免登录测试或测试不同登录状态。

# 获取所有cookie all_cookies = driver.get_cookies() print(all_cookies) # 根据name获取特定cookie cookie = driver.get_cookie("session_id") # 添加cookie (常用于绕过登录) driver.add_cookie({"name": "session_id", "value": "abc123"}) # 添加cookie后需要刷新页面或跳转页面使其生效 driver.refresh() # 删除cookie driver.delete_cookie("session_id") driver.delete_all_cookies()

5.3 常见问题排查与调试技巧

  1. 元素定位不到 (NoSuchElementException)

    • 检查定位器:首先在浏览器开发者工具中,用$x('your_xpath')$$('your_css')验证你的XPath或CSS Selector是否正确。
    • 检查等待:元素是否还没加载出来?增加显式等待。
    • 检查iframe:目标元素是否在iframe里?需要先切换。
    • 检查Shadow DOM:现代前端框架(如Vue, React)可能使用Shadow DOM,Selenium默认无法直接定位其内部元素。需要使用JavaScript穿透:driver.execute_script('return arguments[0].shadowRoot', shadow_host_element)
    • 检查动态ID/Class:有些前端框架会生成随机的ID或类名。尝试使用其他稳定的属性,如>driver.save_screenshot("error_screenshot.png")
    • 打印页面源码:在关键时刻打印driver.page_source,查看当时的DOM状态。
    • 使用pdb或IDE调试器:在代码中设置断点,单步执行,查看变量状态。
    • 降低执行速度:在调试时,在关键操作间加入短暂的time.sleep,方便肉眼观察浏览器状态。

5.4 集成到CI/CD流水线

自动化测试只有集成到持续集成/持续部署(CI/CD)流程中,才能最大化其价值。通常使用Jenkins、GitLab CI、GitHub Actions等工具。

一个典型的GitHub Actions工作流示例 (.github/workflows/ui-test.yml)

name: UI Automation Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-html webdriver-manager - name: Install Chrome and ChromeDriver run: | sudo apt-get update sudo apt-get install -y wget wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list sudo apt-get update sudo apt-get install -y google-chrome-stable - name: Run UI Tests with Pytest run: | python -m pytest tests/ --html=report.html --self-contained-html - name: Upload Test Report uses: actions/upload-artifact@v3 if: always() # 即使测试失败也上传报告 with: name: pytest-report path: report.html

这个工作流会在代码推送或拉取请求时自动触发,在一个干净的Ubuntu环境中安装Python、Chrome、依赖库,然后运行所有UI测试,并生成一个HTML格式的测试报告供下载查看。

关键点

  • 无头模式:在CI中务必使用无头模式(--headless)运行浏览器,节省资源且无需图形界面。
  • 测试报告:使用pytest-htmlpytest-allure等插件生成美观的测试报告,并集成到CI系统中,方便查看失败详情。
  • 失败通知:配置CI job在测试失败时通过邮件、Slack、钉钉等渠道通知相关人员。
  • 测试数据隔离:确保CI环境有独立的测试数据库或测试账号,避免与生产环境混淆。

走到这一步,你的UI自动化测试就不再是孤立的脚本,而成为了保障软件质量流水线中一个自动化的、可靠的环节。它能持续地、静默地为你的产品保驾护航,在每次变更时提供快速反馈,这正是自动化测试最大的魅力所在。