☰
自动化测试验证码处理全攻略:图形、滑块与短信验证码实战
2026/10/13 5:29:00 网站建设 项目流程

自动化测试里最烦人的一件事,就是验证码。我在某公司带自动化项目时,测试环境里的登录用例跑得好好的,一到回归阶段就翻车,定位到最后全是验证码识别失败。项目临上线,CI构建红了一片,那滋味真的不好受。后来我把验证码处理单独拎出来做了一套方案,图形、滑块、短信几类都覆盖,才把这条链路彻底稳住。

这篇文章就围绕"自动化测试之验证码处理"展开,说清楚这几类验证码分别难在哪、实际处理时有哪些可以落地的方案,以及我在实操中踩过的坑和总结的经验。无论你是刚接触UI自动化,还是已经被验证码折磨到想放弃,这套思路都可以直接拿去参考。

1. 验证码类型识别与各自的难点

动手处理验证码之前,先得搞清楚一件事:验证码不是一种东西,而是好几类机制的统称。不同验证码背后的防自动化思路完全不同,你不区分类型就套统一方案,基本等于乱枪打鸟。

1.1 为什么验证码专门跟自动化测试过不去

验证码的初心是区分"真人"和"机器"。网站方怕的是有人用脚本批量注册、刷接口、撞库,所以设计出各种需要"人才能完成"的验证任务。而自动化测试本质上也是用程序模拟人操作,所以在网站方眼里,自动化脚本和被拦截的恶意程序长得一模一样。这不是逻辑漏洞,是根本性的对抗。

理解了这层关系,你就能明白为什么测试环境下不建议直接跟线上验证码死磕——你的目标不是"攻破"验证码,而是"让测试能跑起来,同时不影响被测系统的安全设计"。越是理解验证码校验的层次,越知道该在哪个环节介入。验证码一般分为前端展示层、后端校验层和业务跳出层,自动化要处理的核心是"让校验层放行",而不是真的去破解每一个像素。

1.2 常见验证码的分类与危险程度

我按实际碰到的频率,把常见验证码分成下面几类。

验证码类型常见形态对自动化的威胁程度典型场景
图形验证码四位字母数字、扭曲噪点中高,识别有误差登录、注册、找回密码
滑块验证码拼图滑块、按轨迹滑动高,涉及轨迹和人机判定登录、风控拦截
行为验证码点选文字、选图、无感验证极高,依赖环境特征支付、高风险操作
短信/邮件验证码6位数字、链接Token中,主要是获取链路二次验证、密码重置

这里想特别提醒一句:不要只盯着"识别图像"这一个方向。很多团队花了大量精力去训练识别模型,结果发现测试环境的验证码早就被配置成"万能码"了,或者后端接口可以直接置跳过标记。识别是兜底方案,不是最优方案。实际做方案设计时,我建议把"能否绕行/屏蔽"排在"能否识别"前面考虑,这能省掉至少一半工作量。

1.3 各类验证码的校验逻辑拆解

第一类是图形验证码。它本质是图像生成与识别的对抗,后端把答案存Session或Redis里,比对时忽略大小写。它防的是"无脑重放请求",但单看技术含量并不高,只要预处理干净,传统图像处理就能搞定绝大多数。

第二类是滑块验证码。它校验的不只是"缺口位置对不对",还有你从起点滑到终点的整个轨迹。正常人手滑动是加速度变化的过程,中间有停顿、有微小的抖动,时间通常在300ms到2000ms之间;而脚本如果匀速直线拉过去,哪怕缺口定位精确,也会因为轨迹太过完美被打回。这也就是为什么光做图像识别远远不够。

第三类是行为验证码。它会收集鼠标移动、点击热区、浏览器指纹、WebDriver特征等大量数据,用机器学习判断你是不是真人。这种方案识别层面的工作量最小,因为你甚至看不到传统意义上的"验证码图片",有时候它就是让你点一下按钮。难在需要通过自动化配置和测试环境策略绕过它,而不是硬碰硬去做真人模拟——成本实在太高。

