.Net环境下Playwright如何等待XHR/Ajax请求全部完成
.NET Playwright 等待所有XHR/Ajax请求完成实现方案
核心逻辑和你之前使用的Pending XHR Puppeteer一致,通过统计活跃的XHR/Fetch请求数实现,完全支持同URL并发请求的计数(不按URL去重,每个请求单独计数,不会漏统计)。
实现步骤
- 封装Page扩展方法,全局仅需注册一次事件监听,每个页面独立维护请求计数
- 监听请求生命周期事件:
- 请求发起时,若为XHR/Fetch类型则计数+1
- 请求结束(成功/失败/取消)时,计数-1
- 等待方法循环判断计数是否归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
相关产品推荐
相关产品推荐

