AXElements 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/.yardopts +4 -0
  2. data/History.markdown +41 -0
  3. data/README.markdown +59 -62
  4. data/Rakefile +1 -1
  5. data/ext/accessibility/key_coder/extconf.rb +1 -1
  6. data/ext/accessibility/key_coder/key_coder.c +8 -5
  7. data/lib/accessibility/dsl.rb +261 -87
  8. data/lib/accessibility/enumerators.rb +14 -11
  9. data/lib/accessibility/errors.rb +4 -3
  10. data/lib/accessibility/factory.rb +159 -108
  11. data/lib/accessibility/graph.rb +13 -9
  12. data/lib/accessibility/{pp_inspector.rb → pretty_printer.rb} +4 -5
  13. data/lib/accessibility/qualifier.rb +23 -13
  14. data/lib/accessibility/string.rb +4 -4
  15. data/lib/accessibility/system_info.rb +230 -0
  16. data/lib/accessibility/translator.rb +38 -28
  17. data/lib/accessibility/version.rb +24 -2
  18. data/lib/accessibility.rb +25 -8
  19. data/lib/ax/application.rb +207 -77
  20. data/lib/ax/element.rb +62 -65
  21. data/lib/ax/menu.rb +5 -1
  22. data/lib/ax/row.rb +1 -1
  23. data/lib/ax/scroll_area.rb +7 -6
  24. data/lib/ax/systemwide.rb +38 -5
  25. data/lib/ax_elements/active_support_selections.rb +10 -0
  26. data/lib/ax_elements/mri.rb +57 -0
  27. data/lib/ax_elements/nsarray_compat.rb +97 -17
  28. data/lib/ax_elements.rb +9 -1
  29. data/rakelib/gem.rake +11 -11
  30. data/rakelib/test.rake +0 -9
  31. data/test/helper.rb +10 -18
  32. data/test/integration/accessibility/test_dsl.rb +52 -42
  33. data/test/integration/accessibility/test_enumerators.rb +0 -1
  34. data/test/integration/accessibility/test_graph.rb +1 -0
  35. data/test/integration/accessibility/test_qualifier.rb +2 -2
  36. data/test/integration/ax/test_application.rb +9 -2
  37. data/test/integration/ax/test_element.rb +41 -1
  38. data/test/sanity/accessibility/test_factory.rb +23 -56
  39. data/test/sanity/accessibility/{test_pp_inspector.rb → test_pretty_printer.rb} +9 -9
  40. data/test/sanity/accessibility/test_translator.rb +2 -5
  41. data/test/sanity/accessibility/test_version.rb +15 -0
  42. data/test/sanity/ax/test_application.rb +17 -2
  43. data/test/sanity/ax/test_element.rb +2 -2
  44. data/test/sanity/ax_elements/test_nsobject_inspect.rb +4 -2
  45. data/test/sanity/test_ax_elements.rb +1 -0
  46. metadata +69 -39
  47. data/lib/accessibility/core.rb +0 -973
  48. data/lib/accessibility/highlighter.rb +0 -86
  49. data/lib/ax_elements/vendor/inflection_data.rb +0 -66
  50. data/lib/ax_elements/vendor/inflections.rb +0 -172
  51. data/lib/ax_elements/vendor/inflector.rb +0 -306
  52. data/lib/minitest/ax_elements.rb +0 -175
  53. data/lib/mouse.rb +0 -223
  54. data/lib/rspec/expectations/ax_elements.rb +0 -234
  55. data/test/integration/accessibility/test_core.rb +0 -18
  56. data/test/integration/minitest/test_ax_elements.rb +0 -89
  57. data/test/integration/rspec/expectations/test_ax_elements.rb +0 -102
  58. data/test/sanity/accessibility/test_core.rb +0 -561
  59. data/test/sanity/accessibility/test_highlighter.rb +0 -56
  60. data/test/sanity/minitest/test_ax_elements.rb +0 -17
  61. data/test/sanity/rspec/expectations/test_ax_elements.rb +0 -15
  62. data/test/sanity/test_mouse.rb +0 -19
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ require 'active_support/core_ext/object/blank'
4
+
3
5
  require 'mouse'
