bryan-ash-wx-nobbie 0.0.3.2
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/KNOWN_ISSUES +24 -0
- data/README +153 -0
- data/lib/nobbie/wx/acceptance_test.rb +19 -0
- data/lib/nobbie/wx/command.rb +63 -0
- data/lib/nobbie/wx/command/choose.rb +38 -0
- data/lib/nobbie/wx/command/click_on.rb +35 -0
- data/lib/nobbie/wx/command/get_component.rb +17 -0
- data/lib/nobbie/wx/command/get_options.rb +27 -0
- data/lib/nobbie/wx/command/get_selected_values.rb +25 -0
- data/lib/nobbie/wx/command/is_chosen.rb +21 -0
- data/lib/nobbie/wx/command/is_enabled.rb +18 -0
- data/lib/nobbie/wx/command/select.rb +97 -0
- data/lib/nobbie/wx/command/type_into.rb +73 -0
- data/lib/nobbie/wx/command_executor.rb +25 -0
- data/lib/nobbie/wx/command_factory.rb +52 -0
- data/lib/nobbie/wx/driven.rb +7 -0
- data/lib/nobbie/wx/impl/element/element_path_builder.rb +42 -0
- data/lib/nobbie/wx/impl/operation/choosable.rb +31 -0
- data/lib/nobbie/wx/impl/operation/select.rb +37 -0
- data/lib/nobbie/wx/launcher.rb +75 -0
- data/lib/nobbie/wx/operations.rb +85 -0
- data/lib/nobbie/wx/platform.rb +31 -0
- data/test/all_tests.rb +8 -0
- data/test/suite/app.rb +144 -0
- data/test/suite/nobbie_test_case.rb +46 -0
- data/test/suite/test_choose.rb +52 -0
- data/test/suite/test_click.rb +28 -0
- data/test/suite/test_enabled.rb +13 -0
- data/test/suite/test_launcher.rb +22 -0
- data/test/suite/test_operations.rb +27 -0
- data/test/suite/test_selection.rb +138 -0
- data/test/suite/test_type.rb +58 -0
- metadata +105 -0
data/KNOWN_ISSUES
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= KNOWN ISSUES
|
2
|
+
|
3
|
+
On Windows there seems to be a strange delay between launching the application and it yielding control to the test
|
4
|
+
runner. It seems that the smaller the application, the worse the problem is (i.e. the longer it takes).
|
5
|
+
|
6
|
+
When typing into a TextCtrl, if the value being typed is the same as the current value it seems that on OSX the
|
7
|
+
text changed event is not fired, but on Windows it is. I'm not sure which is correct, but the result should be
|
8
|
+
consistent - so this looks like a WxRuby issue.
|
9
|
+
|
10
|
+
When typing into a readonly ComboBox, on Windows the value changes which is not consistent with the WxRuby
|
11
|
+
documentation for 'set_value'. (might be fixed)
|
12
|
+
|
13
|
+
OSX seems to have some problems with regard to the UI repainting and the timing/order of events. Further
|
14
|
+
investigation is required but there are two problems highlighted in the tests. (1) when a value is set for a TextCtrl
|
15
|
+
during initialisation it is not painted to the screen untill you focus on it. (2) when typing into a ComboBox the
|
16
|
+
events value seems to be behind what is actually typed, e.g. if you type 'ab', two events are raised, but the values
|
17
|
+
are '' and 'a'.
|
18
|
+
|
19
|
+
Element highlighting seems to be a bit too quick in Windows.
|
20
|
+
|
21
|
+
Currently there is no way to interact with the UI if an operation results in a blocking dialog. For example,
|
22
|
+
if 'File -> New' results in a FileDialog being displayed, test execution stops until the blocking dialog
|
23
|
+
has been closed manually. This is annoying and I welcome suggestions of how to overcome this problem, in a platform
|
24
|
+
neutral way.
|
data/README
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
= Wx-Nobbie -- An implementation of the Nobbie GUI Driving API for WxRuby2
|
2
|
+
|
3
|
+
Nobbie is a simple generic api for driving GUI's (both web and rich-client interfaces). This
|
4
|
+
particular implementation is for driving WxRuby2 applications. There are other implementations available for
|
5
|
+
driving Java Swing and Web Applications - but they all use the same generic API.
|
6
|
+
|
7
|
+
Nobbie attempts to make the driving of UI's as simple as possible. Whereas most UI drivers tend to give you multiple
|
8
|
+
ways to find components and subsequently multiple ways to interact with them, Nobbie's philosophy is:
|
9
|
+
|
10
|
+
* finding components by name is the simplest, least brittle approach.
|
11
|
+
* *most* UI interactions boil down to: *typing*, *choosing* and *clicking*
|
12
|
+
|
13
|
+
(Note: if you can't sleep without the ability to find things using xpath or whatever, there are hooks to specify your
|
14
|
+
own component finding strategies).
|
15
|
+
|
16
|
+
With this in mind, Nobbie provides a simple method of {finding components}[link:classes/Nobbie/Wx/ElementPathBuilder.html]
|
17
|
+
and a simple set of {Operations}[link:classes/Nobbie/Wx/Operations.html] to perform on them.
|
18
|
+
|
19
|
+
A quick overview with examples:
|
20
|
+
|
21
|
+
* Paths ...
|
22
|
+
|
23
|
+
In order to perform operations[link:classes/Nobbie/Wx/Operations.html] on a component, you need to first find it.
|
24
|
+
Only very simple 'named' paths are supported by default (but you can implement your own by providing an object that
|
25
|
+
responds to 'find_component'). When specifying a path, if you do not provide an object that responds to
|
26
|
+
'find_component', Nobbie will attempt to coerce the path into the default path type: Nobbie::Wx::ElementPathBuilder.
|
27
|
+
|
28
|
+
That may sound complicated but its really just sugar. The following paths will all find the component
|
29
|
+
named 'text_ctrl':
|
30
|
+
|
31
|
+
'text_ctrl'
|
32
|
+
:in => 'text_ctrl'
|
33
|
+
in_('text_ctrl')
|
34
|
+
Nobbie::Wx::ElementPathBuilder.new('text_ctrl')
|
35
|
+
:text_ctrl
|
36
|
+
|
37
|
+
|
38
|
+
* Typing[link:classes/Nobbie/Wx/Operations.html#type] ...
|
39
|
+
|
40
|
+
type(value, path)
|
41
|
+
|
42
|
+
e.g.
|
43
|
+
|
44
|
+
type('fred', in_('first_name'))
|
45
|
+
|
46
|
+
...or if you're one of those DSL/anglification people
|
47
|
+
|
48
|
+
type 'fred', :in => 'first_name'
|
49
|
+
type 'fred', 'first_name'
|
50
|
+
type 'fred', :first_name
|
51
|
+
|
52
|
+
This will find the component named 'first_name' and type 'fred' into it.
|
53
|
+
...this works for anything you can type into, see:
|
54
|
+
{supported components}[link:classes/Nobbie/Wx/Operations.html#type].
|
55
|
+
|
56
|
+
|
57
|
+
* Selecting[link:classes/Nobbie/Wx/Operations.html#selection] ...
|
58
|
+
|
59
|
+
selection(path).choose(value)
|
60
|
+
|
61
|
+
e.g.
|
62
|
+
|
63
|
+
selection(in_('title')).choose('Mr')
|
64
|
+
|
65
|
+
...or
|
66
|
+
|
67
|
+
selection(:in => 'title').choose 'Mr'
|
68
|
+
selection('title').choose 'Mr'
|
69
|
+
selection(:title).choose 'Mr'
|
70
|
+
|
71
|
+
This will find the component named 'title' and select 'Mr'.
|
72
|
+
...this works for anything where you can make a selection from a number of options
|
73
|
+
(which is quite a lot of things), see:
|
74
|
+
{supported components}[link:classes/Nobbie/Wx/SelectOperations.html#selection].
|
75
|
+
|
76
|
+
You can get the value of the current selection using:
|
77
|
+
|
78
|
+
selection(path).selected_value
|
79
|
+
|
80
|
+
|
81
|
+
* Clicking[link:classes/Nobbie/Wx/Operations.html#click] ...
|
82
|
+
|
83
|
+
click(path) (for buttons you can also use the label instead of a path)
|
84
|
+
|
85
|
+
e.g.
|
86
|
+
|
87
|
+
click('save')
|
88
|
+
|
89
|
+
...or
|
90
|
+
|
91
|
+
click 'save'
|
92
|
+
click :save
|
93
|
+
etc
|
94
|
+
|
95
|
+
This will find the component named (or labelled) 'save' and click it.
|
96
|
+
...this works for anything you can click, see:
|
97
|
+
{supported components}[link:classes/Nobbie/Wx/Operations.html#click].
|
98
|
+
|
99
|
+
|
100
|
+
* Choosing[link:classes/Nobbie/Wx/Operations.html#choosable] ...
|
101
|
+
|
102
|
+
choosable(path).choose
|
103
|
+
|
104
|
+
e.g.
|
105
|
+
|
106
|
+
choosable(in_('female')).choose
|
107
|
+
|
108
|
+
...or
|
109
|
+
|
110
|
+
choosable(:in => 'female').choose
|
111
|
+
choosable('female').choose
|
112
|
+
choosable(:female).choose
|
113
|
+
|
114
|
+
This will find the component named 'female' and choose it
|
115
|
+
...this works for anything that has a chosen/non-chosen state, see:
|
116
|
+
{supported components}[link:classes/Nobbie/Wx/ChoosableOperations.html#choose].
|
117
|
+
|
118
|
+
You can determine if a component is currently chosen using:
|
119
|
+
|
120
|
+
choosable(path).chosen?
|
121
|
+
|
122
|
+
Hopefully that makes some degree of sense, there are a few other operations available, that should be self
|
123
|
+
explanatory from the documentation[link:classes/Nobbie/Wx/Operations.html].
|
124
|
+
|
125
|
+
* Writing that first test ...
|
126
|
+
|
127
|
+
By default Nobbie hooks into test/unit. However, due to a Wx limitation its not possible to launch a new
|
128
|
+
instance of your application for every test run (this would also be very slow anyway). So, instead Nobbie
|
129
|
+
will launch your application, run all the tests in your suite and then close down the application. (Note: If
|
130
|
+
anyone has a better solution to this ... please let me know).
|
131
|
+
|
132
|
+
In order for this to happen, you must advise Nobbie which application you wish to test by setting the constant
|
133
|
+
APPLICATION_UNDER_TEST to an instance of your application, i.e.:
|
134
|
+
|
135
|
+
require 'rubygems'
|
136
|
+
require_gem 'nobbie-wx-preview'
|
137
|
+
|
138
|
+
require 'your_application'
|
139
|
+
require 'your_test_suite'
|
140
|
+
|
141
|
+
APPLICATION_UNDER_TEST = YourApp.new
|
142
|
+
|
143
|
+
Finally, to prevent your application being loaded when its file is 'required', edit your application startup
|
144
|
+
as follows:
|
145
|
+
|
146
|
+
YourApp.new.main_loop if __FILE__ == $0
|
147
|
+
|
148
|
+
* More examples ...
|
149
|
+
|
150
|
+
Nobbie has a suite of tests which are installed as part of the gem. Take a look at the 'test' directory.
|
151
|
+
|
152
|
+
|
153
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'nobbie/wx/operations'
|
3
|
+
|
4
|
+
def require_all_in_directory(dir)
|
5
|
+
Dir.glob("#{dir}/**/*.rb") {|f| require "#{f}" }
|
6
|
+
end
|
7
|
+
|
8
|
+
class Test::Unit::TestCase
|
9
|
+
include Nobbie::Wx::Operations
|
10
|
+
end
|
11
|
+
|
12
|
+
Test::Unit.run = false
|
13
|
+
|
14
|
+
#todo: how the hell does this work(/not work as expected) on OSX?
|
15
|
+
at_exit do
|
16
|
+
unless $! || Test::Unit.run?
|
17
|
+
exit = Nobbie::Wx::ApplicationLauncher.new.with_application { Thread.pass }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class ComponentAwareCommand #:nodoc:
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def component
|
11
|
+
@component ||= @path.find_component
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_unsupported_operation_for_component
|
15
|
+
Kernel.raise(UnsupportedOperationForComponentException,
|
16
|
+
"cannot: #{describe} because component #{component.class} does not support it")
|
17
|
+
end
|
18
|
+
|
19
|
+
def handle_value_not_found
|
20
|
+
Kernel.raise(ValueNotFoundException,
|
21
|
+
"cannot: #{describe} because value #{@value} not found")
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_enabled(id = nil)
|
25
|
+
#is_enabled takes an id for menu's
|
26
|
+
enabled = id.nil? ? component.is_enabled : component.is_enabled(id)
|
27
|
+
|
28
|
+
Kernel.raise(ComponentDisabledException,
|
29
|
+
"cannot: #{describe} because component is disabled") unless enabled
|
30
|
+
end
|
31
|
+
|
32
|
+
def highlight(component = component)
|
33
|
+
Kernel.raise "highlight requires a block" unless block_given?
|
34
|
+
|
35
|
+
begin
|
36
|
+
unless [Menu, Panel].include?(component.class)
|
37
|
+
#puts "highlight on: #{component.class} - #{component.name}"
|
38
|
+
original_colour = component.background_colour
|
39
|
+
component.background_colour = Colour.from_hex('#FFFF00')
|
40
|
+
|
41
|
+
#todo: these were previously disabled
|
42
|
+
# component.refresh
|
43
|
+
component.update
|
44
|
+
end
|
45
|
+
result = yield component
|
46
|
+
unless component.is_a?(Menu)
|
47
|
+
component.update
|
48
|
+
end
|
49
|
+
return result
|
50
|
+
ensure
|
51
|
+
unless [Menu, Panel].include?(component.class)
|
52
|
+
#puts "highlight off: #{component.class} - #{component.name}"
|
53
|
+
component.background_colour = original_colour
|
54
|
+
component.refresh
|
55
|
+
#component.update
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class ChooseCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
highlight {
|
8
|
+
ensure_enabled
|
9
|
+
|
10
|
+
if component.is_a?(RadioButton) || component.is_a?(CheckBox)
|
11
|
+
handle_radio_button_or_check_box
|
12
|
+
else
|
13
|
+
handle_unsupported_operation_for_component
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def describe
|
19
|
+
"Choose #{@path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def handle_radio_button_or_check_box
|
25
|
+
event_type = (component.is_a?(RadioButton) ? EVT_COMMAND_RADIOBUTTON_SELECTED : EVT_COMMAND_CHECKBOX_CLICKED)
|
26
|
+
|
27
|
+
event = CommandEvent.new(event_type, component.get_id)
|
28
|
+
event.int = 1 #no idea why this works .. but it is needed
|
29
|
+
event.event_object = component
|
30
|
+
|
31
|
+
#todo: should this use process_event
|
32
|
+
component.command(event)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class ClickOnCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
highlight {
|
8
|
+
ensure_enabled
|
9
|
+
|
10
|
+
if component.is_a?(Button)
|
11
|
+
handle_button
|
12
|
+
else
|
13
|
+
handle_unsupported_operation_for_component
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def describe
|
19
|
+
"Click on #{@path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def handle_button
|
25
|
+
event = CommandEvent.new(EVT_COMMAND_BUTTON_CLICKED, component.get_id)
|
26
|
+
event.event_object = component
|
27
|
+
|
28
|
+
#todo: should this use process_event
|
29
|
+
component.command(event)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class GetOptionsCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
if component.is_a?(Notebook)
|
8
|
+
result = []
|
9
|
+
component.page_count.times{|i| result << component.page_text(i)}
|
10
|
+
result
|
11
|
+
elsif component.is_a?(ComboBox) || component.is_a?(ListBox) || component.is_a?(Choice)
|
12
|
+
result = []
|
13
|
+
component.count.times{|i| result << component.string(i)}
|
14
|
+
result
|
15
|
+
else
|
16
|
+
handle_unsupported_operation_for_component
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def describe
|
21
|
+
"Get options #{@path}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class GetSelectedValuesCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
if component.is_a?(Notebook)
|
8
|
+
component.page(component.get_selection).name
|
9
|
+
elsif component.is_a?(ComboBox)
|
10
|
+
component.value
|
11
|
+
elsif component.is_a?(ListBox) || component.is_a?(Choice)
|
12
|
+
component.string_selection
|
13
|
+
else
|
14
|
+
handle_unsupported_operation_for_component
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def describe
|
19
|
+
"Get selected values #{@path}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class IsChosenCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
if component.is_a?(RadioButton) || component.is_a?(CheckBox)
|
8
|
+
return component.value
|
9
|
+
else
|
10
|
+
handle_unsupported_operation_for_component
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def describe
|
15
|
+
"Is chosen #{@path}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class IsEnabledCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def execute
|
7
|
+
#todo: are there any components that do not respond to is_enabled?
|
8
|
+
component.is_enabled
|
9
|
+
end
|
10
|
+
|
11
|
+
def describe
|
12
|
+
"Is enabled #{@path}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|