【测试】selenium拦截Ajax(XHR)等异步请求数据

【测试】selenium拦截Ajax(XHR)等异步请求数据

一、背景与问题

在Web自动化测试中,很多页面数据是通过AJAX(Asynchronous JavaScript and XML)或Fetch API异步加载的。这些请求通常通过XHR(XMLHttpRequest)或Fetch API发送,Selenium默认无法直接获取这些请求的响应数据。

例如,在测试一个电商网站的搜索功能时,用户输入关键词后页面会通过AJAX请求后端接口获取商品数据,Selenium在页面加载完成后可能无法直接获取这些数据。这种情况下,测试人员需要通过拦截请求来获取响应数据进行验证。

传统解决方案有两种:

  1. 使用浏览器开发者工具手动查看网络请求
  2. 使用Selenium的get方法获取页面源码后正则提取数据(不推荐)

这两种方案都存在局限性:前者需要人工操作,后者容易受页面结构变化影响。因此,我们需要一种自动化、可复用的方案来拦截和解析异步请求数据。

二、基本原理

Selenium通过WebDriver与浏览器内核通信,可以利用浏览器的DevTools Protocol(简称CDP)实现网络请求拦截。其核心原理如下:

  1. 通过execute_cdp方法发送CDP命令
  2. 注册Network域的监听器
  3. 获取请求的URL、方法、头信息、响应体等数据
  4. 在断言时使用这些数据进行验证

关键CDP命令包括:

  • Network.setRequestInterceptionEnabled(true):启用请求拦截
  • Network.create interception:创建拦截器
  • Network.continueInterception():继续请求处理
  • Network.getResponseBody():获取响应体数据

三、环境准备

# 安装Python依赖
pip install selenium==4.14.0

浏览器要求:

  • Chrome 120+ 需要ChromeDriver 120+
  • Firefox 120+ 需要GeckoDriver 120+
  • 建议使用最新版Chrome浏览器

代码结构建议:

# 项目结构
test_ajax_interception/
├── test_ajax.py        # 测试用例
├── utils/
│   └── interceptors.py # 拦截器工具类
└── config/
    └── browser_config.py # 浏览器配置

四、核心实现

方法一:使用selenium-wire库(推荐)

selenium-wire是基于Selenium的扩展库,可方便地拦截网络请求。

from seleniumwire import webdriver

def intercept_ajax_requests():
    # 创建浏览器实例
    driver = webdriver.Chrome()
    
    # 设置请求拦截
    driver.request_interceptor = lambda request: None
    
    # 获取请求数据
    for request in driver.requests:
        if request.method == 'GET' and 'api' in request.url:
            print(f"拦截到AJAX请求: {request.url}")
            print(f"请求头: {request.headers}")
            print(f"响应体: {request.response.body}")
    
    # 关闭浏览器
    driver.quit()

关键代码解释:

  • request_interceptor用于设置请求拦截器
  • requests属性包含所有网络请求记录
  • request.response.body获取响应体内容(注意:需要等待请求完成)

方法二:使用CDP直接拦截

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

def cdpp_intercept_ajax():
    driver = webdriver.Chrome()
    
    # 注册CDP监听器
    def on_request(request):
        print(f"拦截到AJAX请求: {request.url}")
        print(f"请求头: {request.headers}")
        # 可以选择继续请求或中断
        return webdriver.ChromeOptions().to_capabilities()
    
    # 启用请求拦截
    driver.execute_cdp_cmd('Network.setRequestInterceptionEnabled', {'enabled': True})
    
    # 注册监听器
    driver.execute_cdp_cmd('Network.createIntercept', {'name': 'my_intercept'})
    
    # 等待请求完成
    time.sleep(5)
    
    # 获取响应数据
    for request in driver.requests:
        if 'api' in request.url:
            print(f"响应体: {request.response.body}")
    
    driver.quit()

关键代码解释:

  • Network.setRequestInterceptionEnabled启用拦截
  • createIntercept创建拦截器
  • requests属性包含所有网络请求记录

方法三:自定义拦截器(复杂场景)

def custom_intercept():
    driver = webdriver.Chrome()
    
    # 自定义拦截器
    def on_request(request):
        # 获取请求参数
        params = request.query_string
        
        # 解析JSON参数
        try:
            params = json.loads(params)
        except json.JSONDecodeError:
            pass
        
        # 记录请求数据
        print(f"请求URL: {request.url}")
        print(f"请求参数: {params}")
        
        # 模拟响应
        return {
            'response': {
                'status': 200,
                'body': json.dumps({'token': 'test_token'}),
                'headers': {'Content-Type': 'application/json'}
            }
        }
    
    # 设置拦截器
    driver.request_interceptor = on_request
    
    # 执行页面操作
    driver.get('https://test.example.com')
    
    driver.quit()

关键代码解释:

  • request_interceptor设置自定义拦截逻辑
  • 可以修改请求参数或模拟响应
  • 适用于测试接口时的模拟场景

五、完整案例

案例:测试登录接口的响应数据

from selenium import webdriver
from selenium.webdriver.common.by import By
import json
import time

