Awesomium web browser doesn't show the page - winforms

I have made a form which takes a few URLs in the constructor and loads them into some awesomuim web controls on the form.
It worked fine in the first computer but when I move the program to another computer (with the code), it doesn't show the page in the control. when I move the mouse over the control, it shows the busy icon.
But, if i put a breakpoint somewhere in the constructor and then click continue in visual studio, it works fine! I tried to put a sleep in the constructor but it didn't do the job.
here's the code :
public BrowsersWindow(List<string> links)
{
InitializeComponent();
// Thread.Sleep(5000);
Links = links;
Browsers = groupBox1.Controls;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (index < Links.Count && index < Browsers.Count)
{
Awesomium.Windows.Forms.WebControl current =
(Awesomium.Windows.Forms.WebControl)Browsers[index];
try
{
current.Source = new Uri(Links[index]);
}
catch { }
index++;
}
}

Related

Can WebView2 currently receive Keyboard inputs

I am trying to create a software in WPF which hosts a browser (WebView2 currently 1.0.818.41) and also show a OnScreenKeyboard when there is a input field focused in the browser.
I have done this kind of stuff with CefSharp in WPF before but I cannot do it with WebView2 currently. My problem is I do not find a way to send keystrokes from the OnScreenKeyboard (or from the WPF Window) to the Browser.
In CefSharp there we have a function called ChromiumWebBrowser.GetHost().SendKeyEvent() but I cannot find something similar in WebView2.
Am I blind or is this something which is currently not implemented (or maybe not planed)?
Thank you in advance!
There is no direct way. What can be done is execute some JS, which in turn posts a message to WebView. This message can then be caught back in wv2_WebMessageReceived event.
There is extensive documentation on the interop between.NET and JS and interop between JS and .NET WPF Forms here.
A solution would be to inject a sendMessage JS function in the NavigationStarting event:
private void wv2_NavigationStarting(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args){
var sc = "function sendMessage(txt) { window.chrome.webview.postMessage(txt); }";
wv2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(sc);
}
Now you collect input fields and add onfocus and onblur events to these input fields for example in the NavigationCompleted event like this:
private void wv2_NavigationCompleted(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args){
string script = "const collection ="+
"document.getElementsByTagName(\"input\");" +
"for (let i = 0; i < collection.length; i++){" +
"collection[i].onfocus= ()=>{ sendMessage('onFocus('+collection[i].name')'); }; " +
"collection[i].onblur= (ev)=>{ sendMessage('onBlur('+collection[i].name')'); };"+
"}";
sender.ExecuteScriptAsync(script);
}
Now catch the message in the wv2_WebMessageReceived event:
private void wv2_WebMessageReceived(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2WebMessageReceivedEventArgs args)
{
var postMess = args.TryGetWebMessageAsString();
if (postMess == "onFocus(nameOfField)" )
{
// here activate the button(keyboard)
// store the Name on focusField variable
}
if (postMess == "onBlur" && paneShown)
{
// here deactivate the button(keyboard)
// release the focusField
}
}
Now you can send a click event to the input fields:
private void btn_Clicked(Object sender, EventArgs args)
{
var script = "var field "+
"= document.getElementsByName("+focusField+");" +
" field.value+=field.value"+args.keyValue();
wv2.CoreWebView2.ExecuteScriptAsync(script);
}
wv2 is an instance of WebView2 and the code is typed directly here and not compiled. Hope you get the idea.

Element not found. How to test sequence of steps in multi-page WPF app with winappdriver?

I have a WPF application that features several pages. Navigation takes place after selecting some of the buttons. I am able to start the testing session successfully and to find elements in the main window. However, when clicking the "OK" button that should trigger navigation to another page, the app does not navigate and the test case fails to find UI elements in the next page. I have tried ImplicitWait and Thread.Sleep but the elements still cannot be found. It seems like the app does not navigate at all when the button is clicked.
Any ideas on how to tackle this issue? Below is what I have accomplished so far:
namespace TestDAAC
{
[TestClass]
public class UnitTests
{
protected const string WINAPPDRIVER_URL = "http://127.0.0.1:4723";
private const string DAAC_APP_ID = #"C:\Users\Admin\Desktop\VC PROJECTS\daac\DAAC\bin\x64\Release\DAAC5.exe";
protected static WindowsDriver<WindowsElement> daacSession;
[ClassInitialize]
public static void Setup(TestContext context)
{
if (daacSession == null)
{
var appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalCapability("app", DAAC_APP_ID);
appiumOptions.AddAdditionalCapability("deviceName", "WindowsPC");
daacSession = new WindowsDriver<WindowsElement>(new Uri(WINAPPDRIVER_URL), appiumOptions);
}
}
[TestMethod]
public void SequenceOfSteps()
{
daacSession.FindElementByName("OK").Click();
Thread.Sleep(5000);
// daacSession.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
daacSession.FindElementByName("New Imaging Session").Click();
}
}
}
I was able to find the reason why my test cases were not working. WinAppDriver is compatible only with Windows 10 Home or Pro. My target device runs on Windows Enterprise.