第四类是短信验证码。后端不关心你怎么生成这6位数字,只关心提交的值对不对。自动化测试里真正的阻塞在于"怎么拿到这个值",在于打通短信接收、数据库读取或者后端接口回调的链路。

把每类验证码的底层校验逻辑摸清楚了,后面的方案选择就顺理成章。

2. 图形验证码识别处理实战

图形验证码是历史最悠久的一类,也是教程最多的。但这不意味着简单,我见过很多拿通用OCR去识别的人,成功率只有三四成。核心问题就出在"没有对图像做预处理"。

2.1 图像预处理才是识别率的胜负手

代码里最常用的库就是OpenCV。你拿到验证码图片之后,千万别直接丢给识别引擎,先做三步:灰度化、二值化、去噪。灰度化就是把彩色图转成黑白亮度图,公式大约是灰度值等于红绿蓝三分量按权重求和。二值化则是设定一个阈值,大于阈值的置白,小于阈值的置黑,把文字和背景彻底分开。

这一步的效果我举个例子你就懂了。原始图片里文字是深灰色,背景是浅灰色带一些噪点线条,人眼看着还行,但OCR引擎会把背景残影也当成笔画信息,一识别就乱。做完二值化之后,背景变纯白,文字变纯黑,识别引擎只需要处理标准的黑白图形,准确率直接从40%跳到85%以上。

降噪也很有讲究。常见做法是中值滤波或高斯模糊,把孤立噪点抹掉。如果验证码干扰线太多,还可以用形态学操作,先膨胀把断掉的笔画接起来,再腐蚀把细线去掉。我通常会写一段小脚本批量处理100张样本,肉眼扫一遍输出图,确认文字边缘是否完整。这一步不能省,后面识别引擎再强也架不住输入图像是糊的。

2.2 字符切割与识别引擎选择

预处理做完,接着要处理的是字符切分。四字符验证码虽然粘连不多,但自动切分还是会踩坑。最稳妥的轻量做法是"投影法":统计每一列黑色像素的数量,找到一个波谷(像素数接近0)的位置作为字符边界。如果字符之间有粘连,可以通过固定宽度做二次修正,比如整张图宽度是120像素,四字符就大概按30像素一个去切。参数计算不需要多精确,验证码本身会有一定畸变,切分时宁可多切一点也别漏字符。

切分完之后,每个字符就是一个小图。识别方案我建议分情况选:

  • 字符规整、字体统一的验证码:直接用模板匹配或传统OCR,轻量、快、维护成本低。
  • 字符扭曲严重、字体多变:上深度学习模型,比如YOLO或CRNN,但需要准备几千张标注样本,对团队成本要求很高。
  • 不想自己训模型:直接接成熟的OCR能力,或第三方打码平台,按张计费,适合小团队短期覆盖。

实操中我最常用的是"OpenCV预处理 + OCR识别"的组合,三百张测试样本上稳定达到了93%左右的识别率,而且单张识别速度在200ms以内,对UI自动化的整体耗时几乎没有影响。

这里有个参数需要根据实际图片微调:二值化的阈值。不同验证码的背景色深浅不一样,全局固定阈值容易翻车。更好的做法是用自适应阈值(比如OpenCV里的adaptiveThreshold),它对光照不均和背景渐变有更好的鲁棒性。我实际对比下来,自适应阈值比固定阈值整体提升大约10个百分点的识别率,强烈建议直接作为默认方案。

2.3 图形验证码处理的标准流程代码骨架

如果你用的是Python,结合OpenCV和OCR库,整体流程代码大致长这样。不需要死记,直接把骨架抄过去改改就行。

