☰
抽奖系统Selenium自动化测试全流程解析与实践
2026/10/10 17:21:25 网站建设 项目流程

“抽奖系统Selenium自动化测试流程解析”这个话题,我前前后后做过不下三次,第一次是在某电商平台的营销活动项目里,最后一次是在某个面向会员的积分抽奖小程序后端。每次做都有人问我:抽奖不就是点个按钮看结果吗,有什么好测的?真上手了才发现,这里面全是细节。抽奖系统涉及登录态、活动配置、概率控制、库存扣减、结果弹窗、中奖记录写入,任何一个环节出错,用户看到的可能不是“谢谢参与”,而是整个活动页面崩掉。Selenium做这件事的好处是能完整模拟真实用户的操作链路,从进入活动页、点击抽奖、等待接口返回、读取弹窗结果,到验证记录写入,一条龙都能覆盖。这篇内容我会把完整的自动化测试流程拆开讲,包括环境搭建、用例设计、断言策略、稳定性优化和踩坑记录,适合刚要接触UI自动化的测试同学,也适合正在为抽奖类活动写回归脚本的人参考。

1. 抽奖业务的底层模型与自动化测试的切入点

1.1 抽奖系统的基础业务链路

想测好抽奖系统,不能只盯着“抽奖”这两个字。我习惯先把业务链路画在脑子里:用户登录、进入活动页、查看剩余抽奖次数、点击抽奖、前端发请求、后端校验资格、执行概率逻辑、扣减库存或次数、生成结果、返回前端、弹窗展示结果、记录写入中奖列表,这一串下来,定位层、逻辑层、数据层都有参与。

其中最容易出问题的是三个地方。第一个是资格校验,用户当天是否已经抽满次数、积分是否够扣、活动是否在有效期内,这些条件决定请求能不能正常发起。第二个是概率与库存联动,比如某个奖品总共只有10份,但抽中概率不降,活动第一天就可能被抽完,后端必须同步处理并发下的超发问题。第三个是结果展示与数据一致性,前端弹窗说中了某奖品,但打开中奖记录列表却查不到,或者弹窗显示“未中奖”,数据库里却插入了中奖记录,这类问题用纯接口测试很难发现,只有把前端交互和数据层放到一起验证才能暴露。

我在写自动化用例之前,通常会先拿一份活动的规则文档,把其中每一个判断条件都列出来,做成一张“条件-动作”对照表。比如“用户抽奖次数上限3次/天”“积分扣除200/次”“未登录用户点击抽奖跳转登录页”“奖品库存为0时提示已抢光”等等,这些规则就是后续测试用例的根本来源。不要一上来就写代码,先搞清楚规则,脚本才有依据。

1.2 自动化测试最需要关注的三个痛点

抽奖类项目给自动化测试带来的难点,和普通CRUD页面不太一样,集中在三个方面。

第一是随机性。抽奖结果本身是随机的,或者说是带权重随机的,自动化用例执行一次,不能预期一个固定结果。很多测试同学在这里犯了难,不知道断言该写什么。后面我会专门讲“结果不固定时怎么设计断言”,这里先记住一个原则:测流程合规性和数据一致性,不测具体奖品。

第二是并发下的状态一致性。Selenium本身是单浏览器操作,模拟不了真实并发。但抽奖系统的核心风险恰恰在并发场景,比如人手快速连点、多端同时抽奖。这个问题单靠Selenium无法完整覆盖,需要配合并发接口测试,但在UI自动化层面可以做“快速连点”和“多账号顺序轮询”的模拟,至少能发现一部分前端防重问题。

第三是活动配置不断变化。奖品池、概率、抽奖次数这些参数,运营经常调整,很可能今天脚本还能跑,明天活动规则一改,脚本就秒挂。所以用例数据和一定要和配置解耦,把次数、概率等写成外部参数,而不是硬编码在函数里。

还有一个我经常提醒自己的事:抽奖系统的弹窗样式五花八门,有的用自定义div模拟,有的用原生alert,有的结果出现前有动画延迟,这些都会直接影响元素定位和等待策略。写脚本之前,先人工走一遍流程,看一遍前端加载逻辑,能节约后面排查问题的大量时间。

2. 环境搭建、框架选型与目录组织

2.1 环境准备与依赖安装

我目前用的组合是Python + Selenium 4 + WebDriver Manager + Pytest,这套组合的好处是生态成熟、资料多、团队协作成本低。Python环境下一条命令把依赖装齐:

