win32-autogui 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemfiles +78 -0
- data/.yardopts +0 -1
- data/Gemfile.lock +17 -20
- data/HISTORY.markdown +9 -1
- data/README.markdown +27 -6
- data/Rakefile +16 -2
- data/TODO.markdown +3 -1
- data/VERSION +1 -1
- data/examples/quicknote/lib/quicknote.rb +19 -19
- data/examples/quicknote/spec/quicknote/form_main_spec.rb +4 -4
- data/examples/quicknote/spec/spec_helper.rb +12 -5
- data/examples/skeleton/HISTORY.markdown +11 -0
- data/examples/skeleton/features/step_definitions/application_steps.rb +24 -10
- data/examples/skeleton/lib/myapp.rb +21 -14
- data/examples/skeleton/spec/myapp/form_about_spec.rb +3 -2
- data/examples/skeleton/spec/myapp/form_main_spec.rb +6 -8
- data/examples/skeleton/spec/spec_helper.rb +12 -5
- data/lib/win32/autogui.rb +1 -1
- data/lib/win32/autogui/application.rb +4 -4
- data/lib/win32/autogui/logging.rb +31 -7
- data/lib/win32/autogui/window.rb +35 -2
- data/lib/win32/autogui/windows/window.rb +0 -1
- data/spec/applications/calculator.rb +5 -4
- data/spec/auto_gui/application_spec.rb +3 -2
- data/spec/auto_gui/logging_spec.rb +35 -18
- data/spec/auto_gui/window_spec.rb +63 -0
- data/spec/basic_gem/aruba_helper_spec.rb +33 -0
- data/spec/basic_gem/gemspec_spec.rb +68 -0
- data/spec/spec_helper.rb +12 -5
- data/spec/watchr.rb +43 -22
- data/win32-autogui.gemspec +41 -11
- metadata +37 -42
@@ -4,8 +4,15 @@
|
|
4
4
|
|
5
5
|
class Myapp < Autogui::Application
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
|
7
|
+
def initialize(options = {})
|
8
|
+
# relative path to app using Windows style path
|
9
|
+
@name ="exe\\myapp.exe"
|
10
|
+
defaults = {
|
11
|
+
:title=> "MyApp -",
|
12
|
+
:parameters => '--nosplash',
|
13
|
+
:main_window_timeout => 20
|
14
|
+
}
|
15
|
+
super defaults.merge(options)
|
9
16
|
end
|
10
17
|
|
11
18
|
def edit_window
|
@@ -16,22 +23,22 @@ def status_bar
|
|
16
23
|
main_window.children.find {|w| w.window_class == 'TStatusBar'}
|
17
24
|
end
|
18
25
|
|
19
|
-
def dialog_about
|
20
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
26
|
+
def dialog_about(options={})
|
27
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
21
28
|
w.title.match(/About MyApp/) && (w.pid == pid)
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
|
-
def message_dialog_confirm
|
26
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
32
|
+
def message_dialog_confirm(options={})
|
33
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
27
34
|
w.title.match(/Confirm/) && (w.pid == pid)
|
28
35
|
end
|
29
36
|
end
|
30
37
|
|
31
38
|
# Title and class are the same as dialog_overwrite_confirm
|
32
39
|
# Use child windows to differentiate
|
33
|
-
def dialog_overwrite_confirm
|
34
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
40
|
+
def dialog_overwrite_confirm(options={})
|
41
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
35
42
|
w.title.match(/^Text File Save$/) &&
|
36
43
|
(w.pid == pid) &&
|
37
44
|
(w.window_class == "#32770") &&
|
@@ -40,8 +47,8 @@ def dialog_overwrite_confirm
|
|
40
47
|
end
|
41
48
|
|
42
49
|
# title and class are the same as dialog_overwrite_confirm
|
43
|
-
def file_save_as_dialog
|
44
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
50
|
+
def file_save_as_dialog(options={})
|
51
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
45
52
|
w.title.match(/Text File Save/) &&
|
46
53
|
(w.pid == pid) &&
|
47
54
|
(w.window_class == "#32770") &&
|
@@ -49,14 +56,14 @@ def file_save_as_dialog
|
|
49
56
|
end
|
50
57
|
end
|
51
58
|
|
52
|
-
def file_open_dialog
|
53
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
59
|
+
def file_open_dialog(options={})
|
60
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
54
61
|
w.title.match(/Text File Open/) && (w.pid == pid)
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
58
|
-
def error_dialog
|
59
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
65
|
+
def error_dialog(options={})
|
66
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
60
67
|
w.title.match(/^MyApp$/) && (w.pid == pid) && (w.window_class == "#32770")
|
61
68
|
end
|
62
69
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
include Autogui::Input
|
4
|
+
include Autogui::Logging
|
4
5
|
|
5
6
|
describe "FormAbout" do
|
6
7
|
|
@@ -41,10 +42,10 @@
|
|
41
42
|
@dialog_about.title.should == "About MyApp"
|
42
43
|
end
|
43
44
|
|
44
|
-
it "should have an '
|
45
|
+
it "should have an 'OK' button" do
|
45
46
|
button = @dialog_about.children.find {|w| w.window_class == 'TButton'}
|
46
47
|
button.should_not be_nil
|
47
|
-
button.text.should match(
|
48
|
+
button.text.should match(/^OK$/)
|
48
49
|
end
|
49
50
|
|
50
51
|
end
|
@@ -1,22 +1,22 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
include Autogui::Input
|
4
|
+
include Autogui::Logging
|
5
|
+
|
6
|
+
logger.level = Autogui::Logging::DEBUG
|
4
7
|
|
5
8
|
describe "FormMain" do
|
6
9
|
before(:all) do
|
7
|
-
@debug = false
|
8
|
-
@verbose = true
|
9
10
|
@application = Myapp.new
|
10
11
|
FileUtils.rm_rf(current_dir)
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
#logger.debug "FormMain before(:all)"
|
13
|
+
#logger.debug "application:\n#{@application.inspect}\n"
|
14
|
+
#logger.debug "application.combined_text:\n #{@application.combined_text}\n"
|
14
15
|
end
|
15
16
|
before(:each) do
|
16
17
|
@application = Myapp.new unless @application.running?
|
17
18
|
@application.should be_running
|
18
19
|
@application.set_focus
|
19
|
-
puts "FormMain before(:each)" if @debug
|
20
20
|
end
|
21
21
|
after(:all) do
|
22
22
|
if @application.running?
|
@@ -25,14 +25,12 @@
|
|
25
25
|
@application.close(:wait_for_close => true)
|
26
26
|
@application.should_not be_running
|
27
27
|
end
|
28
|
-
puts "FormMain after(:all)" if @debug
|
29
28
|
end
|
30
29
|
after(:each) do
|
31
30
|
if @application.running?
|
32
31
|
keystroke(VK_N) if @application.message_dialog_confirm || @application.dialog_overwrite_confirm
|
33
32
|
keystroke(VK_ESCAPE) if @application.error_dialog
|
34
33
|
end
|
35
|
-
puts "FormMain after(:each)" if @debug
|
36
34
|
end
|
37
35
|
|
38
36
|
describe "after startup" do
|
@@ -10,15 +10,22 @@
|
|
10
10
|
require 'spec/autorun'
|
11
11
|
require 'aruba/api'
|
12
12
|
|
13
|
-
# aruba
|
13
|
+
# aruba helpers
|
14
|
+
#
|
15
|
+
# @return full path to files in the aruba tmp folder
|
14
16
|
def fullpath(filename)
|
15
17
|
path = File.expand_path(File.join(current_dir, filename))
|
16
|
-
|
18
|
+
if path.match(/^\/cygdrive/)
|
19
|
+
# match /cygdrive/c/path/to and return c:\\path\\to
|
20
|
+
path = `cygpath -w #{path}`.chomp
|
21
|
+
elsif path.match(/.\:/)
|
22
|
+
# match c:/path/to and return c:\\path\\to
|
23
|
+
path = path.gsub(/\//, '\\')
|
24
|
+
end
|
17
25
|
path
|
18
26
|
end
|
19
|
-
|
20
|
-
|
21
|
-
def get_file_content(filename)
|
27
|
+
# @return the contents of "filename" in the aruba tmp folder
|
28
|
+
def get_file_contents(filename)
|
22
29
|
in_current_dir do
|
23
30
|
IO.read(filename)
|
24
31
|
end
|
data/lib/win32/autogui.rb
CHANGED
@@ -108,7 +108,7 @@ class Application
|
|
108
108
|
# @example initialize with logging to file at DEBUG level
|
109
109
|
#
|
110
110
|
# include Autogui::Logging
|
111
|
-
# app = Application.new :name => "calc", :logger_logfile => 'log/calc.log', :logger.level =>
|
111
|
+
# app = Application.new :name => "calc", :logger_logfile => 'log/calc.log', :logger.level => Autogui::Logging::DEBUG
|
112
112
|
#
|
113
113
|
# @example initialize without logging to file and turn it on later
|
114
114
|
#
|
@@ -122,8 +122,8 @@ class Application
|
|
122
122
|
# @option options [Number] :parameters command line parameters used by Process.create
|
123
123
|
# @option options [Number] :create_process_timeout (10) timeout in seconds to wait for the create_process to return
|
124
124
|
# @option options [Number] :main_window_timeout (10) timeout in seconds to wait for main_window to appear
|
125
|
-
# @option options [String] :logger_logfile (nil) initialize
|
126
|
-
# @option options [String] :logger_level (
|
125
|
+
# @option options [String] :logger_logfile (nil) initialize logger's output filename
|
126
|
+
# @option options [String] :logger_level (Autogui::Logging::WARN) initialize logger's initial level
|
127
127
|
#
|
128
128
|
def initialize(options = {})
|
129
129
|
|
@@ -206,7 +206,7 @@ def main_window
|
|
206
206
|
w.title.match(title) && w.pid == pid
|
207
207
|
end
|
208
208
|
sleep 0.1
|
209
|
-
|
209
|
+
end until @main_window
|
210
210
|
end
|
211
211
|
|
212
212
|
# post sanity checks
|
@@ -18,9 +18,15 @@ def logfile
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def logfile=(fn)
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
if fn == nil
|
22
|
+
puts "removing log to file"
|
23
|
+
remove(:logfile) if @filename
|
24
|
+
else
|
25
|
+
FileOutputter.new(:logfile, :filename => fn, :trunc => true)
|
26
|
+
Outputter[:logfile].formatter = Log4r::PatternFormatter.new(:pattern => "[%5l %d] %M [%t]")
|
27
|
+
add(:logfile)
|
28
|
+
end
|
29
|
+
@filename = fn
|
24
30
|
end
|
25
31
|
|
26
32
|
end
|
@@ -28,11 +34,19 @@ def logfile=(fn)
|
|
28
34
|
|
29
35
|
module Autogui
|
30
36
|
|
31
|
-
STANDARD_LOGGER = 'standard'
|
32
|
-
|
33
37
|
# wrapper for Log4r gem
|
34
38
|
module Logging
|
35
39
|
|
40
|
+
# Redefine logging levels so that they can be accessed before
|
41
|
+
# the logger is initialized at the expense of flexibility.
|
42
|
+
DEBUG = 1
|
43
|
+
INFO = 2
|
44
|
+
WARN = 3
|
45
|
+
ERROR = 4
|
46
|
+
FATAL = 5
|
47
|
+
|
48
|
+
STANDARD_LOGGER = 'standard'
|
49
|
+
|
36
50
|
# Logging mixin allows simple logging setup
|
37
51
|
# to STDOUT and optionally, to one filename. Logger is a wrapper
|
38
52
|
# for Log4r::Logger it accepts any methods that
|
@@ -45,7 +59,7 @@ module Logging
|
|
45
59
|
# logger.filename = 'log/autogui.log'
|
46
60
|
# logger.warn "warning message goes to 'log/autogui.log'"
|
47
61
|
#
|
48
|
-
# logger.level =
|
62
|
+
# logger.level = Autogui::Logging::DEBUG
|
49
63
|
# logger.debug "this message goes to 'log/autogui.log'"
|
50
64
|
#
|
51
65
|
def logger
|
@@ -59,7 +73,17 @@ def logger
|
|
59
73
|
# Initialize the logger, defaults to log4r::Warn
|
60
74
|
def init_logger
|
61
75
|
log = Log4r::Logger.new(STANDARD_LOGGER)
|
62
|
-
|
76
|
+
|
77
|
+
# sanity checks since we defined log4r's dynamic levels statically
|
78
|
+
unless (Log4r::DEBUG == DEBUG) &&
|
79
|
+
(Log4r::INFO == INFO) &&
|
80
|
+
(Log4r::WARN == WARN) &&
|
81
|
+
(Log4r::ERROR == ERROR) &&
|
82
|
+
(Log4r::FATAL == FATAL)
|
83
|
+
raise "Logger levels do not match Log4r levels, levels may have been customized"
|
84
|
+
end
|
85
|
+
|
86
|
+
Log4r::Logger[STANDARD_LOGGER].level = WARN
|
63
87
|
Log4r::Logger[STANDARD_LOGGER].trace = true
|
64
88
|
|
65
89
|
Log4r::StderrOutputter.new :console
|
data/lib/win32/autogui/window.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
require 'windows/window'
|
3
3
|
require 'windows/window/message'
|
4
|
+
require 'windows/window/classes'
|
4
5
|
require 'win32/autogui/windows/window'
|
5
6
|
|
6
7
|
module Autogui
|
7
8
|
|
9
|
+
class FindTimeout < Timeout::Error; end
|
10
|
+
|
8
11
|
# Enumerate desktop child windows
|
9
12
|
#
|
10
13
|
# Start at the desktop and work down through all the child windows
|
@@ -12,6 +15,35 @@ module Autogui
|
|
12
15
|
class EnumerateDesktopWindows
|
13
16
|
include Enumerable
|
14
17
|
include Windows::Window
|
18
|
+
include Autogui::Logging
|
19
|
+
|
20
|
+
# @return [Number] timeout (0) in seconds
|
21
|
+
attr_accessor :timeout
|
22
|
+
|
23
|
+
# @option options [Number] :timeout (0) maximum seconds to continue enumerating windows
|
24
|
+
def initialize(options ={})
|
25
|
+
@timeout = options[:timeout] || 0
|
26
|
+
end
|
27
|
+
|
28
|
+
# redefine Enumerable's find to continue looping until a timeout reached
|
29
|
+
def find(ifnone = nil)
|
30
|
+
return to_enum :find, ifnone unless block_given?
|
31
|
+
|
32
|
+
begin
|
33
|
+
Timeout.timeout(timeout, FindTimeout) do
|
34
|
+
begin
|
35
|
+
each { |o| return o if yield(o) }
|
36
|
+
sleep 0.2 unless (timeout == 0)
|
37
|
+
#logger.debug "find looping" unless (timeout == 0)
|
38
|
+
end until (timeout == 0)
|
39
|
+
end
|
40
|
+
rescue FindTimeout
|
41
|
+
logger.warn "EnumerateDesktopWindows.find timeout"
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
ifnone.call if ifnone
|
46
|
+
end
|
15
47
|
|
16
48
|
def each
|
17
49
|
child_after = 0
|
@@ -50,6 +82,7 @@ def each
|
|
50
82
|
class Window
|
51
83
|
include Windows::Window # instance methods from windows-pr gem
|
52
84
|
include Windows::Window::Message # PostMessage and constants
|
85
|
+
include Windows::Window::Classes # GetClassName
|
53
86
|
include Autogui::Logging
|
54
87
|
include Autogui::Input
|
55
88
|
|
@@ -97,11 +130,11 @@ def wait_for_close(options={})
|
|
97
130
|
end
|
98
131
|
end
|
99
132
|
|
100
|
-
# @return [String] the
|
133
|
+
# @return [String] the Windows ClassName
|
101
134
|
#
|
102
135
|
def window_class
|
103
136
|
buffer = "\0" * 255
|
104
|
-
length =
|
137
|
+
length = GetClassName(handle, buffer, buffer.length)
|
105
138
|
length == 0 ? '' : buffer[0..length - 1]
|
106
139
|
end
|
107
140
|
|
@@ -8,7 +8,8 @@ class Calculator < Autogui::Application
|
|
8
8
|
def initialize(options = {})
|
9
9
|
defaults = {
|
10
10
|
:name => "calc",
|
11
|
-
:title => "Calculator"
|
11
|
+
:title => "Calculator",
|
12
|
+
:logger_level => Autogui::Logging::DEBUG
|
12
13
|
}
|
13
14
|
super defaults.merge(options)
|
14
15
|
end
|
@@ -19,12 +20,12 @@ def edit_window
|
|
19
20
|
end
|
20
21
|
|
21
22
|
# About dialog, hotkey (VK_MENU, VK_H, VK_A)
|
22
|
-
def dialog_about
|
23
|
-
Autogui::EnumerateDesktopWindows.new.find do |w|
|
23
|
+
def dialog_about(options = {})
|
24
|
+
Autogui::EnumerateDesktopWindows.new(options).find do |w|
|
24
25
|
w.title.match(/About Calculator/) && (w.pid == pid)
|
25
26
|
end
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
# the 'CE' button
|
29
30
|
def clear_entry
|
30
31
|
set_focus
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
include Autogui::Input
|
4
|
+
include Autogui::Logging
|
4
5
|
|
5
6
|
describe Autogui::Application do
|
6
7
|
|
@@ -109,7 +110,7 @@
|
|
109
110
|
it "should copy the edit window" do
|
110
111
|
@calculator.set_focus
|
111
112
|
type_in("3002")
|
112
|
-
@calculator.edit_window.text.strip.should
|
113
|
+
@calculator.edit_window.text.strip.should match(/3,?002\./)
|
113
114
|
@calculator.edit_window.set_focus
|
114
115
|
keystroke(VK_CONTROL, VK_C)
|
115
116
|
@calculator.clipboard.text.should == "3002"
|
@@ -122,7 +123,7 @@
|
|
122
123
|
@calculator.clipboard.text = "12345"
|
123
124
|
@calculator.edit_window.text.strip.should == "0."
|
124
125
|
keystroke(VK_CONTROL, VK_V)
|
125
|
-
@calculator.edit_window.text.strip.should
|
126
|
+
@calculator.edit_window.text.strip.should match(/12,?345\./)
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
@@ -3,6 +3,10 @@
|
|
3
3
|
include Autogui::Logging
|
4
4
|
|
5
5
|
describe Autogui::Logging do
|
6
|
+
before(:all) do
|
7
|
+
# quiet console output, we are only testing the file output
|
8
|
+
logger.remove(:console)
|
9
|
+
end
|
6
10
|
before(:each) do
|
7
11
|
FileUtils.rm_rf(current_dir)
|
8
12
|
@logfile = "autogui.log"
|
@@ -13,50 +17,63 @@
|
|
13
17
|
@application.close(:wait_for_close => true) if @application.running?
|
14
18
|
@application.should_not be_running
|
15
19
|
end
|
20
|
+
logger.remove(:logfile)
|
21
|
+
end
|
22
|
+
after(:all) do
|
23
|
+
logger.add(:console)
|
16
24
|
end
|
17
25
|
|
18
26
|
describe "to file" do
|
27
|
+
|
19
28
|
it "should truncate the log on create" do
|
20
|
-
|
29
|
+
get_file_contents(@logfile).should == 'the quick brown fox'
|
21
30
|
@application = Calculator.new :logger_logfile => fullpath(@logfile)
|
22
|
-
|
31
|
+
get_file_contents(@logfile).should == ''
|
23
32
|
end
|
24
33
|
|
25
34
|
it "should not log unless 'logger.logfile' is set" do
|
26
35
|
@application = Calculator.new
|
27
|
-
|
28
|
-
logger.warn "warning message
|
29
|
-
|
36
|
+
get_file_contents(@logfile).should == 'the quick brown fox'
|
37
|
+
logger.warn "warning message 0"
|
38
|
+
get_file_contents(@logfile).should == 'the quick brown fox'
|
30
39
|
logger.logfile = fullpath(@logfile)
|
31
|
-
logger.warn "warning message
|
32
|
-
|
40
|
+
logger.warn "warning message 1"
|
41
|
+
get_file_contents(@logfile).should match(/warning message 1/)
|
42
|
+
logger.logfile = nil
|
43
|
+
logger.warn "warning message 2"
|
44
|
+
get_file_contents(@logfile).should_not match(/warning message 2/)
|
33
45
|
end
|
34
46
|
|
35
47
|
it "should log warnings" do
|
36
48
|
@application = Calculator.new :logger_logfile => fullpath(@logfile)
|
37
|
-
|
49
|
+
get_file_contents(@logfile).should == ''
|
38
50
|
logger.warn "warning message here"
|
39
|
-
|
51
|
+
get_file_contents(@logfile).should match(/warning message here/)
|
40
52
|
end
|
41
53
|
|
42
54
|
it "should log application raised exceptions via 'application.raise_error'" do
|
43
|
-
|
55
|
+
get_file_contents(@logfile).should == 'the quick brown fox'
|
44
56
|
begin
|
45
57
|
@application = Calculator.new :logger_logfile => fullpath(@logfile), :name => nil
|
46
58
|
rescue
|
47
|
-
# expected exception
|
48
59
|
end
|
49
|
-
|
60
|
+
get_file_contents(@logfile).should match(/application name not set/)
|
50
61
|
end
|
51
62
|
|
52
63
|
it "should log debug messages when debug level set" do
|
53
64
|
@application = Calculator.new :logger_logfile => fullpath(@logfile)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
65
|
+
level_save = logger.level
|
66
|
+
begin
|
67
|
+
logger.level = Autogui::Logging::WARN
|
68
|
+
get_file_contents(@logfile).should == ''
|
69
|
+
logger.debug "debug message here 1"
|
70
|
+
get_file_contents(@logfile).should_not match(/debug message here 1/)
|
71
|
+
logger.level = Autogui::Logging::DEBUG
|
72
|
+
logger.debug "debug message here 2"
|
73
|
+
get_file_contents(@logfile).should match(/debug message here 2/)
|
74
|
+
ensure
|
75
|
+
logger.level = level_save
|
76
|
+
end
|
60
77
|
end
|
61
78
|
end
|
62
79
|
|