Skip to content

等待页面加载完毕

概述

在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

如果文档对您有帮助,欢迎 请喝咖啡 ☕ | 软件发布 | 源码购买