pip install selenium pytest webdriver-manager allure-pytest

这里特别说一下WebDriver Manager,它解决了浏览器驱动和浏览器版本不匹配的问题。以前我每次Chrome升级,驱动就失效,还要去下载对应版本,非常折腾。用它之后,脚本里直接调用自动匹配版本号的驱动,省了很多事。初始化浏览器的代码可以这样写:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager def create_driver(): options = webdriver.ChromeOptions() options.add_argument("--window-size=1920,1080") options.add_argument("--disable-notifications") driver = webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options ) driver.implicitly_wait(5) return driver

利用隐式等待先兜底,后面核心节点再用显式等待精确控制,这套组合我用了很久,稳定性不错。另外建议用无头模式跑CI任务时保留截屏功能,一旦失败可以把当时的界面保存下来,方便排查。

2.2 为什么Selenium在这个场景比别的工具更顺手

很多人在做接口测试时习惯用Postman或者Requests直接调接口,但抽奖系统的自动化测试不能只做接口层,因为很多业务校验在前端就拦截了,比如未登录时按钮置灰、机会次数不足时按钮不可点击、活动未开始时页面直接打不开。这些前端交互层面的状态,只有真实驱动浏览器才能覆盖到。

对比一下几类工具:

工具适合场景在抽奖系统测试中的优势局限
SeleniumUI自动化回归完整模拟用户路径,从登录到抽奖全链路验证无法模拟真实高并发
Requests/Postman纯接口调用快速验证概率、库存、资格等后端逻辑看不到前端交互,无法发现页面层问题
Playwright较新Web自动化自动等待更强,可模拟移动端团队熟悉成本略高,生态相对小
JMeter性能与并发压测抽奖接口并发,验证超发需要单独维护测试数据,和UI脱节

抽奖系统最大的风险往往在前后端联合逻辑上,所以我倾向于让Selenium承担主流程回归任务,接口层用一套轻量脚本做数据校验,两者互补而不是二选一。Selenium的价值在于它能够完整还原用户视角,和业务方沟通测试结论时也更直观,截个图或者录个视频,比说一堆响应字段更有说服力。

不过也要承认Selenium的短板:跑得慢、依赖浏览器环境、维护成本高。所以我只在抽奖主流程、关键分支、回归核心场景上用Selenium,不追求用UI自动化覆盖所有边角功能,部分纯数据校验放到接口层去做,这才是合理的分工。

2.3 测试目录的常规组织方式

一个能长期维护的抽奖测试工程,目录结构我建议这样组织:

project_root/ ├── config/ │ └── activity_config.json # 活动配置,和用例解耦 ├── pages/ │ ├── login_page.py # 登录页面对象 │ ├── activity_page.py # 活动页面对象 │ └── result_page.py # 中奖结果页面对象 ├── testcases/ │ ├── test_lottery_basic.py # 抽奖主流程用例 │ ├── test_lottery_rule.py # 规则分支用例 │ └── test_lottery_record.py # 结果与记录一致性用例 ├── utils/ │ ├── driver.py # 浏览器初始化 │ └── assertion_utils.py # 自定义断言 └── reports/ # 测试报告输出

页面对象模式(Page Object Model)在写Selenium脚本时非常建议采用。简单来说,把每个页面的定位器和方法封装成类,用例层只写业务步骤和数据校验,不直接暴露元素定位。这样做的好处是当页面改版时,只需要改对应的页面类,其他调用方不用动。我在验证抽奖系统的过程中,活动页的按钮定位改过至少三次,每次只改一个文件就能全部恢复,这就是封装的价值。

3. 用例拆分、数据准备与断言设计

3.1 从抽奖流程拆出可复用的测试场景

拿到抽奖规则之后,我不会急着写代码,而是先把场景表列出来。一个常规抽奖活动通常包含这些核心场景:

  1. 已登录用户正常抽奖,中奖弹窗展示奖品名称
  2. 已登录用户抽奖但未中奖,弹窗展示“谢谢参与”或“未中奖”
  3. 未登录用户点击抽奖,被重定向到登录页
  4. 抽奖次数耗尽后继续点击,按钮置灰或提示“今日次数已用完”
  5. 中奖后的奖品记录出现在“我的奖品”列表中
  6. 积分抽奖场景中,积分不足时无法发起抽奖
  7. 活动开始前与结束后,进入页面时的提示状态

