等待页面加载完毕
概述
在FBro浏览器自动化过程中,等待页面完全加载是一个关键步骤。FBro提供了多种方法来检测和等待页面加载状态,确保在页面内容完全准备就绪后再执行后续操作。
核心概念
页面加载状态
- 基础加载:HTML文档加载完成
- 资源加载:图片、CSS、JavaScript等静态资源加载完成
- 动态内容:JavaScript动态生成的内容加载完成
- AJAX请求:异步请求完成
加载检测方法
- IsLoading():检测浏览器是否正在加载
- JavaScript执行:通过执行JavaScript检测页面状态
- 元素等待:等待特定元素出现
- 超时控制:避免无限等待
回调类定义
FBroSharpJsCallbackSyn 类
在使用JavaScript检测页面状态时,需要使用FBroSharpJsCallbackSyn回调类来处理异步执行的结果。该类位于callback.cs文件中:
csharp
/// <summary>
/// JavaScript异步执行回调类
/// 用于处理JavaScript执行的异步回调结果
/// </summary>
public class FBroSharpJsCallbackSyn : FBroSharpJsCallback
{
/// <summary>
/// 同步辅助类,用于等待和获取JavaScript执行结果
/// </summary>
public FBroSharpSynHelp help;
/// <summary>
/// 构造函数,初始化同步辅助类
/// </summary>
public FBroSharpJsCallbackSyn()
{
// 初始化help类
help = new FBroSharpSynHelp();
}
/// <summary>
/// 回调方法,当JavaScript执行完成时会调用此方法
/// </summary>
/// <param name="type">返回值类型</param>
/// <param name="data">返回的数据</param>
/// <param name="error">错误信息</param>
public override void Callback(FBroSharpJSRequestType type, IFBroSharpValue data, string error)
{
// 将返回的数据保存在help中
if (help != null && help.IsValid)
{
switch (type)
{
case FBroSharpJSRequestType.VALUE_ISVALID:
// 无效值
help.SetStringData(error);
break;
case FBroSharpJSRequestType.VALUE_NULL:
// 空值
help.SetStringData("无值");
break;
case FBroSharpJSRequestType.VALUE_INT:
// 整数值
help.SetIntData(data.GetInt());
break;
case FBroSharpJSRequestType.VALUE_STRING:
// 字符串值
help.SetStringData(data.GetString());
break;
case FBroSharpJSRequestType.VALUE_BOOL:
// 布尔值(页面加载检测中最常用)
help.SetBoolData(data.GetBool());
break;
case FBroSharpJSRequestType.VALUE_DOUBLE:
// 双精度浮点数值
help.SetDoubleData(data.GetDouble());
break;
case FBroSharpJSRequestType.VALUE_DATA_STRING:
// 日期时间字符串值
help.SetStringData(data.GetString());
break;
case FBroSharpJSRequestType.VALUE_ARRAY:
// 数组值 - 注意:只能获取到字符串表示
help.SetStringData(data.GetString());
break;
case FBroSharpJSRequestType.VALUE_ARRAY_BUFFER:
// 数组缓冲区值
help.SetStringData(data.GetString());
break;
case FBroSharpJSRequestType.VALUE_FUNCTION:
// 函数值
help.SetStringData(data.GetString());
break;
case FBroSharpJSRequestType.VALUE_OBJECT:
// 对象值 - 注意:只能获取到"object"字符串
help.SetStringData(data.GetString());
break;
}
// 停止等待,通知等待的线程结果已准备好
help.StopEvent();
}
}
}使用说明
csharp
// 1. 创建回调实例
var callback = new FBroSharpJsCallbackSyn();
// 2. 执行JavaScript代码
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(
"document.readyState === 'complete';",
default,
default,
callback
);
// 3. 等待执行结果(设置超时时间)
if (callback.help != null && callback.help.IsValid)
{
callback.help.WaitEvent(5000); // 等待5秒
// 4. 获取结果
if (callback.help.HavBoolData())
{
bool isComplete = callback.help.GetBoolData();
Console.WriteLine($"页面加载状态: {isComplete}");
}
}基础方法
1. IsLoading() 方法
IsLoading()是FBro提供的最基础的页面加载状态检测方法。
csharp
/// <summary>
/// 检查浏览器是否正在加载
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <returns>true表示正在加载,false表示加载完成</returns>
public bool IsPageLoading(IFBroSharpBrowser browser)
{
// IsLoading()返回true表示浏览器正在加载
// 返回false表示基础加载已完成
return browser.IsLoading();
}2. 简单等待加载完成
csharp
/// <summary>
/// 等待页面基础加载完成
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="timeoutMs">超时时间(毫秒)</param>
public void WaitForPageLoad(IFBroSharpBrowser browser, int timeoutMs = 30000)
{
var startTime = DateTime.Now;
while (browser.IsLoading())
{
// 检查是否超时
if ((DateTime.Now - startTime).TotalMilliseconds > timeoutMs)
{
throw new TimeoutException($"页面加载超时,超过 {timeoutMs} 毫秒");
}
// 短暂等待避免过度占用CPU
Thread.Sleep(100);
}
Console.WriteLine("页面基础加载完成");
}高级等待方法
1. 异步等待页面加载
csharp
/// <summary>
/// 异步等待页面加载完成
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="timeoutMs">超时时间(毫秒)</param>
/// <returns>等待任务</returns>
public async Task<bool> WaitForPageLoadAsync(IFBroSharpBrowser browser, int timeoutMs = 30000)
{
var startTime = DateTime.Now;
while (browser.IsLoading())
{
// 检查是否超时
if ((DateTime.Now - startTime).TotalMilliseconds > timeoutMs)
{
Console.WriteLine($"页面加载超时:{timeoutMs}ms");
return false;
}
// 异步等待,不阻塞UI线程
await Task.Delay(100);
}
Console.WriteLine("页面异步加载完成");
return true;
}2. 等待特定元素出现
csharp
/// <summary>
/// 等待特定元素出现(通过JavaScript)
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <param name="timeoutMs">超时时间(毫秒)</param>
/// <returns>是否找到元素</returns>
public async Task<bool> WaitForElement(IFBroSharpBrowser browser, string selector, int timeoutMs = 10000)
{
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
// 检查元素是否存在
bool elementExists = await CheckElementExists(browser, selector);
if (elementExists)
{
Console.WriteLine($"元素 '{selector}' 已找到");
return true;
}
await Task.Delay(200);
}
Console.WriteLine($"等待元素 '{selector}' 超时");
return false;
}
/// <summary>
/// 检查元素是否存在
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <returns>元素是否存在</returns>
private async Task<bool> CheckElementExists(IFBroSharpBrowser browser, string selector)
{
string jsCode = $@"
var element = document.querySelector('{selector}');
element !== null;
";
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
if (callback.help != null && callback.help.IsValid)
{
await Task.Run(() => callback.help.WaitEvent(5000));
if (callback.help.HavBoolData())
{
return callback.help.GetBoolData();
}
}
return false;
}3. 等待页面完全稳定
csharp
/// <summary>
/// 等待页面完全稳定(包括AJAX请求)
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="timeoutMs">超时时间(毫秒)</param>
/// <returns>是否完全加载</returns>
public async Task<bool> WaitForPageStable(IFBroSharpBrowser browser, int timeoutMs = 30000)
{
var startTime = DateTime.Now;
// 1. 等待基础加载完成
while (browser.IsLoading())
{
if ((DateTime.Now - startTime).TotalMilliseconds > timeoutMs)
{
return false;
}
await Task.Delay(100);
}
// 2. 等待document.readyState为complete
bool documentReady = await WaitForDocumentReady(browser, timeoutMs / 2);
if (!documentReady) return false;
// 3. 等待没有活跃的AJAX请求
bool ajaxComplete = await WaitForAjaxComplete(browser, timeoutMs / 2);
if (!ajaxComplete) return false;
// 4. 额外等待确保页面稳定
await Task.Delay(500);
Console.WriteLine("页面完全稳定");
return true;
}
/// <summary>
/// 等待document.readyState为complete
/// </summary>
private async Task<bool> WaitForDocumentReady(IFBroSharpBrowser browser, int timeoutMs)
{
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
string jsCode = "document.readyState === 'complete';";
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
if (callback.help != null && callback.help.IsValid)
{
await Task.Run(() => callback.help.WaitEvent(2000));
if (callback.help.HavBoolData() && callback.help.GetBoolData())
{
return true;
}
}
await Task.Delay(200);
}
return false;
}
/// <summary>
/// 等待AJAX请求完成
/// </summary>
private async Task<bool> WaitForAjaxComplete(IFBroSharpBrowser browser, int timeoutMs)
{
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
string jsCode = @"
// 检查jQuery AJAX请求
if (typeof jQuery !== 'undefined') {
if (jQuery.active > 0) {
false; // 还有活跃的jQuery AJAX请求
} else {
true; // jQuery AJAX请求完成
}
} else {
// 如果没有jQuery,检查XMLHttpRequest
true;
}
";
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
if (callback.help != null && callback.help.IsValid)
{
await Task.Run(() => callback.help.WaitEvent(2000));
if (callback.help.HavBoolData() && callback.help.GetBoolData())
{
return true;
}
}
await Task.Delay(300);
}
return false;
}综合等待类
PageLoadWaiter 工具类
csharp
/// <summary>
/// 页面加载等待工具类
/// </summary>
public class PageLoadWaiter
{
private readonly IFBroSharpBrowser browser;
private readonly int defaultTimeout;
public PageLoadWaiter(IFBroSharpBrowser browser, int defaultTimeoutMs = 30000)
{
this.browser = browser;
this.defaultTimeout = defaultTimeoutMs;
}
/// <summary>
/// 等待基础加载完成
/// </summary>
public async Task<bool> WaitForBasicLoad(int? timeoutMs = null)
{
int timeout = timeoutMs ?? defaultTimeout;
var startTime = DateTime.Now;
while (browser.IsLoading())
{
if ((DateTime.Now - startTime).TotalMilliseconds > timeout)
{
Console.WriteLine("基础加载超时");
return false;
}
await Task.Delay(100);
}
return true;
}
/// <summary>
/// 等待元素出现
/// </summary>
public async Task<bool> WaitForElement(string selector, int? timeoutMs = null)
{
int timeout = timeoutMs ?? (defaultTimeout / 3);
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeout)
{
string jsCode = $"document.querySelector('{selector}') !== null;";
if (await ExecuteJsAndGetBool(jsCode))
{
return true;
}
await Task.Delay(200);
}
return false;
}
/// <summary>
/// 等待元素可见
/// </summary>
public async Task<bool> WaitForElementVisible(string selector, int? timeoutMs = null)
{
int timeout = timeoutMs ?? (defaultTimeout / 3);
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeout)
{
string jsCode = $@"
var element = document.querySelector('{selector}');
if (element) {{
var style = window.getComputedStyle(element);
element.offsetParent !== null &&
style.visibility !== 'hidden' &&
style.display !== 'none';
}} else {{
false;
}}
";
if (await ExecuteJsAndGetBool(jsCode))
{
return true;
}
await Task.Delay(200);
}
return false;
}
/// <summary>
/// 等待文本内容出现
/// </summary>
public async Task<bool> WaitForText(string text, int? timeoutMs = null)
{
int timeout = timeoutMs ?? (defaultTimeout / 3);
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeout)
{
string jsCode = $"document.body.innerText.includes('{text}');";
if (await ExecuteJsAndGetBool(jsCode))
{
return true;
}
await Task.Delay(300);
}
return false;
}
/// <summary>
/// 等待页面标题包含指定文本
/// </summary>
public async Task<bool> WaitForTitle(string titleText, int? timeoutMs = null)
{
int timeout = timeoutMs ?? (defaultTimeout / 3);
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeout)
{
string jsCode = $"document.title.includes('{titleText}');";
if (await ExecuteJsAndGetBool(jsCode))
{
return true;
}
await Task.Delay(200);
}
return false;
}
/// <summary>
/// 执行JavaScript并获取布尔结果
/// </summary>
private async Task<bool> ExecuteJsAndGetBool(string jsCode)
{
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
if (callback.help != null && callback.help.IsValid)
{
await Task.Run(() => callback.help.WaitEvent(3000));
if (callback.help.HavBoolData())
{
return callback.help.GetBoolData();
}
}
return false;
}
}实际应用示例
1. 完整的页面操作流程
csharp
public class PageOperationExample
{
public async Task<bool> PerformPageOperation(IFBroSharpBrowser browser, string url)
{
try
{
// 1. 导航到页面
browser.GetMainFrame().LoadURL(url);
// 2. 创建等待工具
var waiter = new PageLoadWaiter(browser, 30000);
// 3. 等待基础加载完成
if (!await waiter.WaitForBasicLoad())
{
Console.WriteLine("页面基础加载失败");
return false;
}
// 4. 等待登录按钮出现
if (!await waiter.WaitForElement("#loginButton"))
{
Console.WriteLine("登录按钮未找到");
return false;
}
// 5. 等待按钮可见
if (!await waiter.WaitForElementVisible("#loginButton"))
{
Console.WriteLine("登录按钮不可见");
return false;
}
// 6. 执行点击操作
await ClickLoginButton(browser);
// 7. 等待登录后的页面标题
if (!await waiter.WaitForTitle("用户中心"))
{
Console.WriteLine("登录可能失败,未检测到用户中心页面");
return false;
}
Console.WriteLine("页面操作完成");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"页面操作出错: {ex.Message}");
return false;
}
}
private async Task ClickLoginButton(IFBroSharpBrowser browser)
{
// 点击登录按钮的实现
using (var vip_control = browser.GetVIPControl())
{
var advanced = vip_control.GetAdvancedControl();
// 这里需要先获取按钮的位置
// 简化示例,假设按钮在固定位置
advanced.DispatchMouseClick(300, 200, 0, 100);
}
}
}2. 表单提交等待
csharp
public class FormSubmissionWaiter
{
public async Task<bool> SubmitFormAndWait(IFBroSharpBrowser browser, string formSelector)
{
var waiter = new PageLoadWaiter(browser);
// 1. 确保表单存在
if (!await waiter.WaitForElement(formSelector))
{
Console.WriteLine($"表单 {formSelector} 未找到");
return false;
}
// 2. 提交表单
string submitJs = $@"
var form = document.querySelector('{formSelector}');
if (form) {{
form.submit();
true;
}} else {{
false;
}}
";
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(submitJs, default, default, callback);
// 3. 等待提交完成
await Task.Run(() => callback.help.WaitEvent(5000));
if (callback.help.HavBoolData() && callback.help.GetBoolData())
{
// 4. 等待页面重新加载
await Task.Delay(1000); // 等待导航开始
if (!await waiter.WaitForBasicLoad())
{
Console.WriteLine("表单提交后页面加载失败");
return false;
}
Console.WriteLine("表单提交并等待完成");
return true;
}
Console.WriteLine("表单提交失败");
return false;
}
}方法对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
IsLoading() | 基础页面加载 | 简单直接 | 不能检测动态内容 |
| JavaScript检测 | 复杂页面状态 | 灵活强大 | 需要编写JS代码 |
| 元素等待 | 特定元素出现 | 精确控制 | 需要知道元素选择器 |
| 综合等待 | 完整页面稳定 | 最可靠 | 等待时间较长 |
最佳实践
1. 合理设置超时时间
csharp
// 根据不同操作设置不同的超时时间
var timeouts = new
{
BasicLoad = 30000, // 基础加载:30秒
ElementWait = 10000, // 元素等待:10秒
TextWait = 5000, // 文本等待:5秒
AjaxWait = 15000 // AJAX等待:15秒
};2. 分层等待策略
csharp
public async Task<bool> SmartWait(IFBroSharpBrowser browser)
{
var waiter = new PageLoadWaiter(browser);
// 第一层:基础加载
if (!await waiter.WaitForBasicLoad(30000))
{
return false;
}
// 第二层:关键元素
if (!await waiter.WaitForElement("body", 5000))
{
return false;
}
// 第三层:动态内容(可选)
await waiter.WaitForElement(".dynamic-content", 10000);
return true;
}3. 错误处理和重试
csharp
public async Task<bool> WaitWithRetry(IFBroSharpBrowser browser, string selector, int maxRetries = 3)
{
var waiter = new PageLoadWaiter(browser);
for (int i = 0; i < maxRetries; i++)
{
try
{
if (await waiter.WaitForElement(selector, 10000))
{
return true;
}
Console.WriteLine($"第 {i + 1} 次等待失败,准备重试");
if (i < maxRetries - 1)
{
// 刷新页面重试
browser.GetMainFrame().Reload();
await Task.Delay(2000);
}
}
catch (Exception ex)
{
Console.WriteLine($"等待出错: {ex.Message}");
}
}
return false;
}注意事项
1. 性能考虑
- 避免过于频繁的检测,建议间隔100-300毫秒
- 合理设置超时时间,避免无限等待
- 对于批量操作,考虑并发等待的影响
2. 页面特性
- SPA(单页应用)可能需要特殊的等待策略
- 动态加载内容需要等待特定元素
- 某些页面可能需要等待特定的JavaScript执行完成
3. 错误处理
- 始终设置超时时间
- 记录详细的错误信息
- 考虑网络延迟和服务器响应时间
调试技巧
1. 添加详细日志
csharp
public async Task<bool> WaitWithLogging(IFBroSharpBrowser browser, string selector)
{
Console.WriteLine($"开始等待元素: {selector}");
var startTime = DateTime.Now;
var waiter = new PageLoadWaiter(browser);
bool result = await waiter.WaitForElement(selector);
var duration = DateTime.Now - startTime;
Console.WriteLine($"等待结果: {result}, 耗时: {duration.TotalMilliseconds}ms");
return result;
}2. 检查页面状态
csharp
public async Task LogPageState(IFBroSharpBrowser browser)
{
string jsCode = @"
JSON.stringify({
readyState: document.readyState,
url: location.href,
title: document.title,
bodyExists: !!document.body,
isLoading: typeof jQuery !== 'undefined' ? jQuery.active > 0 : 'jQuery not found'
});
";
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
await Task.Run(() => callback.help.WaitEvent(3000));
if (callback.help.HavStringData())
{
Console.WriteLine($"页面状态: {callback.help.GetStringData()}");
}
}通过这些方法和工具类,您可以灵活地处理各种页面加载等待场景,确保自动化操作的可靠性和稳定性。
c#
IFBroSharpBrowser.IsLoading()
//IsLoading是否读取中,Returns true if the browser is currently loading.
//browser就是IFBroSharpBrowser