Iterating Through Different UI Elements Until a Match is Found

Hi Folks,

I’m trying to write a script that will press the “Translate” Safari extension button. The representing UI element is settled among other buttons of various other extensions, as shown here:

(it’s the second button from the right)

I can refer the button directly, like so:


click button 1 of UI element 1 of group 4 of tool bar 1 of window 1 of process "Safari"

The “button 1” and “UI element 1” are static, but since I tend to mess with the installed extensions constantly, the group number tends to change, and I want this script to work regardless of the group’s number. This is what I’ve been up to, but it doesn’t work.


tell application "Safari"
	activate
end tell

tell application "System Events"
	tell process "Safari"
		tell window 1
			tell toolbar 1
				set theButtons to every button of every UI element of every group
				repeat with i in theButtons
					try
						tell (first button whose description is "Translate")
							perform action "AXPress"
						end tell
					end try
				end repeat
			end tell
		end tell
	end tell
end tell

Help would be extremely appreciated!

Thanks!

Hi. Welcome to MacScripter.

Does this work for you?

tell application "System Events"
	tell process "Safari"
		set frontmost to true
		tell window 1
			tell toolbar 1
				set groupList to (description of every button of every UI element of every group)
				--> A list of lists of lists of texts representing the toolbar's groups' UI elements' (buttons' descriptions).
				
				repeat with g from 1 to (count groupList)
					set UIElementList to item g of groupList
					repeat with u from 1 to (count UIElementList)
						set buttonDescriptionList to item u of UIElementList
						repeat with b from 1 to (count buttonDescriptionList)
							if (item b of buttonDescriptionList is "Translate") then
								perform action "AXPress" of button b of UI element u of group g
								return
							end if
						end repeat
					end repeat
				end repeat
			end tell
		end tell
	end tell
end tell

Amazing. It works incredibly well. Thank you so much!
I wish I could’ve accomplished that myself. :frowning:

In your script, what is return for? Is it AS’s version of “break from loop”?
And if you could elaborate some more on your script and tell me where did I go wrong, it would be extremely appreciated.

And thanks again for the warm (and helpful) welcome. Glad to be here.

I’m new to AppleScript and I’m already in awe of its possibilities. I wish to learn as much as possible and, from the best of the best. What would be the definitive source to learn AS?

Thanks!
Roy

That’s its function here, yes. return jumps right out of the handler (subroutine or script run handler) where it occurs. There’s an exit repeat command which explicitly jumps out of the repeat in which it occurs, but here all three of the nested repeats, and the script itself, have to stop once the button’s clicked. An alternative would be error number -128. This is the error that’s generated when a “Cancel” button’s clicked and simply stops the script. return can also be used to return a result, if needed. If you change the line to return “Success!”, you should see “Success!” appear in Script Editor’s “Result” pane after the button’s clicked.

Your line set theButtons to every button of every UI element of every group doesn’t return a straight list of button references. It returns a list of lists of lists of button references which corresponds to the stucture of the groups, UI elements, and buttons found. So:

every group → Returns a list of groups.
every UI element of every group → Returns a list of lists (corresponding to groups) of UI elements.
every button of every UI element of every group → Returns a list of lists (corresponding to groups) of lists (corresponding to UI elements) of button references.

So your repeat’s iterating through the lists corresponding the groups, not through the buttons themselves. Also, your reference (first button whose description is “Translate”) occurs directly within the context of the tell toolbar 1 statement, so the script thinks you mean a button of the toolbar when you actually mean a button of a UI element of a group of the toolbar.

I usually find whose filters a bit slow, so I started with the list structure returned by your every button of every UI element of every group reference, sticking description of on the front so that the innermost lists would contain the buttons’ descriptions rather than the buttons themselves. The outermost repeat cycles through the lists representing the groups, the middle repeat cycles through the lists (representing UI elements) in each list found by the outermost repeat, and the innermost repeat checks the description texts in each list found by the middle repeat. If item b of item u of item g of the list structure is the required description, it means that button b of UI element u of group g of toolbar 1 is the required button.

It can be mind-bending at first, but it doesn’t take long to get the hang of it. :slight_smile:

Amazing. Thank you so much.

One more question regarding those parts of your code:

repeat with g from 1 to (count groupList)

I thought that was just syntactic sugar for repeats, and one might as well use this version, which is the one I’m familiar with.

repeat with g in groupList

I did some testing, and in your specific script, the latter produces errors, while in some of my other scripts, the FORMER produces errors.

What’s going on here?

Thanks!

OK. There are three different types of repeat in AppleScript. The simplest simply repeats a specified number of times, say:

repeat 10 times

Then there’s your example from my script:

repeat with g from 1 to (count groupList)

In this, the given variable (here g) is automatically set to the from value (an integer) for the first iteration of the repeat and is augmented by 1 on each subsequent iteration until, for the last iteration, it has the to value (also an integer). I used this in my script above because the variable is used to index positions in the lists and the script needs to use the same numbers to index the groups, UI elements and buttons in the application.

It’s optionally possible with this kind of repeat to specify different increment steps:

repeat with g from 1 to 11 by 2
	-- g will be successively 1, 3, 5, 7, 9, and 11.

repeat with g from 1 to 11 by 3
	-- g will be 1, 4, 7, and 10. (13 would go past 11.)

And descending variable values:

repeat with g from 5 to 1 by -1
	-- g will be 5, 4, 3, 2, and 1.

With your other example:

repeat with g in groupList

The variable (g again) is set to a reference to each item in the list (or application object) in turn. So say groupList contains integers instead of lists:

set groupList to {5, 7, 4, 10, 3}
repeat with g in groupList
	get g
	-- Successively:
	--> item 1 of {5, 7, 4, 10, 3}
	--> item 2 of {5, 7, 4, 10, 3}
	--> item 3 of {5, 7, 4, 10, 3}
	--> item 4 of {5, 7, 4, 10, 3}
	--> item 5 of {5, 7, 4, 10, 3}
end repeat

In most cases, this means you can treat the variable as if it contained the item in the list:

set groupList to {5, 7, 4, 10, 3}
repeat with g in groupList
	get g + 1
	-- Successively:
	--> 6
	--> 8
	--> 5
	--> 11
	--> 4
end repeat

The exception to this, which often catches people out, is in tests for equality:

set groupList to {5, 7, 4, 10, 3}
repeat with g in groupList
	if (g = 4) then beep -- Never beeps.
end repeat

This is because g is never actually 4 (which is what the test asks) but a reference to the item in the list. In such cases, the reference has to be explicitly resolved with the contents operator:

set groupList to {5, 7, 4, 10, 3}
repeat with g in groupList
	if (contents of g = 4) then beep -- :)
end repeat

You’re now an expert on AppleScript repeats. :wink:

Edit: Oops. I neglected to mention ‘while’ and ‘until’ repeats, which are fairly self-explanatory:

set g to 1
repeat while (g < 6)
	say g
	set g to g + 1
end repeat
set g to 1
repeat until (g = 6)
	say g
	set g to g + 1
end repeat

Edit 2: And of course simple repeats with (or without!) exit conditions written into the code they contain:

set g to 1
repeat
	say g
	set g to g + 1
	if (g > 5) then exit repeat
end repeat

Wow, thank you so much for the amazing explanation!

I would call that a wild exaggeration, but I’ll sure do my best :slight_smile:

I’ll try to get my head around this, step by step:

About this kind of repeats:

repeat with g from 1 to (count groupList)

So you’re basically suggesting that it is beneficial to get familiar with this kind of repeats, since they’re more versatile?

And regarding what you said here:

What’s the true value of g here?

Again, thank you so much.