Clicking a button in IE using VBA - vba

I am using Excel VBA to try click a button on a site, here's the code from the site using inspect element:
<button class="_ah57t _84y62 _frcv2 _rmr7s">ClickHere</button>
And here's what i'm doing in VBA:
Sub testcode()
Dim ie As InternetExplorer
Dim html As HTMLDocument
Set ie = New InternetExplorer
ie.Visible = True
ie.Navigate "somesite.com"
Do While ie.READYSTATE <> READYSTATE_COMPLETE
DoEvents
Loop
Dim e
Set e = ie.Document.getElementsByClassName("_ah57t _84y62 _frcv2 _rmr7s")
e.Click
End Sub
Using the debug I found that the code seems to be storing something called "[object]" in the variable e and and then gives a Runtime error '438' when it gets to e.click. I have even tried using .Focus first, but get the same error. Any ideas?

The getElementsByClassName() function returns a collection not a single element. You need to specify an index on the returned collection in order to return a single element. If there is only one element within the class you can simply use:
ie.Document.getElementsByClassName("_ah57t _84y62 _frcv2 _rmr7s")(0).Click
The (0) specifies the index of the element within the collection returned from the class.
Its easy to tell whether a function returns a collection or single element:
getElementBy... - Returns a single element.
getElementsBy... - Returns a collection of elements.

Related

Exception 0x800A01B6 using getElementById after the first load

I have created a ribbon for Powerpoint with visual studio XML ribbon. This ribbon has a button that, simplifying, does this:
opens an IE browser
search an element (hiddenfield) in the code by his id
get the value of this element
Print the value in the actual slide
It works correctly the first time I click the button of my ribbon, but it throws an Exception 0x800A01B6 the following times I click the button.
This is the code executed when I click the button:
Dim oType As Type = Type.GetTypeFromProgID("InternetExplorer.Application")
If oType IsNot Nothing Then
Dim ie As SHDocVw.InternetExplorer
ie = Nothing
ie = TryCast(Activator.CreateInstance(oType), SHDocVw.InternetExplorer)
If ie IsNot Nothing Then
Dim oEmpty As Object = [String].Empty
Dim oURL As Object = targetURL
ie.AddressBar = False
ie.MenuBar = False
ie.ToolBar = 0
ie.Visible = True
ie.Height = 800
ie.Width = 1100
ie.Navigate(oURL, oEmpty, oEmpty, oEmpty, oEmpty)
End If
Do While (ie.Busy Or ie.ReadyState <> READYSTATE.READYSTATE_COMPLETE)
Sleep(1000)
Application.DoEvents()
Loop
Sleep(10000) ' 10 seconds for testing purpose
Dim str As String = String.Empty
Dim hdnstring As HTMLInputElement = ie.Document.getElementById("hdnstring")
str = hdnstring.value
DoSomething(str)
ie.Quit()
ie = Nothing
End If
This is the code of the website that opens (targetURL), the code remains identical in every load and only the hidden value changes:
<html>
<body>
<form name="form1" id="form1">
<input type="hidden" name="hdnstring" id="hdnstring" value="Get This String" />
</form>
</body>
</html>
The second time (and following) I execute the function: the IE opens, the website fully loads, it waits 10 seconds and then I get an error in the line:
Dim hdnstring As HTMLInputElement = ie.Document.getElementById("hdnstring")
with Exception 0x800A01B6 message.
The most strange thing is that if I click viewsource in the IE contextual menu while the 10 seconds delay (the ones for testing purpose), it works perfect every time I click the button; but if I don't, the Exception 0x800A01B6 appers.
Any idea of what I'm doing wrong?
Error details image:
The type of the Document property is only resolved at run-time, so it's an Object until then. This is why calling any methods in it results in the so-called late binding - you do not yet know if the getElementById method exists or not, so that has to be determined a run-time.
You most likely get the error because the Document is not of the IHTMLDocument3 type, which is the only document type that includes the getElementById method.
What you can try is casting the Document to an IHTMLDocument3 interface. Since it inherits IHTMLDocument and IHTMLDocument2 you can cast between them even if the document is actually one of the earlier types.
DirectCast(ie.Document, IHTMLDocument3).getElementById("hdnstring")

How to pull data from a website with Excel VBA