4
6
  require 'ax/element'
5
7
  require 'ax/application'
@@ -8,6 +10,8 @@ require 'ax/scroll_area'
8
10
  require 'ax/menu'
9
11
  require 'accessibility'
10
12
  require 'accessibility/enumerators'
13
+ require 'accessibility/highlighter'
14
+
11
15
 
12
16
  ##
13
17
  # DSL methods for AXElements.
@@ -22,14 +26,14 @@ require 'accessibility/enumerators'
22
26
  module Accessibility::DSL
23
27
 
24
28
 
25
- # @group Actions
29
+ # @!group Actions
26
30
 
27
31
  ##
28
32
  # We assume that any method that has the first argument with a type
29
33
  # of {AX::Element} is intended to be an action and so `#method_missing`
30
34
  # will forward the message to the element.
31
35
  #
32
- # @param [String] method an action constant
36
+ # @param meth [String] an action constant
33
37
  def method_missing meth, *args
34
38
  arg = args.first
35
39
  if arg.kind_of? AX::Element
@@ -46,7 +50,7 @@ module Accessibility::DSL
46
50
  ##
47
51
  # Try to perform the `press` action on the given element.
48
52
  #
49
- # @param [AX::Element]
53
+ # @param element [AX::Element]
50
54
  # @return [Boolean]
51
55
  def press element
52
56
  element.perform :press
@@ -55,7 +59,7 @@ module Accessibility::DSL
55
59
  ##
56
60
  # Try to perform the `show_menu` action on the given element.
57
61
  #
58
- # @param [AX::Element]
62
+ # @param element [AX::Element]
59
63
  # @return [Boolean]
60
64
  def show_menu element
61
65
  element.perform :show_menu
@@ -64,7 +68,7 @@ module Accessibility::DSL
64
68
  ##
65
69
  # Try to perform the `pick` action on the given element.
66
70
  #
67
- # @param [AX::Element]
71
+ # @param element [AX::Element]
68
72
  # @return [Boolean]
69
73
  def pick element
70
74
  element.perform :pick
@@ -73,7 +77,7 @@ module Accessibility::DSL
73
77
  ##
74
78
  # Try to perform the `decrement` action on the given element.
75
79
  #
76
- # @param [AX::Element]
80
+ # @param element [AX::Element]
77
81
  # @return [Boolean]
78
82
  def decrement element
79
83
  element.perform :decrement
@@ -82,7 +86,7 @@ module Accessibility::DSL
82
86
  ##
83
87
  # Try to perform the `confirm` action on the given element.
84
88
  #
85
- # @param [AX::Element]
89
+ # @param element [AX::Element]
86
90
  # @return [Boolean]
87
91
  def confirm element
88
92
  element.perform :confirm
@@ -91,7 +95,7 @@ module Accessibility::DSL
91
95
  ##
92
96
  # Try to perform the `increment` action on the given element.
93
97
  #
94
- # @param [AX::Element]
98
+ # @param element [AX::Element]
95
99
  # @return [Boolean]
96
100
  def increment element
97
101
  element.perform :increment
@@ -100,7 +104,7 @@ module Accessibility::DSL
100
104
  ##
101
105
  # Try to perform the `delete` action on the given element.
102
106
  #
103
- # @param [AX::Element]
107
+ # @param element [AX::Element]
104
108
  # @return [Boolean]
105
109
  def delete element
106
110
  element.perform :delete
@@ -109,7 +113,7 @@ module Accessibility::DSL
109
113
  ##
110
114
  # Try to perform the `cancel` action on the given element.
111
115
  #
112
- # @param [AX::Element]
116
+ # @param element [AX::Element]
113
117
  # @return [Boolean]
114
118
  def cancel element
115
119
  element.perform :cancel
@@ -122,20 +126,20 @@ module Accessibility::DSL
122
126
  # Try to perform the `raise` action on the given element.
123
127
  #
124
128
  # @overload raise element
