Module: Accessibility::DSL

Included in:
HasChildShortlyMatcher, HasDescendentShortlyMatcher
Defined in:
lib/accessibility/dsl.rb

Overview

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 section of the AXElements wiki.

Actions collapse

Polling collapse

Mouse Manipulation collapse

Debug Helpers collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args) ⇒ Object

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.

Parameters:

  • method (String)

    an action constant

Raises:

  • (NoMethodError)


33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/accessibility/dsl.rb', line 33

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

Instance Method Details

#app_with_bundle_identifier(id) ⇒ AX::Application? Also known as: app_with_bundle_id, launch

Find the application with the given bundle identifier. If the application is not already running, it will be launched.

Examples:


app_with_bundle_identifier 'com.apple.finder'
launch                     'com.apple.mail'

Parameters:

Returns:



709
710
711
# File 'lib/accessibility/dsl.rb', line 709

def app_with_bundle_identifier id
  Accessibility.application_with_bundle_identifier id
end

#app_with_name(name) ⇒ AX::Application?

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.

Examples:


app_with_name 'Finder'

Parameters:

Returns:



726
727
728
# File 'lib/accessibility/dsl.rb', line 726

def app_with_name name
  AX::Application.new name
end

#app_with_pid(pid) ⇒ AX::Application

Find the application with the given process identifier. An invalid PID will cause an exception to be raised.

Examples:


app_with_pid 35843

Parameters:

  • (Fixnum)

Returns:



740
741
742
# File 'lib/accessibility/dsl.rb', line 740

def app_with_pid pid
  AX::Application.new pid
end

#cancel(element) ⇒ Boolean

Try to perform the cancel action on the given element.

Parameters:

Returns:

  • (Boolean)


114
115
116
# File 'lib/accessibility/dsl.rb', line 114

def cancel element
  element.perform :cancel
end

#click(obj = nil, wait = 0.2) { ... } ⇒ Object

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.

Examples:


click
click window.close_button
click do
  move_mouse_to [0,0]
end

