Working around Selenium.StaleElementReferenceException - vb.net

I'm trying to speed up some slow code by separating page loads from page analysis, but it seems that this breaks some rules that Selenium usually follows. As I understand it (found here) when you find something, Selenium stores references to it, not the actual thing you've found.
The result of this behaviour is that you can't load another page before processing the things you found. ie this sequential example works:
myDriver.Navigate.GoToUrl("mysite")
myVar = myDriver.FindElementsByClassName("upperContainer")
thisPN(i) = Trim(myVar.FindElement(By.ClassName("partNumber")).Text)
myDriver.Navigate.GoToUrl("http://www.google.com")
In order to make it faster though, I'm trying to pass the found items to a new thread for processing while the driver does other page loads.
This is illustrated by the below code which throws an exception An unhandled exception of type 'OpenQA.Selenium.StaleElementReferenceException' occurred in WebDriver.dll
Additional information: stale element reference: element is not attached to the page document
sub main()
myDriver.Navigate.GoToUrl("mysite")
myVar = myDriver.FindElementsByClassName("upperContainer")
Dim myThread As New Thread(Sub() ripitems(myVar))
myThread.Start() 'this line kicks off the sub
myDriver.Navigate.GoToUrl("anothersite") ' and then this line changes the page before the sub has got going
end sub
sub ripitems(ByVal elementCollection As System.Collections.ObjectModel.ReadOnlyCollection(Of IWebElement))
for i = 0 to 100
thisPN(i) = Trim(elementCollection.FindElement(By.ClassName("partNumber")).Text)
next i
end sub
I can cure this exception by adding an arbitrary Threading.Thread.Sleep(500) in after myThread.Start(). So I know it's the page change, not the threading that breaks things.
Is there any function that copies the found HTML into the result variable, so that I can use it even after the page I'm looking at has changed. Something along the lines of:
myVar = **ByVal** myDriver.FindElementsByClassName("upperContainer")

Related

FindElements not working for controls in some parts of some Windows programs

WinAppDriver's FindElement will not always find objects in the program to be automated.
I've gotten this to work with other programs, like Notepad, and even a different dialog in my program to be automated, and it worked in those places.
This is the code I am using so far. The first three lines execute without error, successfully launching the application into it's Login dialog:
Dim appCapabilities As DesiredCapabilities = New DesiredCapabilities()
appCapabilities.SetCapability("app", "C:\[my program].exe")
Dim ProgramSession = New WindowsDriver(Of WindowsElement)(New Uri("http://127.0.0.1:4723"), appCapabilities)
ProgramSession.FindElementByName("Password").SendKeys("Password")
The fourth line should find the element, a text box, and enter the string "Password" into it via sendkeys, but it fails, with the following exception:
System.InvalidOperationException: 'An element could not be located on the page using the given search parameters.'
The target object is on screen, and this should work. I'm using the info shown for the object in Inspect.exe, Name: "Password".
WinAppDriver's window shows the following error information:
{"using":"name","value":"Password"}
HTTP/1.1 404 Not Found
Content-Length: 139
Content-Type: application/json
{"status":7,"value":{"error":"no such element","message":"An element could not be located on the page using the given search parameters."}}
The fourth line of code is executed directly after program startup.
Since a program needs some load time, you'll need to wait for the program to finish loading before trying to search for a control on the GUI. You can do this by using a while loop in combination with a stopwatch for timeout.
Dim shouldContinue As Boolean = True
Dim stopWatch As StopWatch = New StopWatch()
Dim timeOut As TimeSpan = TimeSpan.FromSeconds(30)
stopWatch.Start()
While shouldContinue AndAlso timeOut > stopWatch.Elapsed
If element.IsFound Then
shouldContinue = False
stopWatch.Stop()
End If
End While
element.IsFound is just mock-up code, you will need to fill in that blank. This is a good Q/A to show you how to check if a element has loaded.
Another thing you need to take in account is the possibility that your Login Dialog runs in another window handle. If the window handle winappdriver is using is different from the window handle where your element is at, you won't be able to find that element.
Also check if you can find whatever you are searching for in the PageSource property xml from your driver. I usually do this by calling that property in the visual studio watch window, and copying it's content to a xml formatter tool.
I was able to find the password field by using FindElementByXPath instead of FindElementByName.
In order to find the xpath, I used the Recorder for WinAppDriver.
These xpaths can be VERY long. I was able to shorten some of them by removing some duplicate attributes, but some are over 450 characters long. I can sometimes reduce it further with variables, but I'm not exactly delighted so far with WinAppDriver as a replacement for CodedUI.

