如何在无头单元测试运行时,使web浏览器完成导航? [英] How to make WebBrowser complete navigation when running in a headless unit test?

查看:182
本文介绍了如何在无头单元测试运行时,使web浏览器完成导航?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个WPF应用程序在< WebBrowser /> 控制中加载一些内容,然后根据加载的内容进行一些调用。使用正确的模拟,我们认为我们可以在一个无显示单元测试(在这种情况下为NUnit)中测试。但是 WebBrowser 控件不想播放得很好。

We have a WPF application that loads some content in a <WebBrowser/> control and then makes some calls based on what was loaded. With the right mocks, we think we can test this inside a displayless unit test (NUnit in this case). But the WebBrowser control doesn't want to play nicely.

问题是我们从不收到 LoadCompleted 导航事件。显然,这是因为网页从来没有加载,直到实际呈现(请参阅此MSDN线程)。我们确实收到导航事件,但是对于我们的目的来说太早了。

The problem is that we never receive the LoadCompleted or Navigated events. Apparently this is because a web-page is never "Loaded" until it is actually rendered (see this MSDN thread). We do receive the Navigating event, but that comes far too early for our purposes.

使 WebBrowser 控制完全工作,即使没有输出显示到?

So is there a way to make the WebBrowser control work "fully" even when it has no output to display to?

测试用例的剪切版本:

[TestFixture, RequiresSTA]
class TestIsoView
{
    [Test] public void PageLoadsAtAll()
    {
        Console.WriteLine("I'm a lumberjack and I'm OK");
        WebBrowser wb = new WebBrowser();

        // An AutoResetEvent allows us to synchronously wait for an event to occur.
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);
        //wb.LoadCompleted += delegate  // LoadCompleted is never received
        wb.Navigated += delegate  // Navigated is never received
        //wb.Navigating += delegate // Navigating *is* received
        {
            // We never get here unless we wait on wb.Navigating
            Console.WriteLine("The document loaded!!");
            autoResetEvent.Set();
        };

        Console.WriteLine("Registered signal handler", "Navigating");

        wb.NavigateToString("Here be dramas");
        Console.WriteLine("Asyncronous Navigations started!  Waiting for A.R.E.");
        autoResetEvent.WaitOne();
        // TEST HANGS BEFORE REACHING HERE.
        Console.WriteLine("Got it!");
    }
}


推荐答案

需要从具有消息循环的STA线程分离。您将在该线程上创建一个 WebBrowser 的实例,并抑制脚本错误。注意,WPF WebBrowser 对象需要一个活动主机窗口才能运行。这是它不同于WinForms WebBrowser

You'd need to spin off an STA thread with a message loop for that. You'd create an instance of WebBrowser on that thread and suppress script errors. Note, a WPF WebBrowser object needs a live host window to function. That's how it's different from WinForms WebBrowser.

这是一个如何做到这一点的示例:

Here is an example of how this can be done:

static async Task<string> RunWpfWebBrowserAsync(string url)
{
    // return the result via Task
    var resultTcs = new TaskCompletionSource<string>();

    // the main WPF WebBrowser driving logic
    // to be executed on an STA thread
    Action startup = async () => 
    {
        try
        {
            // create host window
            var hostWindow = new Window();
            hostWindow.ShowActivated = false;
            hostWindow.ShowInTaskbar = false;
            hostWindow.Visibility = Visibility.Hidden;
            hostWindow.Show();

            // create a WPF WebBrowser instance
            var wb = new WebBrowser();
            hostWindow.Content = wb;

            // suppress script errors: http://stackoverflow.com/a/18289217
            // touching wb.Document makes sure the underlying ActiveX has been created
            dynamic document = wb.Document; 
            dynamic activeX = wb.GetType().InvokeMember("ActiveXInstance",
                BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
                null, wb, new object [] { });
            activeX.Silent = true;

            // navigate and handle LoadCompleted
            var navigationTcs = new TaskCompletionSource<bool>();
            wb.LoadCompleted += (s, e) => 
                navigationTcs.TrySetResult(true);
            wb.Navigate(url);
            await navigationTcs.Task;

            // do the WebBrowser automation
            document = wb.Document;
            // ...

            // return the content (for example)
            string content = document.body.outerHTML;
            resultTcs.SetResult(content);
        }
        catch (Exception ex)
        {
            // propogate exceptions to the caller of RunWpfWebBrowserAsync
            resultTcs.SetException(ex);
        }

        // end the tread: the message loop inside Dispatcher.Run() will exit
        Dispatcher.ExitAllFrames();
    };

    // thread procedure
    ThreadStart threadStart = () =>
    {
        // post the startup callback
        // it will be invoked when the message loop pumps
        Dispatcher.CurrentDispatcher.BeginInvoke(startup);
        // run the WPF Dispatcher message loop
        Dispatcher.Run();
        Debug.Assert(true);
    };

    // start and run the STA thread
    var thread = new Thread(threadStart);
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    try
    {
        // use Task.ConfigureAwait(false) to avoid deadlock on a UI thread
        // if the caller does a blocking call, i.e.:
        // "RunWpfWebBrowserAsync(url).Wait()" or 
        // "RunWpfWebBrowserAsync(url).Result"
        return await resultTcs.Task.ConfigureAwait(false);
    }
    finally
    {
        // make sure the thread has fully come to an end
        thread.Join();
    }
}

用法:

// blocking call
string content = RunWpfWebBrowserAsync("http://www.example.com").Result;

// async call
string content = await RunWpfWebBrowserAsync("http://www.example.org")

您还可以尝试直接在 NUnit threadStart >线程,而不实际创建一个新的线程。这样,NUnit线程将运行 Dispatcher 消息循环。我不太熟悉NUnit,足以预测它是否可以工作。

You may also try to run threadStart lambda directly on your NUnit thread, without actually creating a new thread. This way, the NUnit thread will run the Dispatcher message loop. I'm not familiar with NUnit well enough to predict if that works.

如果你不想创建一个主机窗口,请考虑使用 WinForms WebBrowser 。我发布了类似的自包含示例,可通过控制台应用执行此操作。

If you don't want to create a host window, consider using WinForms WebBrowser instead. I posted a similar self-contained example of doing that from a console app.

这篇关于如何在无头单元测试运行时,使web浏览器完成导航?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