Parameters:

  • (#to_point)

Yields:

  • Optionally take a block that is executed between click down and click up events.



547
548
549
550
551
552
553
# File 'lib/accessibility/dsl.rb', line 547

def click obj = nil, wait = 0.2
  move_mouse_to obj, wait: 0 if obj
  Mouse.click_down
  yield if block_given?
  Mouse.click_up
  sleep wait
end

#confirm(element) ⇒ Boolean

Try to perform the confirm action on the given element.

Parameters:

Returns:

  • (Boolean)


87
88
89
# File 'lib/accessibility/dsl.rb', line 87

def confirm element
  element.perform :confirm
end

#decrement(element) ⇒ Boolean

Try to perform the decrement action on the given element.

Parameters:

Returns:

  • (Boolean)


78
79
80
# File 'lib/accessibility/dsl.rb', line 78

def decrement element
  element.perform :decrement
end

#delete(element) ⇒ Boolean

Try to perform the delete action on the given element.

Parameters:

Returns:

  • (Boolean)


105
106
107
# File 'lib/accessibility/dsl.rb', line 105

def delete element
  element.perform :delete
end

#double_click(obj = nil, wait = 0.2) ⇒ Object

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.

Parameters:

  • (#to_point)


576
577
578
579
580
# File 'lib/accessibility/dsl.rb', line 576

def double_click obj = nil, wait = 0.2
  move_mouse_to obj, wait: 0 if obj
  Mouse.double_click
  sleep wait
end

#drag_mouse_to(arg, opts = {}) ⇒ Object

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.

Examples:


drag_mouse_to [100,100]
drag_mouse_to drop_zone, from: desktop_icon

Parameters:

  • (#to_point)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :from (#to_point)

    a point to move to before dragging

  • :duration (Number) — default: 0.2

    in seconds

  • :wait (Number) — default: 0.2

    in seconds



503
504
505
506
507
# File 'lib/accessibility/dsl.rb', line 503

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

#element_at_point(point, opts = {}) ⇒ AX::Element Also known as: element_at

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.

Examples:


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

Parameters:

  • (#to_point)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

Returns:



782
783
784
785
# File 'lib/accessibility/dsl.rb', line 782

def element_at_point point, opts = {}
  base = opts[:for] || system_wide
  base.element_at point
end

#element_under_mouseAX::Element

Return the top most element at the current mouse position.

See #element_at_point for more details.

Returns:



758
759
760
# File 'lib/accessibility/dsl.rb', line 758

def element_under_mouse
  element_at_point Mouse.current_position
end

#graph(element) ⇒ String Also known as: graph_for

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.

Examples:


graph app.main_window

Parameters:

Returns:

  • (String)

    path to the saved image



644
645
646
647
648
649
650
# File 'lib/accessibility/dsl.rb', line 644

def graph element
  require 'accessibility/graph'
  graph = Accessibility::Graph.new(element)
  path  = graph.generate_png!
  `open #{path}`
  path
end

#hide(app) ⇒ Boolean

Tell an app to hide itself.

Parameters:

Returns:

  • (Boolean)


140
141
142
# File 'lib/accessibility/dsl.rb', line 140

def hide app
  app.perform :hide
end

#highlight(obj, opts = {}) ⇒ Accessibility::Highlighter

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.

Examples:


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

Parameters:

  • (#bounds)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :timeout (Number)
  • :colour (NSColor) — default: NSColor.magentaColor

Returns:



611
612
613
614
# File 'lib/accessibility/dsl.rb', line 611

def highlight obj, opts = {}
  require 'accessibility/highlighter'
  Accessibility::Highlighter.new obj.bounds, opts
end

#increment(element) ⇒ Boolean

Try to perform the increment action on the given element.

Parameters:

Returns:

  • (Boolean)


96
97
98
# File 'lib/accessibility/dsl.rb', line 96

def increment element
  element.perform :increment
end

#move_mouse_to(arg, opts = {}) ⇒ Object

Move the mouse cursor to the given point or object on the screen.

Examples:


move_mouse_to button
move_mouse_to [344, 516]
move_mouse_to CGPoint.new(100, 100)

Parameters:

  • (#to_point)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :duration (Number) — default: 0.2

    in seconds

  • :wait (Number) — default: 0.2

    in seconds



476
477
478
479
480
481
482
483
# File 'lib/accessibility/dsl.rb', line 476

def move_mouse_to arg, opts = {}
  duration = opts[:duration] || 0.2
  if Accessibility.debug? && arg.respond_to?(:bounds)
    highlight arg, timeout: duration, color: NSColor.orangeColor
  end
  Mouse.move_to arg.to_point, duration
  sleep(opts[:wait] || 0.2)
end

#pick(element) ⇒ Boolean

Try to perform the pick action on the given element.

Parameters:

Returns:

  • (Boolean)


69
70
71
# File 'lib/accessibility/dsl.rb', line 69

def pick element
  element.perform :pick
end

#press(element) ⇒ Boolean

Try to perform the press action on the given element.

Parameters:

Returns:

  • (Boolean)


51
52
53
# File 'lib/accessibility/dsl.rb', line 51

def press element
  element.perform :press
end

#raise(element) ⇒ Boolean #raise(exception[, message[, backtrace]]) ⇒ Object

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.

Overloads:

  • #raise(element) ⇒ Boolean

    Parameters:

    Returns:

    • (Boolean)
  • #raise(exception[, message[, backtrace]]) ⇒ Object

    The normal way to raise an exception.



130
131
132
133
# File 'lib/accessibility/dsl.rb', line 130

def raise *args
  arg = args.first
  arg.kind_of?(AX::Element) ? arg.perform(:raise) : super(*args)
end

#right_click(obj = nil, wait = 0.2) ⇒ Object Also known as: secondary_click

Perform a right (aka secondary) click action.

If an argument is provided then the mouse will move to that point first; the argument must respond to #to_point.

Parameters:

  • (#to_point)


562
563
564
565
566
# File 'lib/accessibility/dsl.rb', line 562

def right_click obj = nil, wait = 0.2
  move_mouse_to obj, wait: 0 if obj
  Mouse.right_click
  sleep wait
end

#screenshot(name = "AXElements-ScreenShot", dir = '~/Desktop') ⇒ String Also known as: capture_screen

Take a screen shot and save it to disk. If a file name and path are not given then default values will be used; given paths will be expanded automatically.A timestamp and file extension will always automatically be appended to the file name.

Examples:


screenshot
  # => "~/Desktop/AXElements-ScreenShot-20120422184650.png"

screenshot app.title
  # => "~/Desktop/Safari-20120422184650.png"

screenshot app.title, "/Volumes/SecretStash"
  # => "/Volumes/SecretStash/Safari-20120422184650.png"

Parameters:

  • (#to_s)
  • (#to_s)

Returns:

  • (String)

    path to the screenshot



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'lib/accessibility/dsl.rb', line 673

def screenshot name = "AXElements-ScreenShot", dir = '~/Desktop'
  # @todo this could move to its own class, much like
  #       {Accessibility::Highlighter} and expose more options
  #       while retaining good defaults
  dir  = File.expand_path dir.to_s
  file = "#{dir}/#{name}-#{Time.now.strftime '%Y%m%d%H%M%S'}.png"

  cg_image = CGWindowListCreateImage(CGRectInfinite,
                                     KCGWindowListOptionOnScreenOnly,
                                     KCGNullWindowID,
                                     KCGWindowImageDefault)
  NSBitmapImageRep
    .alloc
    .initWithCGImage(cg_image)
    .representationUsingType(NSPNGFileType, properties: nil)
    .writeToFile(file, atomically: false)

  file
end

#scroll(lines, obj = nil, wait = 0.1) ⇒ Object

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.

Parameters:

  • (Number)
  • (#to_point)


521
522
523
524
525
# File 'lib/accessibility/dsl.rb', line 521

def scroll lines, obj = nil, wait = 0.1
  move_mouse_to obj, wait: 0 if obj
  Mouse.scroll lines
  sleep wait
end

#scroll_menu_to(element)

This method returns an undefined value.

Scroll a menu to an item in the menu and then move the mouse pointer to that item.

Examples:


click window.pop_up do
  scroll_menu_to pop_up.menu.item(title: "Expensive Cake")
end

Parameters:

  • (AX:)


307
308
309
# File 'lib/accessibility/dsl.rb', line 307

def scroll_menu_to element
  menu = element.ancestor(:menu).scroll_to element
end

#scroll_to(element) Also known as: scroll_to_visible

This method returns an undefined value.

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.

Examples:


scroll_to table.rows.last

Parameters:



290
291
292
# File 'lib/accessibility/dsl.rb', line 290

def scroll_to element
  element.ancestor(:scroll_area).scroll_to element
end

#select_menu_item(app, *path) ⇒ Boolean

Navigate the menu bar menus for the given application and select the last item in the chain.

Examples:


mail = app_with_name 'Mail'
select_menu_item mail, 'View', 'Sort By', 'Subject'
select_menu_item mail, 'Edit', /Spelling/, /show spelling/i

Parameters:

Returns:

  • (Boolean)


249
250
251
# File 'lib/accessibility/dsl.rb', line 249

def select_menu_item app, *path
  app.select_menu_item *path
end

#set(element, attribute_name: new_value) ⇒ Object #set(element, new_value) ⇒ Object

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.

Examples:


set text_field, selected_text_range: 1..10

set text_field,   'Mark Rada'
set radio_button, 1

Overloads:

  • #set(element, attribute_name: new_value) ⇒ Object

    Set a specified attribute to a new value

    Parameters:

    • element (AX::Element)
    • change (Hash{attribute_name=>new_value})
  • #set(element, new_value) ⇒ Object

    Set the value attribute to a new value

    Parameters:



200
201
202
203
204
205
206
207
208
# File 'lib/accessibility/dsl.rb', line 200

def set element, change
  set_focus_to element

  if change.kind_of? Hash
    element.set *change.first
  else
    element.set :value, change
  end
end

#set_focus_to(element) ⇒ Object Also known as: set_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.

Parameters:



169
170
171
# File 'lib/accessibility/dsl.rb', line 169

def set_focus_to element
  element.set(:focused, true) if element.writable? :focused
end

#show_about_window_for(app) ⇒ AX::Window

Show the “About” window for an app. Returns the window that is opened.

Parameters:

Returns:

  • (AX::Window)


259
260
261
# File 'lib/accessibility/dsl.rb', line 259

def show_about_window_for app
  app.show_about_window
end

#show_menu(element) ⇒ Boolean

Try to perform the show_menu action on the given element.

Parameters:

Returns:

  • (Boolean)


60
61
62
# File 'lib/accessibility/dsl.rb', line 60

def show_menu element
  element.perform :show_menu
end

#show_preferences_window_for(app) ⇒ AX::Window

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.

Parameters:

Returns:

  • (AX::Window)


272
273
274
# File 'lib/accessibility/dsl.rb', line 272

def show_preferences_window_for app
  app.show_preferences_window
end

#subtree(element) ⇒ String Also known as: subtree_for

Get the dump of the subtree of children and descendants for the given element. Each generation down the tree will be indented another level, and each element will be inspected.

Examples:


puts subtree app

Returns:



626
627
628
# File 'lib/accessibility/dsl.rb', line 626

def subtree element
  element.inspect_subtree
end

#system_wideAX::SystemWide

Convenience for AX::SystemWide.new.

Returns:



748
749
750
# File 'lib/accessibility/dsl.rb', line 748

def system_wide
  AX::SystemWide.new
end

#terminate(app) ⇒ Boolean

Tell an app to quit.

Parameters:

Returns:

  • (Boolean)


159
160
161
# File 'lib/accessibility/dsl.rb', line 159

def terminate app
  app.perform :terminate
end

#type(string) ⇒ Object #type(string, app) ⇒ Object Also known as: type_string

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 wiki page.

Examples:


type "Hello, world!"

Overloads:

  • #type(string) ⇒ Object

    Send input to the currently focused application

    Parameters:

    • (#to_s)
  • #type(string, app) ⇒ Object

    Send input to a specific application

    Parameters:



230
231
232
233
# File 'lib/accessibility/dsl.rb', line 230

def type string, app = system_wide
  sleep 0.1
  app.type string.to_s
end

#unhide(app) ⇒ Boolean Also known as: show

Tell an app to unhide itself.

Parameters:

Returns:

  • (Boolean)


149
150
151
# File 'lib/accessibility/dsl.rb', line 149

def unhide app
  app.perform :unhide
end

#wait_for(element, filters = {}, &block) ⇒ AX::Element?

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).

Examples:


# 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

Parameters:

  • (#to_s)
  • filters (Hash) (defaults to: {})

Options Hash (filters):

Returns:



344
345
346
347
348
349
350
351
352
# File 'lib/accessibility/dsl.rb', line 344

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

#wait_for_child(child, parent, filters, &block) ⇒ AX::Element?

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.

Parameters:

Returns:



395
396
397
398
399
400
401
402
403
404
405
# File 'lib/accessibility/dsl.rb', line 395

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 = parent.children.find { |x| q.qualifies? x }
    return result unless result.blank?
    sleep 0.1
  end
  nil
end

#wait_for_descendant(descendant, ancestor, filters, &block) ⇒ AX::Element? Also known as: wait_for_descendent

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.

Parameters:

Returns:



365
366
367
368
369
370
371
372
373
374
# File 'lib/accessibility/dsl.rb', line 365

def wait_for_descendant descendant, ancestor, filters, &block
  timeout = filters.delete(:timeout) || 5
  start   = Time.now
  until Time.now - start > timeout
    result = ancestor.search(descendant, filters, &block)
    return result unless result.blank?
    sleep 0.1
  end
  nil
end

#wait_for_invalidation_of(element) ⇒ Boolean #wait_for_invalidation_of(kind, filters = {}, &block) ⇒ Boolean Also known as: wait_for_invalidation, wait_for_invalid

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.

Examples:


wait_for_invalidation_of table.row(static_text: { value: 'Cake' })

wait_for_invalidation_of :row, parent: table, static_text: { value: 'Cake' }

Parameters:

  • (AX::Element)
  • filters (Hash) (defaults to: {})
  • (#to_s)
  • filters (Hash) (defaults to: {})

Options Hash (filters):

  • :timeout (Number) — default: 5

    in seconds

  • :timeout (Number) — default: 5

    in seconds

Returns:

  • (Boolean)
  • (Boolean)
  • (Boolean)


441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/accessibility/dsl.rb', line 441

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