Accessing document elements when using Windows.Forms.WebBrowser

I'm new to automating webpage access, so forgive what is probably a remedial question. I'm using C#/Windows.Forms in a console app. I need to programmatically enter the value of an input on a webpage that I cannot modify and that is running javascript. I have successfully opened the page (triggering WebBrowser.DocumentCompleted). I set browser emulation mode to IE11 (in registry), so scripts run without errors. When DocumentCompleted() triggers, I am unable to access the document elements without first viewing the document content via MessageBox.Show(), which is clearly not acceptable for my unattended app.
What do I need to do so that my document elements are accessbile in an unattended session (so I can remove MessageBox.Show() from the code below)? Details below. Thank you.
The input HTML is:
<input class="input-class" on-keyup="handleKeyPress($key)" type="password">
My DocumentCompleted event handler is:
private static void LoginPageCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = ((WebBrowser)sender);
var document = wb.Document;
// I'm trying to eliminate these 3 lines
var documentAsIHtmlDocument = (mshtml.IHTMLDocument)document.DomDocument;
var content = documentAsIHtmlDocument.documentElement.innerHTML;
MessageBox.Show(content);
String classname = null;
foreach (HtmlElement input in document.GetElementsByTagName("input"))
{
classname = input.GetAttribute("className");
if (classname == "input-class")
{
input.SetAttribute("value", password);
break;
}
}
}
The problem for me was that the page I'm accessing is being created by javascript. Even though documentComplete event was firing, the page was still not completely rendered. I have successfully processed the first page by waiting for the document elements to be available and if not available, doing Application.DoEvents(); in a loop until they are, so I know now that I'm on the right track.
This SO Question helped me: c# WebBrowser- How can I wait for javascript to finish running that runs when the document has finished loading?
Note that checking for DocumentComplete does not accurately indicate the availability of the document elements on a page generated by javascript. I needed to keep checking for the elements and running Application.DoEvents() until they became available (after the javascript generated them).
If the problem comes from the creation of a STAThread, necessary to instantiate the underlying Activex component of WebBrowser control, this is
a modified version of Hans Passant's code as shown in the SO Question you linked.
Tested in a Console project.
class Program
{
static void Main(string[] args)
{
NavigateURI(new Uri("[SomeUri]", UriKind.Absolute), "SomePassword");
Console.ReadLine();
}
private static string SomePassword = "SomePassword";
private static void NavigateURI(Uri url)
{
Thread thread = new Thread(() => {
WebBrowser browser = new WebBrowser();
browser.DocumentCompleted += browser_DocumentCompleted;
browser.Navigate(url);
Application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
protected static void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser browser = ((WebBrowser)sender);
if (browser.Url == e.Url)
{
while (browser.ReadyState != WebBrowserReadyState.Complete)
{ Application.DoEvents(); }
HtmlDocument Doc = browser.Document;
if (Doc != null)
{
foreach (HtmlElement input in Doc.GetElementsByTagName("input"))
{
if (input.GetAttribute("type") == "password")
{
input.InnerText = SomePassword;
//Or
//input.SetAttribute("value", SomePassword);
break;
}
}
}
Application.ExitThread();
}
}
}

Cefsharp - display page loading indication

I'm implemeting a win form which contains a cefsharp chromium-embedded browser.
I'm facing the following problem - sometimes it takes time until a page is loaded.
The problem is that user has no idea that something happens until the page is actually loaded.
I have no control on the pages that the browser displays.
I need to display some kind of loading indication. I searched the web and the only thing that I found was to show an animated loading image while the loading takes place and hide it when the page is loaded (using the load state changed event).
It seems to make things even slower.
Is there anything in Cefsharp infrastructure that I can use? or any other idea of solving it?
Thanks!
ChromeView = new CefSharp.Wpf.ChromiumWebBrowser();
//Adding event listener
ChromeView.NavStateChanged += ChromeView_NavStateChanged;
//Event listener
private void ChromeView_NavStateChanged(object sender, CefSharp.NavStateChangedEventArgs e)
{
if(!e.IsLoading)
{
this.Dispatcher.Invoke(()=> { //Invoke UI Thread
controller.setLoaderinBack(); //UI Update
});
}
else
{
this.Dispatcher.Invoke(() => { //Invoke UI Thread
controller.setLoaderinFront(); //UI Update
});
}
}
For Higher Versions of CefSharp (mine version 81):
ChromeView = new CefSharp.Wpf.ChromiumWebBrowser();
//Adding event listener
ChromeView.LoadingStateChanged += ChromeView_NavStateChanged;
//Event listener
private void ChromeView_NavStateChanged(object sender, LoadingStateChangedEventArgs e)
{
if(!e.IsLoading)
{
//Stuff...
}
else
{
//Stuff...
}
}

Integration Testing of WPF GUI: How do I identify that current work was finished

I want to run integration UI tests on my WPF application, and I'm not sure how to detect when the current test has finished so that I can proceed to the next one.
Simplifying, suppose I have a button on my window. When the button is clicked I disable it, I modify the model, and I re-enable the button. Once it detects that the model has changed, WPF changes the view on the screen.
Now I want to run a test that simulates clicking the button again and again. To click the button I’ll use automation, as described in this SO question. But how do I know when the work is finished and the display updated, so as to "click" the button again? Do I hook the botton’s IsEnabledChanged, or is there some global indication that the current cycle of processing has finished?
Edit: What was missing in my description is that I want the user to see the interim results on the screen. For example, if the test has 10 phases I want the user to see something like a Step Counter label with values 1 .. 10 appearing on the screen, and not just the number changing immediately from 1 to 10. See my answer below.
how do I know when the work is finished and the display updated, so as to "click" the button again?
According to your description, you said When the button is clicked I disable it, I modify the model, and I re-enable the button.
Therefore, I can only assume that when the model has changed, the Button will be re-enabled. So you could either attach a handler to the model's NotifyPropertyChanged event, or as you suggested, add a handler for the IsEnabledChanged event.
Here is how I managed to get it working. This might be trivial - I'm a novice with GUI. I'm just posting it here in the hope it'll help other novices like me :)
Anyhow, what I used is a two button solutions: Test and Step. Test starts the testing sequence, Step runs each step of the tests. The Step buttons interact with an Integration Tester By Steps helper.
The helper receives an Init with the Number Of Commands as parameter, (currently the helper generates random commands by itself, so it just needs to know how many commands to generate). The helpe provides a Step method to execute the next command, and a Needs More Steps property to indicate whether testing should continue.
The helper derives form INotifyPropertyChanged and has a Counter dependency property that is displayed on the main window.
The states of the Test and Step buttons are controlled by three helper methods: SetButtonsFor_OutsideTesting, SetButtonsFor_InsideTestingOutsideAnyStep and SetButtonsFor_InsideTestingInsideAStep.
First, I verified that everything is working manually, and then I added a timer and automated the process using the Stack Overflow suggestions on how to programmatically click a button in WPF and how to make a WPF Timer Like C# Timer.
Now, here's the Main Window's code:
private void Test_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingOutsideAnyStep();
RunTheTestBySteps();
}
public readonly IntegrationTesterBySteps _integrationTesterBySteps =
new IntegrationTesterBySteps();
void RunTheTestBySteps()
{
SetButtonsFor_InsideTestingOutsideAnyStep();
IntegrationTesterBySteps.Init(10);
StartTheTimer();
}
private void StartTheTimer()
{
DispatcherTimer = new DispatcherTimer();
DispatcherTimer.Tick += DispatcherTimer_Tick;
DispatcherTimer.Interval = new TimeSpan(0, 0, 1);
DispatcherTimer.Start();
}
private void StopTheTimer()
{
DispatcherTimer.Stop();
DispatcherTimer.Tick -= DispatcherTimer_Tick;
}
private DispatcherTimer DispatcherTimer { get; set; }
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
if (!BtnStep.IsEnabled) return;
ClickTheStepButton();
CommandManager.InvalidateRequerySuggested();
}
private void BtnStep_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingInsideAStep();
IntegrationTesterBySteps.Step();
if (this.IntegrationTesterBySteps.NeedsMoreSteps)
SetButtonsFor_InsideTestingOutsideAnyStep();
else
{
SetButtonsFor_OutsideTesting();
StopTheTimer();
}
}
private void ClickTheStepButton()
{
var peer = new ButtonAutomationPeer(BtnStep);
var invokeProv = peer.GetPattern(PatternInterface.Invoke)
as IInvokeProvider;
if (invokeProv != null)
invokeProv.Invoke();
}
void SetButtonsFor_InsideTestingInsideAStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = false;
}
void SetButtonsFor_InsideTestingOutsideAnyStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = true;
}
void SetButtonsFor_OutsideTesting()
{
BtnTest.IsEnabled = true;
BtnStep.IsEnabled = false;
}

Resources