I’m trying to learn a little about clicking on buttons with JavaScript. My test scripts are from here. A MacScripter thread is here. My goal is to click on the “Top” button on the main MacScripter page here. The Inspect data for the “Top” button from Google Chrome is:
My test scripts, none of which work, are as follows:
--test web page is https://www.macscripter.net
--click by ID
clickID("ember136") --doesn't work and ID changes
to clickID(theId)
say "ID"
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementById('" & theId & "').click();" in document 1
end tell -- tells Applescript you are done talking to Safari
end clickID -- lets AppleScript know we are done with the function
--click by name
# clickName("button", 0) --name not available
to clickName(theName, elementnum)
say "name"
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementsByName('" & theName & "')[" & elementnum & "].click();" in document 1
end tell
end clickName
--click by class name
# clickClassName("nav-item_top top ember-view", 0) --doesn't work
to clickClassName(theClassName, elementnum)
say "Class"
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
end tell
end clickClassName
--click by tag name
# clicktagName("Top", 0) --doesn't work
to clicktagName(thetagName, elementnum)
say "Tag"
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementsByTagName('" & thetagName & "')[" & elementnum & "].click();" in document 1
end tell
end clicktagName
How can I get these scripts to work? I have enabled the Allow JavaScript from Apple Events setting in Safari.
I’ve had a few successes but only with class name:
--https://www.google.com
--Sign in button
clickClassName("gb_Ta", 0)
to clickClassName(theClassName, elementnum)
tell application "Safari"
do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
end tell
end clickClassName
--news.google.com
--News Showcase item
--clickClassName("EctEBd", 3)
--to clickClassName(theClassName, elementnum)
-- tell application "Safari"
-- do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
-- end tell
--end clickClassName
--https://www.macscripter.net/c/scripting-forums/5
--Log in button
--clickClassName("d-button-label", 0)
--to clickClassName(theClassName, elementnum)
-- tell application "Safari"
-- do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
-- end tell
--end clickClassName
This same process seems to work with Google Chrome:
--click on "Log In" button
--https://www.macscripter.net/
clickClassName("d-button-label", 0)
to clickClassName(theClassName, elementnum)
tell application "Google Chrome"
execute tab 1 of window 1 javascript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();"
end tell
end clickClassName
It also works in a shortcut, although, if the button is not found, the shortcut shows the following incorrect error message:
Unable to Run JavaScript on Web Page. Make sure that “Allow JavaScript from Apple Events” is enabled in the Develop menu in Safari. The Develop menu can be enabled in the Advanced section of Safari’s Preferences.
I thought I would add a few tentative thoughts on this topic. First, before doing anything else, you have to enable the Safari option to Allow JavaScript from Apple Events.
Second, the only script approach I have been able to use is the one that uses class name, and the general process I follow is:
View the applicable site data in Safari by right-clicking on the button and selecting Inspect Element.
Look for a class name in the displayed data in the highlighted and surrounding areas.
Select a class name for testing up to the first space. Selecting the entire class name doesn’t seem to work for me.
Insert the class name in the script and sequentially test difference element numbers beginning with “0”.
Have the web site active behind the script editor, so that you’ll know when you’ve found the correct combination.
The above process involves a lot of trail and error and doesn’t work in all or even most circumstances. Also, I wonder how reliable a working script will be over time. However, it’s an approach to consider when others fail.
I tested this approach on the slick deals shopping site, and I was able to get a working script in perhaps 4 minutes.
--https://slickdeals.net/forums/forumdisplay.php?f=9
set className to "slickdealsHeader__navItemWrapper"
set elementNumber to "0" -- 1, 2, and 3 work for other buttons (e.g. "Post a Deal")
tell application "Safari" to do JavaScript "document.getElementsByClassName('" & className & "')[" & elementNumber & "].click();" in document 1
This shortcut is functionally equivalent to the above script:
I decided to write a working script for additional learning on this topic. The following script logs me into MacScripter and works in my testing. However, I’d like to replace the GUI scripting with JavaScript, and I wondered if anyone could help me with this? BTW, the second JavaScript code line clicks on a button by ID, and it was good to actually get this to work in a script. Thanks for the help.
--test site is https://www.macscripter.net
--edit needed to eliminate and reduce delays
set userName to "peavine"
set the clipboard to userName
set userPassword to "xxx" --not my real password
tell application "Safari"
activate
tell document 1 to do JavaScript "document.getElementsByClassName('d-button-label')[1].click();" --Log In button
end tell
delay 0.5
tell application "System Events"
keystroke "v" using {command down} --paste user name
delay 0.5
set the clipboard to userPassword
delay 0.5
key code 48 --tab to password text box
delay 0.5
keystroke "v" using {command down} --paste user password
end tell
delay 0.5
tell application "Safari" to tell document 1 to do JavaScript "document.getElementById('login-button').click();" --Log in button
Mockman. I agree with what you say. I’m just using the log-in process for testing purposes to learn JavaScript.
I found the commands I needed to replace the GUI scripting. The only issue is that the forum software does not correctly convert the password string into a hidden password, causing the log-in to fail. So, I used GUI scripting for this one task. Timing appears to be an issue when using Javascript to accomplish tasks such as this and at least some of the delays in the following script are necessary.
--test site is https://www.macscripter.net
set userName to "peavine"
set userPassword to "xxx" --not my actual password
set delayLength to 0.5 --test different values
set the clipboard to userPassword
tell application "Safari"
activate
delay delayLength
tell document 1
do JavaScript "document.getElementsByClassName('d-button-label')[1].click();" --click log in button
delay delayLength
do JavaScript "document.getElementById('login-account-name').value = '';" --empty user name
do JavaScript "document.getElementById('login-account-name').value = 'peavine';" --enter user name
delay delayLength
do JavaScript "document.getElementById('login-account-password').value = '';" --empty password
do JavaScript "document.getElementById('login-account-password').focus();" --move focus to password field
delay delayLength
pasteClipboard() of me --paste password
delay delayLength
do JavaScript "document.getElementById('login-button').click();" --click log in button
end tell
end tell
on pasteClipboard()
tell application "System Events" to keystroke "v" using {command down}
end pasteClipboard
Here is the JavaScript you need to click the “Top” link on the MacScripter landing page.
document.getElementsByTagName(‘a’)[12].click()
As the name of the function implies the ‘getElementsByTagName’ will return an HTML collection containing all of the anchors or links in the full page. You then have to find the one you want, in this case it is the 13th one. As with most collections in JavaScript they are zero based so the index will be 12.
The best way to work with JavaScript in a browser is to use the developer tools. All major modern browsers have these developer tools but they may differ on where they are located or whether they are made available without setting a preference. It is best to look in the Help or documentation for the browsers. I just played with the console and inspector tools in my browser, in this case it was Firefox, to determine the exact command.
HTML offers many of the conventional publishing idioms for rich text and structured documents, but what separates it from most other markup languages is its features for hypertext and interactive documents. This section introduces the link (or hyperlink, or Web link), the basic hypertext construct. A link is a connection from one Web resource to another. Although a simple concept, the link has been one of the primary forces driving the success of the Web.
A link has two ends – called anchors – and a direction. The link starts at the “source” anchor and points to the “destination” anchor, which may be any Web resource (e.g., an image, a video clip, a sound bite, a program, an HTML document, an element within an HTML document, etc.).
Yes “a” stands for anchor which is another name for hypertext link.
tag has a special meaning in html, it refers to the html markup element tags, such as p, a, ul, li, table, div, span, etc… In your attached image from the dev tools it is all of the elements that have the angled brackets around them.
You can find more on these through general sites like Mozilla web docs (HTML: HyperText Markup Language | MDN) or w3 schools (https://www.w3schools.com/). These resources should give you more than you ever wanted to know about the three languages (html, css, javascript) of web browsers.
While it would actually be easier to do what you want in JavaScript, I have modified your script to provide you with a function that will search the web page for a link containing a certain text.
set linkTextOfInterest to "Top"
set linkIndex to my getLinkIndex(linkTextOfInterest)
on getLinkIndex(linkTextOfInterest)
tell application "Safari"
tell document 1
(* Number of anchors in the document *)
set linkCount to do JavaScript "document.getElementsByTagName('a').length"
set linkIndex to 0
repeat with i from 0 to linkCount
(* Text shown as the link in the document *)
set linkInnerText to do JavaScript "document.getElementsByTagName('a')[" & i & "].innerText"
if linkInnerText = linkTextOfInterest then
set linkIndex to i
exit repeat
end if
end repeat
end tell
return linkIndex
end tell
end getLinkIndex
Mockman. Thanks for the link. My search skills are not what they used to be.
Pseudotsuga. Thanks for all the great information. I tested your script on the MacScripter site and it returned the correct element number. I also tested it on some buttons on the Google News site, and it worked there as well. It appears that the innerText property is key to the script’s operation, and, for anyone interested, this property is defined as shown below. The length property is also useful to know.
The innerText property of the HTMLElement interface represents the rendered text content of a node and its descendants. As a getter, it approximates the text the user would get if they highlighted the contents of the element with the cursor and then copied it to the clipboard.
Just as an aside, linkCount at the beginning of the repeat loop might be changed to (linkCount - 1). Otherwise an error is thrown if a match is not found.
Yes, you are right. After warning you about zero-based collections I went ahead and ignored my own advice. Such is life…
Here is a revised version with the added correction and a try-error construct to catch any other potential errors.
set linkTextOfInterest to "Top"
set linkIndex to my getLinkIndex(linkTextOfInterest)
on getLinkIndex(linkTextOfInterest)
try
tell application "Safari"
tell document 1
(* Number of anchors in the document *)
set linkCount to do JavaScript "document.getElementsByTagName('a').length"
set linkIndex to 0
repeat with i from 0 to linkCount - 1
(* Text shown as the link in the document *)
set linkInnerText to do JavaScript "document.getElementsByTagName('a')[" & i & "].innerText"
if linkInnerText = linkTextOfInterest then
set linkIndex to i
exit repeat
end if
end repeat
end tell
return linkIndex
end tell
on error errMsg
display alert errMsg
end try
end getLinkIndex
I’ve been testing my script in post 6 without issue. However, the situation might arise where the script would fail because the web page has not fully loaded. The following script should (hopefully) address this issue.
--test site is https://www.macscripter.net
set userName to "peavine"
set userPassword to "xxx" --not my real password
set delayLength to 0.5 --test different values
set the clipboard to userPassword
tell application "Safari"
activate
tell document 1
set siteFound to false
repeat 5 times --check if web site open by getting value of "Log In" button
delay delayLength
try
do JavaScript "document.getElementsByClassName('d-button-label')[1].innerHTML;"
set aCheck to result --only purpose is to force error if innerHTML value not returned
set siteFound to true
exit repeat
end try
end repeat
if siteFound is false then display dialog "Web site not found" buttons {"OK"} cancel button 1 default button 1
do JavaScript "document.getElementsByClassName('d-button-label')[1].click();" --click log in button
delay delayLength
do JavaScript "document.getElementById('login-account-name').value = '';" --empty user name
do JavaScript "document.getElementById('login-account-name').value = 'peavine';" --enter user name
delay delayLength
do JavaScript "document.getElementById('login-account-password').value = '';" --empty password
do JavaScript "document.getElementById('login-account-password').focus();" --move focus to password field
delay delayLength
pasteClipboard() of me --paste password
delay delayLength
do JavaScript "document.getElementById('login-button').click();" --click log in button
end tell
end tell
on pasteClipboard()
tell application "System Events" to keystroke "v" using {command down}
end pasteClipboard
This is a bit off-topic, but I’m working to learn JavaScript (primarily DOM) for use with AppleScripts and shortcuts, and I’m making good progress. However, I’ve encountered what is probably a simple issue, and I hoped someone could help me.
The following works as expected in a Run JavaScript on Active Safari Tab action in a shortcut:
let theLinks = [];
let theElements = document.querySelectorAll("a");
for (let anElement of theElements) {
theLinks.push(
anElement.href
);
}
completion(theLinks);
However, I can’t get this to work in an AppleScript. Thanks for the help.
tell application "Safari" to tell document 1
set theLinks to do JavaScript "let theLinks = [];
let theElements = document.querySelectorAll('a');
for (let anElement of theElements) {
theLinks.push(
anElement.href
);
}"
end tell
return theLinks
I’m by far no expert - but maybe you get no results because you didn’t used the whole JS code… I would give it a try and use the whole code with the do JavaScript command.