这七个场景基本覆盖了抽奖系统最重要用户链路。我通常把它们拆成不同的测试类,每个测试类对应一组业务规则。比如test_lottery_basic.py放主流程,test_lottery_rule.py放各类条件分支,test_lottery_record.py专门做中奖记录的一致性验证。

每个用例的编写要遵循一个原则:尽量做数据前置。比如测试“次数耗尽”这个场景,最好的做法不是在用例里抽三次再验证第四次,而是通过修改活动配置把抽奖次数调成1,或者准备一个已经抽完次数的账号,直接用这个初始状态进入页面。这样能减少用例的运行时间和不稳定因素。

3.2 随机结果怎么断言才不算自欺欺人

这是抽奖系统自动化测试里很多人会卡住的问题。抽奖结果是随机的,你写一个断言说“弹窗应该出现恭喜你中了xxx”,但实际执行时结果可能是“未中奖”,用例就挂了。那究竟该断言什么?

我实践下来,靠谱的随机结果断言有四种方向。

第一种,断言弹窗出现了,但不指定具体内容。抽奖动作发起后,无论中没中,系统都应该给出一个明确的结果反馈,可能是弹窗也可能是Toast。我们断言“弹窗可见”就足够了,具体内容可以留给人工去观察。

第二种,断言结果样式符合预期。中奖弹窗通常包含奖品名称、兑奖按钮;未中奖弹窗通常只包含一个“知道了”按钮。可以通过判断按钮是否存在来区分两类结果。

第三种,大数据量统计验证。单次跑无法验证概率,但可以通过Selenium循环触发大量抽奖,统计中奖次数占比,再和配置里的概率做粗略区间判断。这个方法不能证明概率精确,但能发现概率配置没生效或者写反的问题。

第四种,一致性校验。这个是抽奖系统测试最关键的一环。弹窗显示“抽中A奖品”之后,打开“我的奖品”页面,检查A奖品是否在列表里。如果弹窗结果和记录结果不一致,说明前端展示或数据写入有Bug,这种用例比单纯断言弹窗内容有价值得多。

我一般把第四种作为主断言,其余作为辅助。抽取永远不可预测,但数据一致性必须可靠,这才是自动化能覆盖住的稳定逻辑。

3.3 测试数据与账号管理

抽奖系统自动化测试需要用到一些专用账号,比如新用户、老用户、积分足够、积分不足、黑名单用户、已抽完次数的用户。这些账号不能靠测试用例临时创建,应该在测试环境里预置好,并用一个账号配置文件管理起来。

测试数据也可以准备一份放在数据库中或配置文件中。比如某次活动设定的“每人每天抽3次”,那测试“次数耗尽”的账号,就可以在初始化数据时把这个账号的抽奖次数置满,而不是在测试过程中真的抽三次。这个细节能显著减少用例执行时间。

另外提醒一点,不要用生产环境账号跑自动化。抽奖涉及真实权益,跑了可能会触发实际发奖,无论对用户还是对数据都是事故级别的风险。自动化测试必须严格限定在测试环境或预发环境。

4. 核心脚本实现与稳定性优化

4.1 登录态处理、抽奖按钮点击与结果弹窗读取

先放一段我常用的核心测试代码,覆盖登录、进入活动、点击抽奖、读取弹窗结果、验证记录,这段代码不依赖某个具体前端实现,但要配合对应的页面对象来用。

import time import pytest from pages.login_page import LoginPage from pages.activity_page import ActivityPage from pages.result_page import ResultRecordPage from utils.driver import create_driver from utils.assertion_utils import assert_result_matches_record class TestLotteryBasic: @pytest.fixture(scope="class") def driver(self): driver = create_driver() yield driver driver.quit() def test_logged_in_user_lottery(self, driver): # 登录并进入活动页 login_page = LoginPage(driver) login_page.login_with("lottery_user_01", "password_123") activity_page = ActivityPage(driver) activity_page.enter_activity("summer_2024_lottery") # 点击抽奖前记录当前次数 before_times = activity_page.get_remaining_lottery_times() # 点击抽奖按钮 activity_page.click_lottery_button() # 等待结果弹窗出现 lottery_result = activity_page.get_lottery_result() assert lottery_result is not None, "抽奖后必须出现结果反馈" # 如果弹窗里有“中奖”关键词,检查记录列表 if lottery_result.get("is_won"): record_page = ResultRecordPage(driver) record_page.navigate_to_my_prizes() assert assert_result_matches_record(lottery_result), "弹窗中奖结果与中奖记录不一致" # 验证抽奖次数变化 after_times = activity_page.get_remaining_lottery_times() assert after_times == before_times - 1, "抽奖后次数未正确扣减"

