rautomation 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Jarmo Pertman
1
+ Copyright (c) 2009-2010 Jarmo Pertman
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -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 implementation for RAutomation, due to the applied <em>Strategy Pattern</em>!
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(:class_name => "Edit1").set "hello, world!"
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 implementation for RAutomation, due to the applied Strategy Pattern!}
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", ">= 1.3.0"
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 'spec/rake/spectask'
28
- Spec::Rake::SpecTask.new(:spec) do |spec|
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
- Spec::Rake::SpecTask.new(:rcov) do |spec|
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.4
1
+ 0.1.0
@@ -1,5 +1,5 @@
1
1
  require "rautomation/wait_helper"
2
- require "rautomation/implementations/helper"
2
+ require "rautomation/adapter/helper"
3
3
  require "rautomation/window"
4
4
  require "rautomation/button"
5
5
  require "rautomation/text_field"
@@ -1,17 +1,18 @@
1
1
  module RAutomation
2
- module Implementations
3
- module AutoIt
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
- :class_name => :classnn,
10
+ [:class, Regexp] => :regexpclass,
11
+ :index => :instance,
11
12
  :value => :text
12
13
  }
13
14
 
14
- # Possible locators are :text, :value, :id, :class, :class_name and :instance.
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 Implementations
3
- module AutoIt
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 Implementations
3
- module AutoIt
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 = {:class_name => :classnn}
9
+ LOCATORS = {
10
+ [:class, Regexp] => :regexpclass,
11
+ :index => :instance
12
+ }
10
13
 
11
- # Possible locators are :id, :class, :class_name and :instance.
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 Implementations
3
- module AutoIt
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 = {[:title, String] => :title,
29
- [:title, Regexp] => :regexptitle,
30
- :hwnd => :handle}
28
+ LOCATORS = {
29
+ [:title, Regexp] => :regexptitle,
30
+ :index => :instance,
31
+ :hwnd => :handle
32
+ }
31
33
 
32
- # Possible locators are :title, :text, :hwnd and :class.
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.respond_to?(name) ? @@autoit.send(name, *args) : super
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 Implementations
3
- autoload :AutoIt, File.dirname(__FILE__) + "/autoit.rb"
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 default_implementation
10
+ def default_adapter
10
11
  case RUBY_PLATFORM
11
12
  when /mswin|msys|mingw32/
12
- Implementations::AutoIt::Window
13
+ :ffi
13
14
  else
14
15
  raise "unsupported platform for RAutomation: #{RUBY_PLATFORM}"
15
16
  end
@@ -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.new("Button #{@locators.inspect} doesn't exist on window #{@window.locators.inspect}!")
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.new("Text field #{@locators.inspect} doesn't exist on window #{@window.locators.inspect}!") unless exists?
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
@@ -7,13 +7,13 @@ module RAutomation
7
7
  end
8
8
 
9
9
  class Window
10
- include Implementations::Helper
10
+ include Adapter::Helper
11
11
 
12
- attr_reader :implementation
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 implementation, but
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 implementation's documentation.
31
+ # Refer to all possible locators in each adapter's documentation.
32
32
  #
33
- # _locators_ may also include a key called :implementation to change default implementation,
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 implementation by using environment variable:
37
- # <em>RAUTOMATION_IMPLEMENTATION</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
- @implementation = locators.delete(:implementation) || ENV["RAUTOMATION_IMPLEMENTATION"] || default_implementation
43
- @window = @implementation.new(locators)
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 implementation's documentation for possible values.
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 implementation's documentation for possible parameters.
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 implementation's documentation for possible parameters.
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 implementation's methods not part of the public API
178
+ # Allow to execute adapter's methods not part of the public API
179
179
  def method_missing(name, *args)
180
- @window.respond_to?(name) ? @window.send(name, *args) : super
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.new("Window with locator #{@window.locators.inspect} doesn't exist!") unless exists?
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
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rautomation}
8
- s.version = "0.0.4"
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-10-27}
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 implementation for RAutomation, due to the applied Strategy Pattern!}
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>, [">= 1.3.0"])
79
+ s.add_development_dependency(%q<rspec>, ["~> 2.3"])
73
80
  else
74
- s.add_dependency(%q<rspec>, [">= 1.3.0"])
81
+ s.add_dependency(%q<rspec>, ["~> 2.3"])
75
82
  end
76
83
  else
77
- s.add_dependency(%q<rspec>, [">= 1.3.0"])
84
+ s.add_dependency(%q<rspec>, ["~> 2.3"])
78
85
  end
79
86
  end
80
87
 
@@ -1,20 +1,19 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  require 'rautomation'
3
- require 'spec'
4
- require 'spec/autorun'
3
+ require 'rspec'
5
4
 
6
5
  module SpecHelper
7
- # Since implementations are different then the windows to be tested
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 implementations.
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 implementation needs Windows OS with Internet Explorer installed into 'c:\program files\internet explorer'.
17
- "RAutomation::Implementations::AutoIt::Window" => {
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
- :window2_text_field_class_name => "Edit1"
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["RAUTOMATION_IMPLEMENTATION"] || RAutomation::Implementations::Helper.default_implementation.to_s]
58
+ }[ENV["RAUTOMATION_ADAPTER"] && ENV["RAUTOMATION_ADAPTER"].to_sym || RAutomation::Adapter::Helper.default_adapter]
37
59
  end
38
60
 
39
- Spec::Runner.configure do |config|
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
@@ -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(:class_name => SpecHelper::DATA[:window2_text_field_class_name]).should exist
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(:class_name => SpecHelper::DATA[:window2_text_field_class_name])}.
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(:class_name => SpecHelper::DATA[:window2_text_field_class_name]).set "hello!"
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(:class_name => "non-existing-field").set "hello!"}.
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(:class_name => SpecHelper::DATA[:window2_text_field_class_name])
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(:class_name => "non-existent-field").clear}.
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(:class_name => SpecHelper::DATA[:window2_text_field_class_name])
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(:class_name => "non-existent-field").value}.
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(:class_name => SpecHelper::DATA[:window2_text_field_class_name])
49
+ field = window.text_field(:class => SpecHelper::DATA[:window2_text_field_class])
50
50
  field.should exist
51
- window.text_field(:class_name => "non-existent-field").should_not exist
51
+ window.text_field(:class => "non-existent-field").should_not exist
52
52
  end
53
53
  end
@@ -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.implementation" do
13
- RAutomation::Window.new(:title => "random").implementation.should == (ENV["RAUTOMATION_IMPLEMENTATION"] || RAutomation::Implementations::Helper.default_implementation)
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: 23
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 4
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-10-27 00:00:00 +03:00
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: 27
29
+ hash: 5
30
30
  segments:
31
- - 1
31
+ - 2
32
32
  - 3
33
- - 0
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 implementation for RAutomation, due to the applied Strategy Pattern!
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
@@ -1,2 +0,0 @@
1
- --color
2
- --format n