125
- # @param [AX::Element] element
129
+ # @param element [AX::Element]
126
130
  # @return [Boolean]
127
131
  #
128
132
  # @overload raise exception[, message[, backtrace]]
129
133
  # The normal way to raise an exception.
130
134
  def raise *args
131
135
  arg = args.first
132
- arg.kind_of?(AX::Element) ? arg.perform(:raise) : super(*args)
136
+ arg.kind_of?(AX::Element) ? arg.perform(:raise) : Kernel.raise(*args)
133
137
  end
134
138
 
135
139
  ##
136
140
  # Tell an app to hide itself.
137
141
  #
138
- # @param [AX::Application]
142
+ # @param app [AX::Application]
139
143
  # @return [Boolean]
140
144
  def hide app
141
145
  app.perform :hide
@@ -144,7 +148,7 @@ module Accessibility::DSL
144
148
  ##
145
149
  # Tell an app to unhide itself.
146
150
  #
147
- # @param [AX::Application]
151
+ # @param app [AX::Application]
148
152
  # @return [Boolean]
149
153
  def unhide app
150
154
  app.perform :unhide
@@ -154,7 +158,7 @@ module Accessibility::DSL
154
158
  ##
155
159
  # Tell an app to quit.
156
160
  #
157
- # @param [AX::Application]
161
+ # @param app [AX::Application]
158
162
  # @return [Boolean]
159
163
  def terminate app
160
164
  app.perform :terminate
@@ -165,7 +169,7 @@ module Accessibility::DSL
165
169
  # focused. It is safe to pass any element into this method as nothing
166
170
  # will happen if it does not have a writable focused state attribute.
167
171
  #
168
- # @param [AX::Element]
172
+ # @param element [AX::Element]
169
173
  def set_focus_to element
170
174
  element.set(:focused, true) if element.writable? :focused
171
175
  end
@@ -180,8 +184,8 @@ module Accessibility::DSL
180
184
  #
181
185
  # @overload set element, attribute_name: new_value
182
186
  # Set a specified attribute to a new value
183
- # @param [AX::Element] element
184
- # @param [Hash{attribute_name=>new_value}] change
187
+ # @param element [AX::Element]
188
+ # @param change [Hash{attribute_name=>new_value}]
185
189
  #
186
190
  # @example
187
191
  #
@@ -189,8 +193,8 @@ module Accessibility::DSL
189
193
  #
190
194
  # @overload set element, new_value
191
195
  # Set the `value` attribute to a new value
192
- # @param [AX::Element] element
193
- # @param [Object] change
196
+ # @param element [AX::Element]
197
+ # @param change [Object]
194
198
  #
195
199
  # @example
196
200
  #
@@ -216,7 +220,7 @@ module Accessibility::DSL
216
220
  #
217
221
  # @overload type string
218
222
  # Send input to the currently focused application
219
- # @param [#to_s]
223
+ # @param string [String]
220
224
  #
221
225
  # @example
222
226
  #
@@ -224,7 +228,7 @@ module Accessibility::DSL
224
228
  #
225
229
  # @overload type string, app
226
230
  # Send input to a specific application
227
- # @param [#to_s]
231
+ # @param String [String]
228
232
  # @param [AX::Application]
229
233
  #
230
234
  def type string, app = system_wide
@@ -243,18 +247,18 @@ module Accessibility::DSL
243
247
  # select_menu_item mail, 'View', 'Sort By', 'Subject'
244
248
  # select_menu_item mail, 'Edit', /Spelling/, /show spelling/i
245
249
  #
246
- # @param [AX::Application]
247
- # @param [String,Regexp] path
250
+ # @param app [AX::Application]
251
+ # @param path [String,Regexp]
248
252
  # @return [Boolean]
249
253
  def select_menu_item app, *path
250
- app.select_menu_item *path
254
+ app.application.select_menu_item *path
251
255
  end
252
256
 
253
257
  ##
254
258
  # Show the "About" window for an app. Returns the window that is
255
259
  # opened.
256
260
  #