I am trying to write VBA codes to pull the price of a product from a website. In order to this, I turned on the "Microsoft HTML Object Library" and "Microsoft Internet Controls" in VBA References. However, when I get up to the point to search the of the item that attaches the price, the codes failed. Appreciate if anyone can provide a solution for it.
Below is the link to the sample webpage that I would like to copy price from.
Link
Below is my initial codes:
Sub Update()
Dim IE As New InternetExplorer
IE.Visible = False
IE.navigate "http://www.chemistwarehouse.com.au/buy/36985/Reach-Dentotape-Waxed-20m"
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Dim Doc As HTMLDocument
Set Doc = IE.document
Dim getprice As String
getprice = Trim(Doc.getElementsByTagName("div class="Price" itemprop="price"").innerText)
Worksheets("Sheet1").Range(C1).Value = getprice
End Sub
The function getElementsByTagName() requires a tag name only as parameter:
e.g. getElementsByTagName("div")
Try getElementsByClassName() instead:
getprice = Trim(Doc.getElementsByClassName("Price").Item.innerText)
There were a few issues with the above code.
Issue 1
getprice = Trim(Doc.getElementsByTagName("div class="Price" itemprop="price"").innerText)
This:
div class="Price" itemprop="price" isn't a TagName. TagNames are things like Input, IMG, Anchors, etc. However, we can see the Class attribute for the price element you are interested in. We can change how we select this element by doing:
getprice = Trim(Doc.getElementsByClassName("Price")(0).innerText)
You may notice (0) at the end of the element selection. This is to indicate which element is being selected of the Price ClassName collection. getElementsByClassName returns multiple elements, the first element being 0.
Issue 2
Worksheets("Sheet1").Range(C1).Value = getprice
I don't C1 referenced anywhere. One way to reference a specific cell, is to use a String to represent the range. From your code this becomes:
Worksheets("Sheet1").Range("C1").Value = getprice

VBA IE click button that requires argument?

I'm using Excel and programming in VBA.
I've located a button that I want to click. However, it seems this button requires an argument.
Using the Console in Chrome, I typed this:
document.getElementById("button1").click
That line resolved function click() { [native code] }
so instead I tried this:
document.getElementById("button1").click ('100', '2016-03-02')
And it worked. So, how do I run the same thing from VBA?
I have tried the following:
Sub ClickTheButton()
Dim IE As SHDocVw.InternetExplorer
Dim Doc As MSHTML.HTMLDocument
Set IE = New SHDocVw.InternetExplorer
With IE
.Visible = True
.Navigate "(webpage here)"
Do Until Not .Busy And .ReadyState = 4
DoEvents
Loop
Set Doc = .Document
Doc.GetElementByID("button1").Click ' nothing happens when running this
Doc.GetElementByID("button1").Click "('100', '2016-03-02')" ' not working (can't even attempt to run this)
Doc.GetElementByID("button1").Click ("'100', '2016-03-02'") ' not working (can't even attempt to run this)
End With
End Sub
I can't run the procedure, because of the code after .Click returning Wrong number of arguments or invalid property assignments.
Any ideas?
Answer found here.
The click button ran a function. Instead of clicking the button I could instead just run the function like this:
Dim CurrentWindow As HTMLWindowProxy
Set CurrentWindow = IE.Document.parentWindow
Call CurrentWindow.execScript("xajax_DoCalReservation('100', '2016-03-02')")

Working with Internet Explorer Object

