1

I am trying to automate Webbrowser with .NET C#. The issue is that the control or should I say IE browser behaves strange on different computers. For example, I am clickin on link and fillup a Ajax popup form on 1st computer like this, without any error:

private void btn_Start_Click(object sender, RoutedEventArgs e)
{
    webbrowserIE.Navigate("http://www.test.com/");
    webbrowserIE.DocumentCompleted += fillup_LoadCompleted; 
}

void fillup_LoadCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{
    System.Windows.Forms.HtmlElement ele = web_BrowserIE.Document.GetElementById("login");
    if (ele != null)
        ele.InvokeMember("Click");

    if (this.web_BrowserIE.ReadyState == System.Windows.Forms.WebBrowserReadyState.Complete)
    {
        web_BrowserIE.Document.GetElementById("login").SetAttribute("value", myUserName);
        web_BrowserIE.Document.GetElementById("password").SetAttribute("value", myPassword);

        foreach (System.Windows.Forms.HtmlElement el in web_BrowserIE.Document.GetElementsByTagName("button"))
        {
            if (el.InnerText == "Login")
            {
                el.InvokeMember("click");
            }
        }

        web_BrowserIE.DocumentCompleted -= fillup_LoadCompleted;        
    }
}

However, the above code wont work on 2nd pc and the only way to click is like this:

private void btn_Start_Click(object sender, RoutedEventArgs e)
{
    webbrowserIE.DocumentCompleted += click_LoadCompleted;
    webbrowserIE.Navigate("http://www.test.com/"); 
}

void click_LoadCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{
    if (this.webbrowserIE.ReadyState == System.Windows.Forms.WebBrowserReadyState.Complete)
    {
        System.Windows.Forms.HtmlElement ele = webbrowserIE.Document.GetElementById("login");
        if (ele != null)
            ele.InvokeMember("Click");

        webbrowserIE.DocumentCompleted -= click_LoadCompleted;
        webbrowserIE.DocumentCompleted += fillup_LoadCompleted;
    }
}

void click_LoadCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{

        webbrowserIE.Document.GetElementById("login_login").SetAttribute("value", myUserName);
        webbrowserIE.Document.GetElementById("login_password").SetAttribute("value", myPassword);

        //If you know the ID of the form you would like to submit:
        foreach (System.Windows.Forms.HtmlElement el in webbrowserIE.Document.GetElementsByTagName("button"))
        {
            if (el.InnerText == "Login")
            {
                el.InvokeMember("click");
            }
        }

        webbrowserIE.DocumentCompleted -= click_LoadCompleted;      
}

So, in second solution I have to call two Load Completed Chains. Could someone advise on how should I can handle this issue? Also, a proposal for more robust approach would be very helpfull. Thank you in advance

Jim
  • 2,760
  • 8
  • 42
  • 66

1 Answers1

3

I could recommend two things:

  • Don't execute your code upon DocumentComplete event, rather do upon DOM window.onload event.
  • To make sure your web page behaves in WebBrowser control the same way as it would in full Internet Explorer browser, consider implementing Feature Control.

[EDITED] There's one more suggestion, based on the structure of your code. Apparently, you perform a series of navigation/handle DocumentComplete actions. It might be more natural and easy to use async/await for this. Here's an example of doing this, with or without async/await. It illustrates how to handle onload, too:

async Task DoNavigationAsync()
{
    bool documentComplete = false;
    TaskCompletionSource<bool> onloadTcs = null;

    WebBrowserDocumentCompletedEventHandler handler = delegate 
    {
        if (documentComplete)
            return; // attach to onload only once per each Document
        documentComplete = true;

        // now subscribe to DOM onload event
        this.wb.Document.Window.AttachEventHandler("onload", delegate
        {
            // each navigation has its own TaskCompletionSource
            if (onloadTcs.Task.IsCompleted)
                return; // this should not be happening

            // signal the completion of the page loading
            onloadTcs.SetResult(true);
        });
    };

    // register DocumentCompleted handler
    this.wb.DocumentCompleted += handler;

    // Navigate to http://www.example.com?i=1
    documentComplete = false;
    onloadTcs = new TaskCompletionSource<bool>();
    this.wb.Navigate("http://www.example.com?i=1");
    await onloadTcs.Task;
    // the document has been fully loaded, you can access DOM here
    MessageBox.Show(this.wb.Document.Url.ToString());

    // Navigate to http://example.com?i=2
    // could do the click() simulation instead

    documentComplete = false;
    onloadTcs = new TaskCompletionSource<bool>(); // new task for new navigation
    this.wb.Navigate("http://example.com?i=2");
    await onloadTcs.Task;
    // the document has been fully loaded, you can access DOM here
    MessageBox.Show(this.wb.Document.Url.ToString());

    // no more navigation, de-register DocumentCompleted handler
    this.wb.DocumentCompleted -= handler;
}

