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 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