这段代码有几个关键点。登录数据从配置读取,点击按钮前先获取剩余次数,弹窗读取放在显式等待之后,中奖后再去校验记录。我不会在点击后立刻就用find_element去拿弹窗元素,因为可能前端动画还在播放或者接口还没有返回,直接定位会偶发失败。

登录态的处理也是抽奖测试的一个坑。很多活动页面要求登录,如果每次用例都重新登录,会很慢。一个比较实用的方案是通过维护Cookie实现登录态复用:先手动登录一次拿到Cookie,然后保存到文件,用例启动时加载Cookie。对于测试环境,只要不做退出登录操作,Cookie在有效期内都能复用。这个方法能让十多个用例跑下来快一倍还多。

4.2 WebDriverWait是稳定性的关键

Selenium脚本不稳定,八成以上都和元素定位时机有关。按钮还没渲染出来你就去点,会报NoSuchElementException;弹窗还在动画过程中你就读文本,可能读到空字符串;接口还没返回结果,你已经在断言了,肯定时好时坏。

解决方式就是显式等待。我建议在稳定性的核心位置都使用WebDriverWait,不要迷信隐式等待。隐式等待只对findElement生效,对判断元素是否可点击、是否可见、文本是否变化这些场景帮不上忙。

我通常封装一个等待工具函数:

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_for_element(driver, locator, timeout=10): wait = WebDriverWait(driver, timeout) return wait.until(EC.visibility_of_element_located(locator)) def wait_for_clickable(driver, locator, timeout=10): wait = WebDriverWait(driver, timeout) return wait.until(EC.element_to_be_clickable(locator))

点击抽奖按钮前,我会用wait_for_clickable,等待按钮可点击后再执行点击操作。弹窗出现后,用wait_for_element等待弹窗可见,再读取文本。多数“测试偶发挂掉”的问题都因此解决。

还有一个小经验:对于结果弹窗这种可能还在加载的内容,有时需要等待元素中文本不为空,而不只是元素可见。我写过类似“等待弹窗内文本从空变为非空”的轮询逻辑,实测下来非常稳。

4.3 并发快速连点与多账号轮询的落地方式

前面讲到Selenium不适合做真正的并发压测,但可以做两件很有价值的事:一是模拟用户快速连点,检查前端是否做了重复提交拦截;二是用多个账号按顺序轮询执行抽奖,检查活动全局的库存。

快速连点场景,我通常用一个循环快速执行多次点击,然后观察页面状态。如果前端防重没做好,后端也没有做幂等校验,可能会出现多个弹窗叠加,或者抽奖次数被多次扣减。这个脚本写起来不复杂:

def test_rapid_click_should_not_duplicate(self, driver): activity_page = ActivityPage(driver) activity_page.enter_activity("summer_2024_lottery") activity_page.click_lottery_button_rapidly(times=5) # 等待稳定后,获取弹窗数量或抽奖记录数量 popup_count = activity_page.get_popup_count() lottery_count = activity_page.get_lottery_record_count() assert popup_count <= 1, "快速连点导致出现多个弹窗" assert lottery_count <= 1, "快速连点导致重复抽奖记录"

多账号轮询则是准备几个不同状态的账号,分别执行抽奖,然后检查每种账号在规则限制下的表现。比如账号A中奖、账号B未中奖、账号C积分不足,这三个结果合起来才能说明规则组合正确。

这类脚本的执行时间通常会比较长,单个用例从登录到断言可能要三十秒到一分钟,一套抽奖回归跑下来半小时很正常。所以一定要把用例设计成可并行执行,或者允许用标记(pytest.mark标签)选择只跑某部分用例。日常开发调试时我只跑主流程,回归时才跑全量。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

这类项目我陆陆续续维护了很长一段时间,遇到的疑难问题不少,挑出几个有代表性的做成表格,方便排查时直接对照。

