VBA+Selenium+Edge Cannot 'find' element - vba

I've tried all kinds of ele.findElementAs--- and cannot "find" a particular (multiply nested) element on a webpage - even though I can visually see the element (and values) when inspecting the webpage.
I've used VBA+Edge+Selenium before and can locate / "find" other elements on this page, but not the one (or similar ones) I need.
url: www.cmegroup.com
item: the Price for the December Corn Futures ("ZCZ2")
JSpath: document.querySelector("#main-content > div > div.component.section.cme-homepage-background-gradient-2.pt-5.reverse > div > div:nth-child(8) > div:nth-child(1) > div.component.react.heat-map.loaded > div > div > div:nth-child(1) > div > a.heat-map-card.heat-map-color_1 > div.product-values > div.rate")
snapshot of webpage code above target:
Code from webpage
my code sample:
Sub FindDecCorn()
Dim Edgdriver As New EdgeDriver
Edgdriver.Start "edge"
Edgdriver.Get "https://cmegroup.com"
' *** this one works - finds "main-content" ***
Dim ch As Selenium.WebElement
Set ch = Edgdriver.FindElementById("main-content")
'Set ch = driver.FindElementByLinkText("www.cmegroup.com/etc.clientlibs/cmegroupaem/clientlibs/heat-map.cc2d1dd424fd10c5642e7137587e27a7.css")
Debug.Print ch.tagname, ch.Attribute("id")
' *** I've tried all kinds of .FindElement(s)ByXXXX --- all failed ***
' *** this one fails to find anything with 'product-code' although there are several ***
Dim myElements As Selenium.WebElements
Set myElements = Edgdriver.FindElementsByCss("div[class='product-code']")
For Each myElement In myElements
Debug.Print myElement.Attribute("innerHTML")
Next myElement
Edgdriver.Quit
End Sub

Think I've found your problem, as I commented the elements your looing for are loaded in after the page is loaded. After playing about it looks like they are not loaded until scrolled into view.
Sub main()
Dim Edge As New EdgeDriver
Edge.Start "edge"
Edge.Get "https://cmegroup.com"
' let the page load
Edge.Wait 500
' scroll the page
'Edge.ExecuteScript "window.scrollTo(0, document.body.scrollHeight/4);"
' scroll to bottom (smoothly no jump)
Edge.ExecuteScript "window.scrollTo({top:document.body.scrollHeight, behavior: 'smooth'});"
' wait a little more...
Edge.Wait 500
Dim List As Selenium.WebElements
Dim Item As WebElement
Dim Index As Long: Index = 1
Set List = Edge.FindElementsByClass("product-code")
If List Is Nothing Then
Debug.Print "couldnt find product-code"
Exit Sub
End If
Do
Set Item = List(Index)
If Item.Text = "ZCZ2" Then
Exit Do
Else
If Index > List.Count Then
Exit Do
Else
Index = Index + 1
End If
End If
Loop
If Item Is Nothing Then
Debug.Print "found product-code but not ZCZ2"
Else
' find the parent element then find the desired child element
Debug.Print Item.FindElementByXPath("../..").FindElementByClass("rate").Text
End If
Edge.Quit
Set Edge = Nothing
End Sub

Related

IE VBA open web page equal to clicking on first web page link

I'm trying to automate filling in a transaction on a series of web pages using Internet Explorer and MS-Access VBA. I've done quite a bit of this, but am stumped on this one pair of pages. I using code like this:
Set htmlDoc = ie.Document
With htmlDoc
Set e = .getElementById("j_idt31_data")
If e Is Nothing Then GoTo FileAppeal_Error
Set elems = e.getElementsByClassName("ui-widget-content ui-datatable-odd ui-datatable-selectable")
For Each e In elems
If InStr(1, e.innerText, "Evans ") > 0 Then
e.Click
Exit For
End If
Next
End with
I start on this page:
https://boe.shelbycountytn.gov/eFile/taxrep.xhtml,
click on one of the radio buttons on the left, then click on [Submit], which takes me to
https://boe.shelbycountytn.gov/eFile/search.xhtml
But I can't figure out how to programmatically get the [Submit] button to succeed. When I programmatically click on it I get a "Tax Rep is Required" error message.
Suggestions greatly appreciated.
I did this using selenium basic. Download then add the selenium type library reference.
Option Explicit
Public Sub test()
With New ChromeDriver
.Start "Chrome"
.Get "https://boe.shelbycountytn.gov/eFile/taxrep.xhtml"
.FindElementByCss("span.ui-radiobutton-icon.ui-icon.ui-icon-blank").Click
.FindElementByCss("span.ui-button-text.ui-c").Click
'other code
Stop
.Quit
End With
End Sub
Dim elems As MSHTML.IHTMLElementCollection, i As MSHTML.HTMLInputElement
Set elems = HTMLDocument.getElementsByClassName("ui-icon-blank")
Set i = elems(1) 'this gets the second option in the list
i.Click

