Searching for Specific Elements - asmagill/hammerspoon GitHub Wiki

This document is currently just a snapshot of notes and examples being made for discussing hs.axuielement:elementSearch. This is the heart of searching for specific elements within an application and as such has a lot of options available. In here I plan to discuss some of the ways this method can be used and even expanded upon.

Because of the power and complexity of this method, I am also in the process of determining what additional support methods might be added that can simplify some of the more common cases, including a way to more specifically direct searches rather than the breadth first search currently used.

In the mean time, you are welcome to examine this and make use of what you can; however, expect it to be incomplete and subject to change until it is more formalized.

It should also be noted that some of the examples here may require some of the bug fixes or additions that are currently in https://github.com/Hammerspoon/hammerspoon/pull/2554, which will hopefully be included in Hammerspoon when 0.9.82 is released.



ax = require("hs.axuielement")
inspect = require("hs.inspect")
finspect = function(...) return inspect({ ... }, { newline = " ", indent = "" }) end

s = ax.applicationElement("Safari")
r1 = s:buildTree(function(msg, r) r2 = r ; print(msg) end)

> r1:matched(), r1:visited(), r1:runTime()
5700	5700	199

> r1[1] == r2
true

> inspect(r2)
<1>{
  AXChildren = { <2>{
      AXAuditIssues = {
        _code = -25212,
        error = "Requested value does not exist"
      },
      AXCancelButton = {
        _code = -25212,
        error = "Requested value does not exist"
      },
      AXChildren = { <3>{
          AXChildren = { <4>{
              AXAuditIssues = {
                _code = -25212,
                error = "Requested value does not exist"
              },
              AXFrame = {
                h = 890.0,
                w = 1.0,
                x = 8.0,
                y = 111.0
              },

... cut for brevity ...

  _actions = {},
  _attributes = { "AXFunctionRowTopLevelElements", "AXChildren", "AXFocusedUIElement", "AXFrontmost", "AXRole", "AXExtrasMenuBar", "AXMainWindow", "AXFocusedWindow", "AXTitle", "AXChildrenInNavigationOrder", "AXEnhancedUserInterface", "AXRoleDescription", "AXHidden", "AXMenuBar", "AXWindows" },
  _element = <userdata 6303> -- hs.axuielement: AXApplication (0x6060002722d8),
  _parameterizedAttributes = { "AXReplaceRangeWithText" }
}

(286460 lines)

> s[1].AXRole
AXWindow

> s[2].AXRole
AXMenuBar

> r3 = s[1]:buildTree(function(msg, r) r4 = r ; print(msg) end)

completed

> r3:matched(), r3:visited(), r3:runTime()
330	330	6

> inspect(r4) -- not shown for brevity -- it's still 19010 lines

> links1 = s[1]:elementSearch(function(msg, r, c) print(msg, c) ; links2 = r ; end, ax.searchCriteriaFunction({ attribute = "AXRole", value = "AXLink" }))

completed	41

> links1:matched(), links1:visited(), links1:runTime(), links1 == links2
41	330	5	true

> for i,v in ipairs(links1) do print(v.AXURL.url) end
https://about.google/?fg=1&utm_source=google-US&utm_medium=referral&utm_campaign=hp-header
https://store.google.com/US?utm_source=hp_header&utm_medium=google_ooo&utm_campaign=GS100042&hl=en-US

... cut for brevity ...

https://podcasts.google.com/
https://www.google.com/intl/en/about/products?tab=wh

-- for the Hammerspoon github page:
> s1 = s:childrenWithRole("AXWindow")[1]

> r1 = s1:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ attribute = "AXRole", value = "AXLink" }))

completed	hs.axuielement.elementSearchObject (0x60600091c8b8)	155

> r1:runTime()
18

> for i,v in ipairs(r1) do print(i, v.AXTitle, v.AXURL.url) end
1	Skip to content	https://github.com/Hammerspoon/hammerspoon#start-of-content
2		https://github.com/
3		https://github.com/pulls

... cut for brevity...

153	Training	https://services.github.com/
154	Blog	https://github.blog/
155	About	https://github.com/about

> a = {} ; for i,v in ipairs(r1) do table.insert(a, hs.canvas.new(v.AXFrame):show():appendElements{ type = "rectangle", action = "stroke", strokeColor = { red = 1 } }) end

> for i,v in ipairs(a) do v:delete() end


> r1 = s1:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction("AXLink"))

completed	hs.axuielement.elementSearchObject (0x606002d8a538)	155

> r2 = s1:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ { attribute = "AXRole", value = "AXLink" }, { attribute = "AXURL", value = { url = "asmagill" }, pattern = true } }))

