Click Button on Web Page

I am trying to use javascript to click a button on a web page as using Applescript did not work if the place on the page changed .

This is detail of the button I want to access

Log in

It does not have name, class or ID I have run the following code using log in. Also changed “getElementsByName” to “getElementsByType”

Thanks for considering this, I know nothing about Javascript and got the information that created the following code from the web

to clickName(theName, elementnum)
	tell application "Safari"
		do JavaScript "document.getElementsByname('" & theName & "')[" & elementnum & "].click();" in document 1
	end tell
end clickName
set elementnum to 0
repeat 10 times
	clickName("Log in", elementnum)
	set elementnum to elementnum + 1
end repeat

Is this where you got your script? http://www.cubemg.com/how-to-click-a-button-on-a-web-page-with-applescript/

If not check it out. Also, use the Elements inspector to get what you need to know about the page.

If there’s not a name, id, class you can use, try the tagname (basically the text displayed by the button)

(From that same page)

to clicktagName(thetagName, elementnum)

tell application "Safari"

do JavaScript "document.getElementsByTagName('" & thetagName & "')[" & elementnum & "].click();" in document 1

end tell

end clicktagName

You might be able to use GUI scripting alone.
What is the website or page you are trying to click on?

Hi

Dividend.com

I was using GUI and that is why I looked for something else as the position of some of the components where moved occasionally. The following is what I use: this is from a simple page some are more complicated and the place references get longer and take a lot more time to figure out

repeat with Y from 30 to 50 --Login with "User Name " & "Password" on 12/09/2021 it was 37
			try
				set value of text field 1 of group 1 of group Y of RefEnd to "xx"
				set value of text field 1 of group 2 of group Y of RefEnd to "xx"
				exit repeat
			end try
			delay 1
		end repeat
		click button 1 of group 3 of group Y of RefEnd
		delay 3
end

This works for the 2 items in the try block and what I cannot work out is the click command.

to inputByName(theName, num, theValue)
	tell application "Safari"
		do JavaScript " 
  document.getElementsByName('" & theName & "')[" & num & "].value ='" & theValue & "';" in document 1
	end tell
end inputByName

inputByName("email", 0, "mitchbvi@mac.com")
inputByName("email", 1, "Div11122021#")

The suggestion of using “TagName” did not work I have expanded the HTML code from the inspector in case this helps.

Log in
= $0 Forgot password

Note the “=$O” I added manually as it shows in the inspector but is grayed out and there for does not copy

I would really like the website/URL so I can test. Unless it is a security issue?

Here is a sample script that will click the link in ESTOCKLY’s post

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

local UIElements, i, anElement
tell application "System Events"
	set anElement to application process "Safari"
	set anElement to UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1 of anElement
	set UIElements to UI elements of anElement
	repeat with i in UIElements
		set i to contents of i
		if UI element "http://www.cubemg.com/how-to-click-a-bu … plescript/" of i exists then
			tell UI element "http://www.cubemg.com/how-to-click-a-bu … plescript/" of i to perform action "AXPress"
			exit repeat
		end if
	end repeat
end tell

The url is https://www.dividend.com/login/?redirect_url=/login. That as you would expect brings up the login page. I see I left both user name and password in my post that described the part that worked. I will change the password.

I may not have explained myself but what you did in your example
set anElement to UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1 of anElement

is what I have been doing and I am trying to avoid GUI scripting as some parts change . I have been using UI Browser to get the GUI portion of the script but that adds time to the process every time something changes .

The part of the script that adds the input vales in this case username and password works what does not work is cling the “Log in” button. I could use GUI for this but the page I was using was to show the issue it gets a lot more complicate at least for me when I need to access a drop down list in say a footer or an item in a drop down list which itself is part of another drop down list.

I apologise if this all sounds unwieldy but Dividend.com often changes its site layout. Also I may be using terms incorrectly. But it seems to me if I can enter data into boxes on a website it should be relatively simple to click on another box using Javascript

