AXElements 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![The Moon](https://github.com/Marketcircle/AXElements/raw/gh-pages/images/next_version.png)
|
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
|