rautomation 0.0.4 → 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/.rspec +2 -0
- data/History.rdoc +9 -0
- data/LICENSE +1 -1
- data/README.rdoc +10 -2
- data/Rakefile +5 -10
- data/VERSION +1 -1
- data/lib/rautomation.rb +1 -1
- data/lib/rautomation/{implementations → adapter}/autoit.rb +0 -0
- data/lib/rautomation/{implementations → adapter}/autoit/button.rb +5 -4
- data/lib/rautomation/{implementations → adapter}/autoit/locators.rb +3 -2
- data/lib/rautomation/{implementations → adapter}/autoit/text_field.rb +7 -4
- data/lib/rautomation/{implementations → adapter}/autoit/window.rb +11 -10
- data/lib/rautomation/adapter/ffi.rb +12 -0
- data/lib/rautomation/adapter/ffi/button.rb +38 -0
- data/lib/rautomation/adapter/ffi/constants.rb +32 -0
- data/lib/rautomation/adapter/ffi/functions.rb +222 -0
- data/lib/rautomation/adapter/ffi/locators.rb +16 -0
- data/lib/rautomation/adapter/ffi/text_field.rb +40 -0
- data/lib/rautomation/adapter/ffi/window.rb +102 -0
- data/lib/rautomation/{implementations → adapter}/helper.rb +5 -4
- data/lib/rautomation/button.rb +1 -1
- data/lib/rautomation/text_field.rb +1 -1
- data/lib/rautomation/window.rb +21 -17
- data/rautomation.gemspec +20 -13
- data/spec/spec_helper.rb +31 -9
- data/spec/text_field_spec.rb +10 -10
- data/spec/window_spec.rb +7 -2
- metadata +23 -17
- data/spec/spec.opts +0 -2
data/.rspec
ADDED
data/History.rdoc
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== Version 0.1.0 / 2010-12-14
|
2
|
+
|
3
|
+
* added new default adapter for Windows: FFI
|
4
|
+
* changes for AutoIt adapter:
|
5
|
+
- added 0-based :index locator for window locators to search for windows with the same criteria.
|
6
|
+
- renamed text_field and button locator :instance to :index instead.
|
7
|
+
- :class_name locator is not allowed anymore. Use :class and :index together instead.
|
8
|
+
- use :value for button locator instead of :text
|
9
|
+
|
1
10
|
=== Version 0.0.4 / 2010-10-27
|
2
11
|
|
3
12
|
* most Window, Button and TextField methods wait until the object exists.
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -12,7 +12,7 @@ RAutomation aims to provide:
|
|
12
12
|
* Easy to use and user-friendly API (inspired by Watir http://www.watir.com).
|
13
13
|
* Cross-platform compatibility
|
14
14
|
* Easy extensibility - have some application, which uses some specialized technology, but isn't supported by RAutomation?
|
15
|
-
You can get dirty and create new
|
15
|
+
You can get dirty and create a new adapter for RAutomation right away!
|
16
16
|
|
17
17
|
== USAGE
|
18
18
|
|
@@ -24,11 +24,14 @@ RAutomation aims to provide:
|
|
24
24
|
window.title # => "blah blah part Of the title blah"
|
25
25
|
window.text # => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies..."
|
26
26
|
|
27
|
-
window.text_field(:
|
27
|
+
window.text_field(:class => "Edit", :index => 0).set "hello, world!"
|
28
28
|
button = window.button(:text => "&Save")
|
29
29
|
button.exists? # => true
|
30
30
|
button.click
|
31
31
|
|
32
|
+
window2 = RAutomation::Window.new(:title => "Other Title", :adapter => :autoit) # use AutoIt adapter
|
33
|
+
window2.WinClose("[TITLE:Other Title]") # use adapter's (in this case AutoIt's) internal methods directly
|
34
|
+
|
32
35
|
See more examples in spec directory!
|
33
36
|
|
34
37
|
== INSTALL
|
@@ -38,6 +41,11 @@ See more examples in spec directory!
|
|
38
41
|
1. gem install rautomation
|
39
42
|
2. create some script and run it
|
40
43
|
|
44
|
+
Available adapters:
|
45
|
+
* :ffi - uses Windows API directly with FFI (default)
|
46
|
+
* :autoit - uses AutoIt for automation
|
47
|
+
|
48
|
+
When using AutoIt adapter:
|
41
49
|
You might need administrative privileges if running for the first time and you haven't installed AutoIt before!
|
42
50
|
|
43
51
|
=== Linux
|
data/Rakefile
CHANGED
@@ -13,26 +13,21 @@ RAutomation aims to provide:
|
|
13
13
|
* Easy to use and user-friendly API (inspired by Watir http://www.watir.com).
|
14
14
|
* Cross-platform compatibility
|
15
15
|
* Easy extensibility - have some application, which uses some specialized technology, but isn't supported by RAutomation?
|
16
|
-
You can get dirty and create new
|
16
|
+
You can get dirty and create a new adapter for RAutomation!}
|
17
17
|
gem.email = "jarmo.p@gmail.com"
|
18
18
|
gem.homepage = "http://github.com/jarmo/RAutomation"
|
19
19
|
gem.authors = ["Jarmo Pertman"]
|
20
|
-
gem.add_development_dependency "rspec", "
|
20
|
+
gem.add_development_dependency "rspec", "~>2.3"
|
21
21
|
end
|
22
22
|
Jeweler::GemcutterTasks.new
|
23
23
|
rescue LoadError
|
24
24
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
25
|
end
|
26
26
|
|
27
|
-
require '
|
28
|
-
|
29
|
-
spec.libs << 'lib' << 'spec'
|
30
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
31
|
-
end
|
27
|
+
require 'rspec/core/rake_task'
|
28
|
+
RSpec::Core::RakeTask.new(:spec)
|
32
29
|
|
33
|
-
|
34
|
-
spec.libs << 'lib' << 'spec'
|
35
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
31
|
spec.rcov = true
|
37
32
|
end
|
38
33
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/rautomation.rb
CHANGED
File without changes
|
@@ -1,17 +1,18 @@
|
|
1
1
|
module RAutomation
|
2
|
-
module
|
3
|
-
module
|
2
|
+
module Adapter
|
3
|
+
module Autoit
|
4
4
|
class Button
|
5
5
|
include WaitHelper
|
6
6
|
include Locators
|
7
7
|
|
8
8
|
# Special-cased locators
|
9
9
|
LOCATORS = {
|
10
|
-
:
|
10
|
+
[:class, Regexp] => :regexpclass,
|
11
|
+
:index => :instance,
|
11
12
|
:value => :text
|
12
13
|
}
|
13
14
|
|
14
|
-
# Possible locators are :
|
15
|
+
# Possible locators are :value, :id, :class and :index.
|
15
16
|
def initialize(window, locators)
|
16
17
|
@window = window
|
17
18
|
extract(locators)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module RAutomation
|
2
|
-
module
|
3
|
-
module
|
2
|
+
module Adapter
|
3
|
+
module Autoit
|
4
4
|
module Locators
|
5
5
|
|
6
6
|
private
|
@@ -8,6 +8,7 @@ module RAutomation
|
|
8
8
|
def extract(locators) #:nodoc:
|
9
9
|
@locators = "[#{locators.map do |locator, value|
|
10
10
|
locator_key = self.class::LOCATORS[locator] || self.class::LOCATORS[[locator, value.class]]
|
11
|
+
value = value.to_i + 1 if locator == :index # use 0-based indexing
|
11
12
|
value = value.to_s(16) if locator == :hwnd
|
12
13
|
"#{(locator_key || locator)}:#{value}"
|
13
14
|
end.join(";")}]"
|
@@ -1,14 +1,17 @@
|
|
1
1
|
module RAutomation
|
2
|
-
module
|
3
|
-
module
|
2
|
+
module Adapter
|
3
|
+
module Autoit
|
4
4
|
class TextField
|
5
5
|
include WaitHelper
|
6
6
|
include Locators
|
7
7
|
|
8
8
|
# Special-cased locators
|
9
|
-
LOCATORS = {
|
9
|
+
LOCATORS = {
|
10
|
+
[:class, Regexp] => :regexpclass,
|
11
|
+
:index => :instance
|
12
|
+
}
|
10
13
|
|
11
|
-
# Possible locators are :id, :class
|
14
|
+
# Possible locators are :id, :class and :index.
|
12
15
|
def initialize(window, locators)
|
13
16
|
@window = window
|
14
17
|
extract(locators)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module RAutomation
|
2
|
-
module
|
3
|
-
module
|
2
|
+
module Adapter
|
3
|
+
module Autoit
|
4
4
|
class Window
|
5
5
|
include WaitHelper
|
6
6
|
include Locators
|
@@ -25,11 +25,13 @@ module RAutomation
|
|
25
25
|
attr_reader :locators
|
26
26
|
|
27
27
|
# Special-cased locators
|
28
|
-
LOCATORS = {
|
29
|
-
|
30
|
-
|
28
|
+
LOCATORS = {
|
29
|
+
[:title, Regexp] => :regexptitle,
|
30
|
+
:index => :instance,
|
31
|
+
:hwnd => :handle
|
32
|
+
}
|
31
33
|
|
32
|
-
# Possible locators are :title, :text, :hwnd and :
|
34
|
+
# Possible locators are :title, :text, :hwnd, :class and :index.
|
33
35
|
def initialize(locators)
|
34
36
|
@hwnd = locators[:hwnd]
|
35
37
|
@locator_text = locators.delete(:text)
|
@@ -80,11 +82,11 @@ module RAutomation
|
|
80
82
|
sleep 1
|
81
83
|
end
|
82
84
|
|
83
|
-
def minimized?
|
85
|
+
def minimized? #:nodoc:
|
84
86
|
@@autoit.WinGetState(locator_hwnd) & 16 == 16
|
85
87
|
end
|
86
88
|
|
87
|
-
def restore
|
89
|
+
def restore #:nodoc:
|
88
90
|
@@autoit.WinSetState(locator_hwnd, "", @@autoit.SW_RESTORE)
|
89
91
|
sleep 1
|
90
92
|
end
|
@@ -94,7 +96,6 @@ module RAutomation
|
|
94
96
|
# Refer to AutoIt documentation for keys syntax.
|
95
97
|
def send_keys(keys)
|
96
98
|
wait_until do
|
97
|
-
restore if minimized?
|
98
99
|
activate
|
99
100
|
active?
|
100
101
|
end
|
@@ -115,7 +116,7 @@ module RAutomation
|
|
115
116
|
end
|
116
117
|
|
117
118
|
def method_missing(name, *args) #:nodoc:
|
118
|
-
@@autoit.
|
119
|
+
@@autoit.send(name, *args)
|
119
120
|
end
|
120
121
|
|
121
122
|
# Used internally.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
begin
|
2
|
+
gem "ffi", "0.6.3"
|
3
|
+
rescue Gem::LoadError
|
4
|
+
raise Gem::LoadError, "Unable to load FFI gem. Install it with:\n\tgem install ffi -v 0.6.3"
|
5
|
+
end
|
6
|
+
require "ffi"
|
7
|
+
require File.dirname(__FILE__) + "/ffi/constants"
|
8
|
+
require File.dirname(__FILE__) + "/ffi/functions"
|
9
|
+
require File.dirname(__FILE__) + "/ffi/locators"
|
10
|
+
require File.dirname(__FILE__) + "/ffi/window"
|
11
|
+
require File.dirname(__FILE__) + "/ffi/button"
|
12
|
+
require File.dirname(__FILE__) + "/ffi/text_field"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
class Button
|
5
|
+
include WaitHelper
|
6
|
+
include Locators
|
7
|
+
|
8
|
+
# Possible locators are :value, :id, :class and :index.
|
9
|
+
def initialize(window, locators)
|
10
|
+
@window = window
|
11
|
+
extract(locators)
|
12
|
+
end
|
13
|
+
|
14
|
+
def click #:nodoc:
|
15
|
+
clicked = false
|
16
|
+
wait_until do
|
17
|
+
hwnd = Functions.control_hwnd(@window.hwnd, @locators)
|
18
|
+
@window.activate
|
19
|
+
@window.active? &&
|
20
|
+
Functions.set_control_focus(hwnd) &&
|
21
|
+
Functions.control_click(hwnd) &&
|
22
|
+
clicked = true # is clicked at least once
|
23
|
+
clicked && !exists?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def value #:nodoc:
|
28
|
+
Functions.control_value(Functions.control_hwnd(@window.hwnd, @locators))
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists? #:nodoc:
|
32
|
+
!!Functions.control_hwnd(@window.hwnd, @locators)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
module Constants
|
5
|
+
WM_GETTEXT = 0xD
|
6
|
+
WM_SETTEXT = 0xC
|
7
|
+
WM_GETTEXTLENGTH = 0xE
|
8
|
+
WM_CLOSE = 0x10
|
9
|
+
|
10
|
+
SW_MAXIMIZE = 3
|
11
|
+
SW_MINIMIZE = 6
|
12
|
+
SW_RESTORE = 9
|
13
|
+
|
14
|
+
SMTO_ABORTIFHUNG = 0x2
|
15
|
+
|
16
|
+
STANDARD_RIGHTS_REQUIRED = 0xF0000
|
17
|
+
SYNCHRONIZE = 0x100000
|
18
|
+
PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF
|
19
|
+
|
20
|
+
BM_CLICK = 0xF5
|
21
|
+
|
22
|
+
# keybd_event constants
|
23
|
+
# http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx
|
24
|
+
#
|
25
|
+
# keycodes themselves are at:
|
26
|
+
# http://msdn.microsoft.com/en-us/library/dd375731(v=VS.85).aspx
|
27
|
+
KEYEVENTF_EXTENDEDKEY = 0x1
|
28
|
+
KEYEVENTF_KEYUP = 0x2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
module Functions
|
5
|
+
extend FFI::Library
|
6
|
+
|
7
|
+
ffi_lib 'user32', 'kernel32'
|
8
|
+
ffi_convention :stdcall
|
9
|
+
|
10
|
+
callback :enum_callback, [:long, :pointer], :bool
|
11
|
+
|
12
|
+
# user32
|
13
|
+
attach_function :enum_windows, :EnumWindows,
|
14
|
+
[:enum_callback, :pointer], :long
|
15
|
+
attach_function :enum_child_windows, :EnumChildWindows,
|
16
|
+
[:long, :enum_callback, :pointer], :long
|
17
|
+
attach_function :_close_window, :CloseWindow,
|
18
|
+
[:long], :bool
|
19
|
+
attach_function :minimized, :IsIconic,
|
20
|
+
[:long], :bool
|
21
|
+
attach_function :_window_title, :GetWindowTextA,
|
22
|
+
[:long, :pointer, :int], :int
|
23
|
+
attach_function :window_title_length, :GetWindowTextLengthA,
|
24
|
+
[:long], :int
|
25
|
+
attach_function :window_exists, :IsWindow,
|
26
|
+
[:long], :bool
|
27
|
+
attach_function :_window_class, :GetClassNameA,
|
28
|
+
[:long, :pointer, :int], :int
|
29
|
+
attach_function :window_visible, :IsWindowVisible,
|
30
|
+
[:long], :bool
|
31
|
+
attach_function :show_window, :ShowWindow,
|
32
|
+
[:long, :int], :bool
|
33
|
+
attach_function :send_message, :SendMessageA,
|
34
|
+
[:long, :uint, :uint, :pointer], :long
|
35
|
+
attach_function :send_message_timeout, :SendMessageTimeoutA,
|
36
|
+
[:long, :uint, :uint, :pointer, :uint, :uint, :pointer], :bool
|
37
|
+
attach_function :post_message, :PostMessageA,
|
38
|
+
[:long, :uint, :uint, :pointer], :bool
|
39
|
+
attach_function :window_thread_process_id, :GetWindowThreadProcessId,
|
40
|
+
[:long, :pointer], :long
|
41
|
+
attach_function :attach_thread_input, :AttachThreadInput,
|
42
|
+
[:long, :long, :bool], :bool
|
43
|
+
attach_function :set_foreground_window, :SetForegroundWindow,
|
44
|
+
[:long], :bool
|
45
|
+
attach_function :bring_window_to_top, :BringWindowToTop,
|
46
|
+
[:long], :bool
|
47
|
+
attach_function :set_active_window, :SetActiveWindow,
|
48
|
+
[:long], :long
|
49
|
+
attach_function :foreground_window, :GetForegroundWindow,
|
50
|
+
[], :long
|
51
|
+
attach_function :send_key, :keybd_event,
|
52
|
+
[:uchar, :uchar, :int, :pointer], :void
|
53
|
+
attach_function :control_id, :GetDlgCtrlID,
|
54
|
+
[:long], :int
|
55
|
+
attach_function :_set_control_focus, :SetFocus,
|
56
|
+
[:long], :long
|
57
|
+
|
58
|
+
# kernel32
|
59
|
+
attach_function :open_process, :OpenProcess,
|
60
|
+
[:int, :bool, :int], :long
|
61
|
+
attach_function :terminate_process, :TerminateProcess,
|
62
|
+
[:long, :uint], :bool
|
63
|
+
attach_function :close_handle, :CloseHandle,
|
64
|
+
[:long], :bool
|
65
|
+
|
66
|
+
class << self
|
67
|
+
def window_title(hwnd)
|
68
|
+
title_length = window_title_length(hwnd) + 1
|
69
|
+
title = FFI::MemoryPointer.new :char, title_length
|
70
|
+
_window_title(hwnd, title, title_length)
|
71
|
+
title.read_string
|
72
|
+
end
|
73
|
+
|
74
|
+
def window_text(hwnd)
|
75
|
+
found_text = ""
|
76
|
+
window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |child_hwnd, _|
|
77
|
+
found_text << text_for(child_hwnd)
|
78
|
+
true
|
79
|
+
end
|
80
|
+
enum_child_windows(hwnd, window_callback, nil)
|
81
|
+
found_text
|
82
|
+
end
|
83
|
+
|
84
|
+
def window_hwnd(locators)
|
85
|
+
find_hwnd(locators) do |hwnd|
|
86
|
+
window_visible(hwnd) && !window_text(hwnd).empty? &&
|
87
|
+
locators_match?(locators, window_properties(hwnd, locators))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def window_class(hwnd)
|
92
|
+
class_name = FFI::MemoryPointer.new :char, 512
|
93
|
+
_window_class(hwnd, class_name, 512)
|
94
|
+
class_name.read_string
|
95
|
+
end
|
96
|
+
|
97
|
+
alias_method :control_class, :window_class
|
98
|
+
|
99
|
+
def close_window(hwnd)
|
100
|
+
_close_window(hwnd)
|
101
|
+
closed = send_message_timeout(hwnd, Constants::WM_CLOSE,
|
102
|
+
0, nil, Constants::SMTO_ABORTIFHUNG, 1000, nil)
|
103
|
+
# force it to close
|
104
|
+
unless closed
|
105
|
+
pid = FFI::MemoryPointer.new :int
|
106
|
+
window_thread_process_id(hwnd, pid)
|
107
|
+
process_hwnd = open_process(Constants::PROCESS_ALL_ACCESS, false, pid.read_int)
|
108
|
+
terminate_process(process_hwnd, 0)
|
109
|
+
close_handle(process_hwnd)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def activate_window(hwnd)
|
114
|
+
set_foreground_window(hwnd)
|
115
|
+
set_active_window(hwnd)
|
116
|
+
bring_window_to_top(hwnd)
|
117
|
+
within_foreground_thread(hwnd) do
|
118
|
+
set_foreground_window(hwnd)
|
119
|
+
set_active_window(hwnd)
|
120
|
+
bring_window_to_top(hwnd)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def control_hwnd(window_hwnd, locators)
|
125
|
+
find_hwnd(locators, window_hwnd) do |hwnd|
|
126
|
+
locators_match?(locators, control_properties(hwnd, locators))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def control_value(control_hwnd)
|
131
|
+
text_for(control_hwnd)
|
132
|
+
end
|
133
|
+
|
134
|
+
def control_click(control_hwnd)
|
135
|
+
post_message(control_hwnd, Constants::BM_CLICK, 0, nil)
|
136
|
+
end
|
137
|
+
|
138
|
+
def set_control_focus(control_hwnd)
|
139
|
+
within_foreground_thread control_hwnd do
|
140
|
+
_set_control_focus(control_hwnd)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_control_text(control_hwnd, text)
|
145
|
+
send_message(control_hwnd, Constants::WM_SETTEXT, 0, text)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def within_foreground_thread(hwnd)
|
151
|
+
foreground_thread = window_thread_process_id(foreground_window, nil)
|
152
|
+
other_thread = window_thread_process_id(hwnd, nil)
|
153
|
+
attach_thread_input(foreground_thread, other_thread, true) unless other_thread == foreground_thread
|
154
|
+
yield
|
155
|
+
ensure
|
156
|
+
attach_thread_input(foreground_thread, other_thread, false) unless other_thread == foreground_thread
|
157
|
+
end
|
158
|
+
|
159
|
+
def window_properties(hwnd, locators)
|
160
|
+
element_properties(:window, hwnd, locators)
|
161
|
+
end
|
162
|
+
|
163
|
+
def control_properties(hwnd, locators)
|
164
|
+
element_properties(:control, hwnd, locators)
|
165
|
+
end
|
166
|
+
|
167
|
+
def element_properties(type, hwnd, locators)
|
168
|
+
locators.inject({}) do |properties, locator|
|
169
|
+
properties[locator[0]] = self.send("#{type}_#{locator[0]}", hwnd) unless locator[0] == :index
|
170
|
+
properties
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def locators_match?(locators, properties)
|
175
|
+
locators.all? do |locator, value|
|
176
|
+
if locator == :index
|
177
|
+
true
|
178
|
+
elsif value.is_a?(Regexp)
|
179
|
+
properties[locator] =~ value
|
180
|
+
else
|
181
|
+
properties[locator] == value
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def find_hwnd(locators, window_hwnd = nil)
|
187
|
+
found_hwnd = nil
|
188
|
+
found_index = -1
|
189
|
+
window_callback = FFI::Function.new(:bool, [:long, :pointer], {:convention => :stdcall}) do |hwnd, _|
|
190
|
+
if yield(hwnd)
|
191
|
+
found_index += 1
|
192
|
+
|
193
|
+
if locators[:index]
|
194
|
+
found_hwnd = hwnd if locators[:index] == found_index
|
195
|
+
else
|
196
|
+
found_hwnd = hwnd
|
197
|
+
end
|
198
|
+
end
|
199
|
+
!found_hwnd
|
200
|
+
end
|
201
|
+
|
202
|
+
unless window_hwnd
|
203
|
+
enum_windows(window_callback, nil)
|
204
|
+
else
|
205
|
+
enum_child_windows(window_hwnd, window_callback, nil)
|
206
|
+
end
|
207
|
+
|
208
|
+
found_hwnd
|
209
|
+
end
|
210
|
+
|
211
|
+
def text_for(hwnd)
|
212
|
+
text_length = send_message(hwnd, Constants::WM_GETTEXTLENGTH, 0, nil) + 1
|
213
|
+
text = FFI::MemoryPointer.new :char, text_length
|
214
|
+
send_message(hwnd, Constants::WM_GETTEXT, text_length, text)
|
215
|
+
text.read_string
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
module Locators
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def extract(locators) #:nodoc:
|
9
|
+
locators[:id] = locators[:id].to_i if locators[:id]
|
10
|
+
locators[:index] = locators[:index].to_i if locators[:index]
|
11
|
+
@locators = locators
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
class TextField
|
5
|
+
include WaitHelper
|
6
|
+
include Locators
|
7
|
+
|
8
|
+
# Possible locators are :id, :class and :index.
|
9
|
+
def initialize(window, locators)
|
10
|
+
@window = window
|
11
|
+
extract(locators)
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(text) #:nodoc:
|
15
|
+
wait_until do
|
16
|
+
hwnd = Functions.control_hwnd(@window.hwnd, @locators)
|
17
|
+
@window.activate
|
18
|
+
@window.active? &&
|
19
|
+
Functions.set_control_focus(hwnd) &&
|
20
|
+
Functions.set_control_text(hwnd, text) &&
|
21
|
+
value == text
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear #:nodoc:
|
26
|
+
set ""
|
27
|
+
end
|
28
|
+
|
29
|
+
def value #:nodoc:
|
30
|
+
Functions.control_value(Functions.control_hwnd(@window.hwnd, @locators))
|
31
|
+
end
|
32
|
+
|
33
|
+
def exists? #:nodoc:
|
34
|
+
!!Functions.control_hwnd(@window.hwnd, @locators)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module RAutomation
|
2
|
+
module Adapter
|
3
|
+
module Ffi
|
4
|
+
class Window
|
5
|
+
include WaitHelper
|
6
|
+
include Locators
|
7
|
+
|
8
|
+
attr_reader :locators
|
9
|
+
|
10
|
+
# Possible locators are :title, :text, :hwnd and :class.
|
11
|
+
def initialize(locators)
|
12
|
+
@hwnd = locators.delete(:hwnd)
|
13
|
+
extract(locators)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns handle of the found window.
|
17
|
+
# Searches only for visible windows with having some text at all.
|
18
|
+
def hwnd #:nodoc:
|
19
|
+
@hwnd ||= Functions.window_hwnd(@locators)
|
20
|
+
end
|
21
|
+
|
22
|
+
def title #:nodoc:
|
23
|
+
Functions.window_title(hwnd)
|
24
|
+
end
|
25
|
+
|
26
|
+
def activate #:nodoc:
|
27
|
+
return if !exists? || active?
|
28
|
+
restore if minimized?
|
29
|
+
Functions.activate_window(hwnd)
|
30
|
+
sleep 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def active? #:nodoc:
|
34
|
+
exists? && Functions.foreground_window == hwnd
|
35
|
+
end
|
36
|
+
|
37
|
+
def text #:nodoc:
|
38
|
+
Functions.window_text(hwnd)
|
39
|
+
end
|
40
|
+
|
41
|
+
def exists? #:nodoc:
|
42
|
+
result = hwnd && Functions.window_exists(hwnd)
|
43
|
+
!!result
|
44
|
+
end
|
45
|
+
|
46
|
+
def visible? #:nodoc:
|
47
|
+
Functions.window_visible(hwnd)
|
48
|
+
end
|
49
|
+
|
50
|
+
def maximize #:nodoc:
|
51
|
+
Functions.show_window(hwnd, Constants::SW_MAXIMIZE)
|
52
|
+
sleep 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def minimize #:nodoc:
|
56
|
+
Functions.show_window(hwnd, Constants::SW_MINIMIZE)
|
57
|
+
sleep 1
|
58
|
+
end
|
59
|
+
|
60
|
+
def minimized? #:nodoc:
|
61
|
+
Functions.minimized(hwnd)
|
62
|
+
end
|
63
|
+
|
64
|
+
def restore #:nodoc:
|
65
|
+
Functions.show_window(hwnd, Constants::SW_RESTORE)
|
66
|
+
sleep 1
|
67
|
+
end
|
68
|
+
|
69
|
+
# Activates the Window and sends keys to it.
|
70
|
+
#
|
71
|
+
# Refer to MSDN documentation at http://msdn.microsoft.com/en-us/library/dd375731(v=VS.85).aspx
|
72
|
+
# for keycodes.
|
73
|
+
def send_keys(*keys)
|
74
|
+
keys.each do |key|
|
75
|
+
wait_until do
|
76
|
+
activate
|
77
|
+
active?
|
78
|
+
end
|
79
|
+
Functions.send_key(key, 0, 0, nil)
|
80
|
+
Functions.send_key(key, 0, Constants::KEYEVENTF_KEYUP, nil)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def close #:nodoc:
|
85
|
+
Functions.close_window(hwnd)
|
86
|
+
end
|
87
|
+
|
88
|
+
def button(locator) #:nodoc:
|
89
|
+
Button.new(self, locator)
|
90
|
+
end
|
91
|
+
|
92
|
+
def text_field(locator) #:nodoc:
|
93
|
+
TextField.new(self, locator)
|
94
|
+
end
|
95
|
+
|
96
|
+
def method_missing(name, *args) #:nodoc:
|
97
|
+
Functions.respond_to?(name) ? Functions.send(name, *args) : super
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module RAutomation
|
2
|
-
module
|
3
|
-
autoload :
|
2
|
+
module Adapter
|
3
|
+
autoload :Autoit, File.dirname(__FILE__) + "/autoit.rb"
|
4
|
+
autoload :Ffi, File.dirname(__FILE__) + "/ffi.rb"
|
4
5
|
|
5
6
|
module Helper
|
6
7
|
extend self
|
7
8
|
|
8
9
|
# @private
|
9
|
-
def
|
10
|
+
def default_adapter
|
10
11
|
case RUBY_PLATFORM
|
11
12
|
when /mswin|msys|mingw32/
|
12
|
-
|
13
|
+
:ffi
|
13
14
|
else
|
14
15
|
raise "unsupported platform for RAutomation: #{RUBY_PLATFORM}"
|
15
16
|
end
|
data/lib/rautomation/button.rb
CHANGED
@@ -35,7 +35,7 @@ module RAutomation
|
|
35
35
|
def wait_until_exists
|
36
36
|
WaitHelper.wait_until(RAutomation::Window.wait_timeout) {exists?}
|
37
37
|
rescue WaitHelper::TimeoutError
|
38
|
-
raise UnknownButtonException
|
38
|
+
raise UnknownButtonException, "Button #{@locators.inspect} doesn't exist on window #{@window.locators.inspect}!"
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -43,7 +43,7 @@ module RAutomation
|
|
43
43
|
def wait_until_exists
|
44
44
|
WaitHelper.wait_until(RAutomation::Window.wait_timeout) {exists?}
|
45
45
|
rescue WaitHelper::TimeoutError
|
46
|
-
raise UnknownTextFieldException
|
46
|
+
raise UnknownTextFieldException, "Text field #{@locators.inspect} doesn't exist on window #{@window.locators.inspect}!" unless exists?
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
data/lib/rautomation/window.rb
CHANGED
@@ -7,13 +7,13 @@ module RAutomation
|
|
7
7
|
end
|
8
8
|
|
9
9
|
class Window
|
10
|
-
include
|
10
|
+
include Adapter::Helper
|
11
11
|
|
12
|
-
attr_reader :
|
12
|
+
attr_reader :adapter
|
13
13
|
|
14
14
|
# Creates a new Window object using the _locators_ Hash parameter.
|
15
15
|
#
|
16
|
-
# Possible Window _locators_ may depend of the used platform and
|
16
|
+
# Possible Window _locators_ may depend of the used platform and adapter, but
|
17
17
|
# following examples will use :title, :class and :hwnd.
|
18
18
|
#
|
19
19
|
# Use window with _some title_ being part of it's title:
|
@@ -28,19 +28,19 @@ module RAutomation
|
|
28
28
|
# It is possible to use multiple locators together where every locator will be matched (AND-ed) to the window:
|
29
29
|
# RAutomation::Window.new(:title => "some title", :class => "IEFrame")
|
30
30
|
#
|
31
|
-
# Refer to all possible locators in each
|
31
|
+
# Refer to all possible locators in each adapter's documentation.
|
32
32
|
#
|
33
|
-
# _locators_ may also include a key called :
|
33
|
+
# _locators_ may also include a key called :adapter to change default adapter,
|
34
34
|
# which is dependent of the platform, to automate windows and their controls.
|
35
35
|
#
|
36
|
-
# It is also possible to change default
|
37
|
-
# <em>
|
36
|
+
# It is also possible to change default adapter by using environment variable:
|
37
|
+
# <em>RAUTOMATION_ADAPTER</em>
|
38
38
|
#
|
39
39
|
# * Object creation doesn't check for window's existence.
|
40
40
|
# * Window to be searched for has to be visible!
|
41
41
|
def initialize(locators)
|
42
|
-
@
|
43
|
-
@window = @
|
42
|
+
@adapter = locators.delete(:adapter) || ENV["RAUTOMATION_ADAPTER"] && ENV["RAUTOMATION_ADAPTER"].to_sym || default_adapter
|
43
|
+
@window = Adapter.const_get(normalize(@adapter)).const_get(:Window).new(locators)
|
44
44
|
end
|
45
45
|
|
46
46
|
class << self
|
@@ -143,12 +143,12 @@ module RAutomation
|
|
143
143
|
@window.restore
|
144
144
|
end
|
145
145
|
|
146
|
-
# Sends keys to the Window. Refer to specific
|
146
|
+
# Sends keys to the Window. Refer to specific adapter's documentation for possible values.
|
147
147
|
#
|
148
148
|
# Raises an UnknownWindowException if the Window itself doesn't exist.
|
149
|
-
def send_keys(keys)
|
149
|
+
def send_keys(*keys)
|
150
150
|
wait_until_exists
|
151
|
-
@window.send_keys(keys)
|
151
|
+
@window.send_keys(*keys)
|
152
152
|
end
|
153
153
|
|
154
154
|
# Closes the Window if it exists.
|
@@ -158,7 +158,7 @@ module RAutomation
|
|
158
158
|
end
|
159
159
|
|
160
160
|
# Returns the Button object by the _locators_ on the Window.
|
161
|
-
# Refer to specific
|
161
|
+
# Refer to specific adapter's documentation for possible parameters.
|
162
162
|
#
|
163
163
|
# Raises an UnknownWindowException if the Window itself doesn't exist.
|
164
164
|
def button(locators)
|
@@ -167,7 +167,7 @@ module RAutomation
|
|
167
167
|
end
|
168
168
|
|
169
169
|
# Returns the TextField object by the _locators_ on the Window.
|
170
|
-
# Refer to specific
|
170
|
+
# Refer to specific adapter's documentation for possible parameters.
|
171
171
|
#
|
172
172
|
# Raises an UnknownWindowException if the Window itself doesn't exist.
|
173
173
|
def text_field(locators)
|
@@ -175,9 +175,9 @@ module RAutomation
|
|
175
175
|
TextField.new(@window, locators)
|
176
176
|
end
|
177
177
|
|
178
|
-
# Allow to execute
|
178
|
+
# Allow to execute adapter's methods not part of the public API
|
179
179
|
def method_missing(name, *args)
|
180
|
-
@window.
|
180
|
+
@window.send(name, *args)
|
181
181
|
end
|
182
182
|
|
183
183
|
private
|
@@ -185,7 +185,11 @@ module RAutomation
|
|
185
185
|
def wait_until_exists
|
186
186
|
WaitHelper.wait_until(RAutomation::Window.wait_timeout) {exists?}
|
187
187
|
rescue WaitHelper::TimeoutError
|
188
|
-
raise UnknownWindowException
|
188
|
+
raise UnknownWindowException, "Window with locator #{@window.locators.inspect} doesn't exist!" unless exists?
|
189
|
+
end
|
190
|
+
|
191
|
+
def normalize adapter
|
192
|
+
adapter.to_s.split("_").map {|word| word.capitalize}.join
|
189
193
|
end
|
190
194
|
|
191
195
|
end
|
data/rautomation.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rautomation}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jarmo Pertman"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-12-14}
|
13
13
|
s.description = %q{RAutomation tries to be a small and easy to use library for helping out to automate windows and their controls
|
14
14
|
for automated testing.
|
15
15
|
|
@@ -17,7 +17,7 @@ RAutomation aims to provide:
|
|
17
17
|
* Easy to use and user-friendly API (inspired by Watir http://www.watir.com).
|
18
18
|
* Cross-platform compatibility
|
19
19
|
* Easy extensibility - have some application, which uses some specialized technology, but isn't supported by RAutomation?
|
20
|
-
You can get dirty and create new
|
20
|
+
You can get dirty and create a new adapter for RAutomation!}
|
21
21
|
s.email = %q{jarmo.p@gmail.com}
|
22
22
|
s.extra_rdoc_files = [
|
23
23
|
"LICENSE",
|
@@ -26,6 +26,7 @@ RAutomation aims to provide:
|
|
26
26
|
s.files = [
|
27
27
|
".document",
|
28
28
|
".gitignore",
|
29
|
+
".rspec",
|
29
30
|
"History.rdoc",
|
30
31
|
"LICENSE",
|
31
32
|
"README.rdoc",
|
@@ -34,19 +35,25 @@ RAutomation aims to provide:
|
|
34
35
|
"ext/AutoItX/AutoItX.chm",
|
35
36
|
"ext/AutoItX/AutoItX3.dll",
|
36
37
|
"lib/rautomation.rb",
|
38
|
+
"lib/rautomation/adapter/autoit.rb",
|
39
|
+
"lib/rautomation/adapter/autoit/button.rb",
|
40
|
+
"lib/rautomation/adapter/autoit/locators.rb",
|
41
|
+
"lib/rautomation/adapter/autoit/text_field.rb",
|
42
|
+
"lib/rautomation/adapter/autoit/window.rb",
|
43
|
+
"lib/rautomation/adapter/ffi.rb",
|
44
|
+
"lib/rautomation/adapter/ffi/button.rb",
|
45
|
+
"lib/rautomation/adapter/ffi/constants.rb",
|
46
|
+
"lib/rautomation/adapter/ffi/functions.rb",
|
47
|
+
"lib/rautomation/adapter/ffi/locators.rb",
|
48
|
+
"lib/rautomation/adapter/ffi/text_field.rb",
|
49
|
+
"lib/rautomation/adapter/ffi/window.rb",
|
50
|
+
"lib/rautomation/adapter/helper.rb",
|
37
51
|
"lib/rautomation/button.rb",
|
38
|
-
"lib/rautomation/implementations/autoit.rb",
|
39
|
-
"lib/rautomation/implementations/autoit/button.rb",
|
40
|
-
"lib/rautomation/implementations/autoit/locators.rb",
|
41
|
-
"lib/rautomation/implementations/autoit/text_field.rb",
|
42
|
-
"lib/rautomation/implementations/autoit/window.rb",
|
43
|
-
"lib/rautomation/implementations/helper.rb",
|
44
52
|
"lib/rautomation/text_field.rb",
|
45
53
|
"lib/rautomation/wait_helper.rb",
|
46
54
|
"lib/rautomation/window.rb",
|
47
55
|
"rautomation.gemspec",
|
48
56
|
"spec/button_spec.rb",
|
49
|
-
"spec/spec.opts",
|
50
57
|
"spec/spec_helper.rb",
|
51
58
|
"spec/test.html",
|
52
59
|
"spec/text_field_spec.rb",
|
@@ -69,12 +76,12 @@ RAutomation aims to provide:
|
|
69
76
|
s.specification_version = 3
|
70
77
|
|
71
78
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
72
|
-
s.add_development_dependency(%q<rspec>, ["
|
79
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.3"])
|
73
80
|
else
|
74
|
-
s.add_dependency(%q<rspec>, ["
|
81
|
+
s.add_dependency(%q<rspec>, ["~> 2.3"])
|
75
82
|
end
|
76
83
|
else
|
77
|
-
s.add_dependency(%q<rspec>, ["
|
84
|
+
s.add_dependency(%q<rspec>, ["~> 2.3"])
|
78
85
|
end
|
79
86
|
end
|
80
87
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
require 'rautomation'
|
3
|
-
require '
|
4
|
-
require 'spec/autorun'
|
3
|
+
require 'rspec'
|
5
4
|
|
6
5
|
module SpecHelper
|
7
|
-
# Since
|
6
|
+
# Since adapters are different then the windows to be tested
|
8
7
|
# might be different also.
|
9
8
|
#
|
10
|
-
# This constant allows to create input data for specs which could differ between the
|
9
|
+
# This constant allows to create input data for specs which could differ between the adapters.
|
11
10
|
#
|
12
11
|
# There has to be 2 windows:
|
13
12
|
# 1) Some random window, which is maximizable, minimizable, close'able and etc.
|
14
13
|
# 2) Browser window, which opens up a test.html where JavaScript prompt with a Button and a TextField objects will be shown.
|
15
14
|
DATA = {
|
16
|
-
# This
|
17
|
-
|
15
|
+
# This adapter needs Windows OS with Internet Explorer installed into 'c:\program files\internet explorer'.
|
16
|
+
:autoit => {
|
18
17
|
# Path to some binary, which opens up a window, what can be
|
19
18
|
# minimized, maximized, activated, closed and etc.
|
20
19
|
:window1 => "mspaint",
|
@@ -31,12 +30,35 @@ module SpecHelper
|
|
31
30
|
# Window 2 should have a button with the following text.
|
32
31
|
:window2_button_text => "OK",
|
33
32
|
# Window 2 should have a text field with the specified class name.
|
34
|
-
:
|
33
|
+
:window2_text_field_class => "Edit",
|
34
|
+
:title_proc => lambda {|win| win.WinGetTitle("[TITLE:Explorer User Prompt]")}
|
35
|
+
},
|
36
|
+
# This adapter needs Windows OS with Internet Explorer installed into 'c:\program files\internet explorer'.
|
37
|
+
:ffi => {
|
38
|
+
# Path to some binary, which opens up a window, what can be
|
39
|
+
# minimized, maximized, activated, closed and etc.
|
40
|
+
:window1 => "mspaint",
|
41
|
+
# Window 1 title, has to be a Regexp.
|
42
|
+
:window1_title => /untitled - paint/i,
|
43
|
+
# Path to some browser's binary.
|
44
|
+
:window2 => '"c:\\program files\\internet explorer\\iexplore.exe"',
|
45
|
+
# Window 2 title, has to be a String.
|
46
|
+
:window2_title => "Explorer User Prompt",
|
47
|
+
# Window 2 should have this text on it.
|
48
|
+
:window2_text => "Where do you want to go today?",
|
49
|
+
# When sending ENTER on Window 2, then the window OK button should be pressed and Window 2 should be closed.
|
50
|
+
# VK_RETURN
|
51
|
+
:window2_send_keys => 0xD,
|
52
|
+
# Window 2 should have a button with the following text.
|
53
|
+
:window2_button_text => "OK",
|
54
|
+
# Window 2 should have a text field with the specified class name.
|
55
|
+
:window2_text_field_class => "Edit",
|
56
|
+
:title_proc => lambda {|win| win.window_title(win.hwnd)}
|
35
57
|
}
|
36
|
-
}[ENV["
|
58
|
+
}[ENV["RAUTOMATION_ADAPTER"] && ENV["RAUTOMATION_ADAPTER"].to_sym || RAutomation::Adapter::Helper.default_adapter]
|
37
59
|
end
|
38
60
|
|
39
|
-
|
61
|
+
RSpec.configure do |config|
|
40
62
|
config.before(:all) do
|
41
63
|
@pid1 = IO.popen(SpecHelper::DATA[:window1]).pid
|
42
64
|
@pid2 = IO.popen(SpecHelper::DATA[:window2] + " " + File.dirname(__FILE__) + "/test.html").pid
|
data/spec/text_field_spec.rb
CHANGED
@@ -3,51 +3,51 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe RAutomation::TextField do
|
4
4
|
it "#text_field" do
|
5
5
|
RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title]).
|
6
|
-
text_field(:
|
6
|
+
text_field(:class => SpecHelper::DATA[:window2_text_field_class]).should exist
|
7
7
|
|
8
8
|
RAutomation::Window.wait_timeout = 0.1
|
9
9
|
lambda {RAutomation::Window.new(:title => "non-existent-window").
|
10
|
-
text_field(:
|
10
|
+
text_field(:class => SpecHelper::DATA[:window2_text_field_class])}.
|
11
11
|
should raise_exception(RAutomation::UnknownWindowException)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "#set" do
|
15
15
|
window = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
16
|
-
window.text_field(:
|
16
|
+
window.text_field(:class => SpecHelper::DATA[:window2_text_field_class]).set "hello!"
|
17
17
|
|
18
18
|
RAutomation::Window.wait_timeout = 0.1
|
19
|
-
lambda {window.text_field(:
|
19
|
+
lambda {window.text_field(:class => "non-existing-field").set "hello!"}.
|
20
20
|
should raise_exception(RAutomation::UnknownTextFieldException)
|
21
21
|
end
|
22
22
|
|
23
23
|
it "#clear"do
|
24
24
|
window = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
25
|
-
field = window.text_field(:
|
25
|
+
field = window.text_field(:class => SpecHelper::DATA[:window2_text_field_class])
|
26
26
|
field.set "hello!"
|
27
27
|
field.value.should == "hello!"
|
28
28
|
field.clear
|
29
29
|
field.value.should be_empty
|
30
30
|
|
31
31
|
RAutomation::Window.wait_timeout = 0.1
|
32
|
-
lambda {window.text_field(:
|
32
|
+
lambda {window.text_field(:class => "non-existent-field").clear}.
|
33
33
|
should raise_exception(RAutomation::UnknownTextFieldException)
|
34
34
|
end
|
35
35
|
|
36
36
|
it "#value" do
|
37
37
|
window = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
38
|
-
field = window.text_field(:
|
38
|
+
field = window.text_field(:class => SpecHelper::DATA[:window2_text_field_class])
|
39
39
|
field.set "hello!"
|
40
40
|
field.value.should == "hello!"
|
41
41
|
|
42
42
|
RAutomation::Window.wait_timeout = 0.1
|
43
|
-
lambda {window.text_field(:
|
43
|
+
lambda {window.text_field(:class => "non-existent-field").value}.
|
44
44
|
should raise_exception(RAutomation::UnknownTextFieldException)
|
45
45
|
end
|
46
46
|
|
47
47
|
it "#exists?" do
|
48
48
|
window = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
49
|
-
field = window.text_field(:
|
49
|
+
field = window.text_field(:class => SpecHelper::DATA[:window2_text_field_class])
|
50
50
|
field.should exist
|
51
|
-
window.text_field(:
|
51
|
+
window.text_field(:class => "non-existent-field").should_not exist
|
52
52
|
end
|
53
53
|
end
|
data/spec/window_spec.rb
CHANGED
@@ -9,8 +9,8 @@ describe RAutomation::Window do
|
|
9
9
|
RAutomation::WaitHelper.wait_until {window.present?}
|
10
10
|
end
|
11
11
|
|
12
|
-
it "RAutomation::Window.
|
13
|
-
RAutomation::Window.new(:title => "random").
|
12
|
+
it "RAutomation::Window.adapter" do
|
13
|
+
RAutomation::Window.new(:title => "random").adapter.should == (ENV["RAUTOMATION_ADAPTER"] && ENV["RAUTOMATION_ADAPTER"].to_sym || RAutomation::Adapter::Helper.default_adapter)
|
14
14
|
end
|
15
15
|
|
16
16
|
it "Window#new by full title" do
|
@@ -102,6 +102,11 @@ describe RAutomation::Window do
|
|
102
102
|
should raise_exception(RAutomation::UnknownWindowException)
|
103
103
|
end
|
104
104
|
|
105
|
+
it "#method_missing" do
|
106
|
+
win = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
107
|
+
SpecHelper::DATA[:title_proc].call(win).should == SpecHelper::DATA[:window2_title]
|
108
|
+
end
|
109
|
+
|
105
110
|
it "#send_keys"do
|
106
111
|
window = RAutomation::Window.new(:title => SpecHelper::DATA[:window2_title])
|
107
112
|
window.minimize # #send_keys should work even if window is minimized
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rautomation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.4
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jarmo Pertman
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-14 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,14 +24,13 @@ dependencies:
|
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 5
|
30
30
|
segments:
|
31
|
-
-
|
31
|
+
- 2
|
32
32
|
- 3
|
33
|
-
|
34
|
-
version: 1.3.0
|
33
|
+
version: "2.3"
|
35
34
|
type: :development
|
36
35
|
version_requirements: *id001
|
37
36
|
description: |-
|
@@ -42,7 +41,7 @@ description: |-
|
|
42
41
|
* Easy to use and user-friendly API (inspired by Watir http://www.watir.com).
|
43
42
|
* Cross-platform compatibility
|
44
43
|
* Easy extensibility - have some application, which uses some specialized technology, but isn't supported by RAutomation?
|
45
|
-
You can get dirty and create new
|
44
|
+
You can get dirty and create a new adapter for RAutomation!
|
46
45
|
email: jarmo.p@gmail.com
|
47
46
|
executables: []
|
48
47
|
|
@@ -54,6 +53,7 @@ extra_rdoc_files:
|
|
54
53
|
files:
|
55
54
|
- .document
|
56
55
|
- .gitignore
|
56
|
+
- .rspec
|
57
57
|
- History.rdoc
|
58
58
|
- LICENSE
|
59
59
|
- README.rdoc
|
@@ -62,19 +62,25 @@ files:
|
|
62
62
|
- ext/AutoItX/AutoItX.chm
|
63
63
|
- ext/AutoItX/AutoItX3.dll
|
64
64
|
- lib/rautomation.rb
|
65
|
+
- lib/rautomation/adapter/autoit.rb
|
66
|
+
- lib/rautomation/adapter/autoit/button.rb
|
67
|
+
- lib/rautomation/adapter/autoit/locators.rb
|
68
|
+
- lib/rautomation/adapter/autoit/text_field.rb
|
69
|
+
- lib/rautomation/adapter/autoit/window.rb
|
70
|
+
- lib/rautomation/adapter/ffi.rb
|
71
|
+
- lib/rautomation/adapter/ffi/button.rb
|
72
|
+
- lib/rautomation/adapter/ffi/constants.rb
|
73
|
+
- lib/rautomation/adapter/ffi/functions.rb
|
74
|
+
- lib/rautomation/adapter/ffi/locators.rb
|
75
|
+
- lib/rautomation/adapter/ffi/text_field.rb
|
76
|
+
- lib/rautomation/adapter/ffi/window.rb
|
77
|
+
- lib/rautomation/adapter/helper.rb
|
65
78
|
- lib/rautomation/button.rb
|
66
|
-
- lib/rautomation/implementations/autoit.rb
|
67
|
-
- lib/rautomation/implementations/autoit/button.rb
|
68
|
-
- lib/rautomation/implementations/autoit/locators.rb
|
69
|
-
- lib/rautomation/implementations/autoit/text_field.rb
|
70
|
-
- lib/rautomation/implementations/autoit/window.rb
|
71
|
-
- lib/rautomation/implementations/helper.rb
|
72
79
|
- lib/rautomation/text_field.rb
|
73
80
|
- lib/rautomation/wait_helper.rb
|
74
81
|
- lib/rautomation/window.rb
|
75
82
|
- rautomation.gemspec
|
76
83
|
- spec/button_spec.rb
|
77
|
-
- spec/spec.opts
|
78
84
|
- spec/spec_helper.rb
|
79
85
|
- spec/test.html
|
80
86
|
- spec/text_field_spec.rb
|
data/spec/spec.opts
DELETED