C# Webbrowser Control - Form Submit Not Working Using Invokemember("Click")

C# WebBrowser Control - Form Submit Not Working using InvokeMember(Click)

The following code works for me, using the live form action URL from the question comments, tested with IE10. Try it as is. If it works for you as well, feel free to use it as a template for your web automation tasks. A couple of points:

  • FEATURE_BROWSER_EMULATION is used to make sure the WebBrowser behaves in the same way as standalone IE browser (or as close as possible). This is a must for almost any WebBrowser-based project. I believe that's what should help to solve the original problem on your side.

  • Asynchronous code is used to improve the automation logic reliability, add support timeouts and cancellation and promote natural linear code flow (using async/await).

C#:

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WebAutomation
{
// http://stackoverflow.com/q/19044659/1768303

public partial class MainForm : Form
{
WebBrowser webBrowser;

// non-deterministic delay to let AJAX code run
const int AJAX_DELAY = 1000;

// keep track of the main automation task
CancellationTokenSource mainCts;
Task mainTask = null;

public MainForm()
{
SetBrowserFeatureControl(); // set FEATURE_BROWSER_EMULATION first

InitializeComponent();

InitBrowser();

this.Load += (s, e) =>
{
// start the automation when form is loaded
// timeout the whole automation task in 30s
mainCts = new CancellationTokenSource(30000);
mainTask = DoAutomationAsync(mainCts.Token).ContinueWith((completedTask) =>
{
Trace.WriteLine(String.Format("Automation task status: {0}", completedTask.Status.ToString()));
}, TaskScheduler.FromCurrentSynchronizationContext());
};

this.FormClosing += (s, e) =>
{
// cancel the automation if form closes
if (this.mainTask != null && !this.mainTask.IsCompleted)
mainCts.Cancel();
};
}

// create a WebBrowser instance (could use an existing one)
void InitBrowser()
{
this.webBrowser = new WebBrowser();
this.webBrowser.Dock = DockStyle.Fill;
this.Controls.Add(this.webBrowser);
this.webBrowser.Visible = true;
}

// the main automation logic
async Task DoAutomationAsync(CancellationToken ct)
{
await NavigateAsync(ct, () => this.webBrowser.Navigate("http://localhost:81/test.html"), 10000); // timeout in 10s
// page loaded, log the page's HTML
Trace.WriteLine(GetBrowserDocumentHtml());

// do the DOM automation
HtmlElementCollection all = webBrowser.Document.GetElementsByTagName("button");
// throw if none or more than one element found
HtmlElement btn = all.Cast<HtmlElement>().Single(
el => el.InnerHtml == "ACCEPT the terms of use");

ct.ThrowIfCancellationRequested();

// simulate a click which causes navigation
await NavigateAsync(ct, () => btn.InvokeMember("click"), 10000); // timeout in 10s

// form submitted and new page loaded, log the page's HTML
Trace.WriteLine(GetBrowserDocumentHtml());

// could continue with another NavigateAsync
// othrwise, the automation session completed
}

// Get the full HTML content of the document
string GetBrowserDocumentHtml()
{
return this.webBrowser.Document.GetElementsByTagName("html")[0].OuterHtml;
}

// Async navigation
async Task NavigateAsync(CancellationToken ct, Action startNavigation, int timeout = Timeout.Infinite)
{
var onloadTcs = new TaskCompletionSource<bool>();
EventHandler onloadEventHandler = null;

WebBrowserDocumentCompletedEventHandler documentCompletedHandler = delegate
{
// DocumentCompleted may be called several time for the same page,
// beacuse of frames
if (onloadEventHandler != null || onloadTcs == null || onloadTcs.Task.IsCompleted)
return;

// handle DOM onload event to make sure the document is fully loaded
onloadEventHandler = (s, e) =>
onloadTcs.TrySetResult(true);
this.webBrowser.Document.Window.AttachEventHandler("onload", onloadEventHandler);
};

using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
if (timeout != Timeout.Infinite)
cts.CancelAfter(Timeout.Infinite);

using (cts.Token.Register(() => onloadTcs.TrySetCanceled(), useSynchronizationContext: true))
{
this.webBrowser.DocumentCompleted += documentCompletedHandler;
try
{
startNavigation();
// wait for DOM onload, throw if cancelled
await onloadTcs.Task;
ct.ThrowIfCancellationRequested();
// let AJAX code run, throw if cancelled
await Task.Delay(AJAX_DELAY, ct);
}
finally
{
this.webBrowser.DocumentCompleted -= documentCompletedHandler;
if (onloadEventHandler != null)
this.webBrowser.Document.Window.DetachEventHandler("onload", onloadEventHandler);
}
}
}
}

// Browser feature conntrol
void SetBrowserFeatureControl()
{
// http://msdn.microsoft.com/en-us/library/ee330720(v=vs.85).aspx

// FeatureControl settings are per-process
var fileName = System.IO.Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);

// make the control is not running inside Visual Studio Designer
if (String.Compare(fileName, "devenv.exe", true) == 0 || String.Compare(fileName, "XDesProc.exe", true) == 0)
return;

SetBrowserFeatureControlKey("FEATURE_BROWSER_EMULATION", fileName, GetBrowserEmulationMode()); // Webpages containing standards-based !DOCTYPE directives are displayed in IE10 Standards mode.
}