My goal is to create a VB application that reads and writes information to various webpages loaded in Internet explorer.
I have created a program that works exactly as I intend in VBA. I am now trying to re-implement the same programming in VB.
I have a function that looks for and returns an Internet Explorer Object where the input matches the LocationName.
Assuming the target page is loaded, I can work with it. Methods such as getElementByID() work perfectly. If the browser window is closed and reopened, and the code is run again, the results are very inconsistent. The getIE function seems to work, but when trying to use methods like document.getElementByID() a NullReferenceException is thrown.
Does anyone know if there is anything I am missing that I need to include to get the document property to update?
EDIT: I have looked over the NullReferenceException article. It hasn't helped me unfortunately. In case I was unclear in my wording, I would like to reiterate that the same bit of code yields a different result when executed the 2nd time under the same conditions (same webpage open in Internet Explorer).
On the second execution IE.locationName is retrievable but IE.Document.title is not. The problem is definitely with the Document property as far as I can tell. I am truly stumped.
Many thanks
Public Function getIE(targetTitle As String) As SHDocVw.InternetExplorer
' Create the shell application and Collection of open windows
Dim shellObj As Object = CreateObject("Shell.Application")
Dim shellWindows As SHDocVw.ShellWindows = shellObj.Windows()
getIE = Nothing
' Scan through the Collection
For I = (shellObj.Windows.Count - 1) To 0 Step -1
' If found, assign this to the function output and exit early
If InStr(shellWindows(I).LocationName, targetTitle) Then
getIE = shellWindows(I)
Debug.Print("Found: " & shellWindows(I).LocationName)
Exit For
End If
Next I
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim IE As SHDocVw.InternetExplorer
IE = getIE("my site title")
If IE Is Nothing Then
Debug.Print("Site not open")
Exit Sub
End If
' The code always gets this far, and despite the page being found, starts throwing exceptions from here on if the window has been closed and reopened whilst my application has stayed running.
' Sample form data insertion code
IE.Document.getElementById("textbox1").value = "my value"
' Click the submit button
IE.Document.getElementById("submit").click()
' Wait for page to load
While IE.Busy
End While
IE.Document.getElementById("textbox2").value = "my 2nd value"
' done
IE = Nothing
End sub

how to refer back to created browser instance

I create a browser like so, and manually navigate to the web page I need to be. I intend to automatically pull certain elements once I get to the page I need to be on via a seperate macro
Sub Test()
Set CAS = New SHDocVw.InternetExplorer ' create a browser
CAS.Visible = True ' make it visible
CAS.navigate "http://intraneturl"
Do Until CAS.readyState = 4
DoEvents
Loop
This works fine, then I do
Public Sub Gather
Set HTMLDoc2 = CAS.document.frames("top").document
Call Timer1
With HTMLDoc2
.getElementById("tab4").FirstChild.Click
End With
Call Timer2
Dim fir, las, add1, add2, cit, stat, zi As String
Dim First As Variant
Dim Last As Variant
Dim addr1 As Variant
Dim addr2 As Variant
Dim city As Variant
Dim Thisstate As Variant
Dim Zip As Variant
Call Timer2
Set HTMLDoc = CAS.document.frames("MainFrame").document
Call Timer2
With HTMLDoc
First = .getElementsByName("IndFirst")
Last = .getElementsByName("IndLast")
addr1 = .getElementsByName("txtAdd_Line1")
addr2 = .getElementsByName("txtAdd_Line2")
city = .getElementsByName("txtAdd_City")
Thisstate = .getElementsByName("cmb_Add_State")
Zip = .getElementsByName("txtAdd_Zip")
End With
fir = First.Value
las = Last.Value
add1 = addr1.Value
add2 = addr2.Value
cit = city.Value
stat = Thisstate.Value
zi = Zip.Value
'navigate back to start page
With HTMLDoc2
.getElementById("tab3").FirstChild.Click
End With
End Sub
This works the first time, but after the first time, I get "Object variable or with block variable not set" when trying to run the gather() sub again, on a different web page that contains similar information. Any Ideas as to what im doing wrong?
"The error "object variable or with block variable not set" occurs on: Set HTMLDoc2 = CAS.document.frames("top").document the second time i try running Gather()."
This is probably one of three things:
CAS is no longer an object
To check this, set a breakpoint on the line, press ctr+G in the VBA Editor and type ?CAS Is Nothing in the Immediate Window; the result should be False; if it is True CAS is no longer an object
Like Daniel Dusek suggested, make sure CAS.document.frames("top") is an actual element on the page.
To check this, open the webpage you are trying to script, press F12 in Internet Explorer, click on the arrow in the toolbar and click on the "top" frame element in the webpage, switch back to the Developer Tool and look at the line highlighted. Make sure the frame element is named "top".
The HTML hasn't fully loaded when you try to reference the frame element. Set a longer delay or a loop.
i.e. (untested):
Do Until HtmlDoc2 Is Nothing = false
Set HTMLDoc2 = CAS.document.frames("top").document
Loop
Maybe the more important question is why manually navigate to another page? Can't you automate that part of your process too?