import cv2 import numpy as np def preprocess_captcha(img_path): img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应阈值,替代固定阈值 binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 41, 15 ) # 去噪:中值滤波 denoised = cv2.medianBlur(binary, 3) return denoised def split_chars(binary_img): # 按列投影切分,返回每个字符的ROI列表 height, width = binary_img.shape col_sums = np.sum(binary_img, axis=0) # 找连续空白区间作为切分点 chars = [] in_char = False start = 0 for x in range(width): if col_sums[x] > 0 and not in_char: in_char = True start = x elif col_sums[x] == 0 and in_char: in_char = False chars.append((start, x)) return chars

实际项目里,切分逻辑还需要处理"某一个字符被拆成了两段"的情况,比如字母i和j的点。我常用的兜底方案是:切出来的片段宽度如果明显小于正常字符宽度的40%,就把左右邻近片段合并,再用固定宽度规则校准。这种细节处理多了,识别率才会一点点往上涨。

2.4 我要泼的冷水:图像识别不是万能的

图像识别方案有一个天然上限,就是验证码的扭曲程度。一旦验证码加了严重的字符重叠、弧线变形、背景纹理模仿文字,传统预处理直接失效,深度学习模型也要大量样本才能硬扛。这时候再投入训练就不划算了。

我的经验是给团队定一条止损线:如果传统方案识别率低于85%,且短时间提不上去,就切换策略。要么改走测试环境屏蔽逻辑,要么接第三方打码平台兜底。死磕图像识别其实是在跟安全团队斗智斗勇,性价比很低。自动化测试要的是稳定和可控,而不是炫技。

3. 滑块验证码的轨迹模拟与缺口定位

滑块验证码是当前最常见的风控手段,处理起来的坑比图形验证码多得多。我最早天真地以为就是"找到位置,拖过去"两件事,结果被连续拦截几十次,才意识到它有两个独立的关卡:定位关和行为关。

3.1 缺口定位的两种实操方案

定位缺口的本质是在一张背景图上找出拼图块应该放进去的位置。背景图通常是一张被切割掉的带缺口的场景图,拼图块在左侧或下方单独显示。

方案一:基于边缘检测的轮廓匹配。把背景图和拼图块都转成灰度图,用Canny边缘检测提取轮廓,再通过模板匹配找到缺口位置。这个方案对清晰场景图效果很好,代码就是OpenCV里的matchTemplate操作,匹配完成后取最大响应点坐标,偏移量换算成滑块需要移动的像素距离。

