-1

The documentation for the webbrowser in WinForms specifies you can invoke events like this:

webBrowser1.Document.GetElementById("signin").InvokeMember("click");

..but it doesn't work for me. I tried a hack:

HtmlElement element = webBrowser1.Document.CreateElement("script");
element.InnerText = "function SubmitForm() { document.getElementById('signin').click(); }";
webBrowser1.Document.Forms[0].AppendChild(element);
webBrowser1.Document.InvokeScript("SubmitForm");

..which didn't work either. However, another hack:

HtmlElement element = webBrowser1.Document.CreateElement("script");
element.InnerText = "function SubmitForm() { alert('Click me!'); 
    document.getElementById('signin').click(); }";
webBrowser1.Document.Forms[0].AppendChild(element);
webBrowser1.Document.InvokeScript("SubmitForm");

..worked perfectly. Except there is user intervention to click the alert box, which I don't want. Is this some kind of race condition? The code is executed in webBrowser1_DocumentCompleted so it should be fine. If you have a way to successfully trigger the event, please do tell.

@Jimi here's the code you requested:

private void Form1_Load(object sender, EventArgs e)
{
    string targetUrl = "https://banking.westpac.com.au/wbc/banking/handler?TAM_OP=login&URL=%2Fsecure%2Fbanking%2Foverview%2Fdashboard";
    webBrowser1.Navigate(targetUrl);
}

private void webBrowser1_DocumentCompleted_1(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    webBrowser1.DocumentCompleted -= webBrowser1_DocumentCompleted_1;
    webBrowser1.DocumentCompleted += webBrowser1_DocumentCompleted_2;

    webBrowser1.Document.GetElementById("fakeusername").InnerText = "andy";
    webBrowser1.Document.GetElementById("password").InnerText = "password";
    //webBrowser1.Document.GetElementById("signin").InvokeMember("click");

    HtmlElement element = webBrowser1.Document.CreateElement("script");
    element.InnerText = "function SubmitForm() { setTimeout(function() { document.getElementById('signin').click(); }, 5000); }";
    webBrowser1.Document.Forms[0].AppendChild(element);
    webBrowser1.Document.InvokeScript("SubmitForm");
}

The alert makes it work (so does setTimeout). It seems like the document is not really completed, but the browser thinks it is. Could JS on the page do that?

Andy
  • 67
  • 1
  • 7
  • This works, but still a hack: element.InnerText = "function SubmitForm() { setTimeout(function(){ document.getElementById('signin').click(); }, 5000); }"; – Andy Mar 15 '21 at 09:50
  • 1
    Can you post the code you have in the `DocumentCompleted` handler? The `DocumentCompleted` event can be raised (almost always) multiple times... Why do you have `alert(...)` there? Doesn't it work without it? Btw, read the notes [here](https://stackoverflow.com/a/53218064/7444103) – Jimi Mar 15 '21 at 10:13
  • 1
    In that event handler, you're clearly not waiting for the Document(s) to complete. See the code in the post I linked. -- Unsubscribing and subscribing to another handler is really not useful. Also, unless you remove that subscription somewhere else, `webBrowser1_DocumentCompleted_2` will be called a multitude of times and its use is unclear; not helping here anyway. – Jimi Mar 15 '21 at 14:29
  • It is useful to subscribe to another handler, since the browser will be directed to another page and I use different code to process page 2, page 3, etc. – Andy Mar 16 '21 at 02:42
  • Why would I wait for the document to complete when I'm already in webBrowser1_DocumentCompleted? How would I? Yes I'm removing the event handlers after each use so there's only ever one handler wired up to the browser. – Andy Mar 16 '21 at 02:56
  • 1
    You didn't read the notes in the post I linked. That event can (will, most of the time) be raised multiple times, since it's raised for each sub--document in the main Document page. It can be raised one or more times for each Frame / IFrame in the Document. That's why there's always this kind of check: `if ([WebBrowser].ReadyState != WebBrowserReadyState.Complete) return;` in that handler. -- If you need to handle different Document sources, use a method that can discriminate what page is currently being read. You cannot add hundreds of event handlers to handle hundreds of different pages. – Jimi Mar 16 '21 at 03:04
  • I disagree, there is nothing wrong with using different event handlers for different pages. Yours is an antipattern with 10 switch..cases instead of clean code divided up into separate methods. The documentation mentions nothing about OnDocumentComplete being called multiple times on a page and I think this is bad design on Microsoft's part. So I'm not here from a lecture, just to have a discussion with other developers. A bit of respect would be nice. – Andy Mar 16 '21 at 04:37
  • 1
    1. The Docs explicitly say so: [DocumentComplete event](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa768282(v=vs.85)). There's no reference there to JavaScript actions, which also can cause the event to raise multiple time, that's the `ReadyState` property is for. It's not a secret, it's a documented standard for **all** WebBrowsers. 2. I don't see what's the anti-patter when you handle the default behavior. You need a method that parses a Web page based on the current URL, when the document is complete. That is all – Jimi Mar 16 '21 at 13:41
  • To note that `ReadyState` is set for each Document and sub-document in the Main HtmlDocument. That's also mentioned in the post I linked. So, for each `.ReadyState = WebBrowserReadyState.Complete` you have to verify to what Document the `Complete` state refers to. That's also mentioned (and handled) in that post. – Jimi Mar 16 '21 at 13:43
  • It's horrible design. The event should only fire once, after the whole document is done. Like $(document).ready() { .... }. It's intuitive and 100x better than using a stupid loop to wait for everything to be done. – Andy Mar 16 '21 at 22:28
  • 1
    It's not a *design*, it's how WebBrowsers (all WebBrowsers) work. You don't need any loop. Just inspect `ReadyState` each time the event is raised and keep track of what HtmlDocument or sub-document the `Complete` status refers to. -- You have this kind of behavior no matter what WebBrowser you handle, this is how these work. HTML pages are dynamic, they can be generated in different stages, the Server may send a partial document, then fill it and resend it partially or entirely, depending on the structure and the source of data, JavaScript can do the same client-side etc. – Jimi Mar 16 '21 at 22:33
  • $(document).ready() { .... } – Andy Mar 17 '21 at 07:08