completed	hs.axuielement.elementSearchObject (0x606002d8a538)	0

> r2 = s1:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ { attribute = "AXURL", value = { url = "asmagill" }, pattern = true } }))

completed	hs.axuielement.elementSearchObject (0x606002d8a538)	0

> r2 = s1:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ { attribute = "AXRole", value = "AXLink" }, { attribute = "AXURL", value = { { url = "asmagill" } }, pattern = true } }))

completed	hs.axuielement.elementSearchObject (0x606002d8a538)	3

> r3 = r1:filter(hs.axuielement.searchCriteriaFunction({ { attribute = "AXURL", value = { { url = "asmagill" } }, pattern = true } }), function(...) print(...) end)

completed	hs.axuielement.filterObject (0x606002d8a538)

> #r3
3

> r1:runTime(), r2:runTime(), r3:runTime()
18	18	1

> for i,v in ipairs(r3) do print(i, v.AXTitle, v.AXURL.url) end
1	@asmagill	https://github.com/asmagill
2	asmagill	https://github.com/Hammerspoon/hammerspoon/commits?author=asmagill
3	@asmagill	https://github.com/asmagill


> for i,v in ipairs(r2) do print(i, v.AXTitle, v.AXURL.url) end
1	@asmagill	https://github.com/asmagill
2	asmagill	https://github.com/Hammerspoon/hammerspoon/commits?author=asmagill
3	@asmagill	https://github.com/asmagill

> r4 = s:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ { attribute = "AXRole", value = "AXWindow"}, { attribute = "AXFrame", value = { { y = 23 } } } }), { depth = 3 })

completed	hs.axuielement.elementSearchObject (0x606001a32678)	2

> for i,v in ipairs(r4) do print(v.AXTitle, finspect(v.AXFrame)) end
Hammerspoon/hammerspoon: Staggeringly powerful macOS desktop automation with Lua	{ h = 978.0, w = 1192.0, x = 9.0, y = 23.0 }
error - How do you get system diagnostic files from macOS? - Ask Different	{ h = 978.0, w = 1192.0, x = 30.0, y = 23.0 }

> r5 = s:elementSearch(function(...) print(...) end, hs.axuielement.searchCriteriaFunction({ { attribute = "AXRole", value = "AXWindow"}, { attribute = "AXFrame", value = { { y = 23 } }, comparison = ">" } }), { depth = 3 })

completed	hs.axuielement.elementSearchObject (0x606001a32678)	2

> for i,v in ipairs(r5) do print(v.AXTitle, finspect(v.AXFrame)) end
DuckDuckGo — Privacy, simplified.	{ h = 978.0, w = 1192.0, x = 360.0, y = 94.0 }
DuckDuckGo — Privacy, simplified.	{ h = 978.0, w = 1192.0, x = 220.0, y = 223.0 }

> r6 = s:elementSearch(function(m, r, c) print(m,c, r:runTime(), (c > 0) and r[#r].AXTitle or "end") end, hs.axuielement.searchCriteriaFunction("AXWindow"), { count = 1, depth = 3 })

countReached	1	0	Hammerspoon/hammerspoon: Staggeringly powerful macOS desktop automation with Lua

> r6:next()
hs.axuielement.elementSearchObject (0x60600478b6b8)
countReached	1	0	DuckDuckGo — Privacy, simplified.

> r6:next()
hs.axuielement.elementSearchObject (0x60600478b6b8)
countReached	1	0	DuckDuckGo — Privacy, simplified.

> r6:next()
hs.axuielement.elementSearchObject (0x60600478b6b8)
countReached	1	0	error - How do you get system diagnostic files from macOS? - Ask Different

> r6:next()
hs.axuielement.elementSearchObject (0x60600478b6b8)
completed	0	3	end