import cv2 bg = cv2.imread("bg.png", 0) block = cv2.imread("block.png", 0) res = cv2.matchTemplate(bg, block, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # max_loc就是拼图块对应在背景图上的左上角

方案二:基于缺口边缘的"凹槽识别"。有些滑块缺口会有一个明显的深色边界,比其他区域梯度大。我常用Sobel算子计算水平梯度,找出梯度突变最大的那一列,加上拼图块宽度的一半,就是目标位置。这个方案不依赖拼图块图片,更适合拼图块跟背景图不是同源素材的场景。

实际项目里,这两种方案我建议都写,然后加一个置信度比较:哪个方案的匹配分数高,就用哪个结果。这比押注单一方案稳得多。

3.2 拖拽轨迹的模拟逻辑与参数计算

缺口定位解决的是"往哪拖",轨迹模拟解决的是"怎么拖"。滑块验证码的后端会记录你拖拽过程中的每一帧坐标位置和时间戳,然后分析速度曲线、加速度变化、是否有停顿、是否匀速直线等特征。真实人手拖动是一个"先加速、后减速、尾部回微调"的过程,全程约800ms到1.5s,中间伴有几毫秒到几十毫秒的停顿。

我分享一个实测有效的轨迹生成思路。先把总距离分成三段:

  • 第一段占60%距离,加速度为正,速度从0快速拉升。对应人手从静止开始发力拖动的阶段。
  • 第二段占30%距离,速度维持较高水平,但加速度开始降低。对应拖动过程中段,手速趋于稳定。
  • 第三段占最后10%距离,速度迅速下降,允许小幅回退。对应滑块到位前的减速和微调。

每一步都随机加上一个几十像素以内的扰动,有些点的时间间隔不均匀,模拟出手部微抖的感觉。提供参考伪代码逻辑:

import random import time def generate_track(distance): track = [] current = 0 t = 0 # 第一段:加速 while current < distance * 0.6: step = random.randint(5, 12) current += step t += random.uniform(0.01, 0.03) track.append((current, t)) mid = current # 第二段:维持高速 while current < distance * 0.9: step = random.randint(7, 15) current += step t += random.uniform(0.01, 0.02) track.append((current, t)) # 第三段:减速逼近,允许轻微回退 while current < distance: step = random.randint(1, 3) current += step t += random.uniform(0.02, 0.05) track.append((current, t)) # 末尾可选微调回退 if random.random() > 0.7: track.append((distance - random.randint(1, 3), t + 0.1)) track.append((distance, t + 0.2)) return track

参数不是写死的,每次生成都要随机化。同一套轨迹模板如果100%复用,后端很容易通过历史轨迹聚类识别出这是同一个自动化程序。

注意:轨迹模拟千万不能做成"直线匀速"。这类轨迹的典型特征就是每帧移动距离完全相等,后端风控模型几乎可以瞬间判定是机器行为。我第一次跑通滑块验证码就是忽略了这点,结果缺口定位全部正确,还是被拦截,后来把轨迹改成变速才通过。

3.3 WebDriver特征掩盖与浏览器环境准备

这是比轨迹模拟更深一层的坑。滑块验证码的后端不仅分析轨迹,还会去检测当前浏览器的自动化特征。比如WebDriver的标记、浏览器窗口的缩放比例、是否有真实的鼠标事件流、甚至Canvas指纹。很多自动化脚本之前没暴露,是因为验证码校验比较简单,一旦上滑块,这些特征全成了判定的依据。

我的做法是尽量不用裸的自动化配置跑滑块场景。一种有效的策略是使用浏览器以非自动化模式启动,配合用户目录加载真实的登录状态,减少被识别为全新自动化实例的概率。另外,执行滑块前先做几个真实的鼠标移动,让浏览器积累一段合法的鼠标事件历史,这样滑过去时显得更自然。

我也试过用无头模式跑滑块,结果稳定性很差。建议滑块相关用例尽量不要无头执行,宁可多开一个真实浏览器窗口,换取通过率。这个是我实际对比后的结论,无头模式在滑块场景的成功率基本只有有头模式的一半。

3.4 滑块处理的成本意识和替代思路

滑块验证码是几类验证码里投入产出比最低的。你要同时维护缺口定位、轨迹随机、环境伪装三块,每块都可能因为网站方策略升级而失效。如果你所在的团队做的是日常回归测试,我强烈建议先跟产品确认一下:测试环境能不能关闭滑块校验?

很多公司的测试环境其实保留了验证码的开关配置,只是默认没关。跟开发提一个配置工单,把测试环境的滑块校验临时关闭或改为简易校验,自动化回归链路瞬间稳定。这个方案不涉及任何对抗逻辑,也不会影响线上安全策略,这才是工程化的解决思路。

如果测试环境确实不能关,那就用第三方打码平台做人工兜底。平台上有真人来处理滑块,虽然慢一点,但稳定。自动化脚本只需要把滑块截图发给平台,再按返回的坐标执行滑动就行。这里要注意,打码平台一定要选支持测试环境验证的,别把真实业务流量引进去,否则容易触发更多风控。

4. 短信验证码的处理与测试环境绕行策略

短信验证码看似简单,却是很多自动化项目里最容易被低估的阻塞点。你脚本跑到了"输入短信验证码"这一步,卡住不动,后面全链路都白搭。处理起来需要区分两种情况:线上联调场景和纯测试环境场景,这两种的处理思路完全不同。

4.1 线上联调场景下的验证码获取

如果自动化脚本必须要走一遍线下的短信验证码流程,常见处理方式有三种,按实现难度排序:

第一种是最直接的数据库查码。很多系统的验证码会明文存表,或者用可逆加密方式存,查询最近一条发给某个手机号的记录就能拿到。实现上就是连数据库执行一条select语句,按手机号倒序排序取最新,再把结果填回去。前提条件是你的自动化环境有数据库读权限,这在不少公司里并不一定具备。

第二种是接短信平台回调。如果被测系统用的是某个短信服务商的接口,一般会有消息推送或回调地址配置,把验证码同步推送到一个指定的接收端,自动化脚本从接收端拉取。这个方案比查数据库要优雅,也不容易被数据权限卡住,但需要开发协助配回调,属于有协作成本的方案。

第三种是邮箱接收兜底。部分系统同时支持短信和邮箱验证,邮箱的接收自动化要简单得多。申请一个专用测试邮箱,开启IMAP,脚本定时轮询收件箱,从邮件正文里用正则把6位数字提取出来。这个方案的优点是完全不依赖被测系统内部结构,只要它在发邮件就行。

这三种方案我全都落地过,综合体验是:数据库查码最稳,短信回调最规范,邮箱兜底最适合临时救急。建议都提前准备好,因为线上环境随时可能调整权限。

4.2 测试环境绕行的核心思路

测试环境的短信验证码,是我个人最喜欢处理的场景。因为这里你可以从"测试设计"的角度去介入,而不是从"验证码对抗"的角度硬解。核心思路就三个字:留后门。

我在某个模拟项目X里推动过一个方案,测试环境的短信验证码逻辑增加一个规则:如果提交的验证码是6个8,直接放行,并且记录一条日志。这个"万能验证码"只对测试环境生效,线上完全不受影响。这么做的好处很明显:

  • 自动化脚本不再需要等待真实短信,执行耗时大幅缩短。
  • 没有外部依赖,CI跑得再频繁也不会因为短信延迟失败。
  • 日志里能看到哪些用例用了万能码,方便追溯。

除了万能码,还可以让开发提供测试专用接口,直接传入手机号返回当前验证码。这种后门接口只在测试环境暴露,并用内网访问控制保护起来。自动化脚本在调登录接口前先调这个后门接口拿到验证码,再走完整流程,既验证了真实校验链路,又不需要等待短信下发。

4.3 优先级建议:绕行>后门>自动接收

处理短信验证码时,我会建议团队按下面的优先级去做决策,而不是上来就研究短信自动接收:

  1. 优先推动测试环境关闭短信验证码或配置万能码。这是投入最小、效果最明显的一步。
  2. 次优是开发测试专用接口直接获取验证码。适合需要完整走校验链路的场景。
  3. 最后才是搭数据库查码或短信接收。工作量最大,链路最长,只推荐在无法绕行的场景使用。

你不用一次性把三个方案全做了,先跟开发聊一下测试环境的配置开关,通常半天时间就能搞定,自动化链路立刻通畅。这个优先级思路适用于绝大多数验证码类型,也是我这些年最想分享给自动化测试新人的一条经验:能在测试设计层面解决的问题,不要拖到测试执行层面去硬扛。

5. 常见问题排查与自动化集成经验

这一节我把这些年做验证码处理时踩过的坑集中整理一下,给出排查思路和解决方向。以下问题基本上涵盖了自动化测试集成验证码时九成以上的故障来源。

5.1 识别率高但用例仍然失败的排查思路

这是最典型的症状:单元测试里跑识别脚本,成功率90%以上,但整套自动化跑起来还是大量失败。原因通常不在识别本身,而是集成环节出了问题。

第一个常见坑是图片尺寸或格式不一致。UI自动化截取的验证码图片,可能包含外边框或多余背景,跟你在样本集里手动保存的干净图片差异很大。识别脚本拿到这种图,预处理直接跑偏。解决办法是写一个统一的截图裁剪函数,截取验证码元素指定区域,再统一缩放成标准尺寸,这样不管页面怎么变,识别入口永远一致。

第二个常见坑是等待时机不对。验证码图片可能还在加载中,脚本就开始截图,截到的是一张半成品。正确做法是先显式等待验证码图片的加载完成标志,等元素可见且图片字节稳定后再截图,避免截到加载中的占位图。

第三个坑是识别结果跟输入框的提交方式不匹配。有些系统提交前会把验证码转小写,有些要求原样提交,识别出来如果带了多余空格或容易混淆的字符,比如0和O、1和l,后端一比对就失败。解决思路是识别后统一清洗,去掉空格、全角转半角、按业务要求转大小写,再提交。

5.2 自动化执行环境导致的验证码异常

同一个验证码处理脚本,在本地跑没问题,一上CI就失败,这种问题我排查过很多回。绝大多数情况下不是脚本失效,而是执行环境变了。

在本地环境,你可能是真实浏览器窗口、有真实鼠标事件、有用户目录缓存,这些条件对滑块验证码和部分行为验证码非常友好。到了CI服务器,默认是无头模式、全新临时目录、没有任何历史数据,风控模型的评分直接拉满,验证码当然过不去。

解决办法有三个思路,按优先级排列:

  1. 给CI服务器增加常驻浏览器环境,固定用户目录,预置登录状态。
  2. 滑块类用例禁止用无头模式,尽量使用桌面会话方式运行。
  3. 在CI里增加失败重试机制,验证码识别失败后等待几秒重新尝试,规避偶发风控波动。

另外,如果CI服务器在海外或跨地域机房,IP归属地和登录行为不匹配也可能触发额外风控。这个问题要跟网络环境负责人确认,必要时做IP白名单申请或调整测试网络出口的固定IP。如果涉及跨机房访问被测环境,一定提前跟DevOps确认网络策略,别等自动化全挂了才去排查。

5.3 并行执行场景下的验证码冲突

很多团队为了让回归跑得快,开启了自动化用例的并行执行。验证码相关的用例一旦并行,概率性失败率飙升,原因很微妙:多个线程同时请求验证码,后端存储的验证码Session互相覆盖,或者共享数据源被并发取码取乱。

这个问题的排查方向主要集中在数据隔离。解决办法是让每个执行线程使用独立的用户身份和独立会话,保证验证码从生成、识别到提交都发生在同一会话内。另外,并行场景下万能码方案的优势极其明显,因为它不依赖具体会话,每个线程提交同一个万能码都能通过。如果你还在用真实短信验证码做并行,建议尽快切到万能码方案。

最后再补充一个经验:验证码处理相关脚本,一定要做独立的失败率统计和日志记录。别把所有失败混在业务断言里。我后来在项目里加了一个简单的验证码识别成功率监控,每次识别失败都会单独落日志,并记录当时的图片、预处理结果和识别结果。这样出了问题不用靠猜,直接看数据就能定位是哪一步崩的。

5.4 常见问题速查表

整理了一份常用速查,方便你排查时对照。

症状可能原因优先排查方向
图形验证码识别率低预处理不充分检查二值化与去噪效果,切换到自适应阈值
识别成功但提交失败结果清洗不干净检查多余空格、大小写、易混淆字符
滑块缺口定位错误匹配方案单一同时跑模板匹配和梯度检测,取置信度高的结果
滑块轨迹无误但仍被拦截轨迹过于匀速改成变速轨迹,加入停顿和微调
CI上验证码必失败无头模式+环境特征不足关闭无头模式,固定浏览器用户目录
并行用例验证码冲突会话/数据未隔离使用独立用户会话,优先切万能码方案
短信验证码收不到环境网络限制检查短信服务商配置,准备邮箱兜底
测试环境无法关闭验证码缺少配置开关跟开发确认开关项,推动万能码或后门接口

这套速查表是我多年实操的一个小结。真正动手时,你遇到的异常现象可能长得五花八门,但只要先判断"是哪一类验证码、卡在哪一个环节",再对照表格里的排查方向去找,一般都能把问题收敛到具体代码层面。自动化测试里的验证码处理,本质上就是一个不断缩小问题域的过程,别指望一把梭哈解决所有问题,能稳住的方案才是好方案。

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

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

立即咨询