257
- # @param [AX::Application]
261
+ # @param app [AX::Application]
258
262
  # @return [AX::Window]
259
263
  def show_about_window_for app
260
264
  app.show_about_window
@@ -267,10 +271,10 @@ module Accessibility::DSL
267
271
  # Try to open the preferences for an app. Returns the window that
268
272
  # is opened.
269
273
  #
270
- # @param [AX::Application]
274
+ # @param app [AX::Application]
271
275
  # @return [AX::Window]
272
276
  def show_preferences_window_for app
273
- app.show_preferences_window
277
+ app.application.show_preferences_window
274
278
  end
275
279
 
276
280
  ##
@@ -285,7 +289,7 @@ module Accessibility::DSL
285
289
  #
286
290
  # scroll_to table.rows.last
287
291
  #
288
- # @param [AX::Element]
292
+ # @param element [AX::Element]
289
293
  # @return [void]
290
294
  def scroll_to element
291
295
  element.ancestor(:scroll_area).scroll_to element
@@ -302,14 +306,39 @@ module Accessibility::DSL
302
306
  # scroll_menu_to pop_up.menu.item(title: "Expensive Cake")
303
307
  # end
304
308
  #
305
- # @param [AX:]
309
+ # @param element [AX::Element]
306
310
  # @return [void]
307
311
  def scroll_menu_to element
308
- menu = element.ancestor(:menu).scroll_to element
312
+ element.ancestor(:menu).scroll_to element
313
+ end
314
+
315
+ ##
316
+ # @note This is a hack to workaround an AXAPI deficiency
317
+ # @note This method may move or be renamed in the next release, though
318
+ # it will not be removed
319
+ #
320
+ # Find a contextual menu that is open near the mouses current position
321
+ #
322
+ # This method assumes that it is being called in the block of a call to
323
+ # {#right_click}, and that the contextual menu is going to be very close
324
+ # to the mouse pointer.
325
+ #
326
+ # @return [AX::Menu,nil]
327
+ def contextual_menu
328
+ # CAST, just like high school trigonometry! :P
329
+ c, a, s, t = quads = Array.new(4) { Mouse.current_position }
330
+ c.x -= 10; c.y += 10
331
+ a.x += 10; a.y += 10
332
+ s.x += 10; s.y -= 10
333
+ t.x -= 10; t.y -= 10
334
+ elements = quads.map { |quad| element_at_point quad }
335
+ elements.uniq!
336
+ elements.map! { |el| el.kind_of?(AX::Menu) ? el : el.ancestor(:menu) }
337
+ elements.find { |element| element.present? }
309
338
  end
310
339
 
311
340
 
312
- # @group Polling
341
+ # @!group Polling
313
342
 
314
343
  ##
315
344
  # Simply wait around for something to show up. This method is similar
@@ -335,11 +364,12 @@ module Accessibility::DSL
335
364
  # # Waiting for something that will never show up
336
365
  # wait_for :a_million_dollars, ancestor: fruit_basket, timeout: 1000000
337
366
  #
338
- # @param [#to_s]
339
- # @param [Hash] filters
367
+ # @param element [#to_s]
368
+ # @param filters [Hash]
340
369
  # @option filters [Number] :timeout (5) timeout in seconds
341
370
  # @option filters [AX::Element] :parent
342
371
  # @option filters [AX::Element] :ancestor
372
+ # @yield Optional block used as a search filter
343
373
  # @return [AX::Element,nil]
344
374
  def wait_for element, filters = {}, &block
345
375
  if filters.has_key? :ancestor
@@ -358,9 +388,9 @@ module Accessibility::DSL
358
388
  #
359
389
  # See {#wait_for} for more details.
360
390
  #
361
- # @param [#to_s]
362
- # @param [AX::Element]
363
- # @param [Hash]
391
+ # @param descendant [#to_s]
392
+ # @param ancestor [AX::Element]
393
+ # @param filters [Hash]
364
394
  # @return [AX::Element,nil]
365
395
  def wait_for_descendant descendant, ancestor, filters, &block
366
396
  timeout = filters.delete(:timeout) || 5