Various errors with VBA Loop to pull HTML data

I've been getting various errors with the below VBA code (most recent error is Run-time error '70': permission denied). Basically the code/worksheet connects to an intranet IE database of customers, searches customer activity and imports any activity to the worksheet (will eventually use the activity for reporting). Here's where I run into the errors, depending on the length of time I'm searching I sometimes have multiple pages of activity to pull which requires clicking the "next" button and pull the data from each page until there is no longer a "next" button (no more activity). The loop I have set up will pull from the first page, click the "next" button then sometimes pull from the second sheet but then it trips the error. So I think the error has something to do with the loading of the pages but I've added pauses to allow for loading but still run into the same errors. I'm really stuck on this and unfortunately I can't move forward with the project until I can solve this issue.
Here is the code snippet:
Dim TDelements As IHTMLElementCollection
Dim TDelement As HTMLTableCell
Dim r As Long, i As Long
Dim e As Object
Set TDelements = IE.document.getElementsByTagName("tr")
r = 0
For i = 1 To 1
Application.Wait Now + TimeValue("00:00:03")
For Each TDelement In TDelements
If TDelement.className = "searchActivityResultsContent" Then
Sheet1.Range("E1").Offset(r, 0).Value = TDelement.ChildNodes(8).innerText
r = r + 1
ElseIf TDelement.className = "searchActivityResultsContent" Then
Sheet1.Range("E1").Offset(r, 0).Value = TDelement.ChildNodes(8).innerText
r = r + 1
End If
Next
Application.Wait Now + TimeValue("00:00:02")
Set elems = IE.document.getElementsByTagName("input")
For Each e In elems
If e.Value = "Next Results" Then
e.Click
i = 0
Exit For
End If
Next e
Next i
Do Until Not IE.Busy And IE.readyState = 4
DoEvents
Loop
IE.Quit
End Sub
Any help/suggestions would be very much appreciated. Thank you!
Looking at your code:
'getting any TD elements here
Set TDelements = IE.document.getElementsByTagName("tr")
'waiting here....
Application.Wait Now + TimeValue("00:00:03")
'now trying to use items in TDelements...
For Each TDelement In TDelements
'...
Next
Are you waiting for the page to load when you use Application.Wait ?
If Yes then you should know that TDelements isn't dynamic - it won't update itself as new TD elements are loaded: it's just a snapshot of the elements which were present when you called getElementsByTagName("tr"). So call that after the wait.

How to get the HTML element and the position in the element's text where a person has clicked

