Driving a website using VBA and Selenium - html

I have to log into SnapSurveys and download 20+ files monthly. Already a tedious process, I now have to do it weekly instead and it will be beyond tedious to do it by hand, so I want to automate it and have installed Selenium to do so. I've tracked the entire process using the SeleniumIDE (in FireFox), so I know what I want to do, and the basics of how to do it, however, I've run into an absolute brick wall trying to understand the web page structure to make it happen.
Using the Dev tools in both Chrome & FireFox, I've identified the "User Name" field as this:
<p>
<label for="UserName">Username</label>
<input data-val="true" data-val-required="The Username field is required." id="UserName" name="UserName" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</p>
I'm using the following code to attempt to locate the "Username" text box so I can type data into it. I've tried each of the values of Text, in turn, with each of the Driver.FindElementBy... possibilities, and they all give me this error:
Run-time Error '7':
NoSuchElementError
Element not found for <By type> = <text>
This is the code:
Private Sub Login()
Const SITE_BASE_NAME As String = "https://www.snapsurveys.com/login"
Dim Driver As IEDriver
Set Driver = New IEDriver
Dim IsSiteLoaded As Boolean
IsSiteLoaded = Driver.Get(SITE_BASE_NAME)
If IsSiteLoaded Then
Dim Text As String
Text = "columns six"
Text = "UserName"
Text = "main"
Text = "//*[#id=""UserName""]"
Dim El As WebElement
Set El = Driver.FindElementByClass(Text)
Set El = Driver.FindElementByCss(Text)
Set El = Driver.FindElementById(Text)
Set El = Driver.FindElementByLinkText(Text)
Set El = Driver.FindElementByName(Text)
Set El = Driver.FindElementByPartialLinkText(Text)
Set El = Driver.FindElementByTag(Text)
Set El = Driver.FindElementByXPath(Text)
End If
Driver.Quit
End Sub
The one combination I've found that's not given me an error is:
Text = "main"
Set El = Driver.FindElementById(Text)
But that only gives me the breadcrumbs at the top of the screen, and I'm not really sure how to get anywhere useful from there.
Obviously, I don't understand enough about web design to have any clue what I'm supposed to be looking for, but I thought I could trial and error my way through it with a little less frustration than this.
What element(s) do I need to be looking for in the page source, and which FindElementBy function do I need to use to search for it in code?
Further, is filling in the UserName & Password fields, clicking "Log In", then clicking on the appropriate links on the next page, etc. the best way to go about this? I think that once I've logged myself in, I should be able to get a collection of links for each file I need and directly download each link, but I'm not 100% certain if I can do that or not.
Some notes:
Yes, I realize that the code is using IE as the browser. The drivers I have for FF & Chrome seem to be out of date (browser opens, but it won't load the web page) and I haven't gotten newer ones. At the moment, I don't care what browser I use so long as I get it working.
Yes, I realize that I'm closing the browser down at the end of the Sub. Again, I'm just testing, trying to find my way in the dark.

The login functionality is inside an iframe. You have to switch selenium's focus to that iframe and then try again and it should work.
You can try this:
Driver.switchtoframe (0)
Driver.FindElementById("UserName")
Some knowledge about web design can help a long way when dealing with selenium to automate, but with just some basic knowledge you can get the work done easily.
What element you should be looking for depends on what you are trying to achieve. If you want to enter something in a text field, you should be looking for <input type="text"> elements, for table's you need to be looking for <table> elements.
I usually prefer using find elements by xpath, but if the element has a unique id or class on the webpage, you can use find element by class name or find element by id.
You seem to be going on the right track to automate your task.

Related

How to tell Chrome form does not contain credit card fields?

Chrome is being overzealous and thinks my HTML form contains credit card information and thus proposes to fill it in with credit card information.
Are there any attributes that I can use to tell Chrome that there is no credit card information to be filled in, in this form?
The field names it is trying fill in credit card information in are:
reg_id (it puts in a CC number here)
emergency_first_name (it puts in first name here)
emergency_last_name (it puts in last name here)
I don't want to have to disable autocomplete if I don't have to.
The frustrating thing here is the Chrome 'knows better' attitude, where it ignores any value to autocomplete, including off:
<input autocomplete="off" value="" size="10" maxlength="10" id="id_reg_id" name="reg_id" type="text">
Edit: updated following answers.
try
input type="custom"
or use textarea with a single row and resize off
Your browser shouldn't remember your credit card number by default -- I can only assume that you entered into a field that had a 'generic' autocomplete value on it. You can always force your browser to forget this information by simply hitting Delete when selecting it (with the arrow keys) in the dropdown of pre-fill options.
As for preventing it appearing in certain fields, it depends on what information you want each field to hold, but there's a wide array of autocomplete values that you can use. You can use number for IDs, and the other two fields you mentioned actually come with specialised autocomplete values, given-name and family-name:
<input name="reg_id" autocomplete="number" />
<input name="emergency_first_name" autocomplete="given-name" />
<input name="emergency_last_name" autocomplete="family-name" />
If number just won't cut it, you can also make use of a JavaScript regular expression to further restrict input:
const regex = new RegExp("^[a-zA-Z]+$");
const form = document.getElementsByTagName('form')[0];
const reg_id = document.getElementsByTagName('input')[0];
form.addEventListener('click', function(e) {
e.preventDefault();
if (regex.test(reg_id)) {
this.submit();
}
});
<form>
<input name="reg_id" autocomplete="number" />
<input name="emergency_first_name" autocomplete="given-name" />
<input name="emergency_last_name" autocomplete="family-name" />
</form>
I have been banging my head against the desk for a while because of this. We have forms to enter Instruments test data, and a field called "Test Card Number", as well as "Kit (Exp. Date)". Guess what Chrome thinks these fields are for?
Needless to say, I'm pretty sure the users would be VERY upset to see chrome us trying to pull their CC information when they're inputing clinical research data.
Even autocomplete="new-password" and autocomplete="nope" are failing to do any good, here.
I tried to load the field with no label and add it dynamically in javascript. No dice. Used html entities instead of characters. Nope.
Well, after a few hours of scouring the web with no solution in sight, I figured one out: insert a few random - within each word of the offending labels. (For me, with Test Card Number, it had to be in BOTH Card and Number. Test was fine left alone).
One could easily write a javascript extension/utility function to split the html of an offending label and slap that invisible span down the middle (and one to remove it in case of needing to use the label value).
Something like this (using jQuery and old js standards because we support old browsers, with no verifications if label is missing or empty, so adapt accordingly. In fact, I'm sure a regex or some other fancy stuff could be used, but I don't have the time to fiddle around with it atm):
jQuery.fn.breakAutofill = function () {
var $lbl = $("label[for='" + this[0].id + "']"),
finalText = $lbl.html().split(" "),
foilSpan = "<span style='display:none;'>-</span>";
for (var idx in finalText) {
var textVal = finalText[idx],
midPos = Math.floor(textVal.length / 2);
finalText[idx] = textVal.substr(0, midPos) + foilSpan + textVal.substr(midPos);
}
$lbl.html(finalText.join(" "));
}
Which you can then call on document ready :
$("your_input_selector").breakAutofill();
I hope that helps someone.

R - How to change the value of an input tag using RSelenium?

I want to change the page size on a web site as shown in the image using RSelenium. Page Size image.
Its actually a combobox with the following html written for it:
<input autocomplete="off" name="ctl00$ContentBody$rgridPDPList$ctl00$ctl03$ctl01$PageSizeComboBox" class="rcbInput" id="ctl00_ContentBody_rgridPDPList_ctl00_ctl03_ctl01_PageSizeComboBox_Input" value="10" readonly="readonly" type="text">
Here the value is 10 and the other options for the drop box are 20, 50.
I tried using the below lines of code:
opt <- remDr$findElement(using = 'xpath', "//*/input[#value = '20']")
opt$clickElement()
But i am getting error as:
Error: Summary: NoSuchElement
Detail: An element could not be located on the page using the given search parameters.
class: org.openqa.selenium.NoSuchElementException
Also i used this:
xpathSApply(elemxml,"//input[#value]",xmlGetAttr,"value")
This is giving me the value as 10, but I am not able to set some other value using this.
I tried using findElements also, but no luck.
Can you please let me know how to change this value to 20 or 50 using RSelenium (or any other method if possible).
Also is it possible to mention a value that is not from the combo box for eg: 1500.
I am sorry I cannot share the link as it is protected.
Kindly let me know if any other inputs are required from my side.
got the solution. findElement and clickElement did the job.

How to get a html element content

I want to ask if there is a way for me to get like a web element content. What i mean is:
the site
the program
You don't need to type the site address or where the element is, in need it only in this case(fully empty site with a few words only).
My question is that, wets say that you have a text on a webpage, and you want that text to appear in a textbox...That's it
you can use this :
Dim We As New System.Net.WebClient()
textbox1.text = We.DownloadString(_Url)
We.Dispose()

Access label controls have different properties listed - how can I resolve this?

I have a problem I believe is related to this problem. I have many label controls. Some of them become "weird" and start exhibiting a weird "extra bold" display:
These labels have the same formatting but obviously have different visual representations.
After looking into the properties for both labels, I noticed something quite strange: they have different properties. The broken label properties section actually is not showing the HyperLink properties nor the on-event actions.
In fact, the "Events" properties sheet is actually empty when I go view it.
I am quite confused what is going on here. I would like a way to fix these controls, preferably using VBA so I can add a programmatic fix to update all the broken labels.
Much to my surprise, the following code works:
Application.Forms(gMananger_FormName).controls("Label13").HyperlinkAddress=""
I was expecting some sort of error since those labels do not show that control.
I have also tried Compact/Repair to no avail. Nor did turning ClearType off in the Access options, as suggested in the link above.
I have also written the following code to copy/add a new control but this breaks completely when controls are in tab spaces (it inserts a new line but doesn't delete the row, perhaps I could fix this if I spend more time, I'm concerned about accidentally deleting a ton of my form controls should I do this).
Sub testCopyingControl()
Dim c
Dim i As Integer, key
Dim prop As Scripting.Dictionary
Set prop = New Scripting.Dictionary
Set c = Application.Forms(gMananger_FormName).Controls("Label3120")
For i = 0 To c.Properties.count - 1
prop.Add c.Properties(i).Name, c.Properties(i).value
Next i
Dim newC As Label
Set newC = CreateControl(gMananger_FormName, acLabel)
DeleteControl gMananger_FormName, c.Name
'there are a fair number of readonly properties unfortunately...
On Error Resume Next
For Each key In prop.Keys
Debug.Print key & vbTab & prop(key)
newC.Properties(key) = prop(key)
Next key
End Sub
I can also fix this individually by cutting/pasting and moving the label back to its grid location. However this is a lot of work as I have quite a few controls doing this.
How can I first fix a single label control exhibiting this behavior?
How can I do this programatically via VBA?

Use VB.NET WebBrowser Control to update text field

I have an HTML form that I would like to fill in automatically. There is one field in particular that I am having trouble with and it has following HTML code:
<input type="text" name="add1" size="13" maxlength="12">
As you can see it does not have a value attribute that I can manipulate directly so have to resort to something else. I use the following code to get the desired element:
Dim element As HtmlElement = WebBrowser.Document.GetElementsByTagName("input").GetElementsByName("add1").Item(0)
This works fine but when I try to use element.InnerText() = "foo" the OuterHtml updates to include the new text (value="foo" to the HTML above) but element.InnerText is stll equal to Nothing and when the page is shown to the user the text element still has no input. So what do I need to do to get the text input to show in the proper field?