@@ -388,9 +418,9 @@ module Accessibility::DSL
388
418
  #
389
419
  # See {#wait_for} for more details.
390
420
  #
391
- # @param [#to_s]
392
- # @param [AX::Element]
393
- # @param [Hash]
421
+ # @param child [#to_s]
422
+ # @param parent [AX::Element]
423
+ # @param filters [Hash]
394
424
  # @return [AX::Element,nil]
395
425
  def wait_for_child child, parent, filters, &block
396
426
  timeout = filters.delete(:timeout) || 5
@@ -418,8 +448,8 @@ module Accessibility::DSL
418
448
  # all search results have been returned.
419
449
  #
420
450
  # @overload wait_for_invalidation_of element
421
- # @param [AX::Element]
422
- # @param [Hash] filters
451
+ # @param element [AX::Element]
452
+ # @param filters [Hash]
423
453
  # @option filters [Number] :timeout (5) in seconds
424
454
  # @return [Boolean]
425
455
  #
@@ -428,8 +458,8 @@ module Accessibility::DSL
428
458
  # wait_for_invalidation_of table.row(static_text: { value: 'Cake' })
429
459
  #
430
460
  # @overload wait_for_invalidation_of kind, filters = {}, &block
431
- # @param [#to_s]
432
- # @param [Hash] filters
461
+ # @param element [#to_s]
462
+ # @param filters [Hash]
433
463
  # @option filters [Number] :timeout (5) in seconds
434
464
  # @return [Boolean]
435
465
  #
@@ -458,7 +488,7 @@ module Accessibility::DSL
458
488
  alias_method :wait_for_invalid, :wait_for_invalidation_of
459
489
 
460
490
 
461
- # @group Mouse Manipulation
491
+ # @!group Mouse Manipulation
462
492
 
463
493
  ##
464
494
  # Move the mouse cursor to the given point or object on the screen.
@@ -469,13 +499,13 @@ module Accessibility::DSL
469
499
  # move_mouse_to [344, 516]
470
500
  # move_mouse_to CGPoint.new(100, 100)
471
501
  #
472
- # @param [#to_point]
473
- # @param [Hash] opts
502
+ # @param arg [#to_point]
503
+ # @param opts [Hash]
474
504
  # @option opts [Number] :duration (0.2) in seconds
475
505
  # @option opts [Number] :wait (0.2) in seconds
476
506
  def move_mouse_to arg, opts = {}
477
507
  duration = opts[:duration] || 0.2
478
- if Accessibility.debug? && arg.respond_to?(:bounds)
508
+ if Accessibility.debug? && arg.kind_of?(AX::Element)
479
509
  highlight arg, timeout: duration, color: NSColor.orangeColor
480
510
  end
481
511
  Mouse.move_to arg.to_point, duration
@@ -495,8 +525,8 @@ module Accessibility::DSL
495
525
  # drag_mouse_to [100,100]
496
526
  # drag_mouse_to drop_zone, from: desktop_icon
497
527
  #
498
- # @param [#to_point]
499
- # @param [Hash] opts
528
+ # @param arg [#to_point]
529
+ # @param opts [Hash]
500
530
  # @option opts [#to_point] :from a point to move to before dragging
501
531
  # @option opts [Number] :duration (0.2) in seconds
502
532
  # @option opts [Number] :wait (0.2) in seconds
@@ -516,14 +546,36 @@ module Accessibility::DSL
516
546
  # If the second argument is provided then the mouse will move to that
517
547
  # point first; the argument must respond to `#to_point`.
518
548
  #
519
- # @param [Number]
520
- # @param [#to_point]
549
+ # @param lines [Number]
550
+ # @param obj [#to_point]
551
+ # @param wait [Number]
521
552
  def scroll lines, obj = nil, wait = 0.1
522
553
  move_mouse_to obj, wait: 0 if obj
523
554
  Mouse.scroll lines
524
555
  sleep wait
525
556
  end
526
557
 
