1. 项目概述:当自动化测试遇上验证码与Cookie
做Web UI自动化测试的朋友,估计都绕不开两个“老大难”问题:验证码处理和Cookie管理。标题里的“12_web自动化测试 _验证码处理和cookie”,一看就是某个系列课程或实战总结的第十二章,核心就是解决这两个在自动化脚本中高频出现、又让人头疼的拦路虎。这不仅仅是写几行代码点个按钮那么简单,它背后涉及的是对Web应用安全机制的理解、对HTTP协议会话状态的掌控,以及在实际项目中如何设计出既稳定又灵活的测试策略。
简单来说,验证码是系统用来区分人和机器的,而Cookie是浏览器用来维持用户状态的。我们的自动化脚本作为“机器”,要模拟“人”的操作,就必须找到合适的方法绕过或处理验证码,同时又要像真人浏览器一样,正确地携带和使用Cookie,保持登录态、传递用户偏好。处理不好,脚本要么卡在登录页,要么动不动就掉线,测试的稳定性和效率无从谈起。这篇文章,我就结合自己趟过的坑,把验证码处理和Cookie管理的核心思路、常见方案以及那些文档里不会写的实操细节,给你掰开揉碎了讲清楚。无论你是刚接触UI自动化的新手,还是想优化现有框架的老手,这里都有你能直接拿去用的“解药”。
2. 验证码处理:从“硬刚”到“智取”的实战策略
验证码的存在,本质上是为了增加自动化攻击的成本,保护系统安全。但对于自动化测试而言,它就成了一个必须跨越的障碍。处理验证码没有银弹,需要根据验证码的类型、复杂度和项目实际情况来选择策略。核心原则是:在保证测试目的的前提下,寻求最高效、最稳定的解决方案,而不是追求百分百的通用识别。
2.1 常见验证码类型与应对思路分析
首先,我们得知道自己面对的是什么“敌人”。常见的Web验证码主要有以下几种:
- 数字/字母图片验证码:最经典的形式,由扭曲、粘连的数字字母组成,可能有背景干扰线。早期很多系统使用,现在逐渐减少,但仍有存量。
- 滑动拼图验证码:需要将缺失的拼图块拖动到正确位置。考验的是对图像缺口位置的识别和模拟拖动轨迹。
- 点选文字验证码:给出图片和一组文字,要求按顺序点击图片中指定的文字。难点在于文字识别和坐标定位。
- 算术验证码:如“3+5=?”,需要计算并输入结果。通常比较简单,但需要解析图片中的算式。
- 行为验证码(如极验、腾讯云验证码):集成了多种交互,如滑动、点选、推理等,背后有复杂的行为分析模型,防御能力最强。
- 短信/邮箱验证码:需要从短信或邮件中提取动态码。这涉及到另一个通道(手机收件箱或邮件服务器)的信息获取。
对于测试而言,我们的目标不是破解验证码(那是安全研究的范畴),而是让测试流程能顺利进行下去。因此,策略上可以分为几个层次:
- 绕过/禁用:这是最理想的情况,需要开发配合。在测试环境,为特定的测试账号或IP地址设置白名单,或者提供一个万能验证码(如“0000”)。
- 后端接口获取:有些系统在生成验证码的同时,会有一个仅供内部或测试使用的接口,返回验证码的明文。这是最稳定可靠的方式。
- OCR识别:对于简单的图形验证码,使用光学字符识别技术。识别成功率受图片复杂度影响大。
- 第三方打码平台:将验证码图片发送到专业平台,由人工或高精度模型识别后返回结果。需要付费,但稳定性和通用性较好。
- Cookie/Session跳过:对于登录后的操作测试,有时可以手动登录一次,然后获取有效的Cookie直接用于脚本,完全避开登录环节。
注意:在选择任何识别方案前,务必与项目负责人、开发人员沟通,并确认其符合法律法规和公司安全政策。严禁对生产环境或他人系统进行未经授权的自动化识别尝试。
2.2 实战方案一:测试环境万能验证码与后端接口调用
这是成本最低、稳定性最高的方案,应作为首选。
场景:你拥有测试环境的部署权限或能与开发团队紧密协作。
操作步骤:
- 沟通确认:向开发团队提出需求,说明自动化测试需要稳定绕过验证码。建议的方案是:在测试环境部署时,通过配置文件开关控制验证码的难度,或者为测试专用的账号/IP设置验证码白名单。
- 实现万能验证码:让开发在验证码校验逻辑中增加一个判断,如果输入的验证码是某个特定字符串(如“test”或“automation”),则直接校验通过。你在脚本中固定输入这个字符串即可。
- 调用后端接口:如果系统有验证码生成接口(例如
/api/captcha/generate),通常也会有一个配套的获取接口(例如/api/captcha/value?key=xxx)。你需要:- 首先调用生成接口,获取一个本次验证码对应的唯一key(可能放在响应头、Cookie或响应体里)。
- 然后调用获取接口,传入这个key,获取明文的验证码。
- 在UI页面上输入这个验证码。
- 这里的关键是模拟浏览器行为,处理好接口调用与页面操作之间的Session或Token关联。
Python + Selenium 示例(假设有获取接口):
import requests from selenium import webdriver driver = webdriver.Chrome() driver.get("https://test.example.com/login") # 1. 首先,可能需要从页面中获取生成验证码时使用的token或key # 例如,可能隐藏在某个img标签的data-key属性里,或者由前端JS生成 captcha_img = driver.find_element(By.ID, “captcha-img”) captcha_key = captcha_img.get_attribute(“data-key”) # 假设如此 # 2. 调用后端接口获取明文验证码(注意处理Cookie同步) session = requests.Session() # 将Selenium驱动当前页面的Cookie同步到requests session selenium_cookies = driver.get_cookies() for cookie in selenium_cookies: session.cookies.set(cookie[‘name’], cookie[‘value’]) # 调用获取验证码的接口 api_url = f“https://test.example.com/api/captcha/value?key={captcha_key}” response = session.get(api_url) captcha_text = response.text # 假设接口直接返回文本 # 3. 在页面上输入验证码 driver.find_element(By.ID, “captcha-input”).send_keys(captcha_text) # ... 继续输入用户名密码并登录实操心得:
- 与开发定好规范,比如万能验证码的字符串、接口的路径和参数,并写入项目的测试Wiki,避免后续人员不知情。
- 使用
requests.Session()可以自动管理Cookie,确保接口调用和浏览器操作处在同一个会话中,这是模拟关联请求的关键。 - 如果接口需要额外的认证头(如Token),可能需要从页面JavaScript变量或网络请求中提取。
2.3 实战方案二:OCR识别与第三方打码平台集成
当无法从后端直接获取验证码时,我们才考虑识别方案。
对于简单图片验证码(OCR): 可以使用开源的Tesseract OCR库,但识别率对图片质量要求高。通常需要先对图片进行预处理。
预处理+识别步骤:
- 截图:定位并截取验证码图片元素。
- 预处理:转灰度、二值化、降噪、去除干扰线、字符分割。这一步非常关键,直接决定识别成功率。
- 识别:使用Tesseract或其它OCR引擎进行识别。
- 后处理:根据验证码可能的字符集(如纯数字)对识别结果进行校正。
Python示例(使用pytesseract和PIL):
from PIL import Image, ImageFilter, ImageEnhance import pytesseract from selenium import webdriver driver = webdriver.Chrome() driver.get(“login_page_url”) # 定位验证码元素并截图 captcha_element = driver.find_element(By.ID, “captcha-img”) captcha_element.screenshot(‘captcha.png’) # 图像预处理 image = Image.open(‘captcha.png’) # 转灰度 image = image.convert(‘L’) # 二值化 (阈值可根据实际情况调整) threshold = 150 image = image.point(lambda p: p > threshold and 255) # 降噪 (中值滤波) image = image.filter(ImageFilter.MedianFilter(size=3)) # 增强对比度 enhancer = ImageEnhance.Contrast(image) image = enhancer.enhance(2.0) image.save(‘captcha_processed.png’) # OCR识别,并配置只识别数字(如果验证码是数字) custom_config = r’--oem 3 --psm 7 -c tessedit_char_whitelist=0123456789’ captcha_text = pytesseract.image_to_string(image, config=custom_config).strip() print(f“识别结果: {captcha_text}”) # 输入识别结果 driver.find_element(By.ID, “captcha-input”).send_keys(captcha_text)对于复杂验证码或追求稳定性: 推荐使用第三方打码平台,如超级鹰、图鉴等。流程通常是:
- 注册平台,获取软件ID和密钥。
- 将验证码图片(字节流或Base64编码)通过平台API发送。
- 平台返回识别结果。
- 将结果输入页面。
优点:识别率高,专人维护模型,支持种类多。缺点:产生费用,有网络依赖。
集成打码平台的核心代码逻辑:
import base64 import requests def identify_captcha_with_platform(image_path, platform_config): “””调用第三方打码平台””” with open(image_path, ‘rb’) as f: img_bytes = f.read() # 通常平台要求base64编码 b64_data = base64.b64encode(img_bytes).decode(‘utf-8’) # 构造请求参数 (以某个平台为例,具体看API文档) data = { ‘username’: platform_config[‘username’], ‘password’: platform_config[‘password’], ‘typeid’: ‘1001’, # 验证码类型编号,如1001代表4位数字字母 ‘image’: b64_data } resp = requests.post(‘http://api.example.com/decode’, data=data) result = resp.json() if result[‘success’]: return result[‘data’][‘code’] # 返回识别结果 else: raise Exception(f“打码失败: {result[‘message’]}”)踩坑记录:
- 识别率问题:本地OCR对扭曲、粘连严重的验证码基本无效。不要花太多时间调优,不如直接考虑打码平台。
- 成本控制:打码平台通常按次计费。在脚本调试阶段,频繁失败会浪费次数。建议在调试时先使用万能验证码或手动处理,脚本稳定后再切换。
- 异步加载:验证码图片可能是点击后动态生成的,需要确保在截图前,图片已经加载完成。使用
WebDriverWait等待元素可见或具备特定属性。 - 超时与重试:识别过程可能失败,需要加入重试机制。例如,识别后提交登录,如果页面提示验证码错误,则刷新验证码重新识别,最多重试3次。
2.4 实战方案三:Cookie跳过登录与滑动验证码处理
Cookie跳过登录:这其实不是处理验证码,而是完全规避了登录流程。适用于测试登录后功能的场景。
操作流程:
- 手动用浏览器正常登录一次系统。
- 打开开发者工具(F12),进入
Application或存储选项卡,找到Cookies,选择当前网站的域名。 - 你会看到一个Cookie列表。你需要找到那些用于维持登录态的关键Cookie,通常是
sessionid,token,auth等名称。将它们全部复制出来(可以右键Copy All,但注意格式)。 - 在你的自动化脚本启动浏览器后,在访问登录页或首页之前,通过
driver.add_cookie()方法将这些Cookie添加到WebDriver中。 - 添加完成后,刷新页面或跳转到目标页,此时应该已经是登录状态。
Python Selenium 示例:
from selenium import webdriver import json driver = webdriver.Chrome() driver.get(“https://target-website.com”) # 先访问域名,以便浏览器设置Cookie的域名上下文 # 加载之前保存的Cookie文件 (手动登录后通过插件或脚本导出的JSON) with open(‘cookies.json’, ‘r’) as f: cookies = json.load(f) for cookie in cookies: # 添加前可能需要删除‘sameSite’等Selenium不支持的属性 if ‘sameSite’ in cookie: if cookie[‘sameSite’] == ‘None’: cookie[‘sameSite’] = ‘Lax’ # 或直接删除该字段 try: driver.add_cookie(cookie) except Exception as e: print(f“添加Cookie {cookie[‘name’]} 时出错: {e}”) # 添加完Cookie后,刷新页面或跳转到需要登录的页面 driver.refresh() # 或者 driver.get(“https://target-website.com/dashboard”) # 此时应已处于登录状态重要提示:Cookie有生命周期(过期时间
expiry)和域名限制(domain)。确保你添加的Cookie对当前驱动访问的域名有效,且未过期。过期时间是一个Unix时间戳。
滑动验证码处理:对于滑动拼图验证码,核心是计算缺口位置,并模拟人的滑动轨迹。
处理步骤:
- 获取背景图和缺口图:通常可以通过元素定位,获取两张图片的
src属性,然后下载或转换为PIL Image对象。 - 识别缺口位置:这是最关键的步骤。可以使用图像处理库(如OpenCV)进行模板匹配。将小的缺口图(滑块)在大的背景图上滑动,找到最匹配的位置,其X轴偏移量就是需要滑动的距离。
- 模拟滑动:计算出滑动距离后,不能简单地将滑块瞬间移动到终点,因为会被检测为机器操作。需要模拟人的滑动轨迹:先加速,再匀速,最后减速。使用Selenium的
ActionChains来拖拽元素。
Python + OpenCV + Selenium 示例:
import cv2 import numpy as np from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By import time, requests from io import BytesIO from PIL import Image driver = webdriver.Chrome() driver.get(“login_page_with_slide”) # 1. 获取背景图和滑块图URL bg_img_url = driver.find_element(By.CLASS_NAME, “bg-img”).get_attribute(“src”) slide_img_url = driver.find_element(By.CLASS_NAME, “slide-img”).get_attribute(“src”) # 2. 下载图片并转换为OpenCV格式 bg_resp = requests.get(bg_img_url) slide_resp = requests.get(slide_img_url) bg_image = cv2.imdecode(np.frombuffer(bg_resp.content, np.uint8), cv2.IMREAD_COLOR) slide_image = cv2.imdecode(np.frombuffer(slide_resp.content, np.uint8), cv2.IMREAD_COLOR) # 3. 使用模板匹配找出缺口位置 result = cv2.matchTemplate(bg_image, slide_image, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) slide_length = max_loc[0] # 匹配位置的x坐标,即缺口起始位置 # 注意:网页上的图片可能经过缩放,需要计算缩放比例 # 获取网页上背景图元素的真实宽度 bg_element = driver.find_element(By.CLASS_NAME, “bg-img”) bg_width = bg_element.size[‘width’] # 获取原始背景图片的宽度 bg_img_original_width = bg_image.shape[1] scale = bg_width / bg_img_original_width # 计算在网页上需要滑动的实际距离 slide_distance = slide_length * scale # 4. 模拟人的滑动轨迹 slider = driver.find_element(By.CLASS_NAME, “slider-button”) action = ActionChains(driver) action.click_and_hold(slider).perform() # 模拟滑动轨迹:先加速,后减速 total_tracks = [] current = 0 mid = slide_distance * 0.8 t = 0.2 v = 0 while current < slide_distance: if current < mid: a = 2 # 加速度 else: a = -3 # 减速度 s = v * t + 0.5 * a * t * t v = v + a * t current += s total_tracks.append(round(s)) # 执行滑动 for track in total_tracks: action.move_by_offset(track, 0).perform() time.sleep(t/10) # 加入微小间隔,更拟人 action.release().perform() time.sleep(1) # 等待验证结果注意事项:
- 缩放比例:网页显示的图片尺寸和原始图片尺寸可能不同,必须计算缩放比例,否则滑动距离会错。
- 轨迹检测:高级的反爬系统会检测滑动轨迹是否符合人类特征。上述的匀加速-匀减速模型是基础,更复杂的系统可能需要更随机的轨迹,甚至加入Y轴的微小抖动。
- 动态加载:有些验证码的缺口位置每次会随机变化,需要每次重新计算。
- 失败重试:滑动验证可能因为轨迹或位置计算偏差而失败,需要加入判断(如检查是否出现成功提示)和重试逻辑。
3. Cookie管理:维持会话状态的基石
如果说验证码是门卫,那Cookie就是你的门禁卡。在Web自动化中,Cookie管理不好,就会出现“登录状态丢失”、“页面跳转后数据没了”等问题。理解Cookie的工作原理,并能在Selenium中熟练操作,是编写稳定自动化脚本的基础。
3.1 Cookie的核心概念与在自动化中的重要性
Cookie是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器下次向同一服务器再发起请求时,会自动携带Cookie,服务器据此识别用户身份和状态。在自动化测试中,Cookie管理主要涉及:
- 登录状态保持:登录成功后,服务器会下发包含会话ID(如
JSESSIONID,PHPSESSID)或Token的Cookie。后续所有请求携带这个Cookie,服务器就知道你是谁。 - 用户偏好记录:如语言设置、主题样式等。
- 跟踪与统计:一些分析工具会使用Cookie。
为什么在自动化中要专门管理Cookie?
- 效率:避免每次测试都从头执行完整的登录流程(尤其是需要验证码时)。
- 稳定性:直接使用有效的Cookie可以绕过不稳定的登录环节。
- 测试特定场景:测试Cookie过期、失效、篡改等安全相关用例。
- 多环境切换:在不同环境(测试、预发布)使用不同的认证Cookie。
3.2 Selenium操作Cookie的完整API详解
Selenium WebDriver提供了完整的Cookie操作API,功能非常直接。
获取所有Cookie:
all_cookies = driver.get_cookies() # 返回一个列表,每个元素是一个字典,包含‘name’, ‘value’, ‘domain’, ‘path’, ‘expiry’(Unix时间戳), ‘secure’, ‘httpOnly’等键。 print(all_cookies)获取指定名称的Cookie:
specific_cookie = driver.get_cookie(‘sessionid’) # 返回一个字典,如果不存在则返回None。添加Cookie:
# 添加一个Cookie。注意:添加Cookie前,driver必须已经访问过该Cookie所属的域名。 driver.add_cookie({ ‘name’: ‘my_cookie’, ‘value’: ‘cookie_value’, ‘domain’: ‘.example.com’, # 可选,默认为当前域名 ‘path’: ‘/’, # 可选 ‘secure’: True, # 可选,仅HTTPS ‘httpOnly’: False, # 可选,脚本是否可访问 ‘expiry’: 1740816000 # 可选,过期时间戳 })关键点:
driver.add_cookie()必须在当前浏览器会话的域名上下文与Cookie的domain匹配时才能成功。通常的做法是先driver.get(“https://domain.com”)访问一下该域名的主页,然后再添加Cookie。
删除Cookie:
# 删除指定Cookie driver.delete_cookie(‘cookie_name’) # 删除所有Cookie driver.delete_all_cookies()3.3 实战:Cookie的持久化、复用与失效处理
单纯会操作API还不够,要在项目中用好Cookie,需要一套管理策略。
场景一:Cookie持久化与复用(实现免登录)这是最常用的技巧。将手动登录后获取的Cookie保存到文件(如JSON),后续脚本运行时直接加载。
步骤:
- 手动登录并导出Cookie。可以写一个简单的脚本,在手动登录后执行,将
driver.get_cookies()的结果保存为JSON文件。# save_cookies.py from selenium import webdriver import json, time driver = webdriver.Chrome() driver.get(“https://your-test-site.com/login”) input(“请手动完成登录,然后按回车键继续...”) # 登录后,Cookie已设置 cookies = driver.get_cookies() with open(‘cookies.json’, ‘w’) as f: json.dump(cookies, f) print(“Cookie已保存至cookies.json”) driver.quit() - 在自动化脚本中加载Cookie。脚本启动后,先访问目标域名,然后加载Cookie文件。
# test_with_cookies.py from selenium import webdriver import json def create_driver_with_cookies(cookie_file_path): driver = webdriver.Chrome() # 必须先访问域名,哪怕是个空白页,以建立正确的域名上下文 driver.get(“https://your-test-site.com/”) # 删除可能存在的旧Cookie,避免冲突 driver.delete_all_cookies() with open(cookie_file_path, ‘r’) as f: cookies = json.load(f) for cookie in cookies: # 处理可能存在的‘expiry’类型问题(JSON保存后可能是float) if ‘expiry’ in cookie: cookie[‘expiry’] = int(cookie[‘expiry’]) try: driver.add_cookie(cookie) except Exception as e: print(f“跳过无效Cookie {cookie.get(‘name’)}: {e}”) # 再次访问目标页面,此时应携带Cookie driver.get(“https://your-test-site.com/dashboard”) return driver driver = create_driver_with_cookies(‘cookies.json’) # 此时driver应处于登录状态,可以开始后续测试
场景二:Cookie失效的自动检测与重新登录Cookie会过期。脚本不能假设Cookie永远有效,必须有容错机制。
策略:
- 启动时检查:加载Cookie后,访问一个需要登录的API或页面,检查返回内容或页面元素,判断是否仍处于登录状态。
- 操作中检查:在关键操作(如提交表单)前,可以检查页面上是否存在登录相关的元素(如用户头像、退出按钮)。
- 失败后重试:如果检测到未登录,则触发重新登录流程(可能需要处理验证码),并更新Cookie文件。
示例代码框架:
class AutoTestWithCookie: def __init__(self, cookie_file=‘cookies.json’): self.cookie_file = cookie_file self.driver = webdriver.Chrome() self.is_logged_in = False def ensure_login(self): “””确保处于登录状态””” if self._check_cookie_valid(): self.is_logged_in = True return # Cookie无效,执行登录 self._perform_login() # 登录成功后保存新的Cookie self._save_cookies() self.is_logged_in = True def _check_cookie_valid(self): “””检查当前Cookie是否有效””” try: # 先尝试加载已有Cookie self.driver.get(“https://your-test-site.com/”) if os.path.exists(self.cookie_file): # ... 加载Cookie代码 ... pass # 访问一个需要登录才能看到的页面或API self.driver.get(“https://your-test-site.com/api/user/profile”) # 检查响应或页面元素,判断是否登录 # 例如,页面包含用户信息,或者API返回了用户数据而非401错误 # 这里简化处理,检查某个登录后特有的元素 user_element = self.driver.find_elements(By.ID, “user-avatar”) return len(user_element) > 0 except Exception as e: print(f“检查登录状态时出错: {e}”) return False def _perform_login(self): “””执行完整的登录流程(可能需要处理验证码)””” self.driver.get(“https://your-test-site.com/login”) # … 输入用户名、密码、处理验证码 … # 点击登录按钮 # 等待登录成功跳转 WebDriverWait(self.driver, 10).until( EC.url_contains(“/dashboard”) ) def _save_cookies(self): “””保存当前有效的Cookie到文件””” cookies = self.driver.get_cookies() with open(self.cookie_file, ‘w’) as f: json.dump(cookies, f) # 后续的测试方法,在开始前调用 ensure_login def test_something(self): self.ensure_login() # … 具体的测试步骤 …场景三:多账号Cookie隔离测试中可能需要切换不同角色的账号。可以为每个账号准备独立的Cookie文件,或者使用不同的浏览器Profile(用户数据目录)。
使用不同Profile:
from selenium import webdriver from selenium.webdriver.chrome.options import Options def create_driver_for_user(user_profile_dir): options = Options() options.add_argument(f“--user-data-dir={user_profile_dir}”) # 可以指定Profile目录,这样每个Driver实例有独立的Cookie、缓存等 driver = webdriver.Chrome(options=options) return driver # 为管理员和普通用户创建不同的驱动实例 admin_driver = create_driver_for_user(“/path/to/admin_profile”) user_driver = create_driver_for_user(“/path/to/user_profile”)3.4 高级话题:处理HttpOnly、Secure Cookie与同源策略
- HttpOnly Cookie:这种Cookie无法通过JavaScript(包括Selenium的
execute_script)读取,主要是为了防范XSS攻击。但Selenium WebDriver是通过浏览器调试协议操作的,可以正常获取和添加HttpOnly Cookie。driver.get_cookies()返回的字典里,httpOnly字段为True的就是这类Cookie。 - Secure Cookie:仅在HTTPS连接时才会被发送。Selenium在访问HTTPS站点时,可以正常处理Secure Cookie。如果你在本地测试HTTP环境,可能需要让开发暂时关闭这个标志,或者配置测试环境使用HTTPS。
- 同源策略:Cookie有
domain和path属性限制。你不能给driver添加一个属于google.com的Cookie,然后期望在访问baidu.com时携带它。添加Cookie前,必须确保driver当前页面的域名与Cookie的domain匹配或符合其规则(例如,Cookie的domain是.example.com,则可以用于a.example.com和b.example.com)。
4. 框架集成与最佳实践
将验证码处理和Cookie管理融入到你的自动化测试框架中,能让代码更清晰、更易维护。
4.1 设计可配置的验证码处理模块
一个好的验证码处理模块应该支持多种策略,并且可以通过配置轻松切换。
模块设计思路:
- 定义策略接口:所有验证码处理方式都实现同一个接口,比如
CaptchaSolver,包含一个solve(captcha_element)方法。 - 实现具体策略:
DisableCaptchaSolver(输入万能码)、OcrCaptchaSolver、ThirdPartyPlatformSolver、ApiCaptchaSolver(调用后端接口)等。 - 工厂模式选择:根据配置文件(如
config.ini或环境变量)决定实例化哪个策略。 - 上下文注入:将验证码处理器注入到需要它的页面对象(Page Object)或测试步骤中。
简化的代码结构:
# captcha_solver.py from abc import ABC, abstractmethod import pytesseract from PIL import Image import requests class CaptchaSolver(ABC): @abstractmethod def solve(self, driver, captcha_element): “””返回识别出的验证码文本””” pass class DisabledCaptchaSolver(CaptchaSolver): def __init__(self, universal_code=“test”): self.universal_code = universal_code def solve(self, driver, captcha_element): return self.universal_code # 直接返回万能码 class OcrCaptchaSolver(CaptchaSolver): def __init__(self, preprocessing_steps=None): self.preprocessing = preprocessing_steps or [] def solve(self, driver, captcha_element): # … 截图、预处理、OCR识别 … return recognized_text class ApiCaptchaSolver(CaptchaSolver): def __init__(self, api_url, auth_params): self.api_url = api_url self.auth = auth_params def solve(self, driver, captcha_element): # 从页面获取key,调用接口 captcha_key = captcha_element.get_attribute(“data-key”) response = requests.get(self.api_url, params={‘key’: captcha_key}, auth=self.auth) return response.text # config.yaml 或 config.ini # captcha: # strategy: “api” # 可选: disabled, ocr, api, third_party # api_url: “http://test-env.internal/api/captcha” # universal_code: “000000” # 在框架初始化时 config = load_config() if config[‘captcha’][‘strategy’] == ‘api’: solver = ApiCaptchaSolver(config[‘captcha’][‘api_url’], …) elif config[‘captcha’][‘strategy’] == ‘disabled’: solver = DisabledCaptchaSolver(config[‘captcha’][‘universal_code’]) # … 其他策略 … # 在登录页面对象中使用 class LoginPage: def __init__(self, driver, captcha_solver): self.driver = driver self.solver = captcha_solver self.captcha_input = (By.ID, “captcha”) self.captcha_img = (By.ID, “captcha-img”) def input_captcha(self): captcha_element = self.driver.find_element(*self.captcha_img) code = self.solver.solve(self.driver, captcha_element) self.driver.find_element(*self.captcha_input).send_keys(code)4.2 Cookie管理器的封装
同样,可以封装一个CookieManager类,负责Cookie的加载、保存、验证和刷新。
核心功能:
load(cookie_file): 从文件加载Cookie到当前driver。save(cookie_file): 将当前driver的Cookie保存到文件。is_valid(): 检查当前Cookie是否有效(通过访问一个鉴权端点)。refresh(): 如果无效,执行重新登录流程并更新Cookie文件。
将其与测试框架的setUp(用例前)和tearDown(用例后)钩子结合,实现全局的登录态管理。
4.3 稳定性与异常处理增强
验证码处理的重试机制:
def solve_captcha_with_retry(solver, driver, element, max_retries=3): for attempt in range(max_retries): try: code = solver.solve(driver, element) # 输入验证码并提交后,检查是否成功(例如,错误提示是否出现) # 这里假设有一个检查验证码是否正确的方法 if verify_captcha_success(driver): return code else: print(f“第{attempt+1}次识别失败,验证码错误。刷新重试...”) refresh_captcha(driver) # 刷新验证码图片 element = driver.find_element(...) # 重新定位元素 except Exception as e: print(f“第{attempt+1}次识别出现异常: {e}”) if attempt == max_retries - 1: raise refresh_captcha(driver) raise Exception(“验证码识别重试次数用尽”)Cookie失效的优雅降级: 在测试用例中,如果关键操作因Cookie失效失败,不应让整个用例崩溃,而应记录错误、尝试重新登录并重试操作,或者标记用例为“阻塞状态”。
5. 常见问题排查与调试技巧
在实际操作中,你会遇到各种各样奇怪的问题。这里记录一些典型的坑和排查思路。
5.1 验证码处理常见问题
问题1:OCR识别率始终很低。
- 排查:首先检查预处理后的图片。把预处理后保存的图片(
captcha_processed.png)打开看看,字符是否清晰可辨?干扰线去除了吗?如果人眼都难分辨,机器识别率自然低。 - 解决:
- 调整预处理参数:二值化的阈值(
threshold)、滤波器的尺寸和类型需要针对特定验证码样式调整。 - 尝试不同OCR引擎和模式:Tesseract有多个OCR引擎模式(
--oem)和页面分割模式(--psm)。对于单行验证码,--psm 7(单行文本)通常比默认模式好。也可以尝试其他OCR库,如easyocr。 - 放弃本地OCR:如果验证码设计复杂,果断转向第三方打码平台,节省时间。
- 调整预处理参数:二值化的阈值(
问题2:使用打码平台,识别成功了但登录还是失败。
- 排查:
- 验证码是否一次性有效?有些验证码在一次识别失败(即使识别正确)或超时后就会失效。确保识别后立即输入并提交。
- 网络延迟:从截图、上传到获取结果有时间差,验证码可能已过期。可以尝试在获取验证码图片元素后立即截图并发送,减少延迟。
- 输入错误:打印出平台返回的识别结果,确认没有多余空格或换行符。有些平台返回的结果可能包含干扰字符。
- 解决:在输入前对识别结果进行
strip()处理。加入重试逻辑,当登录失败提示“验证码错误”时,刷新验证码重试。
问题3:滑动验证码滑动后,提示“操作太快”或“验证失败”。
- 排查:轨迹太假。机器生成的匀速或简单加速减速轨迹容易被识别。
- 解决:
- 引入随机性:在轨迹生成函数中加入小的随机偏移量(
random.uniform(-2, 2)),模拟人手的不稳定。 - 模拟人类犹豫:在滑动开始前和结束后,加入短暂的随机等待(
time.sleep(random.uniform(0.1, 0.5)))。 - 加入回拉:人类操作有时会稍微过头再拉回来。可以在接近终点时,加入一个很小的反向移动。
- 使用更真实的轨迹算法:搜索“Selenium模拟人类滑动轨迹”,有一些开源算法模拟了更复杂的速度曲线。
- 引入随机性:在轨迹生成函数中加入小的随机偏移量(
5.2 Cookie管理常见问题
问题1:add_cookie()失败,报错“Invalid cookie domain”。
- 原因:这是最常见的问题。你尝试添加的Cookie的
domain属性与浏览器当前页面的域名不匹配。 - 解决:
- 在
add_cookie之前,必须先使用driver.get()访问一次该Cookie所属的域名(或父域名)。例如,Cookie的domain是.example.com,你需要先访问https://www.example.com或https://example.com。 - 检查Cookie字典中的
domain字段。从浏览器复制出来的Cookie,其domain可能带有前导点(如.example.com),这表示对该域名及其所有子域名有效。确保你添加的domain值与浏览器中的一致。 - 如果是从
get_cookies()获取后直接保存再添加,通常没问题。如果是手动构造Cookie字典,要特别注意domain格式。
- 在
问题2:添加Cookie后,刷新页面登录状态依然无效。
- 排查:
- Cookie是否关键:确认你添加的Cookie包含了维持登录态的关键Cookie(如
sessionid,token)。有时会有多个Cookie,缺一不可。 - Cookie已过期:检查Cookie字典中的
expiry字段(Unix时间戳)。如果已经过期,自然无效。在添加前可以判断一下。 - 路径问题:
path属性限制了Cookie的有效路径。如果你添加的Cookie的path是/admin,而你访问的是/user页面,则该Cookie不会被发送。通常登录Cookie的path是/。 - Secure标志:如果Cookie设置了
secure=True,但当前页面是HTTP协议,浏览器不会发送它。确保测试环境使用HTTPS,或者让开发在测试环境关闭Secure标志。 - 添加时机:确保在添加Cookie后,刷新了页面或导航到了新页面,浏览器才会在请求中携带新的Cookie。仅仅添加不会立即生效。
- Cookie是否关键:确认你添加的Cookie包含了维持登录态的关键Cookie(如
- 解决:使用浏览器开发者工具的
Application->Storage->Cookies面板,对比手动登录后和脚本添加Cookie后,该域名下的Cookie列表是否完全一致。这是最直接的调试方法。
问题3:从get_cookies()获取的Cookie列表里,没有找到关键的session cookie。
- 原因:可能是
HttpOnlyCookie在某种情况下获取不全,或者该Cookie是Secure且当前连接不是HTTPS。更常见的原因是,你获取Cookie的时机不对。必须在登录成功、页面完全加载后获取。 - 解决:在保存Cookie前,确保已经跳转到了登录后的页面(如仪表盘)。可以加入一个显式等待,等待登录后特有的元素出现,再执行
get_cookies()。
5.3 通用调试技巧
- 多用
driver.get_cookies()和driver.current_url打印信息:在关键步骤前后,打印当前的Cookie和URL,能帮你理清上下文。 - 善用浏览器开发者工具:
- Network面板:查看登录请求和后续请求的
Request Headers,确认Cookie是否正确被携带。这是验证Cookie是否生效的金标准。 - Application面板:实时查看和修改当前页面的Cookie,可以手动修改值来测试,或者对比脚本添加后的状态。
- Network面板:查看登录请求和后续请求的
- 截图辅助:在验证码识别的前后、滑动验证码滑动前后,使用
driver.save_screenshot(‘debug_step1.png’)保存截图,便于离线分析问题。 - 降低执行速度:在调试时,在关键操作间加入
time.sleep(2),让你有足够时间观察浏览器状态。当然,正式脚本中应该用WebDriverWait代替sleep。
处理验证码和Cookie是Web UI自动化从“玩具”走向“实用”的关键一步。没有一劳永逸的方案,最好的策略是根据项目实际情况,组合运用多种手段:在测试环境优先寻求开发支持(万能码或接口),在不得已时使用可靠的第三方服务,并将这些能力封装成框架的稳固组件。记住,自动化测试的目的是提升效率,而不是挑战安全机制。在合法合规的前提下,找到那个平衡点,你的自动化之路才会越走越顺。