thanks

What happens if you use the button class?

to clickClassName(theClassName, elementnum)
	tell application "Safari"
		do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
	end tell
end clickClassName

set cn to "t-w-full t-mt-2 t-bg-indigo-775 hover:t-bg-indigo-750 t-text-white t-font-semibold t-p-3 t-rounded"

clickClassName(cn, 0)

This works for me, and it’s the same approach as mockman’s.

clickClassName("t-w-full t-mt-2 t-bg-indigo-775 hover:t-bg-indigo-750 t-text-white t-font-semibold t-p-3 t-rounded", 0)

to clickClassName(theClassName, elementnum)
	tell application "Safari"
		activate
		do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
	end tell
	
end clickClassName

I know you’re not interested, but I had fun getting a GUI version to work.

Here is my sample

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

local UIElements, i, anElement, subElements, loginPane, anItem, emailFlag, pwdFlag

tell application "System Events"
	set anElement to application process "Safari"
	set anElement to UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1 of anElement
	
	set subElements to groups of anElement whose subrole is "AXLandmarkMain"
	repeat with i in subElements
		set i to contents of i
		if (subrole of i) = "AXLandmarkMain" then
			set loginPane to i
			exit repeat
		end if
	end repeat
	try
		get loginPane
	on error
		return false
	end try
	set emailFlag to false
	set subElements to groups of loginPane whose subrole is not "AXEmptyGroup"
	repeat with i in subElements -- finds the Email group
		set i to contents of i
		if (static text 1 of i) exists then
			if (value of static text 1 of i) = "Email" then
				set anItem to text field 1 of i
				set focused of anItem to true
				tell me to delay 0.2
				set value of anItem to some item of "email@domain.com"
				set emailFlag to true
			end if
			if emailFlag then -- refresh screen to remove optional 'This field is required' notice
				set focused of text field 1 of i to true
				tell me to delay 0.2
				exit repeat
			end if
		end if
	end repeat
	set pwdFlag to false
	set subElements to groups of loginPane whose subrole is not "AXEmptyGroup"
	repeat with i in subElements -- finds the Password group
		set i to contents of i
		if (static text 1 of i) exists then
			if (value of static text 1 of i) = "Password" then
				set anItem to text field 1 of i
				set focused of anItem to true
				tell me to delay 0.2
				set value of anItem to my "mypwd"
				set pwdFlag to true
			end if
		end if
		if pwdFlag then exit repeat
	end repeat
	if emailFlag and pwdFlag then
		repeat with i in subElements -- finds the Login button
			set i to contents of i
			if (button "Log in" of i) exists then
				tell button "Log in" of i to perform action "AXPress"
				exit repeat
			end if
		end repeat
	end if
end tell

hi all

Sorry no response been in hospital for a couple days will get back to this shortly

Back on deck thanks again for all the help. Stockly & Mockman got me there.

The class name approach worked I had thought just the name of the button would work but it requires more precision . For me at least a lot easier to get to than using a GUI approach.

Peter

robertfern

Sorry I failed to acknowledge your GUI approach, thank you. I have been using UI browser to create the GUI part of my script. The web pages in Dividend.com have so many items and sub pages it takes for ever to locate the item I want and when it changes I have to start over again.

Having got past the login page with help from this forum I ma working on trying to access items on a sub page so far no luck.

Thanks again for all the help.

Peter

So, on the browser page with the item you’re trying to access, try this:

Right-click (or control-click) on the item you want to access. Select “inspect” (or “Inspect Element”).

A new pane will open in your safari window (if it’s not already there) and the “Elements” tab will be active, and some portion of the HTML will be selected.

Copy that text.

Run the first script below in your script editor.

A Choose from List dialog will appear and show you the options for addressing that element.

By ID is preferred. If you use name, class name, etc. you’ll also need to provide a number. (The default is 0 for the first instance)