558
+ ##
559
+ # @todo Need to expose the units option? Would allow scrolling by pixel.
560
+ #
561
+ # Horizontally scrolls an arbitrary number of lines at the mouses current
562
+ # point on the screen
563
+ #
564
+ # Use a positive number to scroll left, and a negative number to scroll
565
+ # right.
566
+ #
567
+ # If the second argument is provided then the mouse will move to that
568
+ # point first; the argument must respond to `#to_point`.
569
+ #
570
+ # @param lines [Number]
571
+ # @param obj [#to_point]
572
+ # @param wait [Number]
573
+ def horizontal_scroll lines, obj = nil, wait = 0.1
574
+ move_mouse_to obj, wait: 0 if obj
575
+ Mouse.horizontal_scroll lines
576
+ sleep wait
577
+ end
578
+
527
579
  ##
528
580
  # Perform a regular click.
529
581
  #
@@ -543,25 +595,35 @@ module Accessibility::DSL
543
595
  #
544
596
  # @yield Optionally take a block that is executed between click down
545
597
  # and click up events.
546
- # @param [#to_point]
598
+ # @param obj [#to_point]
547
599
  def click obj = nil, wait = 0.2
548
600
  move_mouse_to obj, wait: 0 if obj
549
601
  Mouse.click_down
550
602
  yield if block_given?
603
+ ensure
551
604
  Mouse.click_up
552
605
  sleep wait
553
606
  end
554
607
 
555
608
  ##
556
- # Perform a right (aka secondary) click action.
609
+ # Perform a right (a.k.a. secondary) click action.
557
610
  #
558
611
  # If an argument is provided then the mouse will move to that point
559
612
  # first; the argument must respond to `#to_point`.
560
613
  #
561
- # @param [#to_point]
614
+ # If a block is given, it will be yielded to between the click down
615
+ # and click up event. This behaviour is the same as passing a block
616
+ # to {DSL#click}.
617
+ #
618
+ # @yield Optionally take a block that is executed between click down
619
+ # and click up events.
620
+ # @param obj [#to_point]
562
621
  def right_click obj = nil, wait = 0.2
563
622
  move_mouse_to obj, wait: 0 if obj
564
- Mouse.right_click
623
+ Mouse.right_click_down
624
+ yield if block_given?
625
+ ensure
626
+ Mouse.right_click_up
565
627
  sleep wait
566
628
  end
567
629
  alias_method :secondary_click, :right_click
@@ -572,15 +634,119 @@ module Accessibility::DSL
572
634
  # If an argument is provided then the mouse will move to that point
573
635
  # first; the argument must respond to `#to_point`.
574
636
  #
575
- # @param [#to_point]
637
+ # @param obj [#to_point]
576
638
  def double_click obj = nil, wait = 0.2
577
639
  move_mouse_to obj, wait: 0 if obj
578
640
  Mouse.double_click
579
641
  sleep wait
580
642
  end
581
643
 
644
+ ##
645
+ # Perform a triple click action
646
+ #
647
+ # If an argument is provided then the mouse will move to that point
648
+ # first; the argument must respond to `#to_point`.
649
+ #
650
+ # @param obj [#to_point]
651
+ def triple_click obj = nil, wait = 0.2
652
+ move_mouse_to obj, wait: 0 if obj
653
+ Mouse.triple_click
654
+ sleep wait
655
+ end
582
656
 
