AXElements 0.8.0 → 0.8.1
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.
- data/README.markdown +7 -8
- data/lib/accessibility/dsl.rb +134 -161
- data/lib/accessibility/errors.rb +7 -5
- data/lib/accessibility/graph.rb +16 -0
- data/lib/accessibility/version.rb +2 -2
- data/lib/accessibility.rb +1 -1
- data/lib/ax/application.rb +8 -12
- data/lib/ax/element.rb +1 -1
- data/lib/ax/menu.rb +67 -0
- data/lib/ax/row.rb +1 -1
- data/lib/ax/scroll_area.rb +33 -0
- data/lib/mouse.rb +2 -17
- data/rakelib/ext.rake +2 -2
- data/test/integration/accessibility/test_dsl.rb +0 -1
- data/test/integration/accessibility/test_enumerators.rb +0 -4
- data/test/integration/accessibility/test_errors.rb +12 -9
- data/test/integration/accessibility/test_graph.rb +12 -0
- data/test/integration/accessibility/test_qualifier.rb +0 -4
- data/test/integration/ax/test_application.rb +1 -5
- data/test/integration/ax/test_element.rb +0 -4
- data/test/integration/ax/test_menu.rb +29 -0
- data/test/integration/ax/test_row.rb +0 -1
- data/test/integration/ax_elements/test_nsarray_compat.rb +0 -4
- data/test/integration/minitest/test_ax_elements.rb +0 -4
- data/test/integration/rspec/expectations/test_ax_elements.rb +0 -5
- metadata +8 -2
data/README.markdown
CHANGED
@@ -64,8 +64,10 @@ You need to have the OS X command line tools installed in order to
|
|
64
64
|
build and install AXElements, but you will also need Xcode in order to
|
65
65
|
run the test suite (sorry). Go ahead and install the tools now if you
|
66
66
|
haven't done that yet, I'll wait. Once you have the developer tools,
|
67
|
-
you should install MacRuby,
|
68
|
-
|
67
|
+
you should install [MacRuby](http://macruby.org/), version 0.12 or
|
68
|
+
later is required and downloads can be found at
|
69
|
+
https://github.com/MacRuby/MacRuby/downloads.
|
70
|
+
If you are on Snow Leopard, you will also need to install the
|
69
71
|
[Bridge Support Preview](http://www.macruby.org/blog/2010/10/08/bridgesupport-preview.html).
|
70
72
|
|
71
73
|
Then you can install AXElements. You can install AXElements via
|
@@ -123,12 +125,9 @@ as the technical underpinnings of AXElements.
|
|
123
125
|
|
124
126
|
## Development
|
125
127
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
increase. Documentation will be overhauled and more examples will be
|
130
|
-
added. It will be magical, so we're code naming the next version
|
131
|
-
"Clefairy".
|
128
|
+
AXElements has reached a point where the main focus is now performance,
|
129
|
+
features, and documentation. It will be magical, so we're code naming
|
130
|
+
the next version "Clefable".
|
132
131
|
|
133
132
|

|
134
133
|
|
data/lib/accessibility/dsl.rb
CHANGED
@@ -4,17 +4,21 @@ require 'mouse'
|
|
4
4
|
require 'ax/element'
|
5
5
|
require 'ax/application'
|
6
6
|
require 'ax/systemwide'
|
7
|
+
require 'ax/scroll_area'
|
8
|
+
require 'ax/menu'
|
7
9
|
require 'accessibility'
|
8
10
|
require 'accessibility/enumerators'
|
9
11
|
|
10
12
|
##
|
11
13
|
# DSL methods for AXElements.
|
12
14
|
#
|
13
|
-
# The
|
14
|
-
# in front of object to
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
15
|
+
# The DSL for AXElements is designed to pull actions out from an object
|
16
|
+
# and put them in front of object to make communicating test steps seem
|
17
|
+
# more like human instructions.
|
18
|
+
#
|
19
|
+
# You can read more about the DSL in the
|
20
|
+
# [Acting](http://github.com/Marketcircle/AXElements/wiki/Acting)
|
21
|
+
# section of the AXElements wiki.
|
18
22
|
module Accessibility::DSL
|
19
23
|
|
20
24
|
|
@@ -156,52 +160,6 @@ module Accessibility::DSL
|
|
156
160
|
app.perform :terminate
|
157
161
|
end
|
158
162
|
|
159
|
-
##
|
160
|
-
# Find the application with the given bundle identifier. If the
|
161
|
-
# application is not already running, it will be launched.
|
162
|
-
#
|
163
|
-
# @example
|
164
|
-
#
|
165
|
-
# app_with_bundle_identifier 'com.apple.finder'
|
166
|
-
# launch 'com.apple.mail'
|
167
|
-
#
|
168
|
-
# @param [String]
|
169
|
-
# @return [AX::Application,nil]
|
170
|
-
def app_with_bundle_identifier id
|
171
|
-
Accessibility.application_with_bundle_identifier id
|
172
|
-
end
|
173
|
-
alias_method :app_with_bundle_id, :app_with_bundle_identifier
|
174
|
-
alias_method :launch, :app_with_bundle_identifier
|
175
|
-
|
176
|
-
##
|
177
|
-
# Find the application with the given name. If the application
|
178
|
-
# is not already running, it will NOT be launched and this
|
179
|
-
# method will return `nil`.
|
180
|
-
#
|
181
|
-
# @example
|
182
|
-
#
|
183
|
-
# app_with_name 'Finder'
|
184
|
-
#
|
185
|
-
# @param [String]
|
186
|
-
# @return [AX::Application,nil]
|
187
|
-
def app_with_name name
|
188
|
-
AX::Application.new name
|
189
|
-
end
|
190
|
-
|
191
|
-
##
|
192
|
-
# Find the application with the given process identifier. An
|
193
|
-
# invalid PID will cause an exception to be raised.
|
194
|
-
#
|
195
|
-
# @example
|
196
|
-
#
|
197
|
-
# app_with_pid 35843
|
198
|
-
#
|
199
|
-
# @param [Fixnum]
|
200
|
-
# @return [AX::Application]
|
201
|
-
def app_with_pid pid
|
202
|
-
AX::Application.new pid
|
203
|
-
end
|
204
|
-
|
205
163
|
##
|
206
164
|
# Focus an element on the screen, but only if it can be directly
|
207
165
|
# focused. It is safe to pass any element into this method as nothing
|
@@ -292,6 +250,64 @@ module Accessibility::DSL
|
|
292
250
|
app.select_menu_item *path
|
293
251
|
end
|
294
252
|
|
253
|
+
##
|
254
|
+
# Show the "About" window for an app. Returns the window that is
|
255
|
+
# opened.
|
256
|
+
#
|
257
|
+
# @param [AX::Application]
|
258
|
+
# @return [AX::Window]
|
259
|
+
def show_about_window_for app
|
260
|
+
app.show_about_window
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# @note This method assumes that the app has setup the standard
|
265
|
+
# CMD+, hotkey to open the pref window
|
266
|
+
#
|
267
|
+
# Try to open the preferences for an app. Returns the window that
|
268
|
+
# is opened.
|
269
|
+
#
|
270
|
+
# @param [AX::Application]
|
271
|
+
# @return [AX::Window]
|
272
|
+
def show_preferences_window_for app
|
273
|
+
app.show_preferences_window
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Scroll through a scroll area until the given element is visible.
|
278
|
+
#
|
279
|
+
# If you need to scroll an unknown amount of units through a table
|
280
|
+
# or another type of object contained in as scroll area, you can
|
281
|
+
# just pass the element that you are trying to get to and this method
|
282
|
+
# will scroll to it for you.
|
283
|
+
#
|
284
|
+
# @example
|
285
|
+
#
|
286
|
+
# scroll_to table.rows.last
|
287
|
+
#
|
288
|
+
# @param [AX::Element]
|
289
|
+
# @return [void]
|
290
|
+
def scroll_to element
|
291
|
+
element.ancestor(:scroll_area).scroll_to element
|
292
|
+
end
|
293
|
+
alias_method :scroll_to_visible, :scroll_to
|
294
|
+
|
295
|
+
##
|
296
|
+
# Scroll a menu to an item in the menu and then move the mouse
|
297
|
+
# pointer to that item.
|
298
|
+
#
|
299
|
+
# @example
|
300
|
+
#
|
301
|
+
# click window.pop_up do
|
302
|
+
# scroll_menu_to pop_up.menu.item(title: "Expensive Cake")
|
303
|
+
# end
|
304
|
+
#
|
305
|
+
# @param [AX:]
|
306
|
+
# @return [void]
|
307
|
+
def scroll_menu_to element
|
308
|
+
menu = element.ancestor(:menu).scroll_to element
|
309
|
+
end
|
310
|
+
|
295
311
|
|
296
312
|
# @group Polling
|
297
313
|
|
@@ -521,17 +537,18 @@ module Accessibility::DSL
|
|
521
537
|
#
|
522
538
|
# click
|
523
539
|
# click window.close_button
|
540
|
+
# click do
|
541
|
+
# move_mouse_to [0,0]
|
542
|
+
# end
|
524
543
|
#
|
544
|
+
# @yield Optionally take a block that is executed between click down
|
545
|
+
# and click up events.
|
525
546
|
# @param [#to_point]
|
526
547
|
def click obj = nil, wait = 0.2
|
527
548
|
move_mouse_to obj, wait: 0 if obj
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
Mouse.click_up
|
532
|
-
else
|
533
|
-
Mouse.click
|
534
|
-
end
|
549
|
+
Mouse.click_down
|
550
|
+
yield if block_given?
|
551
|
+
Mouse.click_up
|
535
552
|
sleep wait
|
536
553
|
end
|
537
554
|
|
@@ -603,13 +620,13 @@ module Accessibility::DSL
|
|
603
620
|
#
|
604
621
|
# @example
|
605
622
|
#
|
606
|
-
# puts
|
623
|
+
# puts subtree app
|
607
624
|
#
|
608
625
|
# @return [String]
|
609
|
-
def
|
626
|
+
def subtree element
|
610
627
|
element.inspect_subtree
|
611
628
|
end
|
612
|
-
alias_method :
|
629
|
+
alias_method :subtree_for, :subtree
|
613
630
|
|
614
631
|
##
|
615
632
|
# @note You will need to have GraphViz command line tools installed
|
@@ -627,16 +644,11 @@ module Accessibility::DSL
|
|
627
644
|
def graph element
|
628
645
|
require 'accessibility/graph'
|
629
646
|
graph = Accessibility::Graph.new(element)
|
630
|
-
graph.
|
631
|
-
|
632
|
-
|
633
|
-
file = Tempfile.new('graph')
|
634
|
-
File.open(file.path, 'w') do |fd| fd.write graph.to_dot end
|
635
|
-
`dot -Tpng #{file.path} > #{file.path}.png`
|
636
|
-
`open #{file.path}.png`
|
637
|
-
|
638
|
-
file.path
|
647
|
+
path = graph.generate_png!
|
648
|
+
`open #{path}`
|
649
|
+
path
|
639
650
|
end
|
651
|
+
alias_method :graph_for, :graph
|
640
652
|
|
641
653
|
##
|
642
654
|
# Take a screen shot and save it to disk. If a file name and path are
|
@@ -659,6 +671,9 @@ module Accessibility::DSL
|
|
659
671
|
# @param [#to_s]
|
660
672
|
# @return [String] path to the screenshot
|
661
673
|
def screenshot name = "AXElements-ScreenShot", dir = '~/Desktop'
|
674
|
+
# @todo this could move to its own class, much like
|
675
|
+
# {Accessibility::Highlighter} and expose more options
|
676
|
+
# while retaining good defaults
|
662
677
|
dir = File.expand_path dir.to_s
|
663
678
|
file = "#{dir}/#{name}-#{Time.now.strftime '%Y%m%d%H%M%S'}.png"
|
664
679
|
|
@@ -675,10 +690,56 @@ module Accessibility::DSL
|
|
675
690
|
file
|
676
691
|
end
|
677
692
|
alias_method :capture_screen, :screenshot
|
678
|
-
alias_method :shoot_screen, :screenshot
|
679
693
|
|
680
694
|
|
681
|
-
# @
|
695
|
+
# @endgroup
|
696
|
+
|
697
|
+
|
698
|
+
##
|
699
|
+
# Find the application with the given bundle identifier. If the
|
700
|
+
# application is not already running, it will be launched.
|
701
|
+
#
|
702
|
+
# @example
|
703
|
+
#
|
704
|
+
# app_with_bundle_identifier 'com.apple.finder'
|
705
|
+
# launch 'com.apple.mail'
|
706
|
+
#
|
707
|
+
# @param [String]
|
708
|
+
# @return [AX::Application,nil]
|
709
|
+
def app_with_bundle_identifier id
|
710
|
+
Accessibility.application_with_bundle_identifier id
|
711
|
+
end
|
712
|
+
alias_method :app_with_bundle_id, :app_with_bundle_identifier
|
713
|
+
alias_method :launch, :app_with_bundle_identifier
|
714
|
+
|
715
|
+
##
|
716
|
+
# Find the application with the given name. If the application
|
717
|
+
# is not already running, it will NOT be launched and this
|
718
|
+
# method will return `nil`.
|
719
|
+
#
|
720
|
+
# @example
|
721
|
+
#
|
722
|
+
# app_with_name 'Finder'
|
723
|
+
#
|
724
|
+
# @param [String]
|
725
|
+
# @return [AX::Application,nil]
|
726
|
+
def app_with_name name
|
727
|
+
AX::Application.new name
|
728
|
+
end
|
729
|
+
|
730
|
+
##
|
731
|
+
# Find the application with the given process identifier. An
|
732
|
+
# invalid PID will cause an exception to be raised.
|
733
|
+
#
|
734
|
+
# @example
|
735
|
+
#
|
736
|
+
# app_with_pid 35843
|
737
|
+
#
|
738
|
+
# @param [Fixnum]
|
739
|
+
# @return [AX::Application]
|
740
|
+
def app_with_pid pid
|
741
|
+
AX::Application.new pid
|
742
|
+
end
|
682
743
|
|
683
744
|
##
|
684
745
|
# Convenience for `AX::SystemWide.new`.
|
@@ -722,94 +783,6 @@ module Accessibility::DSL
|
|
722
783
|
base = opts[:for] || system_wide
|
723
784
|
base.element_at point
|
724
785
|
end
|
725
|
-
|
726
|
-
##
|
727
|
-
# Show the "About" window for an app. Returns the window that is
|
728
|
-
# opened.
|
729
|
-
#
|
730
|
-
# @param [AX::Application]
|
731
|
-
# @return [AX::Window]
|
732
|
-
def show_about_window_for app
|
733
|
-
app.show_about_window
|
734
|
-
end
|
735
|
-
|
736
|
-
##
|
737
|
-
# @note This method assumes that the app has setup the standard
|
738
|
-
# CMD+, hotkey to open the pref window
|
739
|
-
#
|
740
|
-
# Try to open the preferences for an app. Returns the window that
|
741
|
-
# is opened.
|
742
|
-
#
|
743
|
-
# @param [AX::Application]
|
744
|
-
# @return [AX::Window]
|
745
|
-
def show_preferences_window_for app
|
746
|
-
app.show_preferences_window
|
747
|
-
end
|
748
|
-
|
749
|
-
##
|
750
|
-
# Scroll though a scroll area until the given element is visible.
|
751
|
-
#
|
752
|
-
# If you need to scroll an unknown ammount of units through a scroll area
|
753
|
-
# you can just pass the element that you need visible and this method
|
754
|
-
# will scroll to it for you.
|
755
|
-
#
|
756
|
-
# @example
|
757
|
-
#
|
758
|
-
# scroll_to table.rows.last
|
759
|
-
#
|
760
|
-
# @param [AX::Element]
|
761
|
-
# @return [void]
|
762
|
-
def scroll_to element
|
763
|
-
scroll_area = element.ancestor :scroll_area
|
764
|
-
|
765
|
-
return if NSContainsRect(scroll_area.bounds, element.bounds)
|
766
|
-
move_mouse_to scroll_area
|
767
|
-
# calculate direction to scroll
|
768
|
-
direction = element.position.y > scroll_area.position.y ? -5 : 5
|
769
|
-
until NSContainsRect(scroll_area.bounds, element.bounds)
|
770
|
-
Mouse.scroll direction
|
771
|
-
end
|
772
|
-
sleep 0.1
|
773
|
-
end
|
774
|
-
alias_method :scroll_to_visible, :scroll_to
|
775
|
-
|
776
|
-
##
|
777
|
-
# Scroll a menu to an item in the menu and then move the mouse
|
778
|
-
# pointer to that item.
|
779
|
-
#
|
780
|
-
# @example
|
781
|
-
#
|
782
|
-
# scroll_menu_to menu.element(title: "Expensive Cake")
|
783
|
-
#
|
784
|
-
# @param [AX:]
|
785
|
-
# @return [void]
|
786
|
-
def scroll_menu_to element
|
787
|
-
menu = element.ancestor :menu
|
788
|
-
move_mouse_to menu
|
789
|
-
|
790
|
-
row_height = menu.menu_item.size.height
|
791
|
-
point = menu.position
|
792
|
-
point.x += menu.size.width / 2
|
793
|
-
point.y += if element.position.y > menu.position.y
|
794
|
-
menu.size.height - (row_height * 0.1)
|
795
|
-
else
|
796
|
-
row_height * 0.1
|
797
|
-
end
|
798
|
-
|
799
|
-
until NSContainsRect(menu.bounds, element.bounds)
|
800
|
-
move_mouse_to point
|
801
|
-
end
|
802
|
-
|
803
|
-
start = Time.now
|
804
|
-
until Time.now - start > 5
|
805
|
-
# This can happen sometimes with the little arrow bars
|
806
|
-
# in menus covering up the menu item.
|
807
|
-
if element_under_mouse != element
|
808
|
-
move_mouse_to element
|
809
|
-
else
|
810
|
-
break
|
811
|
-
end
|
812
|
-
end
|
813
|
-
end
|
786
|
+
alias_method :element_at, :element_at_point
|
814
787
|
|
815
788
|
end
|
data/lib/accessibility/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'accessibility/qualifier'
|
2
|
+
|
1
3
|
##
|
2
4
|
# Error raised when an implicit search fails to return a result.
|
3
5
|
class Accessibility::SearchFailure < NoMethodError
|
@@ -5,13 +7,13 @@ class Accessibility::SearchFailure < NoMethodError
|
|
5
7
|
# @param [AX::Element]
|
6
8
|
# @param [#to_s]
|
7
9
|
# @param [Hash{Symbol=>Object}]
|
8
|
-
def initialize searcher, searchee, filters
|
10
|
+
def initialize searcher, searchee, filters, &block
|
9
11
|
filters = {} unless filters.kind_of? Hash
|
10
|
-
msg = "Could not find `#{pp_searchee searchee, filters}` "
|
12
|
+
msg = "Could not find `#{pp_searchee searchee, filters, &block}` "
|
11
13
|
msg << "as a child of #{searcher.class}\n"
|
12
14
|
msg << "Element Path:\n\t" << path_to(searcher)
|
13
15
|
# @todo Consider turning this on by default
|
14
|
-
msg << "\nSubtree:\n\n" <<
|
16
|
+
msg << "\nSubtree:\n\n" << searcher.inspect_subtree if Accessibility.debug?
|
15
17
|
super msg
|
16
18
|
end
|
17
19
|
|
@@ -19,8 +21,8 @@ class Accessibility::SearchFailure < NoMethodError
|
|
19
21
|
private
|
20
22
|
|
21
23
|
# Nice string representation of what was being searched for
|
22
|
-
def pp_searchee searchee, filters
|
23
|
-
Accessibility::Qualifier.new(searchee, filters).describe
|
24
|
+
def pp_searchee searchee, filters, &block
|
25
|
+
Accessibility::Qualifier.new(searchee, filters, &block).describe
|
24
26
|
end
|
25
27
|
|
26
28
|
# Nice string representation of element's path from the application root
|
data/lib/accessibility/graph.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
1
3
|
##
|
2
4
|
# DOT graph generator for AXElements. It can generate the digraph code
|
3
5
|
# for a UI subtree. That code can then be given to GraphViz to generate
|
@@ -178,6 +180,7 @@ class Accessibility::Graph
|
|
178
180
|
# should use #size_of(:children), but that doesn't in all cases
|
179
181
|
@edge_queue.concat Array.new(element.children.size, node)
|
180
182
|
end
|
183
|
+
@built = true
|
181
184
|
end
|
182
185
|
|
183
186
|
##
|
@@ -193,4 +196,17 @@ class Accessibility::Graph
|
|
193
196
|
graph << "\n}\n"
|
194
197
|
end
|
195
198
|
|
199
|
+
##
|
200
|
+
# Generate the PNG file for the graph and save it to a temporary
|
201
|
+
# file. The path to the temporary file will be returned.
|
202
|
+
#
|
203
|
+
# @return [String] path to the saved PNG file
|
204
|
+
def generate_png!
|
205
|
+
build! unless @built
|
206
|
+
file = Tempfile.new 'ax_elements_graph'
|
207
|
+
file.write self.to_dot
|
208
|
+
`dot -Tpng #{file.path} > #{file.path}.png`
|
209
|
+
"#{file.path}.png"
|
210
|
+
end
|
211
|
+
|
196
212
|
end
|
data/lib/accessibility.rb
CHANGED
data/lib/ax/application.rb
CHANGED
@@ -197,21 +197,17 @@ class AX::Application < AX::Element
|
|
197
197
|
#
|
198
198
|
# @return [AX::MenuItem]
|
199
199
|
def navigate_menu *path
|
200
|
-
# @todo CLEAN UP
|
201
200
|
perform :unhide # can't navigate menus unless the app is up front
|
202
|
-
|
201
|
+
bar_item = item = self.menu_bar.menu_bar_item(title: path.shift)
|
203
202
|
path.each do |part|
|
204
|
-
|
205
|
-
next_item =
|
206
|
-
|
207
|
-
failure = Accessibility::SearchFailure.new(current, :menu_item, title: part)
|
208
|
-
current.perform :cancel # close menu
|
209
|
-
raise failure
|
210
|
-
else
|
211
|
-
current = next_item
|
212
|
-
end
|
203
|
+
item.perform :press
|
204
|
+
next_item = item.menu_item(title: part)
|
205
|
+
item = next_item
|
213
206
|
end
|
214
|
-
|
207
|
+
item
|
208
|
+
ensure
|
209
|
+
# got to the end
|
210
|
+
bar_item.perform :cancel unless item.title == path.last
|
215
211
|
end
|
216
212
|
|
217
213
|
##
|
data/lib/ax/element.rb
CHANGED
@@ -340,7 +340,7 @@ class AX::Element
|
|
340
340
|
|
341
341
|
elsif @ref.attributes.include? KAXChildrenAttribute
|
342
342
|
if (result = search(method, *args, &block)).blank?
|
343
|
-
raise Accessibility::SearchFailure.new(self, method, args.first)
|
343
|
+
raise Accessibility::SearchFailure.new(self, method, args.first, &block)
|
344
344
|
else
|
345
345
|
return result
|
346
346
|
end
|
data/lib/ax/menu.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'ax/element'
|
2
|
+
require 'mouse'
|
3
|
+
|
4
|
+
##
|
5
|
+
# UI element representing a menu. Not much to it...
|
6
|
+
class AX::Menu < AX::Element
|
7
|
+
|
8
|
+
##
|
9
|
+
# Scroll the menu until the given element, which must be in the menu
|
10
|
+
# is visible in the bounds of the menu. This method will also move the
|
11
|
+
# mouse pointer to the given element.
|
12
|
+
#
|
13
|
+
# If you need to scroll an unknown number of units through a menu,
|
14
|
+
# you can just pass the element that you need visible and this method
|
15
|
+
# will scroll to it for you.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# click window.pop_up do
|
20
|
+
# menu = pop_up.menu
|
21
|
+
# menu.scroll_to menu.item(title: "Expensive Cake")
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @param [AX::Element]
|
25
|
+
# @return [void]
|
26
|
+
def scroll_to element
|
27
|
+
Mouse.move_to self.to_point
|
28
|
+
|
29
|
+
# calculate which scroll arrow to move to
|
30
|
+
fudge_factor = self.item.size.height * 0.1
|
31
|
+
point = self.position
|
32
|
+
size = self.size
|
33
|
+
point.x += size.width / 2
|
34
|
+
point.y += if element.position.y > point.y
|
35
|
+
size.height - fudge_factor
|
36
|
+
else
|
37
|
+
fudge_factor
|
38
|
+
end
|
39
|
+
|
40
|
+
# scroll until element is visible
|
41
|
+
until NSContainsRect(self.bounds, element.bounds)
|
42
|
+
Mouse.move_to point
|
43
|
+
end
|
44
|
+
|
45
|
+
start = Time.now
|
46
|
+
until Time.now - start > 5
|
47
|
+
# Sometimes the little arrow bars in menus covering
|
48
|
+
# up the menu item and we have to move just a bit more.
|
49
|
+
if self.application.element_at(Mouse.current_position) != element
|
50
|
+
Mouse.move_to element.to_point
|
51
|
+
else
|
52
|
+
break
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Search the menu for a `menu_item` that matches the given
|
59
|
+
# filters. The filters should be specified just as they would
|
60
|
+
# be when calling {#search}.
|
61
|
+
def item filters = {}, &block
|
62
|
+
result = self.search :menu_item, filters, &block
|
63
|
+
return result unless result.blank?
|
64
|
+
raise Accessibility::SearchFailure.new(self, :menu_item, filters, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/ax/row.rb
CHANGED
@@ -31,7 +31,7 @@ class AX::Row < AX::Element
|
|
31
31
|
qualifier = Accessibility::Qualifier.new(:Column, filters, &block)
|
32
32
|
column = self.parent.columns.index { |x| qualifier.qualifies? x }
|
33
33
|
return self.children.at(column) if column
|
34
|
-
raise Accessibility::SearchFailure.new(self.parent, 'column', filters)
|
34
|
+
raise Accessibility::SearchFailure.new(self.parent, 'column', filters, &block)
|
35
35
|
end
|
36
36
|
|
37
37
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ax/element'
|
2
|
+
|
3
|
+
##
|
4
|
+
# UI element for a view that can scroll? I'm not sure how else to
|
5
|
+
# describe it, the class name says it all.
|
6
|
+
class AX::ScrollArea < AX::Element
|
7
|
+
|
8
|
+
##
|
9
|
+
# Scroll through the receiver until the given element is visible.
|
10
|
+
#
|
11
|
+
# If you need to scroll an unknown amount of units through a scroll
|
12
|
+
# area, or something in a scroll area (i.e. a table), you can just
|
13
|
+
# pass the element that you are trying to get to and this method
|
14
|
+
# will scroll to it for you.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
#
|
18
|
+
# scroll_area.scroll_to table.rows.last
|
19
|
+
#
|
20
|
+
# @param [AX::Element]
|
21
|
+
# @return [void]
|
22
|
+
def scroll_to element
|
23
|
+
return if NSContainsRect(self.bounds, element.bounds)
|
24
|
+
Mouse.move_to self.to_point
|
25
|
+
# calculate direction to scroll
|
26
|
+
direction = element.position.y > self.position.y ? -5 : 5
|
27
|
+
until NSContainsRect(self.bounds, element.bounds)
|
28
|
+
Mouse.scroll direction
|
29
|
+
end
|
30
|
+
sleep 0.1
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/mouse.rb
CHANGED
@@ -92,30 +92,15 @@ module Mouse
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
95
|
-
##
|
96
|
-
# A standard click. Default position is the current position.
|
97
|
-
#
|
98
|
-
# This `duration` parameter is used to add a minimum time between
|
99
|
-
# down and up click as clicking up too fast has shown to screw things up in
|
100
|
-
# some cases. However, the duration is arbitrary and if the system lags it
|
101
|
-
# could cause certain timing behaviours (consider the two behaviours of
|
102
|
-
# pop up buttons).
|
103
|
-
#
|
104
|
-
# @param [CGPoint]
|
105
|
-
def click point = current_position, duration = 12
|
106
|
-
click_down
|
107
|
-
sleep QUANTUM*duration
|
108
|
-
click_up
|
109
|
-
end
|
110
|
-
|
111
95
|
##
|
112
96
|
# Perform a down click. You should follow this up with a call to
|
113
97
|
# {#click_up} to finish the click.
|
114
98
|
#
|
115
99
|
# @param [CGPoint]
|
116
|
-
def click_down point = current_position
|
100
|
+
def click_down point = current_position, duration = 12
|
117
101
|
event = new_event KCGEventLeftMouseDown, point, KCGMouseButtonLeft
|
118
102
|
post event
|
103
|
+
sleep QUANTUM*duration
|
119
104
|
end
|
120
105
|
|
121
106
|
##
|
data/rakelib/ext.rake
CHANGED
@@ -12,7 +12,6 @@ class TestAccessibilityDSL < MiniTest::Unit::TestCase
|
|
12
12
|
@dsl ||= DSL.new
|
13
13
|
end
|
14
14
|
|
15
|
-
def app; @@app ||= AX::Application.new REF end
|
16
15
|
def text_area; @@text_area ||= app.main_window.text_area end
|
17
16
|
def pop_up; @@pop_up ||= app.main_window.pop_up end
|
18
17
|
def pref_window; app.children.find { |x| x.attribute(:title) == 'Preferences' } end
|
@@ -2,10 +2,6 @@ require 'test/integration/helper'
|
|
2
2
|
|
3
3
|
class TestAccessibilityEnumeratorsBreadthFirst < MiniTest::Unit::TestCase
|
4
4
|
|
5
|
-
def app
|
6
|
-
@@app ||= AX::Application.new REF
|
7
|
-
end
|
8
|
-
|
9
5
|
def test_each_iterates_in_correct_order
|
10
6
|
tab_group = app.main_window.children.find { |x| x.class == AX::TabGroup }
|
11
7
|
enum = Accessibility::Enumerators::BreadthFirst.new(tab_group)
|
@@ -1,38 +1,41 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'test/integration/helper'
|
2
3
|
|
3
4
|
class TestAccessibilityErrors < MiniTest::Unit::TestCase
|
4
5
|
|
5
6
|
def test_search_failure_shows_arguments
|
6
|
-
e = Accessibility::SearchFailure.new(
|
7
|
+
e = Accessibility::SearchFailure.new(app, :list, {herp: :derp}) { }
|
8
|
+
def e.backtrace; []; end
|
9
|
+
assert_match /Could not find `List\(herp: :derp\)\[✔\]`/, e.message
|
10
|
+
|
11
|
+
e = Accessibility::SearchFailure.new(app, :list, {herp: :derp})
|
7
12
|
def e.backtrace; []; end
|
8
13
|
assert_match /Could not find `List\(herp: :derp\)`/, e.message
|
9
14
|
assert_match /as a child of AX::Application/, e.message
|
10
15
|
assert_match /Element Path:\n\t\#<AX::Application/, e.message
|
11
16
|
|
12
|
-
e = Accessibility::SearchFailure.new(
|
17
|
+
e = Accessibility::SearchFailure.new(app, :list, {})
|
13
18
|
def e.backtrace; []; end
|
14
19
|
assert_match /Could not find `List`/, e.message
|
15
20
|
|
16
|
-
e = Accessibility::SearchFailure.new(
|
21
|
+
e = Accessibility::SearchFailure.new(app, :list, nil)
|
17
22
|
def e.backtrace; []; end
|
18
23
|
assert_match /Could not find `List`/, e.message
|
19
24
|
end
|
20
25
|
|
21
26
|
def test_search_failure_shows_element_path
|
22
|
-
|
23
|
-
e = Accessibility::SearchFailure.new(l, :trash_dock_item, nil)
|
27
|
+
e = Accessibility::SearchFailure.new(app.menu_bar, :trash_dock_item, nil)
|
24
28
|
def e.backtrace; []; end
|
25
29
|
assert_match /AX::Application/, e.message
|
26
|
-
assert_match /AX::
|
30
|
+
assert_match /AX::MenuBar/, e.message
|
27
31
|
end
|
28
32
|
|
29
33
|
def test_search_failure_includes_subtree_in_debug_mode
|
30
34
|
assert Accessibility.debug?, 'Someone turned debugging off'
|
31
|
-
|
32
|
-
e = Accessibility::SearchFailure.new(l, :trash_dock_item, nil)
|
35
|
+
e = Accessibility::SearchFailure.new(app.menu_bar, :trash_dock_item, nil)
|
33
36
|
def e.backtrace; []; end
|
34
37
|
assert_match /Subtree:/, e.message
|
35
|
-
assert_match
|
38
|
+
assert_match app.menu_bar.inspect_subtree, e.message
|
36
39
|
end
|
37
40
|
|
38
41
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test/integration/helper'
|
2
|
+
require 'accessibility/graph'
|
3
|
+
|
4
|
+
class TestAccessibilityGraph < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def test_generate
|
7
|
+
p = Accessibility::Graph.new(app.main_window).generate_png!
|
8
|
+
assert File.exists? p
|
9
|
+
assert_match /^PNG image/, `file --brief #{p}`
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -2,12 +2,8 @@ require 'test/integration/helper'
|
|
2
2
|
|
3
3
|
class TestAXApplication < MiniTest::Unit::TestCase
|
4
4
|
|
5
|
-
def app
|
6
|
-
@app ||= AX::Application.new REF
|
7
|
-
end
|
8
|
-
|
9
5
|
def running_app
|
10
|
-
|
6
|
+
app.instance_variable_get :@app
|
11
7
|
end
|
12
8
|
|
13
9
|
def test_initialize_args
|
@@ -2,10 +2,6 @@ require 'test/integration/helper'
|
|
2
2
|
|
3
3
|
class TestAXElement < MiniTest::Unit::TestCase
|
4
4
|
|
5
|
-
def app
|
6
|
-
@@app ||= AX::Application.new PID
|
7
|
-
end
|
8
|
-
|
9
5
|
def test_path_returns_correct_elements_in_correct_order
|
10
6
|
list = app.window.close_button.ancestry
|
11
7
|
assert_equal 3, list.size
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test/integration/helper'
|
2
|
+
|
3
|
+
class TestAXMenu < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
def test_item
|
6
|
+
pop_up = app.main_window.pop_up
|
7
|
+
click pop_up
|
8
|
+
|
9
|
+
assert_respond_to pop_up.menu, :item
|
10
|
+
|
11
|
+
assert_equal 'Togusa', pop_up.menu.item(title: 'Togusa').title
|
12
|
+
|
13
|
+
pop_up.menu.item(title: '38') { |x| @got_called = true }
|
14
|
+
assert @got_called
|
15
|
+
|
16
|
+
ensure
|
17
|
+
click pop_up if pop_up
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def test_item_raises
|
22
|
+
pop_up = app.main_window.pop_up
|
23
|
+
click pop_up
|
24
|
+
assert_raises(Accessibility::SearchFailure) { pop_up.menu.item(herp: 'derp') }
|
25
|
+
ensure
|
26
|
+
click pop_up if pop_up
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: AXElements
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.8.
|
5
|
+
version: 0.8.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mark Rada
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-25 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -88,9 +88,11 @@ files:
|
|
88
88
|
- lib/ax/application.rb
|
89
89
|
- lib/ax/button.rb
|
90
90
|
- lib/ax/element.rb
|
91
|
+
- lib/ax/menu.rb
|
91
92
|
- lib/ax/pop_up_button.rb
|
92
93
|
- lib/ax/radio_button.rb
|
93
94
|
- lib/ax/row.rb
|
95
|
+
- lib/ax/scroll_area.rb
|
94
96
|
- lib/ax/static_text.rb
|
95
97
|
- lib/ax/systemwide.rb
|
96
98
|
- lib/ax_elements/awesome_print.rb
|
@@ -117,9 +119,11 @@ files:
|
|
117
119
|
- test/integration/accessibility/test_dsl.rb
|
118
120
|
- test/integration/accessibility/test_enumerators.rb
|
119
121
|
- test/integration/accessibility/test_errors.rb
|
122
|
+
- test/integration/accessibility/test_graph.rb
|
120
123
|
- test/integration/accessibility/test_qualifier.rb
|
121
124
|
- test/integration/ax/test_application.rb
|
122
125
|
- test/integration/ax/test_element.rb
|
126
|
+
- test/integration/ax/test_menu.rb
|
123
127
|
- test/integration/ax/test_row.rb
|
124
128
|
- test/integration/ax_elements/test_nsarray_compat.rb
|
125
129
|
- test/integration/minitest/test_ax_elements.rb
|
@@ -174,9 +178,11 @@ test_files:
|
|
174
178
|
- test/integration/accessibility/test_dsl.rb
|
175
179
|
- test/integration/accessibility/test_enumerators.rb
|
176
180
|
- test/integration/accessibility/test_errors.rb
|
181
|
+
- test/integration/accessibility/test_graph.rb
|
177
182
|
- test/integration/accessibility/test_qualifier.rb
|
178
183
|
- test/integration/ax/test_application.rb
|
179
184
|
- test/integration/ax/test_element.rb
|
185
|
+
- test/integration/ax/test_menu.rb
|
180
186
|
- test/integration/ax/test_row.rb
|
181
187
|
- test/integration/ax_elements/test_nsarray_compat.rb
|
182
188
|
- test/integration/minitest/test_ax_elements.rb
|