Using internet explorer I would like to get the position where a person has clicked on text. An error of 3 to 4 characters is fine. The text is not editable and is usually in a span element.
I am aware I could set up a click event listener for the HTMLDocument however I do not always have the HTMLDocument object and thus may miss the event.
I have tried getting a IHTMLSelectionObject, then creating a text range with the IHTMLTxtRange, however when the web page is simply clicked as opposed to at least 1 character being selected then the IHTMLTxtRange has a parent of the HTMLBody and not of the element that was clicked.
The HTMLDocument.activeElement is also unreliable. In my tests it never actually returns the element clicked, it usually returns a major parent of the element somewhere up the tree.
Using MSHTML is there another way to achieve this?
I have also tried using the WIN API GetCursorPos however I do not know what to do with this position, I do not know how to convert this into the actual element.
EDIT:
I also thought of an interesting idea. When I need to know the element that has the cursor, I set a mouseDown or click event on the whole document. Then fire my own click and catch the event. In the IHTMLEventObj of the event is a FromElement which I had hoped would tell me where the cursor was. It seems it is always nothing for mouseDown and click events. For me at least this object is only used in for example mouseover events.
The following is what I have when at least a character is selected.
Private Function GetHTMLSelection(ByVal aDoc As IHTMLDocument2, ByRef htmlText As String) As Integer
Dim sel As IHTMLSelectionObject = Nothing
Dim selectionRange As IHTMLTxtRange = Nothing
Dim rangeParent As IHTMLElement4 = Nothing
Dim duplicateRange As IHTMLTxtRange = Nothing
Dim i As Integer
Dim x As Integer
Dim found As Boolean
Try
'get a selection
sel = TryCast(aDoc.selection, IHTMLSelectionObject)
If sel Is Nothing Then
Return -1
End If
'the range of the selection.
selectionRange = TryCast(sel.createRange, IHTMLTxtRange)
If selectionRange Is Nothing Then
Return -1
End If
'the the parent element of the range.
rangeParent = TryCast(selectionRange.parentElement, IHTMLElement4)
'duplicate our range so we can manipulate it.
duplicateRange = TryCast(selectionRange.duplicate, IHTMLTxtRange)
'make the dulicate range the whole element text.
duplicateRange.moveToElementText(rangeParent)
'get the length of the whole text
i = duplicateRange.text.Length
For x = 1 To i
duplicateRange.moveStart("character", 1)
If duplicateRange.compareEndPoints("StartToStart", selectionRange) = 0 Then
found = True
Exit For
End If
Next
If found Then
Debug.Print("Position is: " + x.ToString)
htmlText = duplicateRange.text
Return x
Else
Return -1
End If
Catch ex As Exception
Return -1
Finally
End Try
End Function
I cannot post answer with a nice function that shows how to do this but I will explain the important parts.
user the Win32 API GetCursorPos to get the point on the screen where the user last clicked.
If you have iFrames which means more than one HTMLDocument then you need to loop through your iFrames and use the HTMLFrameElement clientWidth and clientHeight along with a IHTMLWindow3 screenTop and screenLeft to find out which HTMLDocument your point is on.
Convert this point to a relative point using the IHTMLWindow you found in number 2.
Once you have the right HTMLDocument and a point relative to this document you can then use the elementFromPoint method on a IHTMLDocument2 object.
Once you have this you now know the point and element that was clicked on.
Private Function getElementTextPosition() As Boolean
Dim sel As IHTMLSelectionObject = Nothing
Dim selectionRange As IHTMLTxtRange = Nothing
Dim duplicateRange As IHTMLTxtRange = Nothing
Dim i As Integer = 0
Dim found As Boolean
Dim x As Integer
Try
'elementWithCursor is a IHTMLElement class variable
If elementWithCursor IsNot Nothing Then
ReleaseComObject(elementWithCursor)
elementWithCursor = Nothing
End If
'docWithCursor is also a IHTMLDocument2 class variable
'cursorPointInDoc is the point relative to the actual document
elementWithCursor = TryCast(docWithCursor.elementFromPoint(cursorPointInDoc.X, cursorPointInDoc.Y), IHTMLElement)
If elementWithCursor Is Nothing Then
Return False
End If
'get a selection
sel = TryCast(docWithCursor.selection, IHTMLSelectionObject)
If sel Is Nothing Then
Return False
End If
selectionRange = TryCast(sel.createRange, IHTMLTxtRange)
If selectionRange Is Nothing Then
Return False
End If
'First check if We have selection text so we will use that as the selected text
'_SelectedText relates to a class property
If selectionRange.text IsNot Nothing Then
_SelectedText = selectionRange.text
selectionRange.collapse(True)
Else
'the the parent element of the range.
selectionRange.moveToPoint(cursorPointInDoc.X, cursorPointInDoc.Y)
End If
'duplicate our range so we can manipulate it.
duplicateRange = TryCast(selectionRange.duplicate, IHTMLTxtRange)
'make the dulicate range the whole element text.
duplicateRange.moveToElementText(elementWithCursor)
'get the length of the whole text
i = duplicateRange.text.Length
For x = 0 To i
If duplicateRange.compareEndPoints("StartToStart", selectionRange) = 0 Then
found = True
Exit For
End If
duplicateRange.moveStart("character", 1)
Next
If found Then
'_CursorPositionInText is a class property and relates to the position where the person clicked in the html text.
_CursorPositionInText = x
_HTMLElementText = elementWithCursor.innerText
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function

Extracting a specific varying element from website source code