583
- # @group Debug Helpers
657
+ ##
658
+ # Perform a swipe gesture in the given `direction`
659
+ #
660
+ # Valid directions are:
661
+ #
662
+ # - `:up`
663
+ # - `:down`
664
+ # - `:left`
665
+ # - `:right`
666
+ #
667
+ # An optional second argument can be provided. If the argument
668
+ # is provided then the mouse pointer will move to that point first.
669
+ #
670
+ # @example
671
+ #
672
+ # swipe :left, safari.web_area
673
+ #
674
+ # @param direction [Symbol]
675
+ # @param obj [#to_point]
676
+ def swipe direction, obj = nil, wait = 0.2
677
+ move_mouse_to obj, wait: 0 if obj
678
+ Mouse.swipe direction
679
+ sleep wait
680
+ end
681
+
682
+ ##
683
+ # Perform a pinch gesture in the given `direction`
684
+ #
685
+ # You can optionally specify the `magnification` factor and
686
+ # `position` for the pinch event.
687
+ #*
688
+ # Available pinch directions are:
689
+ #
690
+ # - `:zoom` or `:expand`
691
+ # - `:unzoom` or `:contract`
692
+ #
693
+ # Magnification is a relative magnification setting. A zoom value of
694
+ # `1.0` means `1.0` more than the current zoom level. `2.0` would be
695
+ # `2.0` levels higher than the current zoom.
696
+ #
697
+ # You can also optionally specify an object/point on screen for the mouse
698
+ # pointer to be moved to before the gesture begins.
699
+ #
700
+ # @param direction [Symbol]
701
+ # @param magnification [Float]
702
+ # @param obj [#to_point]
703
+ def pinch direction, magnification = 1, obj = nil, wait = 0.2
704
+ move_mouse_to obj, wait: 0 if obj
705
+ Mouse.pinch direction, magnification
706
+ sleep wait
707
+ end
708
+
709
+ ##
710
+ # Perform a rotation gesture in the given `direction` the given `angle` degrees
711
+ #
712
+ # Possible directions are:
713
+ #
714
+ # - `:cw`, ':clockwise`, ':clock_wise` to rotate in the clockwise
715
+ # direction
716
+ # - `:ccw`, ':counter_clockwise`, `:counter_clock_wise` to rotate in
717
+ # the the counter clockwise direction
718
+ #
719
+ # The `angle` parameter is a number of degrees to rotate. There are 360
720
+ # degrees in a full rotation, as you would expect in Euclidian geometry.
721
+ #
722
+ # You can also optionally specify an object/point on screen for the mouse
723
+ # pointer to be moved to before the gesture begins. The movement will
724
+ # be instantaneous.
725
+ #
726
+ # @param direction [Symbol]
727
+ # @param angle [Float]
728
+ # @param obj [#to_point]
729
+ def rotate direction, angle, obj = nil, wait = 0.2
730
+ move_mouse_to obj, wait: 0 if obj
731
+ Mouse.rotate direction, angle
732
+ sleep wait
733
+ end
734
+
735
+ ##
736
+ # Perform a smart magnify (double tap on trackpad)
737
+ #
738
+ # You can optionally specify an object/point on the screen where to perform
739
+ # the smart magnification. The mouse will move to this point first
740
+ #
741
+ # @param obj [#to_point]
742
+ def smart_magnify obj = nil, wait = 0.2
743
+ move_mouse_to obj, wait: 0 if obj
744
+ Mouse.smart_magnify
745
+ sleep wait
746
+ end
747
+
748
+
749
+ # @!group Debug Helpers
584
750
 
585
751
  ##
586
752
  # Highlight an element on screen. You can optionally specify the
@@ -603,32 +769,17 @@ module Accessibility::DSL
603
769
  # # highlighter automatically turns off after 5 seconds
604
770
  # highlight window.outline.row, colour: NSColor.greenColor, timeout: 5
605
771
  #
606
- # @param [#bounds]
607
- # @param [Hash] opts
772
+ # @param obj [#bounds]
773
+ # @param opts [Hash]
608
774
  # @option opts [Number] :timeout
609
775
  # @option opts [NSColor] :colour (NSColor.magentaColor)
610
776
  # @return [Accessibility::Highlighter]
611
777
  def highlight obj, opts = {}
612
- require 'accessibility/highlighter'
613
778
  Accessibility::Highlighter.new obj.bounds, opts
614
779
  end
615
780
 
616
781
  ##
617
- # Get the dump of the subtree of children and descendants for the given
618
- # element. Each generation down the tree will be indented another level,
619
- # and each element will be inspected.
620
- #
621
- # @example
622
- #
623
- # puts subtree app
624
- #
625
- # @return [String]
626
- def subtree element
627
- element.inspect_subtree
628
- end
629
- alias_method :subtree_for, :subtree
630
-
631
- ##
782
+ # @note This method is currently experimental
632
783
  # @note You will need to have GraphViz command line tools installed