Trying to get htmlelementcollection from web browser I get NullReferenceException that disappears if I leave the sub and I run it again

I open a webpage with webbrowser and then I submit a form.
After that I'm trying to use this code to extract data from tags of a table:
Dim WebDoc As MSHTML.IHTMLDocument = DirectCast(WebBrowser1.Document.DomDocument, MSHTML.IHTMLDocument)
Dim FTbl As MSHTML.IHTMLTable = WebDoc.getElementById("F")
Dim aElems As MSHTML.IHTMLElementCollection = FascTbl.GetElementsByTagName("a")
I get "NullReferenceException" on aElems (because FTbl is nothing).
But I noticed a strange thing: if (in debug) I leave the sub and execute again the three lines of code (without reloading the webpage), I do not receive errors and aElems is properly valued.
What is it that makes me appear the error in the first run?

Code getting skipped after 'For' loop

I'm seeing some strange behavior during my form load event. Everything works as expected until I run a For loop. Any code below the Next line won't fire. I get no error the form just loads like every things good but it ignores those lines. I have placed a msgbox("test") above and below the loop to confirm this behavior.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Do some form loading stuff
msgbox("test1") 'This will Fire
For i = 0 To DataGridView1.Columns.Count
DataGridView1.Columns(i).SortMode = DataGridViewColumnSortMode.NotSortable
Next
msgbox("test2") 'This wont fire
End Sub
I could fix this by just putting the loop at the bottom of the form load but it bugs to me to not understand why this is happening.
EDIT: After further testing I've found out that if I just run the FOR loop without changing the sort mode then the test2 messagebox will fire. If I comment out the sortmode line everything works fine. Something about setting the sort mode in a loop is preventing the rest of the code from running.
P.S. If anyone knows of a better way to make a databound datagridview with extra columns not sort-able i'm all ears.
The problem is the loop that executes once too often
Arrays in NET starts at index zero and the maximum index is the length of the array minus one
For i = 0 To DataGridView1.Columns.Count - 1
DataGridView1.Columns(i).SortMode = DataGridViewColumnSortMode.NotSortable
Next
Your loop causes an exception that is probably trapped in the caller code and silently suppressed or, being in the load method and if you execute this code inside VS debugger, it is simply not catched for a well know problem with the load event in 64bit code with a debugger attached.
There's an error in your code: you can't enumerate untill DataGridView1.Columns.Count
Fix it like this:
For i = 0 To DataGridView1.Columns.Count -1
DataGridView1.Columns(i).SortMode = DataGridViewColumnSortMode.NotSortable
Next
By the way, you don't see the MsgBox because DataGridView1.Columns(i) is going to raise an exception, but this exception will be ignored if it's in the Load method.

AcessViolationException was unhandled VB.net

