wx-nobbie 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,17 @@
1
+ module Nobbie
2
+ module Wx
3
+ module Command
4
+
5
+ class GetComponentCommand < ComponentAwareCommand #:nodoc:
6
+ def execute
7
+ component
8
+ end
9
+
10
+ def describe
11
+ "Get component #{@path}"
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ 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