void SetBrowserFeatureControlKey(string feature, string appName, uint value)
{
using (var key = Registry.CurrentUser.CreateSubKey(
String.Concat(@"Software\Microsoft\Internet Explorer\Main\FeatureControl\", feature),
RegistryKeyPermissionCheck.ReadWriteSubTree))
{
key.SetValue(appName, (UInt32)value, RegistryValueKind.DWord);
}
}

UInt32 GetBrowserEmulationMode()
{
int browserVersion = 7;
using (var ieKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Internet Explorer",
RegistryKeyPermissionCheck.ReadSubTree,
System.Security.AccessControl.RegistryRights.QueryValues))
{
var version = ieKey.GetValue("svcVersion");
if (null == version)
{
version = ieKey.GetValue("Version");
if (null == version)
throw new ApplicationException("Microsoft Internet Explorer is required!");
}
int.TryParse(version.ToString().Split('.')[0], out browserVersion);
}

UInt32 mode = 10000; // Internet Explorer 10. Webpages containing standards-based !DOCTYPE directives are displayed in IE10 Standards mode. Default value for Internet Explorer 10.
switch (browserVersion)
{
case 7:
mode = 7000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE7 Standards mode. Default value for applications hosting the WebBrowser Control.
break;
case 8:
mode = 8000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE8 mode. Default value for Internet Explorer 8
break;
case 9:
mode = 9000; // Internet Explorer 9. Webpages containing standards-based !DOCTYPE directives are displayed in IE9 mode. Default value for Internet Explorer 9.
break;
default:
// use IE10 mode by default
break;
}

return mode;
}
}
}

The content of http://localhost:81/test.html:

<!DOCTYPE html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
</head>
<body>
<form action="<the URL from OP's comments>" method="post">
<input name="StepCheck" value="U2FsdGVkX18zMTk5MzE5OUgFyFgD3V5yf5Rwbtfhf3gjdH4KSx4hqj4vkrw7K6e-" type="hidden">
<button type="submit" name="continue" value="y">ACCEPT the terms of use</button>
<button type="submit" name="continue" value="n">DECLINE the terms of use</button>
</form>
</body>

Bug when I press the button with invokemember () Webbrowser

Try this

First, get the submit button if you have ID. Then check whether it is exit or not

HtmlElement htmlElement = automationWebBrowser.Document.GetElementById("submitBtnRight");
if (htmlElement != null)
{
htmlElement.InvokeMember("click");
}

If possible post your button HTML

InvokeMember(submit) on form does not redirect browser

Ok, sorry for delay, I moved on to something else...

Anyway, it's REALLY rough but you should be able to pick the bits you need.

The original idea was from Noseratio found here.

The raisedynamicevent method, works in a similar way to async nav, by monitoring the state of the document after event. when ok again, returns. should deal with ajax stuff. needs re factoring a bit and probs lots wrong with it but will hopefully help someone.

/// the _profileQueue was a queue of URLs i wanted to nav through and find an 
/// form elem and "click" the submit button on
private async void Next()
{
Submission res = null;
if (_profileQueue.TryDequeue(out res))
{
// dirty, but hold the details of the url i'm navigating to in the Tag
_browser.Tag = res;

var cts = new CancellationTokenSource(Properties.Settings.Default.BrowserNavigationTimeout); // cancel in 10s
var html = await LoadDynamicPage(res.SiteProfile.URL, cts.Token);

// this parses the dom once loaded (awaits for the page)
ProcessSiteProfile();
Next();
}
}

// navigate and download
async Task<string> LoadDynamicPage(string url, CancellationToken token)
{
// navigate and await DocumentCompleted
var tcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = (s, arg) =>
tcs.TrySetResult(true);

// i'm keeping the tcs in a concurrentdictionary against the browser object
// again, this is pretty dirty but obviously felt like i needed it.
_browserTasks[_browser] = tcs;

using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{

// nav to page async
this._browser.DocumentCompleted += handler;
try
{
if (!string.IsNullOrWhiteSpace(url))
{
this._browser.Navigate(url);
await tcs.Task; // wait for DocumentCompleted
}
}
finally
{
this._browser.DocumentCompleted -= handler;
}
}

// get the root element
var documentElement = this._browser.Document.GetElementsByTagName("html")[0];

// poll the current HTML for changes asynchronosly
var html = documentElement.OuterHtml;
while (true)
{
// wait asynchronously, this will throw if cancellation requested
await Task.Delay(Properties.Settings.Default.BrowserNavigationWait, token);

// continue polling if the WebBrowser is still busy
if (this._browser.IsBusy)
continue;

var htmlNow = documentElement.OuterHtml;
if (html == htmlNow)
break; // no changes detected, end the poll loop

html = htmlNow;
}

// consider the page fully rendered
token.ThrowIfCancellationRequested();

// remove from task dictionary
_browserTasks[this._browser] = null;

return html;
}

