You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.Net环境下Playwright如何等待XHR/Ajax请求全部完成

.NET Playwright 等待所有XHR/Ajax请求完成实现方案

核心逻辑和你之前使用的Pending XHR Puppeteer一致,通过统计活跃的XHR/Fetch请求数实现,完全支持同URL并发请求的计数(不按URL去重,每个请求单独计数,不会漏统计)。

实现步骤

  1. 封装Page扩展方法,全局仅需注册一次事件监听,每个页面独立维护请求计数
  2. 监听请求生命周期事件:
    • 请求发起时,若为XHR/Fetch类型则计数+1
    • 请求结束(成功/失败/取消)时,计数-1
  3. 等待方法循环判断计数是否归0,支持超时兜底避免无限等待

完整代码

using Microsoft.Playwright;
using System.Collections.Concurrent;
using System.Threading;

public static class PageXhrExtensions
{
    // 存储每个页面对应的活跃XHR计数
    private static readonly ConcurrentDictionary<IPage, int> _pendingXhrCounts = new();
    // 标记页面是否已注册事件监听,避免重复注册
    private static readonly ConcurrentDictionary<IPage, bool> _eventRegisteredPages = new();

    /// <summary>
    /// 等待所有XHR/Fetch请求完成
    /// </summary>
    /// <param name="page">Playwright页面对象</param>
    /// <param name="timeout">超时时间,默认30秒</param>
    public static async Task WaitForAllXhrFinishedAsync(this IPage page, TimeSpan? timeout = null)
    {
        timeout ??= TimeSpan.FromSeconds(30);
        var cancellationTokenSource = new CancellationTokenSource(timeout.Value);

        // 首次调用时注册事件
        if (!_eventRegisteredPages.TryGetValue(page, out _))
        {
            page.Request += (_, request) =>
            {
                if (request.ResourceType is "xhr" or "fetch")
                {
                    _pendingXhrCounts.AddOrUpdate(page, 1, (_, old) => old + 1);
                }
            };

            page.RequestFinished += (_, request) =>
            {
                if (request.ResourceType is "xhr" or "fetch")
                {
                    _pendingXhrCounts.AddOrUpdate(page, 0, (_, old) => Math.Max(0, old - 1));
                }
            };

            page.RequestFailed += (_, request) =>
            {
                if (request.ResourceType is "xhr" or "fetch")
                {
                    _pendingXhrCounts.AddOrUpdate(page, 0, (_, old) => Math.Max(0, old - 1));
                }
            };

            // 页面关闭时清理缓存
            page.Close += (_, _) =>
            {
                _pendingXhrCounts.TryRemove(page, out _);
                _eventRegisteredPages.TryRemove(page, out _);
            };

            _eventRegisteredPages.TryAdd(page, true);
        }

        // 等待计数归0或超时
        while (!cancellationTokenSource.IsCancellationRequested)
        {
            _pendingXhrCounts.TryGetValue(page, out var count);
            if (count <= 0)
            {
                return;
            }
            await Task.Delay(50, cancellationTokenSource.Token);
        }

        // 超时后可根据需求选择抛异常或者静默返回
        throw new TimeoutException($"等待XHR请求完成超时,超时时间:{timeout.Value.TotalSeconds}秒");
    }
}

使用示例

// 页面导航后调用等待即可
await page.GotoAsync("https://你的目标地址");
await page.WaitForAllXhrFinishedAsync();

// 自定义超时时间示例
await page.WaitForAllXhrFinishedAsync(TimeSpan.FromSeconds(60));

注意事项

  • 如果需要过滤特定请求(比如埋点上报、心跳轮询等不需要等待的接口),可以在请求事件的判断逻辑中添加路径排除规则
  • 该实现不按URL去重,相同URL的并发请求会分别计数,完全符合你的业务场景需求
  • 页面刷新/导航前不需要手动重置计数,事件逻辑会自动统计新页面的请求

内容的提问来源于stack exchange,提问作者Martin Thompson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 08:24:08