module Accessibility::DSL
DSL
methods for AXElements.
The DSL
for AXElements is designed to pull actions out from an object and put them in front of object to make communicating test steps seem more like human instructions.
You can read more about the DSL
in the [Acting](github.com/Marketcircle/AXElements/wiki/Acting) section of the AXElements wiki.
Public Instance Methods
Find the application with the given bundle identifier. If the application is not already running, it will be launched.
@example
app_with_bundle_identifier 'com.apple.finder' launch 'com.apple.mail'
@param id [String] @return [AX::Application,nil]
# File lib/accessibility/dsl.rb, line 889 def app_with_bundle_identifier id AX::Application.new id end
Find the application with the given name. If the application is not already running, it will NOT be launched and this method will return ‘nil`.
@example
app_with_name 'Finder'
@param name [String] @return [AX::Application,nil]
# File lib/accessibility/dsl.rb, line 906 def app_with_name name AX::Application.new name end
Find the application with the given process identifier. An invalid PID will cause an exception to be raised.
@example
app_with_pid 35843
@param pid [Fixnum] @return [AX::Application]
# File lib/accessibility/dsl.rb, line 920 def app_with_pid pid AX::Application.new pid end
Try to perform the ‘cancel` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 117 def cancel element element.perform :cancel end
Perform a regular click.
If a parameter is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
If a block is given, it will be yielded to between the click down and click up event.
@example
click click window.close_button click do move_mouse_to [0,0] end
@yield Optionally take a block that is executed between click down
and click up events.
@param obj [#to_point]
# File lib/accessibility/dsl.rb, line 620 def click obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.click_down yield if block_given? ensure Mouse.click_up sleep wait end
Try to perform the ‘confirm` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 90 def confirm element element.perform :confirm end
Try to perform the ‘decrement` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 81 def decrement element element.perform :decrement end
Try to perform the ‘delete` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 108 def delete element element.perform :delete end
Perform a double click action.
If an argument is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
@param obj [#to_point]
# File lib/accessibility/dsl.rb, line 659 def double_click obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.double_click sleep wait end
Click and drag the mouse from its current position to the given position.
There are many reasons why you would want to cause a drag event with the mouse. Perhaps you want to drag an object to another place, or maybe you want to select a group of objects on the screen.
@example
drag_mouse_to [100,100] drag_mouse_to drop_zone, from: desktop_icon
@param arg [#to_point] @param opts [Hash] @option opts [#to_point] :from a point to move to before dragging @option opts [Number] :duration (0.2) in seconds @option opts [Number] :wait (0.2) in seconds
# File lib/accessibility/dsl.rb, line 554 def drag_mouse_to arg, opts = {} move_mouse_to opts[:from] if opts[:from] Mouse.drag_to arg.to_point, (opts[:duration] || 0.2) sleep(opts[:wait] || 0.2) end
Get the top most object at an arbitrary point on the screen for the given application. The given point can be a CGPoint, an Array
, or anything else that responds to ‘#to_point`.
Optionally, you can look for the top-most element for a specific application by passing an {AX::Application} object using the ‘for:` key.
@example
element_at [100, 456] element_at CGPoint.new(33, 45), for: safari element_at window # find out what is in the middle of the window
@param point [#to_point] @param opts [Hash] @option opts [AX::Application] :for @return [AX::Element]
# File lib/accessibility/dsl.rb, line 962 def element_at_point point, opts = {} base = opts[:for] || system_wide base.element_at point end
Return the top most element at the current mouse position.
See {#element_at_point} for more details.
@return [AX::Element]
# File lib/accessibility/dsl.rb, line 938 def element_under_mouse element_at_point Mouse.current_position end
@note This method is currently experimental @note You will need to have GraphViz command line tools installed
in order for this to work.
Make and open a ‘dot` format graph of the tree, meant for graphing with GraphViz.
@example
graph app.main_window
@param element [AX::Element] @return [String] path to the saved image
# File lib/accessibility/dsl.rb, line 816 def graph element require 'accessibility/graph' graph = Accessibility::Graph.new(element) path = graph.generate_png! `open #{path}` path end
Tell an app to hide itself.
@param app [AX::Application] @return [Boolean]
# File lib/accessibility/dsl.rb, line 143 def hide app app.perform :hide end
Highlight an element on screen. You can optionally specify the highlight colour or pass a timeout to automatically have the highlighter disappear.
The highlighter is actually a window, so if you do not set a timeout, you will need to call ‘#stop` or `#close` on the returned highlighter object in order to get rid of the highlighter.
You could use this method to highlight an arbitrary number of elements on screen, with a rainbow of colours. Great for debugging.
@example
highlighter = highlight window.outline # wait a few seconds... highlighter.stop # highlighter automatically turns off after 5 seconds highlight window.outline.row, colour: NSColor.greenColor, timeout: 5
@param obj [#to_rect] @param opts [Hash] @option opts [Number] :timeout @option opts [NSColor] :colour (NSColor.magentaColor) @return [Accessibility::Highlighter]
# File lib/accessibility/dsl.rb, line 798 def highlight obj, opts = {} Accessibility::Highlighter.new obj, opts end
@todo Need to expose the units option? Would allow scrolling by pixel.
Horizontally scrolls an arbitrary number of lines at the mouses current point on the screen
Use a positive number to scroll left, and a negative number to scroll right.
If the second argument is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
@param lines [Number] @param obj [#to_point] @param wait [Number]
# File lib/accessibility/dsl.rb, line 594 def horizontal_scroll lines, obj = nil, wait = 0.1 move_mouse_to obj, wait: 0 if obj Mouse.horizontal_scroll lines sleep wait end
Try to perform the ‘increment` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 99 def increment element element.perform :increment end
We assume that any method that has the first argument with a type of {AX::Element} is intended to be an action and so ‘#method_missing` will forward the message to the element.
@param meth [String] an action constant
# File lib/accessibility/dsl.rb, line 36 def method_missing meth, *args arg = args.first if arg.kind_of? AX::Element return arg.perform meth if arg.actions.include? meth raise ArgumentError, "`#{meth}' is not an action of #{self}:#{self.class}" end # @todo do we still need this? we should just call super # should be able to just call super, but there is a bug in MacRuby (#1320) # so we just recreate what should be happening message = "undefined method `#{meth}' for #{self}:#{self.class}" raise NoMethodError, message, caller(1) end
Move the mouse cursor to the given point or object on the screen.
@example
move_mouse_to button move_mouse_to [344, 516] move_mouse_to CGPoint.new(100, 100)
@param arg [#to_point] @param opts [Hash] @option opts [Number] :duration (0.2) in seconds @option opts [Number] :wait (0.2) in seconds
# File lib/accessibility/dsl.rb, line 527 def move_mouse_to arg, opts = {} duration = opts[:duration] || 0.2 if Accessibility.debug? && arg.kind_of?(AX::Element) highlight arg, timeout: duration, color: NSColor.orangeColor end Mouse.move_to arg.to_point, duration sleep(opts[:wait] || 0.2) end
Try to perform the ‘pick` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 72 def pick element element.perform :pick end
Perform a pinch gesture in the given `direction` You can optionally specify the `magnification` factor and `position` for the pinch event.
*
Available pinch directions are: - `:zoom` or `:expand` - `:unzoom` or `:contract` Magnification is a relative magnification setting. A zoom value of `1.0` means `1.0` more than the current zoom level. `2.0` would be `2.0` levels higher than the current zoom. You can also optionally specify an object/point on screen for the mouse pointer to be moved to before the gesture begins. @param direction [Symbol] @param magnification [Float] @param obj [#to_point]
# File lib/accessibility/dsl.rb, line 724 def pinch direction, magnification = 1, obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.pinch direction, magnification sleep wait end
Try to perform the ‘press` action on the given element.
@param element [AX::Element] @return [Boolean]
# File lib/accessibility/dsl.rb, line 54 def press element element.perform :press end
@note This method overrides ‘Kernel#raise` so we have to check the
class of the first argument to decide which code path to take.
Try to perform the ‘raise` action on the given element.
@overload raise element
@param element [AX::Element] @return [Boolean]
@overload raise exception[, message[, backtrace]]
The normal way to raise an exception.
# File lib/accessibility/dsl.rb, line 133 def raise *args arg = args.first arg.kind_of?(AX::Element) ? arg.perform(:raise) : Kernel.raise(*args) end
See (ScreenRecorder) for details on the screen recording options
@example
file = record do run_tests end `open '#{file}'`
@param file [String] @yield @yieldparam recorder [ScreenRecorder] @return [String]
# File lib/accessibility/dsl.rb, line 866 def record file = nil, &block require 'screen_recorder' if file ScreenRecorder.record file, &block else ScreenRecorder.record &block end end
Perform a right (a.k.a. secondary) click action.
If an argument is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
If a block is given, it will be yielded to between the click down and click up event. This behaviour is the same as passing a block to {DSL#click}.
@yield Optionally take a block that is executed between click down
and click up events.
@param obj [#to_point]
# File lib/accessibility/dsl.rb, line 642 def right_click obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.right_click_down yield if block_given? ensure Mouse.right_click_up sleep wait end
Perform a rotation gesture in the given ‘direction` the given `angle` degrees
Possible directions are:
- `:cw`, ':clockwise`, ':clock_wise` to rotate in the clockwise direction - `:ccw`, ':counter_clockwise`, `:counter_clock_wise` to rotate in the the counter clockwise direction
The ‘angle` parameter is a number of degrees to rotate. There are 360 degrees in a full rotation, as you would expect in Euclidian geometry.
You can also optionally specify an object/point on screen for the mouse pointer to be moved to before the gesture begins. The movement will be instantaneous.
@param direction [Symbol] @param angle [Float] @param obj [#to_point]
# File lib/accessibility/dsl.rb, line 750 def rotate direction, angle, obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.rotate direction, angle sleep wait end
Take a screen shot and save it to disk. If a file path is not given then the default value will put it on the desktop. The actual file name will automatically generated with a timestamp.
@example
screenshot # => "~/Desktop/AXElements-ScreenShot-20120422184650.png" screenshot app.main_window # => "~/Desktop/Safari-20120422184650.png" screenshot app.main_window, "/Volumes/SecretStash" # => "/Volumes/SecretStash/AXElements-ScreenShot-20150622032250.png"
@param rect [#to_rect] @param path [#to_s] @return [String] path to the screenshot
# File lib/accessibility/dsl.rb, line 844 def screenshot rect = CGRect.new(CGPoint.new(0, 0), CGSize.new(-1, -1)), path = '~/Desktop' require 'accessibility/screen_shooter' ScreenShooter.shoot rect.to_rect, path end
@todo Need to expose the units option? Would allow scrolling by pixel.
Scrolls an arbitrary number of lines at the mouses current point on the screen. Use a positive number to scroll down, and a negative number to scroll up.
If the second argument is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
@param lines [Number] @param obj [#to_point] @param wait [Number]
# File lib/accessibility/dsl.rb, line 573 def scroll lines, obj = nil, wait = 0.1 move_mouse_to obj, wait: 0 if obj Mouse.scroll lines sleep wait end
Scroll through a scroll area until the given element is visible.
If you need to scroll an unknown amount of units through a table or another type of object contained in as scroll area, you can just pass the element that you are trying to get to and this method will scroll to it for you.
@example
scroll_to table.rows.last
@param element [AX::Element] @return [void]
# File lib/accessibility/dsl.rb, line 296 def scroll_to element element.ancestor(:scroll_area).scroll_to element end
Set the value of an attribute on an element.
This method will try to set focus to the element first; this is to compensate for cases where app developers assumed an element would have to have focus before a user could change the value.
@overload set element, attribute_name: new_value
Set a specified attribute to a new value @param element [AX::Element] @param change [Hash{attribute_name=>new_value}]
@example
set text_field, selected_text_range: 1..10
@overload set element, new_value
Set the `value` attribute to a new value @param element [AX::Element] @param change [Object]
@example
set text_field, 'Mark Rada' set radio_button, 1
# File lib/accessibility/dsl.rb, line 206 def set element, change set_focus_to element if change.kind_of? Hash element.set *change.first else element.set :value, change end end
@todo Introduce a method to set focus to closest ancestor that
supports focus.
Focus an element on the screen, but only if it can be directly focused. It is safe to pass any element into this method as nothing will happen if it does not have a writable focused state attribute.
@param element [AX::Element]
# File lib/accessibility/dsl.rb, line 175 def set_focus_to element element.set(:focused, true) if element.writable? :focused end
Show the “About” window for an app. Returns the window that is opened.
@param app [AX::Application] @return [AX::Window]
# File lib/accessibility/dsl.rb, line 265 def show_about_window_for app app.show_about_window end
@note This method assumes that the app has setup the standard
CMD+, hotkey to open the pref window
Try to open the preferences for an app. Returns the window that is opened.
@param app [AX::Application] @return [AX::Window]
# File lib/accessibility/dsl.rb, line 278 def show_preferences_window_for app app.application.show_preferences_window end
Perform a smart magnify (double tap on trackpad)
You can optionally specify an object/point on the screen where to perform the smart magnification. The mouse will move to this point first
@param obj [#to_point]
# File lib/accessibility/dsl.rb, line 763 def smart_magnify obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.smart_magnify sleep wait end
Perform a swipe gesture in the given ‘direction`
Valid directions are:
- `:up` - `:down` - `:left` - `:right`
An optional second argument can be provided. If the argument is provided then the mouse pointer will move to that point first.
@example
swipe :left, safari.web_area
@param direction [Symbol] @param obj [#to_point]
# File lib/accessibility/dsl.rb, line 697 def swipe direction, obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.swipe direction sleep wait end
Synonym for ‘AX::SystemWide.new`.
@return [AX::SystemWide]
# File lib/accessibility/dsl.rb, line 928 def system_wide AX::SystemWide.new end
Tell an app to quit.
@param app [AX::Application] @return [Boolean]
# File lib/accessibility/dsl.rb, line 162 def terminate app app.perform :terminate end
Perform a triple click action
If an argument is provided then the mouse will move to that point first; the argument must respond to ‘#to_point`.
@param obj [#to_point]
# File lib/accessibility/dsl.rb, line 672 def triple_click obj = nil, wait = 0.2 move_mouse_to obj, wait: 0 if obj Mouse.triple_click sleep wait end
Simulate keyboard input by typing out the given string. To learn more about how to encode modifier keys (e.g. Command), see the dedicated documentation page on [Keyboard Events](github.com/Marketcircle/AXElements/wiki/Keyboarding) wiki page.
@overload type string
Send input to the currently focused application @param string [String]
@example
type "Hello, world!"
@overload type string, app
Send input to a specific application @param String [String] @param [AX::Application]
# File lib/accessibility/dsl.rb, line 236 def type string, app = system_wide sleep 0.1 app.type string.to_s end
Tell an app to unhide itself.
@param app [AX::Application] @return [Boolean]
# File lib/accessibility/dsl.rb, line 152 def unhide app app.perform :unhide end
Simply wait around for something to show up. This method is similar to performing an explicit search on an element except that the search filters take two extra options which can control the timeout period and the search subtree. You __MUST__ supply either the parent or ancestor option to specify where to search from. Searching from the parent implies that what you are waiting for is a child of the parent and not a more distant descendant.
This is an alternative to using the notifications system. It is far easier to use than notifications in most cases, but it will perform more slowly (and without all the fun crashes).
@example
# Waiting for a dialog window to show up wait_for :dialog, parent: app # Waiting for a hypothetical email from Mark Rada to appear wait_for :static_text, value: 'Mark Rada', ancestor: mail.main_window # Waiting for something that will never show up wait_for :a_million_dollars, ancestor: fruit_basket, timeout: 1000000
@param element [#to_s] @param filters [Hash] @option filters [Number] :timeout (5) timeout in seconds @option filters [AX::Element] :parent @option filters [AX::Element] :ancestor @yield Optional block used as a search filter @return [AX::Element,nil]
# File lib/accessibility/dsl.rb, line 376 def wait_for element, filters = {}, &block if filters.has_key? :ancestor wait_for_descendant element, filters.delete(:ancestor), filters, &block elsif filters.has_key? :parent wait_for_child element, filters.delete(:parent), filters, &block else raise ArgumentError, 'parent/ancestor filter required' end end
@note This is really just an optimized case of
{#wait_for_descendant} when you know what you are waiting for is a child of a particular element. Use {#wait_for_descendant} if you are unsure of the relationship.
Wait around for particular element and then return that element. The parent argument must be the parent of the element you are waiting for, this method will not look further down the hierarchy. The options you pass to this method can be any search filter that you can normally use.
See {#wait_for} for more details.
@param child [#to_s] @param parent [AX::Element] @param filters [Hash] @return [AX::Element,nil]
# File lib/accessibility/dsl.rb, line 437 def wait_for_child child, parent, filters, &block timeout = filters.delete(:timeout) || 5 start = Time.now q = Accessibility::Qualifier.new(child, filters, &block) until Time.now - start > timeout result = nil begin result = parent.children.find { |x| q.qualifies? x } rescue RuntimeError => e # This is a temporary workaround; accessibility_core should # raise a specific error class for this issue or not use # exceptions for this problem at all... raise e unless e.message.match(/application is busy/) end return result unless result.blank? sleep 0.1 end nil end
Wait around for particular element and then return that element. The options you pass to this method can be any search filter that you can normally use.
See {#wait_for} for more details.
@param descendant [#to_s] @param ancestor [AX::Element] @param filters [Hash] @return [AX::Element,nil]
# File lib/accessibility/dsl.rb, line 397 def wait_for_descendant descendant, ancestor, filters, &block timeout = filters.delete(:timeout) || 5 start = Time.now until Time.now - start > timeout result = nil begin result = ancestor.search(descendant, filters, &block) rescue RuntimeError => e # This is a temporary workaround; accessibility_core should # raise a specific error class for this issue or not use # exceptions for this problem at all... raise e unless e.message.match(/application is busy/) end return result unless result.blank? sleep 0.1 end nil end
Simply wait for an element to disappear. Optionally wait for the element to appear first.
Like {#wait_for}, you can pass any search filters that you normally would, including blocks. However, this method also supports the ability to pass an {AX::Element} and simply wait for it to become invalid.
An example usage would be typing into a search field and then waiting for the busy indicator to disappear and indicate that all search results have been returned.
@overload wait_for_invalidation_of
element
@param element [AX::Element] @param filters [Hash] @option filters [Number] :timeout (5) in seconds @return [Boolean] @example wait_for_invalidation_of table.row(static_text: { value: 'Cake' })
@overload wait_for_invalidation_of
kind, filters = {}, &block
@param element [#to_s] @param filters [Hash] @option filters [Number] :timeout (5) in seconds @return [Boolean] @example wait_for_invalidation_of :row, parent: table, static_text: { value: 'Cake' }
# File lib/accessibility/dsl.rb, line 492 def wait_for_invalidation_of element, filters = {}, &block timeout = filters[:timeout] || 5 start = Time.now unless element.kind_of? AX::Element element = wait_for element, filters, &block # this is a tricky situation, return true unless element end until Time.now - start > timeout return true if element.invalid? sleep 0.1 end false end