def test_login_ajax():
    # 创建浏览器实例
    driver = webdriver.Chrome()
    
    # 设置请求拦截器
    driver.request_interceptor = lambda request: None
    
    # 访问测试页面
    driver.get('https://test.example.com/login')
    
    # 填写表单
    driver.find_element(By.ID, 'username').send_keys('test_user')
    driver.find_element(By.ID, 'password').send_keys('test_pass')
    driver.find_element(By.ID, 'submit').click()
    
    # 等待请求完成
    time.sleep(5)
    
    # 获取并验证响应数据
    for request in driver.requests:
        if 'api/login' in request.url:
            print(f"登录请求响应体: {request.response.body}")
            assert 'token' in json.loads(request.response.body)
    
    driver.quit()

完整流程说明:

  1. 创建Chrome浏览器实例
  2. 设置请求拦截器(此处未实现具体逻辑)
  3. 访问登录页面并填写表单
  4. 等待登录请求完成
  5. 遍历所有请求查找登录接口
  6. 验证响应体中包含token字段

六、源码解析

以selenium-wire库为例,其核心机制如下:

  1. 在创建浏览器实例时注册拦截器

    driver = webdriver.Chrome()
    driver.request_interceptor = lambda request: None
  2. 在浏览器启动时注入JavaScript代码

    // 注入的JS代码
    navigator.__webdriver_request_interceptor__ = function (request) {
     return {
         url: request.url,
         headers: request.headers,
         method: request.method
     };
    };
  3. 在请求完成后收集数据

    for request in driver.requests:
     print(request.url, request.headers)

七、进阶使用

1. 处理加密数据

对于加密响应体,需要先解密:

import base64

def decrypt_data(encrypted_data):
    # 假设使用AES加密
    decrypted = AES.new(key, AES.MODE_ECB).decrypt(base64.b64decode(encrypted_data))
    return decrypted.decode('utf-8')

2. 处理跨域请求

def handle_cors():
    # 设置请求头
    headers = {
        'Origin': 'https://test.example.com',
        'Referer': 'https://test.example.com'
    }
    # 在拦截器中设置头信息
    request.headers.update(headers)

3. 集成其他工具

import requests

def send_to_postman(url, data):
    response = requests.post(url, json=data)
    print(f"Postman响应: {response.status_code}")

八、性能与工程实践

性能优化

  1. 避免不必要的拦截:仅拦截关键接口

    def filter_requests(request):
     return 'api' in request.url and 'token' in request.url
  2. 异步处理请求:使用线程池处理响应数据

    from concurrent.futures import ThreadPoolExecutor
    
    def process_request(request):
     # 处理请求数据
     pass
    
    with ThreadPoolExecutor() as executor:
     executor.map(process_request, filtered_requests)
  3. 缓存机制:对重复请求进行缓存

    from functools import lru_cache
    
    @lru_cache(maxsize=100)
    def cache_request(url):
     # 返回缓存数据

异常处理

try:
    driver.get('https://test.example.com')
except Exception as e:
    print(f"页面加载失败: {e}")
    driver.quit()

安全风险

  1. 数据泄露:拦截的敏感信息可能被日志记录
  2. 伪造请求:模拟的响应可能导致业务逻辑错误
  3. 竞态条件:多线程环境下可能引发数据不一致

九、常见问题与踩坑

问题1:请求未被拦截

原因:未正确设置拦截器或未等待请求完成

解决方案:

  • 确保使用最新版ChromeDriver
  • 增加等待时间
  • 使用显式等待

    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
    WebDriverWait(driver, 10).until(
      EC.presence_of_element_located((By.ID, 'submit'))
    )

问题2:响应体为空

原因:未正确获取响应数据

解决方案:

  • 确认请求已成功
  • 检查响应头Content-Type是否正确
  • 使用request.response.body而非request.body

问题3:浏览器兼容性问题

原因:不同浏览器的CDP实现差异

解决方案:

  • 使用Chrome浏览器
  • 更新到最新版本
  • 使用浏览器兼容性模式

十、最佳实践

  1. 仅拦截关键接口:避免拦截所有请求影响性能
  2. 使用专用测试环境:隔离生产环境数据
  3. 数据脱敏处理:对敏感信息进行加密存储
  4. 结合其他测试方法:如API测试、UI测试互补验证
  5. 定期更新拦截规则:适应业务变更
  6. 性能监控:监控拦截器对测试执行时间的影响

十一、总结

通过Selenium拦截AJAX请求,我们可以获取异步请求的完整数据,这对于自动化测试具有重要价值。本文深入解析了其工作原理,提供了三种不同的实现方案,并给出了完整的测试案例。在实际应用中,需要注意性能优化、安全风险和浏览器兼容性等问题。

建议在以下场景使用该方案:

  • 需要验证API接口返回数据
  • 需要模拟接口响应进行测试
  • 需要调试前端代码与后端API的交互

不建议在以下场景使用:

  • 需要快速完成的简单测试
  • 有严格的性能要求的场景
  • 需要处理大量并发请求的场景

通过合理使用拦截器,我们可以更全面地验证Web应用的业务逻辑,提高自动化测试的覆盖率和准确性。

最后修改于:2026年09月21日 01:11

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日