I'm trying to extract a specific link from a website and I'm having trouble pulling into a String.
I have to search about 5000 companies from a website and all of the links vary. A link to the source code of an example company (Nokia) is this: view-source:http://finder.fi/yrityshaku/Nokia+oyj this is the part I'm looking at:
<div class="itemName">
<!-- Yritysnimi -->
<!-- Aukeaa aina yhteystiedot-vÃ?lilehdelle -->
<a href="/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia+Oyj/TAMPERE/yhteystiedot/159838" class="resultGray">
I want to extract the Substring between
<!-- Yritysnimi -->
<!-- Aukeaa aina yhteystiedot-vÃ?lilehdelle -->
<a href="
and
" class="resultGray">
this substring will vary with each company I search and so I will only know what the strings are around the substring I'm trying to extract.
I've tried to use browserIE.Document.body.innerHTML
Sub Macro1()
Set browserIE = CreateObject("InternetExplorer.Application")
browserIE.Top = 0
browserIE.Left = 800
browserIE.Width = 800
browserIE.Height = 1200
browserIE.Visible = True
Set ws = ThisWorkbook.Worksheets("Sheet1")
browserIE.Navigate ("http://www.finder.fi/yrityshaku")
Do
DoEvents
Loop Until browserIE.ReadyState = 4
browserIE.Document.getElementById("companysearchform_query_companySearchTypename").Click
browserIE.Document.getElementById("SearchInput").Value = "nokia oyj"
browserIE.Document.getElementById("SearchSubmit").Click
Application.Wait (Now + TimeValue("0:00:4"))
codeArea = Mid(V, InStr(V, "<div class=""itemName""> <!-- Yritysnimi --> <!-- Aukeaa aina yhteystiedot-vÃ?lilehdelle --> <a href="""), Len(V))
Debug.Print codeArea
theLink = Mid(codeArea, 117, InStr(codeArea, """ class=""resultGray"">" - 1))
End Sub
but I get an invalid procedure call or argument
I've researched some but I haven't found a suitable solution yet. Some have suggested pulling just an element from the source code and others copying the whole source code into a string variable. As a person who's not too expert in vba I'd prefer pulling the whole code into a string as I think this way would be easier to understand.
Original website (in finnish) http://finder.fi/yrityshaku/nokia+oyj
You need to locate all of the <div> elements with a classname of itemName. Loop through those to find the <a> element(s) and use the first one to get the href property.
Sub Macro1()
Dim browserIE As Object, ws As Worksheet
Set browserIE = CreateObject("InternetExplorer.Application")
browserIE.Top = 0
browserIE.Left = 800
browserIE.Width = 800
browserIE.Height = 1200
browserIE.Visible = True
Set ws = ThisWorkbook.Worksheets("Sheet1")
browserIE.Navigate ("http://www.finder.fi/yrityshaku")
Do While browserIE.ReadyState <> 4 And browserIE.Busy: DoEvents: Loop
browserIE.Document.getElementById("companysearchform_query_companySearchTypename").Click
browserIE.Document.getElementById("SearchInput").Value = "nokia oyj"
browserIE.Document.getElementById("SearchSubmit").Click
Do While browserIE.ReadyState <> 4 And browserIE.Busy: DoEvents: Loop
'Application.Wait (Now + TimeValue("0:00:4"))
Dim iDIV As Long
With browserIE.Document.body
If CBool(.getelementsbyclassname("itemName").Length) Then
'there is at least one div with the itemName class
For iDIV = 0 To .getelementsbyclassname("itemName").Length - 1
With .getelementsbyclassname("itemName")(iDIV)
If CBool(.getelementsbytagname("a").Length) Then
'there is at least one anchor element inside this div
Debug.Print .getelementsbytagname("a")(0).href
End If
End With
Next iDIV
End If
End With
End Sub
I added Microsoft HTML Object library and Microsoft Internet controls to the project via the VBE's Tools ► References.
Results from the Immediate window.
http://www.finder.fi/Televiestint%C3%A4laitteita+ja+palveluja/Nokia+Oyj/ESPOO/yhteystiedot/159843
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia/SALO/yhteystiedot/960395
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia/TAMPERE/yhteystiedot/853264
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia/ESPOO/yhteystiedot/2931747
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia/ESPOO/yhteystiedot/2931748
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia/TAMPERE/yhteystiedot/835172
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia+Oyj/TAMPERE/yhteystiedot/159838
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia+Oyj/SALO/yhteystiedot/159839
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia+Oyj/TAMPERE/yhteystiedot/159850
http://www.finder.fi/Tietoliikennepalveluja%2C+tietoliikennelaitteita/Nokia+Oyj/TAMPERE/yhteystiedot/159857

Run-time error '91' (Object variable or With block variable not set)

I am relatively new to VBA and am trying to put together a msgbox that will give me a specific number from a web scrape, however I keep running into a run-time error '91' and I simply cannot figure out how to fix this. I have searched countless stackoverflow questions, youtube videos and generic google searches, however have not been successful in finding out the error on my own.
Here is the code:
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate ("http://brokercheck.finra.org")
Do
DoEvents
Loop Until IE.ReadyState = 4
'Enter values from the corresponding sheet
'Set some generic typing for ease
Set doc = IE.document
doc.GetElementbyID("GenericSearch_IndividualSearchText").Value = Worksheets("Master").Range("D203")
doc.GetElementbyID("GenericSearch_EmploymingFirmSearchText").Value = Worksheets("Master").Range("C203")
Set elements = doc.getElementsByTagName("button")
For Each element In elements
If element.getAttribute("type") = "submit" Then
element.Click
Exit For
End If
Next element
Do
DoEvents
Loop Until IE.ReadyState = 4
'find CRD#
Set crd = doc.getElementsByClassName("summarydisplaycrd")(0).innerText 'here is where the run time error occurs
MsgBox crd
and the HTML I am trying to get the information from:
<div class="searchresulttext">
<div class="bcrow">
<div class=""> <span class="summarydisplayname">[redacted]</span> <span class="summarydisplaycrd text-nowrap">(CRD# 5944070)</span></div>
I'm reviewing this code and the finra.org site, and have the following observations, which when addressed, should resolve the problem.
The HTML example you provided is simply incorrect, based on the actual HTML that is returned from the "Check" button.
The actual HTML returned looks like this, and the classname is "displayname", not "summarydisplaycrd":
<div class="SearchResultItemColor bcrow">
<div class="searchresulttext">
<div class="bcsearchresultfirstcol">
<span class="displayname">[redacted]</span> <span class="displaycrd">(CRD# 123456789)</span>
Your code exits the For each element loop upon finding the first "submit" button. This may not be the "Check" button (although I can get results either way, you may want to add more logic in the code to ensure the "Check " button is submit.
UPDATE
On further review, while I can replicate the Type 91 error, I still don't know why your class name appears different than mine (maybe an IE11 thing, dunno...) in any case, I'm able to resolve that by forcing a longer delay, as in this case the DoEvents loop is simply not adequate (sometimes this is the case when data is served dynamically from external functions, the browser is ReadyState=4 and .Busy=True, so the loop doesn't do anything)
I use the WinAPI Sleep function and force a 1 second delay after the "Click" button pressed, looping on condition of ReadyState = 4 and .Busy=True.
NOTE you will need to modify the classname parameter depending on how it is appearing on your HTML.
Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub finra()
Dim IE As Object
Dim doc As Object, element As Object, elements As Object, crd
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate ("http://brokercheck.finra.org")
Call WaitIE(IE, 1000)
'Enter values from the corresponding sheet
'Set some generic typing for ease
Set doc = IE.document
doc.GetElementbyID("GenericSearch_IndividualSearchText").Value = "steve"
doc.GetElementbyID("GenericSearch_EmploymingFirmSearchText").Value = "ed"
Set elements = doc.getElementsByTagName("button")
For Each element In elements
If element.getAttribute("type") = "submit" Then
If element.innerText = "Check " Then
element.Click
Exit For
End If
End If
Next element
Call WaitIE(IE, 1000)
Dim itms As Object
'Set itms = doc.getElementsByClassName("displaycrd")
crd = doc.getElementsByClassName("displaycrd")(0).innerText 'here is where the run time error occurs
MsgBox crd
End Sub
Sub WaitIE(IE As Object, Optional time As Long = 250)
Dim i As Long
Do
Sleep time
Debug.Print CStr(i) & vbTab & "Ready: " & CStr(IE.ReadyState = 4) & _
vbCrLf & vbTab & "Busy: " & CStr(IE.Busy)
i = i + 1
Loop Until IE.ReadyState = 4 And Not IE.Busy
End Sub