Here's the same code without async/await pattern (for .NET 4.0):

Task DoNavigationAsync()
{
    // save the correct continuation context for Task.ContinueWith
    var continueContext = TaskScheduler.FromCurrentSynchronizationContext(); 

    bool documentComplete = false;
    TaskCompletionSource<bool> onloadTcs = null;

    WebBrowserDocumentCompletedEventHandler handler = delegate 
    {
        if (documentComplete)
            return; // attach to onload only once per each Document
        documentComplete = true;

        // now subscribe to DOM onload event
        this.wb.Document.Window.AttachEventHandler("onload", delegate
        {
            // each navigation has its own TaskCompletionSource
            if (onloadTcs.Task.IsCompleted)
                return; // this should not be happening

            // signal the completion of the page loading
            onloadTcs.SetResult(true);
        });
    };

    // register DocumentCompleted handler
    this.wb.DocumentCompleted += handler;

    // Navigate to http://www.example.com?i=1
    documentComplete = false;
    onloadTcs = new TaskCompletionSource<bool>();
    this.wb.Navigate("http://www.example.com?i=1");

    return onloadTcs.Task.ContinueWith(delegate 
    {
        // the document has been fully loaded, you can access DOM here
        MessageBox.Show(this.wb.Document.Url.ToString());

        // Navigate to http://example.com?i=2
        // could do the 'click()' simulation instead

        documentComplete = false;
        onloadTcs = new TaskCompletionSource<bool>(); // new task for new navigation
        this.wb.Navigate("http://example.com?i=2");

        onloadTcs.Task.ContinueWith(delegate 
        {
            // the document has been fully loaded, you can access DOM here
            MessageBox.Show(this.wb.Document.Url.ToString());

            // no more navigation, de-register DocumentCompleted handler
            this.wb.DocumentCompleted -= handler;
        }, continueContext);

    }, continueContext);
}

Note, it both cases it is still a piece of asynchronous code which returns a Task object. Here's an example of how to handle the completion of such task:

private void Form1_Load(object sender, EventArgs e)
{
    DoNavigationAsync().ContinueWith(_ => {
        MessageBox.Show("Navigation complete!");
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

The benefit of using TAP pattern here is that DoNavigationAsync is a self-contained, independent method. It can be reused and it doesn't interfere with the state of parent object (in this case, the main form).

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Actually, I already implement the Feature Control as it was my question:), I am not sure, about DOM onload event since I need to complete download of webpage event to implement the clicks? or? – Jim Sep 02 '13 at 12:46
  • Oh.. I should have checked, I did feel like I spoke with @Jim before :] Feel free to try the [code](http://stackoverflow.com/a/18370524/1768303) I linked, `onload` indeed gets fired there when the page has fully loaded (including any frames). This method is a more reliable way to make sure the page has finished its own processing of `onload` or `onreadystatechange` DOM events. – noseratio Sep 02 '13 at 12:54
  • can you explain a bit since you are sign up for DocumentCompleted event and inside it attach for AttachEventHandler – Jim Sep 02 '13 at 13:29
  • `DocumentCompleted` gets fired first, which is the right time and place to get subscribed to DOM `onload` event. `onload` gets fired very next to `DocumentCompleted`, but always after it. – noseratio Sep 02 '13 at 13:32
  • 1
    You are correct that I perform a series of DocumentComplete actions. I am developin against .net 4 so not sure about async/await is availble. Also, The strange thing is that with event handling I can work on one pc while not on other. So according to your experince will async/await be more robust solution? – Jim Sep 02 '13 at 13:40
  • Sigh, you cannot use `async/await` with .NET 4.0. I wouldn't say it's more robust in the context of your question. Rather it's much easier to code as it gives you linear code structure (sort of) and thus it's less error-prone. Using `onload` event is what should make it more robust. Let's see if I can refactor this code so it still uses TAP pattern, but without `async/await`. – noseratio Sep 02 '13 at 13:48
  • I really uppreciate your effort, also if it is no trouble pls include how could I call async Task DoNavigationAsync – Jim Sep 02 '13 at 14:03
  • Done, check the update. Surprisingly, it is not that bad without `async/await`, IMO. I also fixed a bug where checking for possible multiple `DocumentCompleted` for frames. – noseratio Sep 02 '13 at 14:45
  • 1
    Feature Control Works for Me. Thanks – Asad Ali Mar 14 '17 at 05:35