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,144 @@
|
|
|
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
|
+
class Note
|
|
10
|
+
attr_reader :path
|
|
11
|
+
|
|
12
|
+
@@app = nil
|
|
13
|
+
@@titles = {}
|
|
14
|
+
|
|
15
|
+
def self.open(*args)
|
|
16
|
+
@@app.new *args
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DefaultOptions = {
|
|
21
|
+
:password => 'password',
|
|
22
|
+
:confirmation => true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def save_as(name, with_options = {})
|
|
26
|
+
options = DefaultOptions.merge with_options #(1)
|
|
27
|
+
|
|
28
|
+
@path = @@app.path_to(name) #(2)
|
|
29
|
+
File.delete @path if File.exist? @path
|
|
30
|
+
|
|
31
|
+
menu 'File', 'Save As...' #(3)
|
|
32
|
+
|
|
33
|
+
enter_filename @path #(4)
|
|
34
|
+
assign_password options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def exit!(with_options = {})
|
|
41
|
+
options = DefaultOptions.merge with_options
|
|
42
|
+
|
|
43
|
+
menu 'File', 'Exit'
|
|
44
|
+
|
|
45
|
+
@prompted[:to_confirm_exit] = dialog(@@titles[:exit]) do |d|
|
|
46
|
+
d.click(options[:save_as] ? '_Yes' : '_No')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if options[:save_as]
|
|
50
|
+
path = @@app.path_to options[:save_as]
|
|
51
|
+
enter_filename path
|
|
52
|
+
assign_password options
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def change_password(with_options = {})
|
|
59
|
+
old_options = {
|
|
60
|
+
:password => with_options[:old_password]} #(5)
|
|
61
|
+
|
|
62
|
+
new_options = {
|
|
63
|
+
:password => with_options[:password],
|
|
64
|
+
:confirmation =>
|
|
65
|
+
with_options[:confirmation] ||
|
|
66
|
+
with_options[:password]} #(6)
|
|
67
|
+
|
|
68
|
+
menu 'File', 'Change Password...'
|
|
69
|
+
|
|
70
|
+
unlock_password old_options
|
|
71
|
+
assign_password new_options
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def about
|
|
76
|
+
menu 'Help', @@titles[:about_menu]
|
|
77
|
+
|
|
78
|
+
@prompted[:with_help_text] = dialog(@@titles[:about]) do |d|
|
|
79
|
+
d.click '_OK'
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
[:undo, :cut, :copy, :paste, :find_next].each do |method|
|
|
85
|
+
item = method.to_s.split('_').collect {|m| m.capitalize}.join(' ')
|
|
86
|
+
define_method(method) {menu 'Edit', item, :wait} #(7)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def has_prompted?(kind)
|
|
92
|
+
@prompted[kind]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def enter_filename(path, approval = '_Save')
|
|
100
|
+
dialog(@@titles[:file]) do |d| #(8)
|
|
101
|
+
d.type_in path
|
|
102
|
+
d.click approval
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def unlock_password(with_options = {})
|
|
109
|
+
options = DefaultOptions.merge with_options
|
|
110
|
+
options[:confirmation] = false #(9)
|
|
111
|
+
|
|
112
|
+
enter_password options
|
|
113
|
+
watch_for_error
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def assign_password(with_options = {})
|
|
119
|
+
options = DefaultOptions.merge with_options
|
|
120
|
+
|
|
121
|
+
enter_password options
|
|
122
|
+
watch_for_error
|
|
123
|
+
|
|
124
|
+
if @prompted[:with_error]
|
|
125
|
+
enter_password :cancel_password => true #(10)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
require 'fileutils'
|
|
134
|
+
|
|
135
|
+
class Note
|
|
136
|
+
def self.fixture(name)
|
|
137
|
+
source = @@app.path_to(name + 'Fixture')
|
|
138
|
+
target = @@app.path_to(name)
|
|
139
|
+
|
|
140
|
+
FileUtils.rm target if File.exist? target
|
|
141
|
+
FileUtils.copy source, target
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
@@ -0,0 +1,191 @@
|
|
|
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
|
+
require 'spec_helper'
|
|
10
|
+
|
|
11
|
+
describe 'The main window' do
|
|
12
|
+
it_should_behave_like 'a new document'
|
|
13
|
+
|
|
14
|
+
it 'launches with a welcome message' do
|
|
15
|
+
@note.text.should include('Welcome')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'exits without a prompt if nothing has changed' do
|
|
19
|
+
@note.exit!
|
|
20
|
+
@note.should_not have_prompted(:to_confirm_exit)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'prompts before exiting if the document has changed' do
|
|
24
|
+
@note.text = 'changed'
|
|
25
|
+
@note.exit!
|
|
26
|
+
@note.should have_prompted(:to_confirm_exit)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'offers information about the program' do
|
|
30
|
+
@note.about
|
|
31
|
+
@note.should have_prompted(:with_help_text)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
describe 'Saving a document for the first time' do
|
|
37
|
+
it_should_behave_like 'a new document'
|
|
38
|
+
|
|
39
|
+
it 'requires a password' do
|
|
40
|
+
@note.save_as 'MyNote' #(1)
|
|
41
|
+
@note.should have_prompted(:for_password) #(2)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
describe 'The password assignment prompt' do
|
|
48
|
+
it_should_behave_like 'a new document'
|
|
49
|
+
|
|
50
|
+
it 'ignores the new password if cancelled' do
|
|
51
|
+
@note.text = 'changed'
|
|
52
|
+
@note.save_as 'MyNote', :cancel_password => true
|
|
53
|
+
@note.exit!
|
|
54
|
+
@note.should have_prompted(:to_confirm_exit) #(3)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'ignores an unconfirmed password' do
|
|
58
|
+
@note.text = 'changed'
|
|
59
|
+
@note.save_as 'SavedNote', :confirmation => 'mismatch'
|
|
60
|
+
@note.should have_prompted(:with_error)
|
|
61
|
+
@note.exit!
|
|
62
|
+
@note.should have_prompted(:to_confirm_exit) #(4)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
describe 'The password entry prompt' do
|
|
69
|
+
it_should_behave_like 'a saved document' #(5)
|
|
70
|
+
|
|
71
|
+
it 'ignores the password if cancelled' do
|
|
72
|
+
note = Note.open 'SavedNote', :cancel_password => true
|
|
73
|
+
note.should_not be_running
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'exits with an error message for an invalid password' do
|
|
77
|
+
note = Note.open 'SavedNote', :password => 'invalid'
|
|
78
|
+
note.should_not be_running
|
|
79
|
+
note.should have_prompted(:with_error)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
describe 'A previously saved document' do
|
|
86
|
+
it_should_behave_like 'a saved document'
|
|
87
|
+
it_should_behave_like 'a reopened document' #(6)
|
|
88
|
+
|
|
89
|
+
it 'preserves and encrypts the contents of the file' do
|
|
90
|
+
@note.text.should include('Welcome')
|
|
91
|
+
IO.read(@note.path).should_not include('Welcome') #(7)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'does not require a password on subsequent saves' do
|
|
95
|
+
@note.text = 'changed'
|
|
96
|
+
@note.exit! :save_as => 'MyNote' #(8)
|
|
97
|
+
@note.should_not have_prompted(:for_password)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
it 'supports changing the password' do
|
|
102
|
+
@note.change_password \
|
|
103
|
+
:old_password => 'password',
|
|
104
|
+
:password => 'new'
|
|
105
|
+
@note.exit!
|
|
106
|
+
|
|
107
|
+
@note = Note.open 'SavedNote', :password => 'new'
|
|
108
|
+
@note.should_not have_prompted(:with_error)
|
|
109
|
+
@note.should be_running
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
describe 'The editor' do
|
|
117
|
+
it_should_behave_like 'a new document'
|
|
118
|
+
|
|
119
|
+
it 'supports multiple levels of undo' do
|
|
120
|
+
@note.text = 'abc'
|
|
121
|
+
|
|
122
|
+
@note.undo
|
|
123
|
+
@note.text.should == 'ab'
|
|
124
|
+
|
|
125
|
+
@note.undo
|
|
126
|
+
@note.text.should == 'a'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'supports copying and pasting text' do
|
|
130
|
+
@note.text = 'itchy'
|
|
131
|
+
@note.select_all
|
|
132
|
+
@note.copy
|
|
133
|
+
@note.text.should == 'itchy'
|
|
134
|
+
|
|
135
|
+
@note.text = 'scratchy'
|
|
136
|
+
@note.select_all
|
|
137
|
+
@note.paste
|
|
138
|
+
@note.text.should == 'itchy'
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
it 'supports cutting and pasting text' do
|
|
143
|
+
@note.text = 'pineapple'
|
|
144
|
+
@note.select_all
|
|
145
|
+
@note.cut
|
|
146
|
+
@note.text.should be_empty
|
|
147
|
+
|
|
148
|
+
@note.text = 'mango'
|
|
149
|
+
@note.select_all
|
|
150
|
+
@note.paste
|
|
151
|
+
@note.text.should == 'pineapple'
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
describe 'The Find window' do
|
|
159
|
+
it_should_behave_like 'a new document'
|
|
160
|
+
it_should_behave_like 'a searchable document'
|
|
161
|
+
|
|
162
|
+
it 'supports searching forward' do
|
|
163
|
+
@note.go_to :beginning
|
|
164
|
+
@note.find @term
|
|
165
|
+
@note.selection.begin.should == @first_match
|
|
166
|
+
|
|
167
|
+
@note.find_next
|
|
168
|
+
@note.selection.begin.should == @second_match
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'supports searching backward' do
|
|
172
|
+
@note.go_to :end
|
|
173
|
+
@note.find @term, :direction => :back
|
|
174
|
+
@note.selection.begin.should == @reverse_match
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'can restrict its search to whole words' do
|
|
178
|
+
pending 'on hold' do #(9)
|
|
179
|
+
@note.go_to :beginning
|
|
180
|
+
@note.find @term, :whole_word => true
|
|
181
|
+
@note.selection.begin.should == @word_match
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'can restrict its search to exact case matches' do
|
|
186
|
+
@note.go_to :beginning
|
|
187
|
+
@note.find @term, :exact_case => true
|
|
188
|
+
@note.selection.begin.should == @case_match
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
describe 'a new document', :shared => true do #(1)
|
|
11
|
+
before do #(2)
|
|
12
|
+
@note = Note.open
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after do #(3)
|
|
16
|
+
@note.exit! if @note.running? #(4)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe 'a saved document', :shared => true do
|
|
23
|
+
before do
|
|
24
|
+
Note.fixture 'SavedNote' #(5)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe 'a reopened document', :shared => true do
|
|
31
|
+
before do
|
|
32
|
+
@note = Note.open 'SavedNote'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
after do
|
|
36
|
+
@note.exit! if @note.running?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
describe 'a searchable document', :shared => true do
|
|
43
|
+
before do
|
|
44
|
+
@example = 'The longest island is Isabel Island.'
|
|
45
|
+
@term = 'Is'
|
|
46
|
+
|
|
47
|
+
@first_match = @example.index(/Is/i)
|
|
48
|
+
@second_match = @example.index(/Is/i, @first_match + 1)
|
|
49
|
+
@reverse_match = @example.rindex(/Is/i)
|
|
50
|
+
@word_match = @example.index(/Is\b/i)
|
|
51
|
+
@case_match = @example.index(/Is/)
|
|
52
|
+
|
|
53
|
+
@note.text = @example
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
require 'java'
|
|
10
|
+
require 'jemmy.jar'
|
|
11
|
+
|
|
12
|
+
include_class 'org.netbeans.jemmy.JemmyProperties'
|
|
13
|
+
include_class 'org.netbeans.jemmy.TestOut'
|
|
14
|
+
|
|
15
|
+
JemmyProperties.set_current_output TestOut.get_null_output
|
|
16
|
+
|
|
17
|
+
%w(Frame TextArea MenuBar Dialog Button TextField).each do |o|
|
|
18
|
+
include_class "org.netbeans.jemmy.operators.J#{o}Operator"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JDialogOperator
|
|
23
|
+
def click(title)
|
|
24
|
+
b = JButtonOperator.new self, title.delete('_')
|
|
25
|
+
b.push
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def type_in(message)
|
|
29
|
+
e = JTextFieldOperator.new self
|
|
30
|
+
e.type_text message
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
module SwingGui
|
|
37
|
+
def dialog(title, seconds=3)
|
|
38
|
+
JemmyProperties.set_current_timeout \
|
|
39
|
+
'DialogWaiter.WaitDialogTimeout', seconds * 1000
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
d = JDialogOperator.new title
|
|
43
|
+
yield d #(1)
|
|
44
|
+
d.wait_closed
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
rescue NativeException
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -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
|
+
require 'Win32API'
|
|
10
|
+
|
|
11
|
+
class String
|
|
12
|
+
def snake_case
|
|
13
|
+
gsub(/([a-z])([A-Z0-9])/, '\1_\2').downcase
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_keys
|
|
17
|
+
unless size == 1
|
|
18
|
+
raise "conversion is for single characters only"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ascii = unpack('C')[0]
|
|
22
|
+
|
|
23
|
+
case self
|
|
24
|
+
when '0'..'9'
|
|
25
|
+
[ascii - ?0 + 0x30]
|
|
26
|
+
when 'A'..'Z'
|
|
27
|
+
[WindowsGui.const_get(:VK_SHIFT), ascii]
|
|
28
|
+
when 'a'..'z'
|
|
29
|
+
[ascii - ?a + ?A]
|
|
30
|
+
when ' '
|
|
31
|
+
[ascii]
|
|
32
|
+
when ','
|
|
33
|
+
[WindowsGui.const_get(:VK_OEM_COMMA)]
|
|
34
|
+
when '.'
|
|
35
|
+
[WindowsGui.const_get(:VK_OEM_PERIOD)]
|
|
36
|
+
when ':'
|
|
37
|
+
[:VK_SHIFT, :VK_OEM_1].map {|s| WindowsGui.const_get s}
|
|
38
|
+
when "\\"
|
|
39
|
+
[WindowsGui.const_get(:VK_OEM_102)]
|
|
40
|
+
else
|
|
41
|
+
raise "Can't convert unknown character #{self}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module WindowsGui
|
|
47
|
+
def self.def_api(function, parameters, return_value, rename = nil)
|
|
48
|
+
api = Win32API.new 'user32', function, parameters, return_value
|
|
49
|
+
define_method(rename || function.snake_case) do |*args|
|
|
50
|
+
api.call *args
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def self.load_symbols(header)
|
|
56
|
+
File.open(header) do |f|
|
|
57
|
+
f.grep(/#define\s+(ID\w+)\s+(\w+)/) do
|
|
58
|
+
name = $1
|
|
59
|
+
value = (0 == $2.to_i) ? $2.hex : $2.to_i #(1)
|
|
60
|
+
WindowsGui.const_set name, value #(2)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def_api 'FindWindow', ['P', 'P'], 'L'
|
|
68
|
+
def_api 'FindWindowEx', ['L', 'L', 'P', 'P'], 'L'
|
|
69
|
+
def_api 'SendMessage', ['L', 'L', 'L', 'P'], 'L', :send_with_buffer
|
|
70
|
+
def_api 'SendMessage', ['L', 'L', 'L', 'L'], 'L'
|
|
71
|
+
def_api 'PostMessage', ['L', 'L', 'L', 'L'], 'L'
|
|
72
|
+
def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
|
|
73
|
+
def_api 'GetDlgItem', ['L', 'L'], 'L'
|
|
74
|
+
def_api 'GetWindowRect', ['L', 'P'], 'I'
|
|
75
|
+
def_api 'SetCursorPos', ['L', 'L'], 'I'
|
|
76
|
+
def_api 'mouse_event', ['L', 'L', 'L', 'L', 'L'], 'V'
|
|
77
|
+
def_api 'IsWindow', ['L'], 'L'
|
|
78
|
+
def_api 'IsWindowVisible', ['L'], 'L'
|
|
79
|
+
def_api 'SetForegroundWindow', ['L'], 'L'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Windows messages - general
|
|
84
|
+
WM_COMMAND = 0x0111
|
|
85
|
+
WM_SYSCOMMAND = 0x0112
|
|
86
|
+
SC_CLOSE = 0xF060
|
|
87
|
+
|
|
88
|
+
# Windows messages - text
|
|
89
|
+
WM_GETTEXT = 0x000D
|
|
90
|
+
EM_GETSEL = 0x00B0
|
|
91
|
+
EM_SETSEL = 0x00B1
|
|
92
|
+
|
|
93
|
+
# Commonly-used control IDs
|
|
94
|
+
IDOK = 1
|
|
95
|
+
IDCANCEL = 2
|
|
96
|
+
IDYES = 6
|
|
97
|
+
IDNO = 7
|
|
98
|
+
|
|
99
|
+
# Mouse and keyboard flags
|
|
100
|
+
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
101
|
+
MOUSEEVENTF_LEFTUP = 0x0004
|
|
102
|
+
KEYEVENTF_KEYDOWN = 0
|
|
103
|
+
KEYEVENTF_KEYUP = 2
|
|
104
|
+
|
|
105
|
+
# Modifier keys
|
|
106
|
+
VK_SHIFT = 0x10
|
|
107
|
+
VK_CONTROL = 0x11
|
|
108
|
+
VK_MENU = 0x12 # Alt
|
|
109
|
+
|
|
110
|
+
# Commonly-used keys
|
|
111
|
+
VK_BACK = 0x08
|
|
112
|
+
VK_TAB = 0x09
|
|
113
|
+
VK_RETURN = 0x0D
|
|
114
|
+
VK_ESCAPE = 0x1B
|
|
115
|
+
VK_OEM_1 = 0xBA # semicolon (US)
|
|
116
|
+
VK_OEM_102 = 0xE2 # backslash (US)
|
|
117
|
+
VK_OEM_PERIOD = 0xBE
|
|
118
|
+
VK_HOME = 0x24
|
|
119
|
+
VK_END = 0x23
|
|
120
|
+
VK_OEM_COMMA = 0xBC
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def keystroke(*keys)
|
|
124
|
+
return if keys.empty?
|
|
125
|
+
|
|
126
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
|
|
127
|
+
sleep 0.05
|
|
128
|
+
keystroke *keys[1..-1]
|
|
129
|
+
sleep 0.05
|
|
130
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def type_in(message)
|
|
134
|
+
message.scan(/./m) do |char|
|
|
135
|
+
keystroke(*char.to_keys)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def wait_for_window(title, seconds = 5)
|
|
140
|
+
timeout(seconds) do
|
|
141
|
+
sleep 0.2 while
|
|
142
|
+
(h = find_window nil, title) <= 0 ||
|
|
143
|
+
window_text(h) != title
|
|
144
|
+
h
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
class Window
|
|
149
|
+
include WindowsGui
|
|
150
|
+
extend WindowsGui
|
|
151
|
+
|
|
152
|
+
attr_reader :handle
|
|
153
|
+
|
|
154
|
+
def initialize(handle)
|
|
155
|
+
@handle = handle
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def close
|
|
159
|
+
post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def wait_for_close
|
|
163
|
+
timeout(5) do
|
|
164
|
+
sleep 0.2 until 0 == is_window_visible(@handle)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def text(max_length = 2048)
|
|
169
|
+
buffer = '\0' * max_length
|
|
170
|
+
length = send_with_buffer @handle, WM_GETTEXT, buffer.length, buffer
|
|
171
|
+
length == 0 ? '' : buffer[0..length - 1]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def child(id)
|
|
175
|
+
result = case id
|
|
176
|
+
when String
|
|
177
|
+
by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&') #(3)
|
|
178
|
+
by_class = find_window_ex @handle, 0, id, nil
|
|
179
|
+
by_title > 0 ? by_title : by_class
|
|
180
|
+
when Fixnum
|
|
181
|
+
get_dlg_item @handle, id
|
|
182
|
+
else
|
|
183
|
+
0
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
raise "Control '#{id}' not found" if result == 0
|
|
187
|
+
Window.new result
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def click(id)
|
|
191
|
+
h = child(id).handle
|
|
192
|
+
|
|
193
|
+
rectangle = [0, 0, 0, 0].pack 'LLLL'
|
|
194
|
+
get_window_rect h, rectangle
|
|
195
|
+
left, top, right, bottom = rectangle.unpack 'LLLL'
|
|
196
|
+
|
|
197
|
+
center = [(left + right) / 2, (top + bottom) / 2]
|
|
198
|
+
set_cursor_pos *center
|
|
199
|
+
|
|
200
|
+
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
|
|
201
|
+
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def self.top_level(title, seconds=10, wnd_class = nil)
|
|
205
|
+
@handle = timeout(seconds) do
|
|
206
|
+
loop do
|
|
207
|
+
h = find_window wnd_class, title
|
|
208
|
+
break h if h > 0
|
|
209
|
+
sleep 0.3
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
Window.new @handle
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
DialogWndClass = '#32770'
|
|
218
|
+
def dialog(title, seconds=5)
|
|
219
|
+
close, dlg = begin
|
|
220
|
+
sleep 0.25
|
|
221
|
+
w = Window.top_level(title, seconds, DialogWndClass)
|
|
222
|
+
Window.set_foreground_window w.handle
|
|
223
|
+
sleep 0.25
|
|
224
|
+
|
|
225
|
+
[yield(w), w]
|
|
226
|
+
rescue TimeoutError
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
dlg.wait_for_close if dlg && close
|
|
230
|
+
return dlg
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
#---
|
|
3
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
4
|
+
# published by The Pragmatic Bookshelf.
|
|
5
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
6
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
7
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
8
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
9
|
+
#---
|
|
10
|
+
export PATH=/opt/jruby/bin:$PATH
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
#---
|
|
3
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
4
|
+
# published by The Pragmatic Bookshelf.
|
|
5
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
6
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
7
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
8
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
9
|
+
#---
|
|
10
|
+
/usr/bin/env jruby -J-Dapple.laf.useScreenMenuBar=true -J-Xdock:name=JunqueNote $*
|