Select the ones you want to try, and paste them into the second script below (after the use statement).

That has the appropriate JavaScript handlers to click an element.

Uncomment handler calls for the method(s) you selected and pasted in above.

See if that works. (If you want to do more than click the element I may have a handler for that too.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

 set elementStuff to the clipboard

set clipboardText to ParseElementInspector(elementStuff)
set userSelection to choose from list clipboardText ¬
	with title ("Element Name, Id or Class") ¬
	with prompt ("Sected variable(s) for this element to copy/paste into your script") ¬
	default items 1 ¬
	OK button name ¬
	"Okay" cancel button name ¬
	"Cancel" multiple selections allowed true ¬
	with empty selection allowed
if userSelection is not in {false, {}} then
	
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {return & return}
	set clipboardText to clipboardText as text
	set AppleScript's text item delimiters to saveTID
	set the clipboard to clipboardText
end if

on ParseElementInspector(elementInfo)
	set elementInfoText to {}
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {" name="}
	set nameElements to text items of elementInfo
	if the (count of nameElements) > 1 then
		set elementName to text item 2 of nameElements
		set AppleScript's text item delimiters to {"\""}
		set elementName to text item 2 of elementName
		set the end of elementInfoText to "set theName to \"" & elementName & "\""
	end if
	set AppleScript's text item delimiters to {" id="}
	
	set idElements to text items of elementInfo
	if the (count of idElements) > 1 then
		set elementID to text item 2 of idElements
		set AppleScript's text item delimiters to {"\""}
		set elementID to text item 2 of elementID
		set the end of elementInfoText to "set theId to \"" & elementID & "\""
	end if
	
	set AppleScript's text item delimiters to {" class="}
	set classElements to text items of elementInfo
	if the (count of classElements) > 1 then
		set elementClass to text item 2 of classElements
		set AppleScript's text item delimiters to {"\""}
		set elementClass to text item 2 of elementClass
		set the end of elementInfoText to "set theClass to \"" & elementClass & "\""
	end if
	set AppleScript's text item delimiters to saveTID
	return elementInfoText
end ParseElementInspector

Handler Script


use scripting additions

set the elementNumber to 0
--set theScript to JSForClickByID(theId)
--set theScript to JSForClickByName(theName, elementnum)
--set theScript to JSForClickByClassName(theClassName, elementnum)
--set theScript to JSForClickBytagName(thetagName, elementnum)

tell application "Safari"
	activate
	set safariJSScriptResults to do JavaScript " " & theScript in current tab of window 1
end tell

to JSForClickByID(theId)
	return " document.getElementById('" & theId & "').click();"
end JSForClickByID

to JSForClickByName(theName, elementnum)
	return " document.getElementsByName('" & theName & "')[" & elementnum & "].click();"
end JSForClickByName

to JSForClickByClassName(theClassName, elementnum)
	return " document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();"
end JSForClickByClassName

to JSForClickBytagName(thetagName, elementnum)
	return " document.getElementsByTagName('" & thetagName & "')[" & elementnum & "].click();"
end JSForClickBytagName


NOTE: These two snippets are from a JavaScriptBrowser library I’m working on. When it’s ready I’ll share if anyone’s interested.

Hi eStockly

I know I must be thick but I just cannot follow your I instructions properly.

When I select the box with the Ex-Div date I get this

Run the 1st routine and use selection is

Clipboard text is

I am sure I am not making this very clear. But I cannot copy the data from the listbox it just beeps but even if I could not sure where to put the result.

My apologies

It’s not you, it’s the script. It is definitely a work in progress, but I think you gave me some useful information.

What were looking for in doing the Inspect Element right-click is a way to identify the object. In this case it’s a button we want to click.

It’s best to use ID (because those are unique and you don’t need an element number).

In this case we have a class we can identify it with. The class a long and complicated name, but that makes it more likely it will be the right one, in my experience.

Try running this script with that page open in Safari.

If it clicks a button, but not the right button, you’ll need to try different numbers for the element number (1, 2, 3, etc.)

If it doesn’t work at all we can try something else.


 set theClassName to "t-py-3 t-mr-2 t-mb-2 t-w-max t-px-3 t-text-black hover:t-bg-mitre-gray-200 t-text-sm t-flex t-items-center t-relative t-min-w-150 t-justify-between"

set the elementNumber to 0
set theScript to JSForClickByClassName(theClassName, elementnum)

tell application "Safari"
	activate
	set safariJSScriptResults to do JavaScript " " & theScript in current tab of window 1
end tell

to JSForClickByClassName(theClassName, elementnum)
	return " document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();"
end JSForClickByClassName


Do you have an example of using xPath?

Ideally you would use the “id” of an element, but when that is not available you use name, classname, etc. with the element number. It does take trial and error to get right, and is vulnerable to the page layout changing, but as far as I know all schemes like this are.

Using the element number is also vulnerable to a page loading different HTML elements contextually, but (so far) I’ve found when they do that the elements I need generally have an ID.

Hi elstockly

That worked once I got the right element number “4” ,as you may have thought that produces a drop down box that needs a start and end date they have to be selected from calendars that appear when the drop down box is activated . Cannot write the dates one has to select. When was using GUI I selected the next 7 days.

So now I have to loop your solution to opening the drop down box then get the date selection. I do not know how to use xpath so I figured I would loop until I had the name of the box. That may be giving me work than I need absent xpath so if there is a link to that it would be appreciated .

thanks again for all the help this site is the best.

Peter

Irrelevant but this was how I got the dates suing GUI

on NewStocks()
	log "In NewStocks"
	set {TodaysDate, Date1, cnt, Cnt2, CheckSYM, GrpNo} to {day of (current date), "", 0, 0, "", 20}
	tell application "System Events" to tell process "Safari"
		--Open "All Filters" box so dates can be set
		set RefEnd to UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1
		repeat until CheckSYM is "Stocks"
			set GrpNo to GrpNo + 1
			try --Using "Stocks" as the button has a name and the filter button does not
				set CheckSYM to get name of button 1 of group GrpNo of RefEnd
			end try
			delay 0.25
		end repeat
		delay 2
		click button 2 of group GrpNo of RefEnd --"All Filters" icon. 
		delay 2 --Need find way to check the box "All Filters" is open
		set CheckName to ""
		repeat with cnt from 10 to 50
			set CheckName to name of button cnt of group GrpNo of RefEnd --Ex Div Date
			if CheckName is "Ex-div Date" then exit repeat
		end repeat
		click button cnt of group GrpNo of RefEnd --Ex Div Date
		repeat with cnt from 175 to 250 --Open the From Calendar
			try
				click text field 1 of group cnt of group GrpNo of RefEnd --opens From calendar
				exit repeat
			end try
		end repeat
		--the "From" Date	
		repeat with Cnt3 from 20 to 45 --get major group no
			try
				set Date1 to value of static text 1 of group 1 of group 1 of group 9 of group Cnt3 of RefEnd --The Date
				exit repeat
			end try
		end repeat
		delay 2 --Added as next leg could stall
		repeat until Date1 as number = TodaysDate as number --Data Starting to day
			set Cnt2 to Cnt2 + 1
			set Date1 to value of static text 1 of group Cnt2 of group 1 of group 9 of group Cnt3 of RefEnd --The Date
		end repeat
		click static text 1 of group Cnt2 of group 1 of group 9 of group Cnt3 of RefEnd --The Date
		--The "To" date
		click text field 2 of group cnt of group GrpNo of RefEnd --opens the "To" calendar
		click static text 1 of group (Cnt2 + 7) of group 1 of group 9 of group Cnt3 of RefEnd -- "To" Date + 7 days 
		click button 2 of group GrpNo of RefEnd --Closes the Date box
		delay 2
		click button 50 of group GrpNo of RefEnd --Close the "ALL FILTERS" box
		click UI element 25 of group GrpNo of RefEnd --Download Button
		delay 5 --Give it time to download
	end tell
end NewStocks

I edited my previous script to be way better.

It now will create a tab or window if needed then go to the URL
It will wait till the page is done loading.
Then it will find email & password groups and fill them out. in my case it generates a random name and password since I don’t have an account

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
-- https://www.dividend.com/login/?redirect_url=/login

property myURL : "https://www.dividend.com/login/?redirect_url=/login"
property eNames : {"Robert", "Mark", "Steven", "Joseph", "Anthony", "Edward", "James", "Ethan", "Henry", "Carlos", "Noah", "Arthur", "Louis", "Zachary", "Thomas", "Alexander", "Jacob", "Vincent", "Dylan", "William", "Jack", "Samuel", "Andrew", "Gabriel", "Benjamin", "Lawrence", "Christopher", "Matthew", "George", "Nicholas", "Daniel", "Nathan", "Peter", "Paul", "Harold", "Adam", "Charles", "Aaron", "Kenneth", "Walter", "Patrick", "Philip", "Austin", "Jesse", "Ryan", "Bruce", "Albert", "Michael", "Eugene", "David", "Eric", "Jonathan", "Frank", "Paul", "Jason", "Brian", "Richard", "Jordan", "Keith", "Kevin", "Russell", "Roger", "Jeremy", "Justin", "Timothy", "Greg", "Alan", "Brandon", "Tyler", "Scott", "Wayne", "Jeffrey", "Dennis", "Douglas", "Roy", "Sean", "Carl", "Ronald", "Donald", "Gerald", "Gary", "Randolph", "Terry"}

on run
	local myTab, myURLs, UIElements, subElements, loginPane, anElement, anItem, i, uname, emailFlag, pwdFlag, tmp
	tell application "Safari"
		if it is not running then activate it
		set tmp to windows
		if (count tmp) = 0 then -- Safari is running but has no windows
			make new document
			set myTab to current tab of window 1
			set URL of myTab to myURL
		else
			set myURLs to URL of tabs of window 1
			if myURL is in myURLs then -- check to see if one of it's tabs already has the URL
				repeat with i from 1 to count myURLs
					if (item i of myURLs) = myURL then
						exit repeat
					end if
				end repeat
				if current tab of window 1 ≠ tab i of window 1 then
					set current tab of window 1 to tab i of window 1
				end if
			else -- no tab or window with URL was found
				if (source of current tab of window 1) = "" then -- empty page found, so load URL
					set myTab to current tab of window 1
				else -- no empty window found, so create a new tab
					set myTab to (make new tab at window 1)
					set current tab of window 1 to myTab
				end if
				set URL of myTab to myURL
			end if
		end if
		repeat
			if (name of window 1) = "Login - Dividend.com - Dividend.com" then exit repeat
			delay 0.2
		end repeat
	end tell
	
	tell application "System Events"
		set anElement to application process "Safari"
		set subElements to groups of toolbar 1 of window 1 of anElement -- test if page done loading
		repeat with i in subElements -- find group in toolbar that has 'Reload' button
			set tmp to contents of i
			if exists (button "Add page to Reading List" of tmp) then exit repeat
		end repeat
		tell tmp
			repeat until exists button "Reload this page"
				delay 0.2
			end repeat
		end tell
		set flag to false
		repeat until flag
			try
				window 1 of anElement
				set anElement to UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1 of anElement
				--get anElement
				set flag to true
			on error --errMsg number errNum
				delay 0.2
			end try
			if anElement = missing value then set flag to false
		end repeat
		--tell me to display alert "anElement " & ((class of anElement) as text)
		set flag to false
		repeat until flag
			try
				set subElements to groups of anElement whose subrole is "AXLandmarkMain"
				--get subElements
				set flag to true
			on error --errMsg number errNum
				beep 1
			end try
			if (subElements = missing value) or ((count subElements) = 0) then set flag to false
			if flag then
				set loginPane to item 1 of subElements
				try
					set subElements to groups of loginPane whose subrole is not "AXEmptyGroup"
				on error
					set flag to false
				end try
				if (count subElements) = 0 then set flag to false
				if not flag then delay 0.2
			end if
		end repeat
		--tell me to display alert "Flags " & (count of subElements)
		set emailFlag to false -- **EDIT** forgot to initialize the email flag
		repeat with i in subElements -- finds the Email group
			set i to contents of i
			if (static text 1 of i) exists then
				if (value of static text 1 of i) = "Email" then
					set anItem to text field 1 of i
					set focused of anItem to true
					tell me to delay 0.2
					set tmp to value of anItem
					set uname to tmp
					repeat while uname = tmp
						set uname to some item of eNames
					end repeat
					set value of anItem to uname --"Robert"
					set emailFlag to true
					set focused of text field 1 of i to true -- screen refreshes to remove 'This field is required' notice (if there is one)
					tell me to delay 0.2
					exit repeat
				end if
			end if
		end repeat
		set pwdFlag to false
		set subElements to groups of loginPane whose subrole is not "AXEmptyGroup"
		repeat with i in subElements -- finds the Password group
			set i to contents of i
			if (static text 1 of i) exists then
				if (value of static text 1 of i) = "Password" then
					set anItem to text field 1 of i
					set focused of anItem to true
					tell me to delay 0.2
					set value of anItem to my getPWD() --"mypwd"
					set pwdFlag to true
					exit repeat
				end if
			end if
		end repeat
		if emailFlag and pwdFlag then
			repeat with i in subElements -- finds the Login button
				set i to contents of i
				if (button "Log in" of i) exists then
					tell button "Log in" of i to perform action "AXPress"
					exit repeat
				end if
			end repeat
		end if
	end tell
end run

on getPWD()
	local pwd, c
	set pwd to ""
	set c to random number from 6 to 12
	repeat with i from 1 to c
		set pwd to pwd & some character of "abcdeefghijklmnopqrstuvwxyz!#$%^*-+?~"
	end repeat
end getPWD

Ed

Stuck again, I now cannot figure out how to stop the loop at present I have amended your script (see below) to run 5 times works as I know the element I want is 5 but that of course that could change. I need to return the name in the box with I thought would be easy, but a day later it’s not at least for me.

The start is the is in the box below and is classified as button class. The last line in the quote is classified as span class and do not know if that changes things. But it is that line that will confirm I have reached the correct block and can exit the loop.

Easy enough parse to out the “Ex-div date” but obviously I 1st need to return the whole line.

I apologise if I am wasting your time but cannot get their myself.

Thanks again

--This selects the "Ex-Div Date" box code macscripter Ed Stockly
--M variation but have to stop when "Ex-Div Date" selected
--So need to work out how to check name of each box selected
set theClassName to "t-py-3 t-mr-2 t-mb-2 t-w-max t-px-3 t-text-black hover:t-bg-mitre-gray-200 t-text-sm t-flex t-items-center t-relative t-min-w-150 t-justify-between"

set the elementNumber to 0
repeat 5 times --added  PM
	set theScript to JSForClickByClassName(theClassName, elementNumber)
	log theScript
	
	tell application "Safari"
		activate
		set safariJSScriptResults to do JavaScript " " & theScript in current tab of window 1
		log
	end tell
	set the elementNumber to elementNumber + 1
end repeat

to JSForClickByClassName(theClassName, elementNumber)
	log elementNumber
	return " document.getElementsByClassName('" & theClassName & "')[" & elementNumber & "].click();"
end JSForClickByClassName

Robert

Thank you, I had that part working but like yours better so will incorporate that as soon as I have tested it.

Thanks

Peter