常见现象可能原因排查思路与解决方案
点击抽奖按钮后无任何反馈按钮点击事件未绑定,或接口报错被前端吞掉打开浏览器开发者工具看Console报错;用Requests直接调抽奖接口看后端是否返回5xx
弹窗元素时有时无前端有动画延迟,或结果接口返回慢把隐式等待换成WebDriverWait,等待弹窗可见;必要时轮询等待文本非空
断言中奖记录查不到前端展示弹窗了,但后端写入失败,或页面列表刷新延迟先用接口查询中奖记录;区分页面刷新时机和真实数据缺失
脚本在无头模式下频繁失败无头模式渲染差异,部分弹窗样式加载异常先本地有头模式复跑;如果无头必现,改用xvfb或恢复有头模式跑关键用例
抽奖次数扣减不一致前后端对次数计算逻辑不一致,或前端重复请求对比接口返回的次数字段和页面展示字段;检查是否存在重复请求
登录态失效导致抽奖用例失败Cookie过期或测试环境单点登录退出维护Cookie刷新机制,前置步骤统一做登录态检查和续期
活动配置变更后脚本大面积失败元素定位依赖文案,活动文案被运营修改元素定位优先用data-id、name等稳定属性,避免依赖可见文案

5.2 两个印象深刻的Bug排查

第一个是弹窗显示已中奖,但奖品列表里什么都没有。这个问题特别坑,因为不是必现,而是偶发。排查过程大概是:先看后端日志,发现接口确实返回了中奖结果,但数据库写入时因为主键冲突失败,返回了异常段代码,前端拿不到最终确认状态就开始弹窗展示了。也就是说,前端弹窗触发时机不是等后端事务完全成功,而是等响应包返回就算成功,导致数据没真正落库。后来后端把抽奖发奖改成了事务,并增加了前端对最终状态的二次校验,问题才解决。Selenium在这个问题里的价值是:它用用户真实路径的方式,最容易暴露这种“界面看起来对了、数据实际错了”的隐形故障。

第二个是活动刚开始时,测试账号并发抽奖导致库存超发。这个在主流程用例里很难发现,因为单个用例执行的是串行操作。后来我写了一个脚本,用多个账号几乎同时发起抽奖,再统计中奖记录中同一个奖品被领取的数量,发现确实超过了配置库存。这个测试本质上不是Selenium做得最好的领域,但它帮助项目组确认了风险点,后续专门用接口压测工具覆盖并发场景。

5.3 几个自己总结的避坑技巧

  • 不要用time.sleep(3)这种方式硬等,不够稳定,等待时间短了会偶发失败,长了会拖慢整个测试集。除非是处理长动画,我一般不用固定sleep。
  • 元素定位少用XPath中的文本,比如“//div[contains(text(),'恭喜')]”,这类定位最容易受到文案调整影响。优先用id、name、data-testid这类稳定属性。
  • 断言结果时要保持耐心,先想清楚“这个步骤真正的业务意义是什么”,再决定断言什么。抽奖系统的断言重点不是奖品名,而是前后端数据一致性。
  • 保留测试截图。一旦用例失败,我习惯自动保存当前页面截图并附加到测试报告。很多问题看代码看不出原因,一看截图就明白了,比如按钮被遮罩层挡住、弹窗被浏览器通知挤掉等。

5.4 报告与持续集成配置

Selenium自动化测试做完之后,关键是能持续地跑,让团队在发布前自动回归。目前我维护的抽奖项目里,测试任务通过CI的定时任务触发,每天凌晨跑全量回归用例,发布前再手动触发一次。报告用pytest结合Allure生成,失败用例自动截图并挂到报告里,团队直接点开看失败现场。

Allure报告的配置不复杂,在项目根目录放一个pytest.ini,指定测试用例目录和报告输出位置:

[pytest] testpaths = testcases addopts = -s -q --alluredir=reports/allure-results

跑完测试之后执行一句命令生成HTML报告,传到统一位置供团队查看。整个流程下来,相当于每天自动把抽奖系统的主要链路“人肉走一遍”,但比人肉靠谱的是它不会因为困了累了就漏看弹窗。

6. 写在最后的一次真实体会

这套抽奖系统Selenium自动化流程,我从用例设计到落地维护经历了完整周期,最大的体会是:自动化测试的价值不在于“写了多少条用例”,而在于“每一条用例是不是真的替用户把住了风险点”。抽奖系统看似简单,实际最容易在数据一致性上翻车,而UI自动化恰好是把前端表现和数据落库串起来验证的有效手段。如果你是刚接手类似的自动化测试任务,我的建议是先花一半时间理清业务规则和异常分支,再花另一半时间写脚本,顺序反了,代码写得再漂亮也容易返工。如果你已经有一套在跑的用例,不妨重点检查一下“弹窗展示”和“记录列表”之间有没有做到交叉校验,这个位置的测试价值,远比多测一次按钮点击事件要高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询