So I have this form that loads a page, and after the page is loaded I want to insert the source into RichTextBox1.Text
However after the page load the program crashes(?) and gives me this error
"An unhandled exception of type 'System.AccessViolationException' occurred in Awesomium.Windows.Forms.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
This is my code and it is worth mentioning that I am using awesomium for this!
Public Class Form1
Dim Thread As System.Threading.Thread
Dim html_source_code As String = ""
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
Thread = New System.Threading.Thread(AddressOf Load_Page)
Thread.Start()
End Sub
Private Sub Load_Page()
While html_source_code = ""
If WebControl1.IsDocumentReady Then
html_source_code = WebControl1.ExecuteJavascriptWithResult("document.documentElement.outerHTML").ToString()
RichTextBox1.Text = html_source_code
End If
End While
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Thread = New System.Threading.Thread(AddressOf Load_Page)
Thread.Start()
End Sub
End Class
Thanks in advance!
Here is your first clue...
Control.CheckForIllegalCrossThreadCalls = False
This is a pretty obvious red flag. You should never use this except perhaps for very specific debugging purposes, and for very good reason. Given that the remainder of your code is full of illegal cross thread calls, it's no wonder your program is crashing.
Every single line of code you have written that is executed in a worker thread is touching UI components. You cannot do this. See this post for examples : Cross-thread operation not valid
UI controls are continuously operated on by the main thread of your application. The main thread must be able to access them to handle their associated events, draw them to the screen, etc. If another thread is modifying their contents while the main thread is attempting to read or write to them then bad things happen.
Consider a text box that the main thread is attempting to draw to the screen. It obtains a reference to the location of the string that contains the textbox's text and, in the middle of reading the string, another thread modifies the string, invalidating the reference that the main thread is using. When the main thread tries to read, its existing reference now points to an invalid memory location and, voila, access violation.
In almost all circumstances you should never, ever set CheckForIllegalCrossThreadCalls to False. Leaving it set True will raise an exception, but it gives you the opportunity to see where the problematic code is and allows you to correct it. Setting it False simply hides the illegal call, which may or may not fail, and your application will be vulnerable to random, buggy failures when those illegal calls end up causing problems. Suggest you do some reading about how to write multithreaded .NET applications. A good start would be here : StackOverflow Search.
See also : How to: Make Thread-Safe Calls to Windows Forms Controls

The form referred to itself during construction from a default instance, which led to infinite recursion

I am currently trying to create a simple client (Initiator) using QuickFix with a graphical user interface. I'm using Visual Studio 2012 and programming in VB.Net.
Here is my problem :
When I launch my app, I have this error : "An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsApplication1.exe
Additional information: An error occurred creating the form. See Exception.InnerException for details. The error is: The form referred to itself during construction from a default instance, which led to infinite recursion. Within the Form's constructor refer to the form using 'Me.'"
I have two files in my project which are Client GUI.vb (http://pastebin.com/virgVNyS) and MyQuickFixApp.vb (http://pastebin.com/tQ1GXNSx). The second one contains the class that integrates the IApplication, with all the subs.
The error happens when it executes this line : "Dim initiator As New SocketInitiator(myApp, storeFactory, settings, logFactory)" from Client GUI.vb
but the software highlights a line from the file Application.Designer.vb which is :
Protected Overrides Sub OnCreateMainForm()
Me.MainForm = Global.WindowsApplication1.ClientGUI
End Sub
Can you help me and tell me what is wrong ?
Thank you very much !
When dealing with WinForms, the best proceeding to avoid problems is initialise everything (except simple variable assignation, for example: Dim filename As String = "initiator.cfg" is fine) after the GUI has been constructed/loaded (on the _Load method). The reason why you are getting this error is because of referring to the main Form (Me.MainForm =) before it has been actually created.
Move Dim initiator As New SocketInitiator(myApp, storeFactory, settings, logFactory) to ClientGUI_Load (the Load Event method of your main form) and the error will disappear.
NOTE: if you want to access initiator from "anywhere", you should keep the global declaration but move the assignation to the Load event, that is:
Dim initiator As SocketInitiator 'at the Class level, outside any sub/function (as previously)
And
initiator = New SocketInitiator(myApp, storeFactory, settings, logFactory) 'Inside the ClientGUI_Load method.
I had a similar problem; I'm hoping my description of it and the solution I found may provide clarity to others in the future.
I declared two constants for a grid background color. Selected rows should be yellow. I also said that nonselected rows should be the default background color of one of the grids on the form:
Private MatchColor As Color = Color.Yellow
Private NormalColor As Color = MyFormsDataGridView.BackgroundColor ' <<< this line is bad.
This was in the form's declarations section, before any code could run! I got the error and was flummoxed for hours. I even read this post but it didn't sink in.
The problem of course is that I was referring to a property of a grid on a form that had not yet been instantiated!
The solution was:
Private MatchColor As Color = Color.Yellow
Private NormalColor As Color = Color.White ' <<< this is the fix.
Then it loaded just fine!
I hope that helps.