633
784
  # in order for this to work.
634
785
  #
@@ -639,7 +790,7 @@ module Accessibility::DSL
639
790
  #
640
791
  # graph app.main_window
641
792
  #
642
- # @param [AX::Element]
793
+ # @param element [AX::Element]
643
794
  # @return [String] path to the saved image
644
795
  def graph element
645
796
  require 'accessibility/graph'
@@ -667,8 +818,8 @@ module Accessibility::DSL
667
818
  # screenshot app.title, "/Volumes/SecretStash"
668
819
  # # => "/Volumes/SecretStash/Safari-20120422184650.png"
669
820
  #
670
- # @param [#to_s]
671
- # @param [#to_s]
821
+ # @param name [#to_s]
822
+ # @param dir [#to_s]
672
823
  # @return [String] path to the screenshot
673
824
  def screenshot name = "AXElements-ScreenShot", dir = '~/Desktop'
674
825
  # @todo this could move to its own class, much like
@@ -691,8 +842,31 @@ module Accessibility::DSL
691
842
  end
692
843
  alias_method :capture_screen, :screenshot
693
844
 
845
+ ##
846
+ # See (ScreenRecorder)[http://rdoc.info/gems/screen_recorder/frames]
847
+ # for details on the screen recording options
848
+ #
849
+ # @example
850
+ #
851
+ # file = record do
852
+ # run_tests
853
+ # end
854
+ # `open '#{file}'`
855
+ #
856
+ # @param file [String]
857
+ # @yield
858
+ # @yieldparam recorder [ScreenRecorder]
859
+ # @return [String]
860
+ def record file = nil, &block
861
+ require 'screen_recorder'
862
+ if file
863
+ ScreenRecorder.record file, &block
864
+ else
865
+ ScreenRecorder.record &block
866
+ end
867
+ end
694
868
 
695
- # @endgroup
869
+ # @!endgroup
696
870
 
697
871
 
698
872
  ##
@@ -704,10 +878,10 @@ module Accessibility::DSL
704
878
  # app_with_bundle_identifier 'com.apple.finder'
705
879
  # launch 'com.apple.mail'
706
880
  #
707
- # @param [String]
881
+ # @param id [String]
708
882
  # @return [AX::Application,nil]
709
883
  def app_with_bundle_identifier id
710
- Accessibility.application_with_bundle_identifier id
884
+ AX::Application.new id
711
885
  end
712
886
  alias_method :app_with_bundle_id, :app_with_bundle_identifier
713
887
  alias_method :launch, :app_with_bundle_identifier
@@ -721,7 +895,7 @@ module Accessibility::DSL
721
895
  #
722
896
  # app_with_name 'Finder'
723
897
  #
724
- # @param [String]
898
+ # @param name [String]
725
899
  # @return [AX::Application,nil]
726
900
  def app_with_name name
727
901
  AX::Application.new name
@@ -735,14 +909,14 @@ module Accessibility::DSL
735
909
  #
736
910
  # app_with_pid 35843
737
911
  #
738
- # @param [Fixnum]
912
+ # @param pid [Fixnum]
739
913
  # @return [AX::Application]
740
914
  def app_with_pid pid
741
915
  AX::Application.new pid
742
916
  end
743
917
 
744
918
  ##
745
- # Convenience for `AX::SystemWide.new`.
919
+ # Synonym for `AX::SystemWide.new`.
746
920
  #
747
921
  # @return [AX::SystemWide]
748
922
  def system_wide
@@ -775,8 +949,8 @@ module Accessibility::DSL
775
949
  #
776
950
  # element_at window # find out what is in the middle of the window
777
951
  #
778
- # @param [#to_point]
779
- # @param [Hash] opts
952
+ # @param point [#to_point]
953
+ # @param opts [Hash]
780
954
  # @option opts [AX::Application] :for
781
955
  # @return [AX::Element]
782
956
  def element_at_point point, opts = {}