async void ProcessSiteProfile()
{
// now process submission

HtmlElement parentForm = null;

/////////////////
// parse dom to find the form you're looking for
// couple of helpers below
///////////////////////

parentForm = HtmlElementQuery(_browser.Document, "myTextFieldInput");

var sub = (_browser.Tag as Submission);

HtmlDocument doc = _browser.Document;

if (parentForm != null)
{
var elements = parentForm.GetElementsByTagName("input");
foreach (HtmlElement el in elements)
{
// If there's more than one button, you can check the
// element.InnerHTML to see if it's the one you want
if (el.GetAttribute("type").ToLower() == "submit")
{
var cts = new CancellationTokenSource(Properties.Settings.Default.BrowserNavigationTimeout); // cancel in 10s

var html = await RaiseDynamicEvent(el, "click", cts.Token);
}
}
}
}

// used to raise an event with a dom element that would cause the document to change
async Task<string> RaiseDynamicEvent(HtmlElement element, string evt, CancellationToken token)
{
// navigate and await DocumentCompleted
var tcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = (s, arg) =>
tcs.TrySetResult(true);
_browserTasks[_browser] = tcs;
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
this._browser.DocumentCompleted += handler;

try
{
element.InvokeMember(evt);
try
{
await tcs.Task; // wait for DocumentCompleted
}
catch (TaskCanceledException)
{
// no the end of the world

}
}
finally
{
this._browser.DocumentCompleted -= handler;
}
}

// get the root element
var documentElement = this._browser.Document.GetElementsByTagName("html")[0];

// poll the current HTML for changes asynchronosly
var html = documentElement.OuterHtml;
while (true)
{
// wait asynchronously, this will throw if cancellation requested
await Task.Delay(500, token);

// continue polling if the WebBrowser is still busy
if (this._browser.IsBusy)
continue;

var htmlNow = documentElement.OuterHtml;
if (html == htmlNow)
break; // no changes detected, end the poll loop

html = htmlNow;
}

// consider the page fully rendered
token.ThrowIfCancellationRequested();

// remove from task dictionary
_browserTasks[this._browser] = null;

return html;
}

// couple of useful helpers

HtmlElement FindParentByElement(string elementName, HtmlElement element)
{
if (element.Parent != null)
{
if (element.Parent.TagName.ToLower() == elementName.ToLower())
{
return element.Parent;
}
else
{
return FindParentByElement(elementName, element.Parent);
}
}
else
{
return null;
}
}

HtmlElement HtmlElementQuery(HtmlDocument container, string query)
{
HtmlElement el = null;
if (query.StartsWith("#"))
{
el = container.GetElementById(query.TrimStart('#'));
}
else
{
el = container.All[query];
}

return el;
}

System.Windows.Forms.WebBrowser wait until page has been fully loaded

Here is an extension method for easy awaiting of the DocumentCompleted event:

public static class WebBrowserExtensions
{
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task;
}
}

It can be used like this:

private async void button1_Click(object sender, EventArgs e)
{

webBrowser1.Navigate("https://company.crm4.dynamics.com/main.aspx");
await webBrowser1.DocumentCompletedAsync(); // async magic
HtmlElement fbLink = webBrowser1.Document.GetElementById("quickFind_button_0");
fbLink.InvokeMember("click");
}

The lines after the await will run after the page has completed loading.


Update: Here is another extension method for awaiting a specific element to appear in the page:

public static async Task<HtmlElement> WaitForElementAsync(this WebBrowser wb,
string elementId, int timeout = 30000, int interval = 500)
{
var stopwatch = Stopwatch.StartNew();
while (true)
{
try
{
var element = wb.Document.GetElementById(elementId);
if (element != null) return element;
}
catch { }
if (stopwatch.ElapsedMilliseconds > timeout) throw new TimeoutException();
await Task.Delay(interval);
}
}

It can be used for example after invoking a click event that modifies the page using XMLHttpRequest:

someButton.InvokeMember("click");
var mainContentElement = await webBrowser1.WaitForElementAsync("mainContent", 5000);


Related Topics



Leave a reply



Submit