win_gui 0.1.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +43 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/book_code/early_success/bundle.rb +34 -0
- data/book_code/early_success/english.txt +1 -0
- data/book_code/early_success/jruby_basics.rb +47 -0
- data/book_code/early_success/windows_basics.rb +97 -0
- data/book_code/guessing/locknote.rb +379 -0
- data/book_code/guessing/monkeyshines.rb +14 -0
- data/book_code/guessing/note.rb +120 -0
- data/book_code/guessing/note_spec.rb +175 -0
- data/book_code/guessing/replay.rb +21 -0
- data/book_code/guessing/seed.rb +9 -0
- data/book_code/guessing/spec_helper.rb +69 -0
- data/book_code/guessing/windows_gui.rb +247 -0
- data/book_code/home_stretch/junquenote.rb +151 -0
- data/book_code/home_stretch/locknote.rb +180 -0
- data/book_code/home_stretch/note.rb +144 -0
- data/book_code/home_stretch/note_spec.rb +191 -0
- data/book_code/home_stretch/spec_helper.rb +55 -0
- data/book_code/home_stretch/swing_gui.rb +50 -0
- data/book_code/home_stretch/windows_gui.rb +232 -0
- data/book_code/junquenote/exports.sh +10 -0
- data/book_code/junquenote/jruby_mac.sh +10 -0
- data/book_code/junquenote/junquenote_app.rb +262 -0
- data/book_code/novite/Rakefile +10 -0
- data/book_code/novite/app/controllers/application.rb +18 -0
- data/book_code/novite/app/controllers/guests_controller.rb +28 -0
- data/book_code/novite/app/controllers/parties_controller.rb +77 -0
- data/book_code/novite/app/helpers/application_helper.rb +11 -0
- data/book_code/novite/app/helpers/guests_helper.rb +10 -0
- data/book_code/novite/app/helpers/parties_helper.rb +10 -0
- data/book_code/novite/app/models/guest.rb +11 -0
- data/book_code/novite/app/models/party.rb +32 -0
- data/book_code/novite/app/models/party_mailer.rb +19 -0
- data/book_code/novite/app/views/layouts/application.rhtml +44 -0
- data/book_code/novite/app/views/parties/new.html.erb +42 -0
- data/book_code/novite/app/views/parties/show.html.erb +43 -0
- data/book_code/novite/app/views/party_mailer/invite.erb +17 -0
- data/book_code/novite/config/boot.rb +117 -0
- data/book_code/novite/config/database.yml +19 -0
- data/book_code/novite/config/environment.rb +67 -0
- data/book_code/novite/config/environments/development.rb +29 -0
- data/book_code/novite/config/environments/production.rb +27 -0
- data/book_code/novite/config/environments/test.rb +30 -0
- data/book_code/novite/config/initializers/inflections.rb +18 -0
- data/book_code/novite/config/initializers/mime_types.rb +13 -0
- data/book_code/novite/config/routes.rb +47 -0
- data/book_code/novite/db/migrate/001_create_parties.rb +26 -0
- data/book_code/novite/db/migrate/002_create_guests.rb +23 -0
- data/book_code/novite/db/schema.rb +41 -0
- data/book_code/novite/log/empty.txt +0 -0
- data/book_code/novite/public/.htaccess +40 -0
- data/book_code/novite/public/404.html +38 -0
- data/book_code/novite/public/422.html +38 -0
- data/book_code/novite/public/500.html +38 -0
- data/book_code/novite/public/dispatch.cgi +10 -0
- data/book_code/novite/public/dispatch.fcgi +24 -0
- data/book_code/novite/public/dispatch.rb +18 -0
- data/book_code/novite/public/favicon.ico +0 -0
- data/book_code/novite/public/images/rails.png +0 -0
- data/book_code/novite/public/index.html +285 -0
- data/book_code/novite/public/javascripts/application.js +10 -0
- data/book_code/novite/public/javascripts/controls.js +971 -0
- data/book_code/novite/public/javascripts/dragdrop.js +980 -0
- data/book_code/novite/public/javascripts/effects.js +1128 -0
- data/book_code/novite/public/javascripts/prototype.js +4233 -0
- data/book_code/novite/public/robots.txt +5 -0
- data/book_code/novite/script/about +3 -0
- data/book_code/novite/script/console +3 -0
- data/book_code/novite/script/destroy +3 -0
- data/book_code/novite/script/generate +3 -0
- data/book_code/novite/script/performance/benchmarker +3 -0
- data/book_code/novite/script/performance/profiler +3 -0
- data/book_code/novite/script/performance/request +3 -0
- data/book_code/novite/script/plugin +3 -0
- data/book_code/novite/script/process/inspector +3 -0
- data/book_code/novite/script/process/reaper +3 -0
- data/book_code/novite/script/process/spawner +3 -0
- data/book_code/novite/script/runner +3 -0
- data/book_code/novite/script/server +3 -0
- data/book_code/novite/test/test_helper.rb +46 -0
- data/book_code/one_more_thing/applescript.rb +68 -0
- data/book_code/one_more_thing/note_spec.rb +50 -0
- data/book_code/one_more_thing/spec_helper.rb +17 -0
- data/book_code/one_more_thing/textedit-pure.rb +28 -0
- data/book_code/one_more_thing/textedit.applescript +26 -0
- data/book_code/one_more_thing/textedit.rb +32 -0
- data/book_code/one_more_thing/textnote.rb +87 -0
- data/book_code/simplify/junquenote.rb +48 -0
- data/book_code/simplify/locknote.rb +46 -0
- data/book_code/simplify/note.rb +35 -0
- data/book_code/simplify/note_spec.rb +28 -0
- data/book_code/simplify/swing_gui.rb +45 -0
- data/book_code/simplify/windows_gui.rb +232 -0
- data/book_code/simplify/windows_gui_spec.rb +35 -0
- data/book_code/story/invite.story +19 -0
- data/book_code/story/journal.txt +29 -0
- data/book_code/story/novite_stories.rb +156 -0
- data/book_code/story/party.rb +149 -0
- data/book_code/story/password.rb +61 -0
- data/book_code/story/password.story +26 -0
- data/book_code/story/rsvp.story +29 -0
- data/book_code/tables/TestTime.html +93 -0
- data/book_code/tables/TestTimeSample.html +63 -0
- data/book_code/tables/calculate_time.rb +39 -0
- data/book_code/tables/calculator.rb +108 -0
- data/book_code/tables/calculator_actions.rb +27 -0
- data/book_code/tables/calculator_spec.rb +47 -0
- data/book_code/tables/fit.rb +32 -0
- data/book_code/tables/matrix.rb +109 -0
- data/book_code/tables/pseudocode.rb +17 -0
- data/book_code/tubes/book_selenium.rb +67 -0
- data/book_code/tubes/book_watir.rb +60 -0
- data/book_code/tubes/dragdrop.html +81 -0
- data/book_code/tubes/html_capture.rb +33 -0
- data/book_code/tubes/joke_list.rb +67 -0
- data/book_code/tubes/list_spec.rb +41 -0
- data/book_code/tubes/search_spec.rb +32 -0
- data/book_code/tubes/selenium_example.rb +66 -0
- data/book_code/tubes/selenium_link.rb +23 -0
- data/book_code/tubes/web_server.rb +14 -0
- data/book_code/windows/wgui.rb +29 -0
- data/book_code/windows/wobj.rb +25 -0
- data/book_code/windows/wsh.rb +25 -0
- data/book_code/with_rspec/empty_spec.rb +13 -0
- data/book_code/with_rspec/junquenote.rb +60 -0
- data/book_code/with_rspec/locknote.rb +129 -0
- data/book_code/with_rspec/note_spec.rb +32 -0
- data/book_code/with_rspec/should_examples.rb +18 -0
- data/exp/exp.rb +6 -0
- data/exp/exp_encodings.rb +40 -0
- data/exp/exp_enum_windows.rb +60 -0
- data/exp/exp_quik.rb +38 -0
- data/exp/exp_wsh.rb +115 -0
- data/exp/old/windows_basics.rb +80 -0
- data/exp/old/wnote.rb +80 -0
- data/exp/old/wnote_spec.rb +20 -0
- data/features/step_definitions/win_gui_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/features/win_gui.feature +9 -0
- data/lib/note/java/jemmy.jar +0 -0
- data/lib/note/java/jnote.rb +48 -0
- data/lib/note/java/jruby_basics.rb +37 -0
- data/lib/note/java/junquenote_app.rb +262 -0
- data/lib/note/java/note_spec.rb +20 -0
- data/lib/note/win/locknote.rb +19 -0
- data/lib/note.rb +15 -0
- data/lib/win_gui/constants.rb +66 -0
- data/lib/win_gui/string_extensions.rb +24 -0
- data/lib/win_gui/win_gui.rb +274 -0
- data/lib/win_gui/window.rb +70 -0
- data/lib/win_gui.rb +3 -0
- data/spec/note/win/locknote_spec.rb +7 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/test_apps/locknote/LockNote.exe +0 -0
- data/spec/win_gui/string_extensions_spec.rb +61 -0
- data/spec/win_gui/win_gui_spec.rb +733 -0
- data/spec/win_gui/window_spec.rb +124 -0
- metadata +251 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
class String
|
|
11
|
+
def snake_case
|
|
12
|
+
gsub(/([a-z])([A-Z0-9])/, '\1_\2').downcase #(1)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
require 'Win32API'
|
|
20
|
+
|
|
21
|
+
module WindowsGui
|
|
22
|
+
def self.def_api(function, parameters, return_value) #(2)
|
|
23
|
+
api = Win32API.new 'user32', function, parameters, return_value
|
|
24
|
+
|
|
25
|
+
define_method(function.snake_case) do |*args| #(3)
|
|
26
|
+
api.call *args #(4)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
module WindowsGui
|
|
35
|
+
def_api 'FindWindow', ['P', 'P'], 'L'
|
|
36
|
+
def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
|
|
37
|
+
|
|
38
|
+
# rest of API definitions here...
|
|
39
|
+
|
|
40
|
+
WM_GETTEXT = 0x000D
|
|
41
|
+
WM_SYSCOMMAND = 0x0112
|
|
42
|
+
|
|
43
|
+
# rest of constant definitions here...
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
module WindowsGui
|
|
49
|
+
def_api 'FindWindow', ['P', 'P'], 'L'
|
|
50
|
+
def_api 'FindWindowEx', ['L', 'L', 'P', 'P'], 'L'
|
|
51
|
+
def_api 'PostMessage', ['L', 'L', 'L', 'L'], 'L'
|
|
52
|
+
def_api 'SendMessage', ['L', 'L', 'L', 'P'], 'L'
|
|
53
|
+
def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
|
|
54
|
+
def_api 'GetDlgItem', ['L', 'L'], 'L'
|
|
55
|
+
def_api 'GetWindowRect', ['L', 'P'], 'I'
|
|
56
|
+
def_api 'SetCursorPos', ['L', 'L'], 'I'
|
|
57
|
+
def_api 'mouse_event', ['L', 'L', 'L', 'L', 'L'], 'V'
|
|
58
|
+
def_api 'IsWindowVisible', ['L'], 'L'
|
|
59
|
+
|
|
60
|
+
SC_CLOSE = 0xF060
|
|
61
|
+
|
|
62
|
+
IDNO = 7
|
|
63
|
+
|
|
64
|
+
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
65
|
+
MOUSEEVENTF_LEFTUP = 0x0004
|
|
66
|
+
|
|
67
|
+
KEYEVENTF_KEYDOWN = 0
|
|
68
|
+
KEYEVENTF_KEYUP = 2
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
module WindowsGui
|
|
74
|
+
VK_SHIFT = 0x10
|
|
75
|
+
VK_CONTROL = 0x11
|
|
76
|
+
VK_BACK = 0x08
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class String
|
|
83
|
+
def to_keys
|
|
84
|
+
unless size == 1 #(5)
|
|
85
|
+
raise "conversion is for single characters only"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
ascii = unpack('C')[0] #(6)
|
|
89
|
+
|
|
90
|
+
case self #(7)
|
|
91
|
+
when '0'..'9'
|
|
92
|
+
[ascii - ?0 + 0x30]
|
|
93
|
+
when 'A'..'Z'
|
|
94
|
+
[WindowsGui.const_get(:VK_SHIFT), ascii]
|
|
95
|
+
when 'a'..'z'
|
|
96
|
+
[ascii - ?a + ?A]
|
|
97
|
+
when ' '
|
|
98
|
+
[ascii]
|
|
99
|
+
else
|
|
100
|
+
raise "Can't convert unknown character #{self}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
module WindowsGui
|
|
109
|
+
def keystroke(*keys)
|
|
110
|
+
return if keys.empty?
|
|
111
|
+
|
|
112
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
|
|
113
|
+
sleep 0.05
|
|
114
|
+
keystroke *keys[1..-1]
|
|
115
|
+
sleep 0.05
|
|
116
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
module WindowsGui
|
|
124
|
+
def type_in(message)
|
|
125
|
+
message.scan(/./m) do |char|
|
|
126
|
+
keystroke(*char.to_keys)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
module WindowsGui
|
|
135
|
+
class Window
|
|
136
|
+
include WindowsGui #(8)
|
|
137
|
+
|
|
138
|
+
attr_reader :handle
|
|
139
|
+
|
|
140
|
+
def initialize(handle)
|
|
141
|
+
@handle = handle
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def close
|
|
145
|
+
post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def wait_for_close #(9)
|
|
149
|
+
timeout(5) do
|
|
150
|
+
sleep 0.2 until 0 == is_window_visible(@handle)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def text
|
|
155
|
+
buffer = '\0' * 2048
|
|
156
|
+
length = send_message @handle, WM_GETTEXT, buffer.length, buffer
|
|
157
|
+
length == 0 ? '' : buffer[0..length - 1]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class WindowsGui::Window
|
|
166
|
+
extend WindowsGui #(10)
|
|
167
|
+
|
|
168
|
+
def self.top_level(title, seconds=3)
|
|
169
|
+
@handle = timeout(seconds) do
|
|
170
|
+
sleep 0.2 while (h = find_window nil, title) <= 0; h
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
Window.new @handle
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class WindowsGui::Window
|
|
181
|
+
def child(id)
|
|
182
|
+
result = case id
|
|
183
|
+
when String
|
|
184
|
+
by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&') #(11)
|
|
185
|
+
by_class = find_window_ex @handle, 0, id, nil
|
|
186
|
+
by_title > 0 ? by_title : by_class
|
|
187
|
+
when Fixnum
|
|
188
|
+
get_dlg_item @handle, id
|
|
189
|
+
else
|
|
190
|
+
0
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
raise "Control '#{id}' not found" if result == 0
|
|
194
|
+
Window.new result
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class WindowsGui::Window
|
|
202
|
+
def click(id)
|
|
203
|
+
h = child(id).handle
|
|
204
|
+
|
|
205
|
+
rectangle = [0, 0, 0, 0].pack 'LLLL'
|
|
206
|
+
get_window_rect h, rectangle
|
|
207
|
+
left, top, right, bottom = rectangle.unpack 'LLLL'
|
|
208
|
+
|
|
209
|
+
center = [(left + right) / 2, (top + bottom) / 2]
|
|
210
|
+
set_cursor_pos *center
|
|
211
|
+
|
|
212
|
+
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
|
|
213
|
+
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
module WindowsGui
|
|
221
|
+
def dialog(title, seconds=3)
|
|
222
|
+
d = begin
|
|
223
|
+
w = Window.top_level(title, seconds)
|
|
224
|
+
yield(w) ? w : nil #(12)
|
|
225
|
+
rescue TimeoutError
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
d.wait_for_close if d
|
|
229
|
+
return d
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
require 'windows_gui'
|
|
11
|
+
|
|
12
|
+
describe WindowsGui do #(1)
|
|
13
|
+
include WindowsGui
|
|
14
|
+
|
|
15
|
+
it 'wraps a Windows call with a method' do
|
|
16
|
+
find_window(nil, nil).should_not == 0 #(2)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'enforces the argument count' do
|
|
20
|
+
lambda {find_window}.should raise_error #(3)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
describe String, '#snake_case' do
|
|
28
|
+
it 'transforms CamelCase strings' do
|
|
29
|
+
'GetCharWidth32'.snake_case.should == 'get_char_width_32'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'leaves snake_case strings intact' do
|
|
33
|
+
'keybd_event'.snake_case.should == 'keybd_event'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Story: minimalist invites
|
|
2
|
+
As a host with lots to do
|
|
3
|
+
I want to plan a party with a minimum of mouse clicks
|
|
4
|
+
So that I can get on with the rest of my tasks
|
|
5
|
+
|
|
6
|
+
Scenario: manual invites
|
|
7
|
+
Given a party called "Celebration"
|
|
8
|
+
And a description of "There's a party goin' on"
|
|
9
|
+
And a location of "Right here"
|
|
10
|
+
And a starting time of September 29, 2010 at 12:30 PM
|
|
11
|
+
And an ending time of September 29, 2010 at 12:35 PM
|
|
12
|
+
|
|
13
|
+
When I view the invitation
|
|
14
|
+
Then I should see the Web address to send to my friends
|
|
15
|
+
And the name should be "Celebration"
|
|
16
|
+
And the description should be "There's a party goin' on"
|
|
17
|
+
And the location should be "Right here"
|
|
18
|
+
And the party should begin on September 29, 2010 at 12:30 PM
|
|
19
|
+
And the party should end on September 29, 2010 at 12:35 PM
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Make sure you install the Selenium gem, with a capital S.
|
|
2
|
+
|
|
3
|
+
First story.
|
|
4
|
+
|
|
5
|
+
Add browser code.
|
|
6
|
+
|
|
7
|
+
./script/generate rspec_scaffold Party name:string description:text location:text begins_at:datetime ends_at:datetime
|
|
8
|
+
|
|
9
|
+
rake db:migrate
|
|
10
|
+
|
|
11
|
+
./script/generate migration AddLinkToParty link:string
|
|
12
|
+
|
|
13
|
+
Edit view.
|
|
14
|
+
|
|
15
|
+
RESTful with to_param
|
|
16
|
+
|
|
17
|
+
Make sure date/time updates!!!!
|
|
18
|
+
|
|
19
|
+
Scenario: email invites
|
|
20
|
+
Given a blank invitation
|
|
21
|
+
When I plan a party called "Celebration"
|
|
22
|
+
And I enter a description of "Good times, come on"
|
|
23
|
+
And I enter a location of "Right here in Jersey City, NJ"
|
|
24
|
+
And I enter a starting time of "Sep. 29, 1980, 8 PM"
|
|
25
|
+
And I enter an ending time of "Sep. 29, 1980, 10 PM"
|
|
26
|
+
And I enter a recipient address of "example@example.com"
|
|
27
|
+
Then I should see an email preview
|
|
28
|
+
When I confirm the email
|
|
29
|
+
Then I should see a confirmation
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'spec/story'
|
|
12
|
+
require 'chronic'
|
|
13
|
+
require 'party'
|
|
14
|
+
|
|
15
|
+
class Listener
|
|
16
|
+
attr_reader :browser
|
|
17
|
+
|
|
18
|
+
def run_started(num_scenarios)
|
|
19
|
+
@browser = Selenium::SeleniumDriver.new \
|
|
20
|
+
'localhost', 4444, '*firefox', 'http://localhost:3000', 10000
|
|
21
|
+
@browser.start
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run_ended
|
|
25
|
+
@browser.stop
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def method_missing(name, *args, &block)
|
|
29
|
+
# We don't care about the rest of the Story Runner events.
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
listener = Listener.new
|
|
34
|
+
Spec::Story::Runner.register_listener(listener)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
steps_for :planning do
|
|
40
|
+
Given 'a party called "$name"' do |name|
|
|
41
|
+
@party = Party.new(listener.browser)
|
|
42
|
+
@party.name = name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Given 'a description of "$desc"' do |desc|
|
|
46
|
+
@party.description = desc
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Given 'a location of "$loc"' do |loc|
|
|
50
|
+
@party.location = loc
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Given /an? $event time of $sometime/ do |event, sometime| #(1)
|
|
54
|
+
clean = sometime.gsub ',', ' '
|
|
55
|
+
date_time = Chronic.parse clean, :now => Time.now - 86400 #(2)
|
|
56
|
+
|
|
57
|
+
if event == 'starting'
|
|
58
|
+
@party.begins_at = date_time
|
|
59
|
+
else
|
|
60
|
+
@party.ends_at = date_time
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
When 'I view the invitation' do
|
|
65
|
+
@party.save_and_view
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
steps_for :reviewing do
|
|
73
|
+
Then 'the $setting should be "$value"' do |setting, value|
|
|
74
|
+
@party.send(setting).should == value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Then 'the party should $event on $date_time' do |event, date_time|
|
|
78
|
+
actual_time =
|
|
79
|
+
(event == 'begin') ?
|
|
80
|
+
@party.begins_at :
|
|
81
|
+
@party.ends_at
|
|
82
|
+
|
|
83
|
+
clean = date_time.gsub ',', ' '
|
|
84
|
+
expected_time = Chronic.parse clean, :now => Time.now - 86400
|
|
85
|
+
|
|
86
|
+
actual_time.should == expected_time
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
Then 'I should see the Web address to send to my friends' do
|
|
90
|
+
@party.link.should match(%r{^http://})
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
steps_for :rsvp do
|
|
98
|
+
Then 'I should see the party details' do
|
|
99
|
+
@party.should have_name
|
|
100
|
+
@party.should have_description
|
|
101
|
+
@party.should have_location
|
|
102
|
+
@party.should have_times
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
When /I answer that "$guest" will( not)? attend/ do |guest, answer|
|
|
106
|
+
attending = !answer.include?('not')
|
|
107
|
+
@party.rsvp guest, attending
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Then 'I should see "$guest" in the list of $type' do |guest, type|
|
|
111
|
+
want_attending = (type == 'partygoers')
|
|
112
|
+
@party.responses(want_attending).should include(guest)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
steps_for :email do
|
|
120
|
+
Given 'a guest list of "$list"' do |list|
|
|
121
|
+
@party.recipients = list
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Then 'I should see that e-mail was sent to "$list"' do |list|
|
|
125
|
+
@party.notice.include?(list).should be_true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
When 'I view the e-mail that was sent to "$address"' do |address|
|
|
129
|
+
@email = @party.email_to address
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
Then 'I should see "Yes/No" links' do
|
|
133
|
+
@email.should match(%r{Yes - http://})
|
|
134
|
+
@email.should match(%r{No - http://})
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
When 'I follow the "$answer" link' do |answer|
|
|
138
|
+
link = %r{#{answer} - (http://.+)}.match(@email)[1]
|
|
139
|
+
@party.rsvp_at link
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
with_steps_for :planning, :reviewing do
|
|
147
|
+
run 'invite.story'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
with_steps_for :planning, :reviewing, :rsvp, :email do
|
|
154
|
+
run 'rsvp.story'
|
|
155
|
+
end
|
|
156
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'selenium'
|
|
12
|
+
require 'time'
|
|
13
|
+
|
|
14
|
+
class Party
|
|
15
|
+
def initialize(browser)
|
|
16
|
+
@browser = browser
|
|
17
|
+
@browser.open '/parties/new'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.def_setting(setting, type = :read_write)
|
|
21
|
+
if type == :readable || type == :read_write
|
|
22
|
+
define_method(setting) do
|
|
23
|
+
@browser.get_text("id=party_#{setting}")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
define_method("has_#{setting}?") do
|
|
27
|
+
send(setting) rescue nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if type == :writable || type == :read_write
|
|
32
|
+
define_method("#{setting}=") do |value|
|
|
33
|
+
@browser.type "id=party_#{setting}", value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def_setting :name
|
|
39
|
+
def_setting :description
|
|
40
|
+
def_setting :location
|
|
41
|
+
def_setting :link, :readable
|
|
42
|
+
def_setting :notice, :readable
|
|
43
|
+
def_setting :recipients, :writable
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Party
|
|
50
|
+
def begins_at=(time); set_time(:begin, time) end
|
|
51
|
+
def ends_at= (time); set_time(:end, time) end
|
|
52
|
+
|
|
53
|
+
def set_time(event, time)
|
|
54
|
+
['%Y', '%B', '%d', '%H', '%M'].each_with_index do |part, index|
|
|
55
|
+
element = "id=party_#{event}s_at_#{index + 1}i"
|
|
56
|
+
value = time.strftime part
|
|
57
|
+
|
|
58
|
+
@browser.select element, value
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Party
|
|
67
|
+
def begins_at; get_times.first end
|
|
68
|
+
def ends_at; get_times.last end
|
|
69
|
+
|
|
70
|
+
def get_times
|
|
71
|
+
begins_on = @browser.get_text 'party_begins_on'
|
|
72
|
+
begins_at = @browser.get_text 'party_begins_at'
|
|
73
|
+
ends_at = @browser.get_text 'party_ends_at'
|
|
74
|
+
|
|
75
|
+
begins = Time.parse(begins_on + ' ' + begins_at)
|
|
76
|
+
ends = Time.parse(begins_on + ' ' + ends_at)
|
|
77
|
+
ends += 86400 if ends < begins
|
|
78
|
+
|
|
79
|
+
[begins, ends]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def has_times?
|
|
83
|
+
get_times rescue nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Party
|
|
91
|
+
def save_and_view
|
|
92
|
+
@browser.click 'id=party_submit'
|
|
93
|
+
@browser.wait_for_page_to_load 5000
|
|
94
|
+
@saved = true
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Party
|
|
102
|
+
def rsvp(name, attending)
|
|
103
|
+
@browser.type 'guest_name', name
|
|
104
|
+
@browser.click 'guest_attending' unless attending
|
|
105
|
+
@browser.click 'rsvp'
|
|
106
|
+
@browser.wait_for_page_to_load 5000
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Party
|
|
114
|
+
RsvpItem = '//ul[@id="guests"]/li'
|
|
115
|
+
|
|
116
|
+
def responses(want_attending)
|
|
117
|
+
num_guests = @browser.get_xpath_count(RsvpItem).to_i
|
|
118
|
+
return [] unless num_guests >= 1
|
|
119
|
+
|
|
120
|
+
all = (1..num_guests).map do |i|
|
|
121
|
+
name = @browser.get_text \
|
|
122
|
+
"#{RsvpItem}[#{i}]/span[@class='rsvp_name']"
|
|
123
|
+
rsvp = @browser.get_text \
|
|
124
|
+
"#{RsvpItem}[#{i}]/span[@class='rsvp_attending']"
|
|
125
|
+
[name, rsvp]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
matching = all.select do |name, rsvp|
|
|
129
|
+
is_attending = !rsvp.include?('not')
|
|
130
|
+
!(want_attending ^ is_attending)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
matching.map {|name, rsvp| name}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Party
|
|
141
|
+
def email_to(address)
|
|
142
|
+
@browser.open link + '.txt?email=' + address
|
|
143
|
+
@browser.get_body_text
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def rsvp_at(rsvp_link)
|
|
147
|
+
@browser.open rsvp_link
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'spec/story'
|
|
12
|
+
|
|
13
|
+
steps_for :app_state do #(1)
|
|
14
|
+
Given 'a new document' do
|
|
15
|
+
@note = Note.open
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
When 'I exit the app' do
|
|
19
|
+
@note.exit!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Then 'the app should be running' do
|
|
23
|
+
@note.should be_running
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
steps_for :documents do
|
|
31
|
+
When 'I type "$something"' do |something|
|
|
32
|
+
@note.text = something
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
When 'I save the document as "$name" with password "$password"' do #(2)
|
|
36
|
+
|name, password|
|
|
37
|
+
@note.save_as name, :password => password
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
When 'I open the document "$name" with password "$password"' do
|
|
41
|
+
|name, password|
|
|
42
|
+
@note = Note.open name, :password => password
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
When 'I change the password from "$old" to "$password"' do
|
|
46
|
+
|old, password|
|
|
47
|
+
@note.change_password :old_password => old, :password => password
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Then 'the text should be "$something"' do |something|
|
|
51
|
+
@note.text.should == something
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
with_steps_for :app_state, :documents do
|
|
59
|
+
run 'password.story'
|
|
60
|
+
end
|
|
61
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
START: password_story
|
|
2
|
+
Story: Password protection
|
|
3
|
+
|
|
4
|
+
As a security-conscious person
|
|
5
|
+
I want to encrypt each document with a password
|
|
6
|
+
So that only I (and the NSA) can read it
|
|
7
|
+
END: password_story
|
|
8
|
+
|
|
9
|
+
START: password_scenario
|
|
10
|
+
Scenario: Changing the password
|
|
11
|
+
Given a new document
|
|
12
|
+
When I type "this is my document"
|
|
13
|
+
And I save the document as "Secrets" with password "unguessable"
|
|
14
|
+
And I exit the app
|
|
15
|
+
And I open the document "Secrets" with password "unguessable"
|
|
16
|
+
Then the app should be running
|
|
17
|
+
And the text should be "this is my document"
|
|
18
|
+
|
|
19
|
+
When I change the password from "unguessable" to "uncrackable"
|
|
20
|
+
And I exit the app
|
|
21
|
+
And I open the document "Secrets" with password "uncrackable"
|
|
22
|
+
Then the app should be running
|
|
23
|
+
And the text should be "this is my document"
|
|
24
|
+
END: password_scenario
|
|
25
|
+
|
|
26
|
+
When I exit the app
|