AXElements 0.7.8 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -10
- data/README.markdown +7 -14
- data/ext/accessibility/key_coder/key_coder.c +7 -0
- data/lib/AXElements.rb +0 -2
- data/lib/accessibility/core.rb +180 -123
- data/lib/accessibility/dsl.rb +310 -191
- data/lib/accessibility/enumerators.rb +9 -8
- data/lib/accessibility/errors.rb +7 -8
- data/lib/accessibility/factory.rb +16 -9
- data/lib/accessibility/graph.rb +68 -22
- data/lib/accessibility/highlighter.rb +86 -0
- data/lib/accessibility/pp_inspector.rb +4 -4
- data/lib/accessibility/qualifier.rb +11 -9
- data/lib/accessibility/string.rb +12 -4
- data/lib/accessibility/translator.rb +19 -10
- data/lib/accessibility/version.rb +3 -1
- data/lib/accessibility.rb +42 -17
- data/lib/ax/application.rb +90 -30
- data/lib/ax/button.rb +5 -2
- data/lib/ax/element.rb +133 -149
- data/lib/ax/pop_up_button.rb +12 -0
- data/lib/ax/radio_button.rb +5 -2
- data/lib/ax/row.rb +2 -2
- data/lib/ax/static_text.rb +5 -2
- data/lib/ax/systemwide.rb +24 -12
- data/lib/ax_elements/awesome_print.rb +13 -0
- data/lib/ax_elements/exception_workaround.rb +5 -0
- data/lib/ax_elements/nsarray_compat.rb +1 -0
- data/lib/ax_elements.rb +2 -1
- data/lib/minitest/ax_elements.rb +60 -4
- data/lib/mouse.rb +47 -20
- data/lib/rspec/expectations/ax_elements.rb +180 -88
- data/rakelib/doc.rake +7 -0
- data/test/helper.rb +2 -1
- data/test/integration/accessibility/test_dsl.rb +126 -18
- data/test/integration/accessibility/test_errors.rb +1 -1
- data/test/integration/ax/test_element.rb +17 -0
- data/test/integration/minitest/test_ax_elements.rb +33 -38
- data/test/integration/rspec/expectations/test_ax_elements.rb +68 -19
- data/test/sanity/accessibility/test_core.rb +45 -37
- data/test/sanity/accessibility/test_highlighter.rb +56 -0
- data/test/sanity/ax/test_application.rb +8 -0
- data/test/sanity/ax/test_element.rb +7 -3
- data/test/sanity/minitest/test_ax_elements.rb +2 -0
- data/test/sanity/rspec/expectations/test_ax_elements.rb +3 -0
- data/test/sanity/test_accessibility.rb +9 -0
- data/test/sanity/test_mouse.rb +2 -2
- metadata +11 -38
- data/docs/AccessibilityTips.markdown +0 -119
- data/docs/Acting.markdown +0 -340
- data/docs/Debugging.markdown +0 -165
- data/docs/Inspecting.markdown +0 -261
- data/docs/KeyboardEvents.markdown +0 -122
- data/docs/NewBehaviour.markdown +0 -151
- data/docs/Notifications.markdown +0 -271
- data/docs/Searching.markdown +0 -250
- data/docs/TestingExtensions.markdown +0 -52
- data/docs/images/all_the_buttons.jpg +0 -0
- data/docs/images/next_version.png +0 -0
- data/docs/images/ui_hierarchy.dot +0 -34
- data/docs/images/ui_hierarchy.png +0 -0
- data/lib/accessibility/debug.rb +0 -164
- data/test/integration/accessibility/test_debug.rb +0 -44
- data/test/sanity/accessibility/test_debug.rb +0 -63
@@ -1,52 +0,0 @@
|
|
1
|
-
# Test Helpers
|
2
|
-
|
3
|
-
@todo Pretend that this does not exist yet
|
4
|
-
|
5
|
-
There are some types of assertions that you would like to make during
|
6
|
-
testing that simply does not make sense using the build in
|
7
|
-
assertions. These can include things like existence checks which you
|
8
|
-
would have to implement by searching, or ... that is really the only
|
9
|
-
one I have so far :)
|
10
|
-
|
11
|
-
## RSpec
|
12
|
-
|
13
|
-
RSpec 2 is the Marketcircle choice for implementing functional and
|
14
|
-
behavioural tests for apps using AXElements. It is great and offers
|
15
|
-
the flexibility and features that make using it very good for large
|
16
|
-
test suites.
|
17
|
-
|
18
|
-
You can load the RSpec matchers that AXElements adds by requiring
|
19
|
-
|
20
|
-
require 'rspec/ax_elements'
|
21
|
-
|
22
|
-
### Existence
|
23
|
-
|
24
|
-
Checking for the existence of an object in RSpec with AXElements looks
|
25
|
-
a bit awkward:
|
26
|
-
|
27
|
-
window.search(:button).should be_empty
|
28
|
-
# or
|
29
|
-
window.search(:button).should_not be_empty
|
30
|
-
|
31
|
-
That does not communicate intent as clearly as it could. What if you
|
32
|
-
could say something like:
|
33
|
-
|
34
|
-
window.should have_a :button
|
35
|
-
# or
|
36
|
-
window.should_not have_a :button
|
37
|
-
|
38
|
-
What if you have filters? Well, that is handled as well, though maybe
|
39
|
-
not as nicely:
|
40
|
-
|
41
|
-
window.should have_a(:button).with(title: 'Hello')
|
42
|
-
|
43
|
-
## Minitest
|
44
|
-
|
45
|
-
AXElements uses minitest for its own regression test suite. Minitest
|
46
|
-
is pretty cool, and there is good cause to support it as well. To that
|
47
|
-
end, we have provided the equivalent assertions.
|
48
|
-
|
49
|
-
You can load it like so:
|
50
|
-
|
51
|
-
require 'minitest/ax_elements'
|
52
|
-
|
Binary file
|
Binary file
|
@@ -1,34 +0,0 @@
|
|
1
|
-
digraph {
|
2
|
-
Application [label = "Application: Mail", style=filled]
|
3
|
-
MainWindow [label = "MainWindow: Inbox"]
|
4
|
-
Outline [label = "Outline"]
|
5
|
-
Table [label = "Table: Emails"]
|
6
|
-
TableRow1 [label = "TableRow: Spam"]
|
7
|
-
TableRow2 [label = "TableRow: Daily LOLcat"]
|
8
|
-
MenuBar [label = "MenuBar", style=filled]
|
9
|
-
MenuBarItem1 [label = "MenuBarItem: File", style=filled]
|
10
|
-
MenuBarItem2 [label = "MenuBarItem: Edit"]
|
11
|
-
MenuBarItem3 [label = "MenuBarItem: Help"]
|
12
|
-
Menu1 [label = "Menu", style=filled]
|
13
|
-
MenuItem1 [label = "MenuItem: Preferences"]
|
14
|
-
MenuItem2 [label = "MenuItem: Services", style=filled]
|
15
|
-
MenuItem3 [label = "MenuItem: Quit"]
|
16
|
-
Menu2 [label = "Menu", style=filled]
|
17
|
-
MenuItem4 [label = "MenuItem: Services Preferences", style=filled]
|
18
|
-
|
19
|
-
Application -> MainWindow
|
20
|
-
Application -> MenuBar
|
21
|
-
MainWindow -> Outline
|
22
|
-
MainWindow -> Table
|
23
|
-
Table -> TableRow1
|
24
|
-
Table -> TableRow2
|
25
|
-
MenuBar -> MenuBarItem1
|
26
|
-
MenuBar -> MenuBarItem2
|
27
|
-
MenuBar -> MenuBarItem3
|
28
|
-
MenuBarItem1 -> Menu1
|
29
|
-
Menu1 -> MenuItem1
|
30
|
-
Menu1 -> MenuItem2
|
31
|
-
Menu1 -> MenuItem3
|
32
|
-
MenuItem2 -> Menu2
|
33
|
-
Menu2 -> MenuItem4
|
34
|
-
}
|
Binary file
|
data/lib/accessibility/debug.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
require 'accessibility/version'
|
2
|
-
framework 'Cocoa'
|
3
|
-
|
4
|
-
##
|
5
|
-
# Collection of utility methods helpful when trying to debug issues.
|
6
|
-
module Accessibility::Debug
|
7
|
-
|
8
|
-
# Initialize the DEBUG value
|
9
|
-
@on = ENV.fetch 'AXDEBUG', $DEBUG
|
10
|
-
|
11
|
-
|
12
|
-
class << self
|
13
|
-
|
14
|
-
##
|
15
|
-
# Whether or not to turn on DEBUG features in AXElements. The
|
16
|
-
# value is initially inherited from `$DEBUG` but can be overridden
|
17
|
-
# by an environment variable named `AXDEBUG` or changed dynamically
|
18
|
-
# at runtime.
|
19
|
-
#
|
20
|
-
# @return [Boolean]
|
21
|
-
attr_accessor :on
|
22
|
-
alias_method :on?, :on
|
23
|
-
|
24
|
-
##
|
25
|
-
# Get a list of elements, starting with an element you give, and riding
|
26
|
-
# the hierarchy up to the top level object (i.e. the {AX::Application}).
|
27
|
-
#
|
28
|
-
# @example
|
29
|
-
#
|
30
|
-
# element = AX::DOCK.list.application_dock_item
|
31
|
-
# path_for element
|
32
|
-
# # => [AX::ApplicationDockItem, AX::List, AX::Application]
|
33
|
-
#
|
34
|
-
# @param [AX::Element]
|
35
|
-
# @return [Array<AX::Element>] the path in ascending order
|
36
|
-
def path *elements
|
37
|
-
element = elements.last
|
38
|
-
return path(elements << element.parent) if element.respond_to? :parent
|
39
|
-
return elements
|
40
|
-
end
|
41
|
-
|
42
|
-
##
|
43
|
-
# @note This is an unfinished feature
|
44
|
-
#
|
45
|
-
# Make a `dot` format graph of the tree, meant for graphing with
|
46
|
-
# GraphViz.
|
47
|
-
#
|
48
|
-
# @return [String]
|
49
|
-
def graph_subtree root
|
50
|
-
require 'accessibility/graph'
|
51
|
-
dot = Accessibility::Graph.new(root)
|
52
|
-
dot.build!
|
53
|
-
dot.to_s
|
54
|
-
end
|
55
|
-
|
56
|
-
##
|
57
|
-
# Dump a tree to the console, indenting for each level down the
|
58
|
-
# tree that we go, and inspecting each element.
|
59
|
-
#
|
60
|
-
# @example
|
61
|
-
#
|
62
|
-
# puts subtree_for app
|
63
|
-
#
|
64
|
-
# @return [String]
|
65
|
-
def text_subtree element
|
66
|
-
output = element.inspect + "\n"
|
67
|
-
# @todo should use each_child_with_level instead
|
68
|
-
enum = Accessibility::Enumerators::DepthFirst.new element
|
69
|
-
enum.each_with_level do |element, depth|
|
70
|
-
output << "\t"*depth + element.inspect + "\n"
|
71
|
-
end
|
72
|
-
output
|
73
|
-
end
|
74
|
-
|
75
|
-
##
|
76
|
-
# Highlight an element on screen. You can optionally specify the
|
77
|
-
# highlight colour or pass a timeout to automatically have the
|
78
|
-
# highlighter disappear.
|
79
|
-
#
|
80
|
-
# The highlighter is actually a window, so if you do not set a
|
81
|
-
# timeout, you will need to call `#stop` or `#close` on the `NSWindow`
|
82
|
-
# object that this method returns in order to get rid of the
|
83
|
-
# highlighter.
|
84
|
-
#
|
85
|
-
# You could use this method to highlight an arbitrary number of
|
86
|
-
# elements on screen, with a rainbow of colours for debugging.
|
87
|
-
#
|
88
|
-
# @example
|
89
|
-
#
|
90
|
-
# highlighter = highlight window.outline
|
91
|
-
# highlight window.outline.row, colour: NSColor.greenColor, timeout: 5
|
92
|
-
# highlighter.stop
|
93
|
-
#
|
94
|
-
# @param [AX::Element]
|
95
|
-
# @param [Hash] opts
|
96
|
-
# @option opts [Number] :timeout
|
97
|
-
# @option opts [NSColor] :colour
|
98
|
-
# @return [NSWindow]
|
99
|
-
def highlight element, opts = {}
|
100
|
-
app = NSApplication.sharedApplication
|
101
|
-
colour = opts[:colour] || opts[:color] || NSColor.magentaColor
|
102
|
-
window = highlight_window_for element.bounds, colour
|
103
|
-
|
104
|
-
if opts.has_key? :timeout
|
105
|
-
Dispatch::Queue.new('window_killer').after opts[:timeout] do
|
106
|
-
window.close
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
window
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
##
|
117
|
-
# Create the window that acts as the highlighted portion of the screen.
|
118
|
-
#
|
119
|
-
# @param [NSRect]
|
120
|
-
# @param [NSColor]
|
121
|
-
# @return [NSWindow]
|
122
|
-
def highlight_window_for bounds, colour
|
123
|
-
bounds.flip!
|
124
|
-
window = NSWindow.alloc.initWithContentRect bounds,
|
125
|
-
styleMask: NSBorderlessWindowMask,
|
126
|
-
backing: NSBackingStoreBuffered,
|
127
|
-
defer: true
|
128
|
-
|
129
|
-
window.setOpaque false
|
130
|
-
window.setAlphaValue 0.20
|
131
|
-
window.setLevel NSStatusWindowLevel
|
132
|
-
window.setBackgroundColor colour
|
133
|
-
window.setIgnoresMouseEvents true
|
134
|
-
window.setFrame bounds, display: false
|
135
|
-
window.makeKeyAndOrderFront NSApp
|
136
|
-
def window.stop
|
137
|
-
close
|
138
|
-
end
|
139
|
-
window
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
##
|
147
|
-
# AXElements extensions to `CGRect`.
|
148
|
-
class CGRect
|
149
|
-
##
|
150
|
-
# Treats the rect as belonging to one co-ordinate system and then
|
151
|
-
# converts it to the other system.
|
152
|
-
#
|
153
|
-
# This is useful because accessibility API's expect to work with
|
154
|
-
# the flipped co-ordinate system (origin in top left), but AppKit
|
155
|
-
# prefers to use the cartesian co-ordinate system (origin in bottom
|
156
|
-
# left).
|
157
|
-
#
|
158
|
-
# @return [CGRect]
|
159
|
-
def flip!
|
160
|
-
screen_height = NSMaxY(NSScreen.mainScreen.frame)
|
161
|
-
origin.y = screen_height - NSMaxY(self)
|
162
|
-
self
|
163
|
-
end
|
164
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'test/integration/helper'
|
2
|
-
|
3
|
-
class TestAccessibilityDebug < MiniTest::Unit::TestCase
|
4
|
-
|
5
|
-
def app
|
6
|
-
@app ||= AX::Application.new PID
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_path_returns_correct_elements_in_correct_order
|
10
|
-
list = Accessibility::Debug.path(app.window.close_button)
|
11
|
-
assert_equal 3, list.size
|
12
|
-
assert_instance_of AX::CloseButton, list.first
|
13
|
-
assert_instance_of AX::StandardWindow, list.second
|
14
|
-
assert_instance_of AX::Application, list.third
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_dump_works_for_nested_tab_groups
|
18
|
-
element = app.window.tab_group
|
19
|
-
output = Accessibility::Debug.subtree_for element
|
20
|
-
|
21
|
-
expected = [
|
22
|
-
['AX::TabGroup', 0],
|
23
|
-
['AX::RadioButton', 1], ['AX::RadioButton', 1], ['AX::TabGroup', 1],
|
24
|
-
['AX::RadioButton', 2], ['AX::RadioButton', 2], ['AX::TabGroup', 2],
|
25
|
-
['AX::RadioButton', 3], ['AX::RadioButton', 3], ['AX::TabGroup', 3],
|
26
|
-
['AX::RadioButton', 4], ['AX::RadioButton', 4],
|
27
|
-
['AX::Group', 4],
|
28
|
-
['AX::TextField', 5], ['AX::StaticText', 6],
|
29
|
-
['AX::TextField' , 5], ['AX::StaticText', 6]
|
30
|
-
]
|
31
|
-
|
32
|
-
refute_empty output
|
33
|
-
output = output.split("\n")
|
34
|
-
|
35
|
-
until output.empty?
|
36
|
-
line = output.shift
|
37
|
-
klass, indents = expected.shift
|
38
|
-
assert_equal indents, line.match(/^\t*/).to_a.first.length, line
|
39
|
-
line.strip!
|
40
|
-
assert_match /^\#<#{klass}/, line
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
require 'test/runner'
|
2
|
-
require 'accessibility/debug'
|
3
|
-
|
4
|
-
class TestAccessibilityDebug < MiniTest::Unit::TestCase
|
5
|
-
|
6
|
-
def mock_element
|
7
|
-
@mock = Object.new
|
8
|
-
def @mock.bounds; CGRectMake(100,100,100,100); end
|
9
|
-
@mock
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_debug_setting
|
13
|
-
assert_respond_to Accessibility::Debug, :on?
|
14
|
-
assert_respond_to Accessibility::Debug, :on=
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_highlight_returns_created_window
|
18
|
-
w = Accessibility::Debug.highlight mock_element
|
19
|
-
assert_kind_of NSWindow, w
|
20
|
-
assert_respond_to w, :stop
|
21
|
-
ensure
|
22
|
-
w.close if w
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_highlight_can_take_a_timeout
|
26
|
-
w = Accessibility::Debug.highlight mock_element, timeout: 0.1
|
27
|
-
assert w.visible?
|
28
|
-
sleep 0.15
|
29
|
-
refute w.visible? # Not exactly the assertion I want, but close enough
|
30
|
-
ensure
|
31
|
-
w.close if w
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_highlight_can_have_custom_colour
|
35
|
-
w = Accessibility::Debug.highlight mock_element, color: NSColor.cyanColor
|
36
|
-
assert w.backgroundColor == NSColor.cyanColor
|
37
|
-
w.close
|
38
|
-
|
39
|
-
# test both spellings of colour
|
40
|
-
w = Accessibility::Debug.highlight mock_element, colour: NSColor.purpleColor
|
41
|
-
assert w.backgroundColor == NSColor.purpleColor
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_highlight_highlights_correct_rect
|
45
|
-
w = Accessibility::Debug.highlight mock_element
|
46
|
-
assert_equal w.frame, mock_element.bounds.flip!
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
class TestCGRectExtensions < MiniTest::Unit::TestCase
|
52
|
-
|
53
|
-
def test_flipping
|
54
|
-
size = NSScreen.mainScreen.frame.size
|
55
|
-
assert_equal CGRectMake( 0, size.height, 0, 0), CGRectZero.dup.flip!
|
56
|
-
assert_equal CGRectMake(100, size.height-200,100,100), CGRectMake(100,100,100,100).flip!
|
57
|
-
end
|
58
|
-
|
59
|
-
def test_flipping_twice_returns_to_original
|
60
|
-
assert_equal CGRectZero.dup, CGRectZero.dup.flip!.flip!
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|