2 Answers2

0

Based on your workarounds, I guess that it might be due to document not loaded yet, when you trying to execute code. In order to assure the page is loaded, you can subscribe to DocumentCompleted event and there execute your action. This worked for me:

public Form1()
{
    InitializeComponent();

    webBrowser1.DocumentCompleted += WebBrowser1_DocumentCompleted;
    string curDir = Directory.GetCurrentDirectory();
    webBrowser1.Navigate($"file://{curDir}/test.html");
}

private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    webBrowser1.Document.GetElementById("btn").InvokeMember("onclick");
}

Another issue I can see, you are using click member, since I am not sure what the element is (maybe a button?), then try invoking onclick, like I did in the provided code.

Such snippet worked for me as well:

public Form1()
{
    InitializeComponent();

    webBrowser1.DocumentCompleted += WebBrowser1_DocumentCompleted;
    webBrowser1.Navigate(@"https://banking.westpac.com.au/wbc/banking/handler?TAM_OP=login&URL=%2Fsecure%2Fbanking%2Foverview%2Fdashboard");
}

private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    webBrowser1.Document.GetElementById("signin").InvokeMember("click");
}
Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
  • Thanks but I'm already using webBrowser1_DocumentCompleted and 'onclick' didn't work.. – Andy Mar 15 '21 at 11:13
  • @Andy Could you share the JS and HTML as well? Is it some website or is it local file? – Michał Turczyn Mar 15 '21 at 12:48
  • The full HTML can be found at the target URL: https://banking.westpac.com.au/wbc/banking/handler?TAM_OP=login&URL=%2Fsecure%2Fbanking%2Foverview%2Fdashboard – Andy Mar 16 '21 at 02:41
  • @Andy Similair code worked for me as well, see edit. – Michał Turczyn Mar 16 '21 at 06:53
  • Thanks, I tried the same code and still got an error. The only difference is I have ScriptErrorsSuppressed set true. If I make it false it works, but probably due to the interactive popups. I'm using VS2013 for this project (long story). So perhaps it's a bug that was fixed since then..? – Andy Mar 16 '21 at 08:24
  • @Andy Oh, right... There's an error in the website which breaks all scripts, so they stop executing in web browser. I also stumbled upon that issue. – Michał Turczyn Mar 16 '21 at 09:03
  • Yes likely due to webbrowser using IE in background. Many scripts only work on the better browsers. If it was Chrome then the issue would not arise, I tried CefSharp for this kind of thing but it has it's own problems. Oh well.. – Andy Mar 16 '21 at 22:30
-1

My theory turned out to be correct, I finally found an answer:

StackOverflow: https://stackoverflow.com/questions/23680817/webbrowser-documentcompleted-is-useless-in-my-case

webBrowser1.Navigate(@"somesite.com");
while (WebBrowser1.ReadyState != WebBrowserReadyState.Complete)
{
    txtLoad.Text = WebBrowser1.ReadyState.ToString();
    Application.DoEvents();
    System.Threading.Thread.Sleep(1);
}
webBrowser1.Document.GetElementById("gwt-uid-126").InvokeMember("click");

Seems that the completed event doesn't wait for all scripts to stop executing.

Thanks all of you for your contributions.

Andy
  • 67
  • 1
  • 7