How to work with result collections from Selenium - vba

Or how to work with collections (or arrayss) in VBA.
The issue is most probably myself, but I couldn't find an answer yet.
I am trying to go trough a some pages on a web-site with Selenium-vba to find some data.
As usual if there is more to display, the site shows a 'NEXT' button. The button has <a href ... > when the link is activated, else it's just plain text.
To test if there is another page I have found the way to use findElementsByLinkText, and either there is a link or the the collection is empty. So this can be tested by the size of the collection.
This works so far.
But when I try to use the collection (aside from a for each loop) for further action I can't get it to operate.
This is the code:
Dim driver As New SeleniumWrapper.WebDriver
Dim By As New By, Assert As New Assert, Verify As New Verify, Waiter As New Waiter
On Error GoTo ende1
driver.Start "chrome", "http://www.domain.tld/"
driver.setImplicitWait 5000
driver.get "//......."
Set mynext = driver.findElementsByLinkText("Next")
if mynext.Count >0 Then
mynext(1).Click 'THIS STATEMENT DOES NOT WORK
End If
So please help me to get around my understanding issue (which I am convinced it is)
How can I access an element from the collection.
My workaround so far is to execute
driver.findElementByLinkText("Next").Click
but this is unprofessional as it executes the query again.

The Next button is probably loaded asynchonously after the page is completed.
This implies that findElementsByLinkText("Next") returns no elements at the time it's called.
A way to handle this case is to silent the error, adjust the timeout and test the returned element:
Dim driver As New Selenium.ChromeDriver
driver.Get "https://www.google.co.uk/search?q=selenium"
Set ele = driver.FindElementByLinkText("Next", Raise:=False, timeout:=1000)
If Not ele Is Nothing Then
ele.Click
End If
driver.Quit
To get the latest version in date working with the above example:
https://github.com/florentbr/SeleniumBasic/releases/latest

Related

Vba Selenium problems

I had this pop out box stated with "no such element error". I really don't know what's wrong with the code. This basic code is actually looking up for the search box and input text which is ABC. I hope I can find someone who can kindly guide me. Your help will be very much appreciated. TQ
Sub AlwayUseTheToTest()
'
' AlwayUseTheToTest Macro
'
'
Dim mybrowser As Selenium.ChromeDriver
Set mybrowser = New Selenium.ChromeDriver
sel.Start "chrome", "https://www.carousell.sg"
sel.Get "/"
'Stop
sel.FindElementByCss("a.D_pG.D_pH.D_pT.D_mz").SendKeys "ABC"
End Sub
NoSuchElementException is one of the different WebDriver Exceptions and this Exception occurs, when the locators (i.e. id / xpath/ css selectors etc) we mentioned in the Selenium Program code is unable to find the web element on the web page. There are two possibilities for getting this Exception, i.e. either we have provided a incorrect locator and trying to find the web element or we have provided correct locator, but the web element related to the locator is not available on the web page.
Please try using the below
XPATH
//input[#placeholder='Search for an item']
CSS SELECTOR
input[placeholder='Search for an item']
Note: Put some waits while locating to the element, as I do not know in Vba, but in java like below
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement elem = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//input[#placeholder='Search for an item']")));
elem.sendKeys("ABC");
Hope this answer your question

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.

Macro single step works when routine doesn't

I have been running this macro and it come up with an 424 Object Required Error but the macro works and I get the expected result when I run it with a single step button "F8".
Sub FileUpload()
Dim IEexp As InternetExplorer
Set IEexp = CreateObject("InternetExplorer.Application")
IEexp.Visible = False
IEexp.navigate "https://www.google.co.uk/?gws_rd=ssl#q=lenti+a+contatto+colorate"
Do While IEexp.ReadyState <> 4: DoEvents: Loop
Dim inputElement As HTMLDivElement
Set inputElement = IEexp.Document.getElementById("brs")
MsgBox inputElement.textContent
IEexp.Quit
Set IEexp = Nothing
End Sub
The error comes up on the Set inputElement = IEexp.Document.getElementById("brs") line.
You’re checking the ReadyState of the browser, but with some modern web pages the DOM isn’t actually updated with some objects until at least that point.
IE automation in VBA is quite primitive, and it sounds like in this scenario you’re trying to access a node in the DOM before it exists - despite your best efforts to wait until the browser is ready. In some cases this can literally be a matter of milliseconds out in timings.
Your quickest fix here is to simply add Application.Wait() in your loop to cause an actual time delay. A more elegant option might be to introduce a check in your loop and exit the loop when the desired DOM object actually exists. If you do this, there’s a danger of ending up in an infinite loop and so I would always recommend setting a maximum number of increments as a backup.

How to get the last child of an HTMLElement

I have written a macro in Excel that opens and parses a website and pulls the data from it. The trouble I'm having is once I'm done with all of the data on the current page I want to go to the next page. To do this I want to get the last child of the "result-stats" node. I found the lastChild function, and so came up with the following code:
'Checks to see if there is a next page
If html.getElementById("result-stats").LastChild.innerText = "Next" Then
html.getElementById("result-stats").LastChild.Click
End If
And here is the HTML that it is accessing:
<p id="result-stats">
949 results
<span class="optional"> (1.06 seconds)</span>
Modify search
Show more columns
Next
</p>
When I try to run this, I get an error. After a lot of searching I think I found the reason. According to what I read, getElementById returns an element and not a node. lastChild only works on nodes, which is why the function doesn't work here.
My question is this. Is there a clean and simple way to grab the last child of an element? Or is there a way to typecast an element to that of a node? I feel like I'm missing something obvious, but I've been at this way longer than I should have been. Any help anyone could provide would be greatly appreciated.
Thanks.
Here's a shell of how to do it. If my comments are not clear, ask away. I assumed knowledge of how to navigate to the page, wait for the browser, etc.
Sub ClickLink()
Dim IE As Object
Set IE = CreateObject("InternetExplorer.Application")
'load up page and all that stuff
'process data ...
'click link
Dim doc As Object
Set doc = IE.document
Dim aLinks As Object, sLink As Object
For Each sLink In doc.getElementsByTagName("a")
If sLink.innerText = "Next" Then 'may need to play with this, if `innerttext' doesn't work
sLink.Click
Exit For
End If
Next
End Sub

logic to loop until web page element is not nothing

*Using HTML Object library with vba
*(CAS is set as Browser instance (shdocvw))
Set HTMLDoc = CAS.document.frames("MainFrame").document 'pull the main frame
Do Until Not HTMLDoc Is Nothing
DoEvents
Loop
I dont think this is correct since, It will only set HTMLDoc one time, and if it is nothing, its going to keep looping itself over and over, checking for it to be something, but since it's only called once. A better way to go, imho, would be can check for an element and loop until the element exists, since the page can load, but my elements pulling from a DB take half a second longer or so. Im just not sure how to write the loop to keep setting the htmldoc, and then keep checking for an element within it to be not nothing. (The point is so even if my wait timer isnt waiting long enough, it should not proceed until the element exists)
If you wanted to wait for a specific element:
Dim el As Object
Do
Set el = Nothing
On Error Resume Next
Set el = CAS.document.frames("MainFrame").document.getElementById("idHere")
On Error GoTo 0
DoEvents
Loop While el Is Nothing
You probably want to build in a maximum wait time though, so you don't loop endlessly if for some reason the element never appears.