rukuli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 79bcc51a1ba07f799f46d15d579e03cc5f239fc1
4
+ data.tar.gz: 65a8c98c4ba3e54433c2379e5a4818593cd33a81
5
+ SHA512:
6
+ metadata.gz: 0a821a6a259c5587cba8d0ade7608a68928dc2a34d4c909c4e512576d5777fbaca20bcb627866a4db9ed6f82f1f53c1cc754b9f2c41b43c314a68a8b0c92a428
7
+ data.tar.gz: a7fb19df115ec199224408e31c6ed0623d0034c1415f912ebc7f6206456a59d7ec28dbf9cc7136a87248b364f8223b5c02f5276393352905668f0ba4aaac87bb
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sikuli_ruby.gemspec
4
+ gemspec
data/License.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 - 2014 André Anastácio
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Rukuli
2
+
3
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/andreanastacio/rukuli/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
4
+
5
+ This project is a fork of [sikuli_ruby](https://github.com/chaslemley/sikuli_ruby)!
6
+
7
+ [Sikuli](http://sikuli.org/) allows you to interact with your application's user interface using image based search to automate user actions.
8
+
9
+ ## Requirements
10
+ * [Sikuli-Script 1.0.1](https://launchpad.net/sikuli/+download) (Install Sikuli-Script via sikuli-setup.jar)
11
+ * [JRuby](http://jruby.org/download) or ```rvm install jruby```
12
+
13
+ ## Compatibility
14
+
15
+ Make sure to set SIKULI_HOME to the Sikuli installation directory and to add the Sikuli installation directory and Sikuli libs directory to the include path.
16
+
17
+ ### Windows
18
+
19
+ ```
20
+ setx SIKULI_HOME C:/path/to/sikuli-script.jar
21
+ ```
22
+
23
+ ### Linux / OSX
24
+ ```bash
25
+ export SIKULI_HOME="~/path/to/sikuli-script.jar"
26
+ ```
27
+
28
+ # Installation
29
+ ```
30
+ gem install rukuli
31
+ ```
32
+
33
+ # Usage
34
+
35
+ ```ruby
36
+ require 'java'
37
+ require 'rukuli'
38
+
39
+ Rukuli::Config.run do |config|
40
+ config.image_path = "#{Dir.pwd}/images/"
41
+ config.logging = false
42
+ end
43
+
44
+ screen = Rukuli::Screen.new
45
+ screen.click(10, 10) # should open your apple menu
46
+
47
+ app = Rukuli::App.new("iPhone Simulator")
48
+ app.window.click('ui_element.png') if app.window.find('ui_element.png')
49
+ ```
50
+
51
+ # Running the test suite
52
+
53
+ 1. You need to open `test_area.jpg` in **Preview** from `spec/support/images/` directory
54
+ before running tests.
55
+ 2. You also need to open the **TextEdit** app
56
+
57
+ # Examples
58
+
59
+ * [Cucumber Suite](https://github.com/chaslemley/cucumber_sikuli)
60
+
61
+ # Contributing
62
+
63
+ * Fork it
64
+ * Create your feature branch (git checkout -b my-new-feature)
65
+ * Commit your changes (git commit -am 'Add some feature')
66
+ * Push to the branch (git push origin my-new-feature)
67
+ * Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/lib/rukuli.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "rukuli/platform"
2
+ require Rukuli::Platform.sikuli_script_path
3
+ require "rukuli/version"
4
+
5
+ require "rukuli/app"
6
+ require "rukuli/exception"
7
+ require "rukuli/region"
8
+ require "rukuli/screen"
9
+ require "rukuli/key_code"
10
+ require "rukuli/config"
data/lib/rukuli/app.rb ADDED
@@ -0,0 +1,33 @@
1
+ # An App object represents a running app on the system.
2
+ #
3
+ module Rukuli
4
+ class App
5
+
6
+ # Public: creates a new App instance
7
+ #
8
+ # app_name - String name of the app
9
+ #
10
+ # Examples
11
+ #
12
+ # App.new("TextEdit")
13
+ #
14
+ # Returns the newly initialized App
15
+ def initialize(app_name)
16
+ @java_obj = org.sikuli.script::App.new(app_name)
17
+ end
18
+
19
+ # Public: brings the App to focus
20
+ #
21
+ # Returns nothing
22
+ def focus
23
+ @java_obj.focus()
24
+ end
25
+
26
+ # Public: the Region instance representing the app's window
27
+ #
28
+ # Returns the newly initialized Region
29
+ def window
30
+ Region.new(@java_obj.window())
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,278 @@
1
+ # The Clickable module defines interaction with the mouse. It is included in
2
+ # the Region class.
3
+ #
4
+ module Rukuli
5
+ module Clickable
6
+
7
+ # Public: Performs a single click on an image match or point (x, y)
8
+ #
9
+ # args - String representing filename of image to find and click
10
+ # args - Fixnum, Fixnum representing x and y coordinates within
11
+ # a Region (0,0) is the top left
12
+ #
13
+ # Examples
14
+ #
15
+ # region.click('smile.png')
16
+ # region.click(123, 432)
17
+ #
18
+ # Returns nothing
19
+ def click(*args)
20
+ case args.length
21
+ when 1 then click_image(args[0])
22
+ when 2 then click_point(args[0], args[1])
23
+ else raise ArgumentError
24
+ end
25
+ end
26
+
27
+ # Public: Performs a double click on an image match or point (x, y)
28
+ #
29
+ # args - String representing filename of image to find and click
30
+ # args - Fixnum, Fixnum representing x and y coordinates within
31
+ # a Region (0,0) is the top left
32
+ #
33
+ # Examples
34
+ #
35
+ # region.double_click('smile.png')
36
+ # region.double_click(123, 432)
37
+ #
38
+ # Returns nothing
39
+ def double_click(*args)
40
+ case args.length
41
+ when 1 then click_image(args[0], true)
42
+ when 2 then click_point(args[0], args[1], true)
43
+ else raise ArgumentError
44
+ end
45
+ end
46
+
47
+ # Public: Performs a click and hold on an image match or point (x, y)
48
+ #
49
+ # args - String representing filename of image to find and click
50
+ # args - Fixnum, Fixnum representing x and y coordinates within
51
+ # a Region (0,0) is the top left
52
+ # seconds - Fixnum representing the number of seconds to hold down
53
+ # before releasing
54
+ #
55
+ # Examples
56
+ #
57
+ # region.click_and_hold('smile.png', 2)
58
+ # region.click_and_hold(123, 432, 2)
59
+ #
60
+ # Returns nothing
61
+ def click_and_hold(seconds = 1, *args)
62
+ case args.length
63
+ when 1 then click_image_and_hold(args[0], seconds)
64
+ when 2 then click_point_and_hold(args[0], args[1], seconds)
65
+ else raise ArgumentError
66
+ end
67
+ end
68
+
69
+ # Public: Performs a mouse down, drag, and mouse up
70
+ #
71
+ # start_x - Fixnum representing the x of the mouse down
72
+ # start_y - Fixnum representing the y of the mouse down
73
+ # end_x - Fixnum representing the x of the mouse up
74
+ # end_y - Fixnum representing the y of the mouse up
75
+ #
76
+ # Examples
77
+ #
78
+ # region.drag_drop(20, 12, 23, 44)
79
+ #
80
+ # Returns nothing
81
+ def drag_drop(start_x, start_y, end_x, end_y)
82
+ @java_obj.dragDrop(
83
+ offset_location(start_x, start_y),
84
+ offset_location(end_x, end_y)
85
+ )
86
+ end
87
+
88
+ # Public: Simulates turning of the mouse wheel up
89
+ #
90
+ # steps - Fixnum representing the number of steps to turn the mouse wheel
91
+ #
92
+ # Examples
93
+ #
94
+ # region.wheel_up(10)
95
+ #
96
+ # Returns nothing
97
+ def wheel_up(steps = 1)
98
+ @java_obj.wheel(-1, steps)
99
+ end
100
+
101
+ # Public: Simulates turning of the mouse wheel down
102
+ #
103
+ # steps - Fixnum representing the number of steps to turn the mouse wheel
104
+ #
105
+ # Examples
106
+ #
107
+ # region.wheel_down(10)
108
+ #
109
+ # Returns nothing
110
+ def wheel_down(steps = 1)
111
+ @java_obj.wheel(1, steps)
112
+ end
113
+
114
+ # Public: Performs a hover on an image match or point (x, y)
115
+ #
116
+ # args - String representing filename of image to find and hover
117
+ # args - Fixnum, Fixnum representing x and y coordinates within
118
+ # a Region (0,0) is the top left
119
+ #
120
+ # Examples
121
+ #
122
+ # region.hover('smile.png')
123
+ # region.hover(123, 432)
124
+ #
125
+ # Returns nothing
126
+ def hover(*args)
127
+ case args.length
128
+ when 1 then hover_image(args[0])
129
+ when 2 then hover_point(args[0], args[1])
130
+ else raise ArgumentError
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ # Private: turns the mouse wheel
137
+ #
138
+ # direction - Fixnum represeting direction to turn wheel
139
+ # steps - the number of steps to turn the mouse wheel
140
+ #
141
+ # Returns nothing
142
+ def wheel(direction, steps)
143
+ @java_obj.wheel(direction, steps)
144
+ end
145
+
146
+ # Private: clicks on a matched Region based on an image based search
147
+ #
148
+ # filename - A String representation of the filename of the region to
149
+ # match against
150
+ # seconds - The length in seconds to hold the mouse
151
+ #
152
+ # Returns nothing
153
+ #
154
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
155
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
156
+ def click_image_and_hold(filename, seconds)
157
+ begin
158
+ pattern = org.sikuli.script::Pattern.new(filename).similar(0.9)
159
+ @java_obj.hover(pattern)
160
+ @java_obj.mouseDown(java.awt.event.InputEvent::BUTTON1_MASK)
161
+ sleep(seconds.to_i)
162
+ @java_obj.mouseUp(0)
163
+ rescue NativeException => e
164
+ raise_exception e, filename
165
+ end
166
+ end
167
+
168
+ # Private: clicks on a point within the region
169
+ #
170
+ # filename - A String representation of the filename of the region to
171
+ # match against
172
+ #
173
+ # Returns nothing
174
+ #
175
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
176
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
177
+ def click_point_and_hold(x, y, seconds)
178
+ begin
179
+ @java_obj.hover(location(x, y))
180
+ @java_obj.mouseDown(java.awt.event.InputEvent::BUTTON1_MASK)
181
+ sleep(seconds.to_i)
182
+ @java_obj.mouseUp(0)
183
+ rescue NativeException => e
184
+ raise_exception e, filename
185
+ end
186
+ end
187
+
188
+ # Private: clicks on a matched Region based on an image based search
189
+ #
190
+ # filename - A String representation of the filename of the region to
191
+ # match against
192
+ # is_double - (optional) Boolean determining if should be a double click
193
+ #
194
+ # Returns nothing
195
+ #
196
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
197
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
198
+ def click_image(filename, is_double = false, and_hold = false)
199
+ begin
200
+ if is_double
201
+ @java_obj.doubleClick(filename, 0)
202
+ else
203
+ @java_obj.click(filename, 0)
204
+ end
205
+ rescue NativeException => e
206
+ raise_exception e, filename
207
+ end
208
+ end
209
+
210
+ # Private: clicks on a point relative to a Region's top left corner
211
+ #
212
+ # x - a Fixnum representing the x component of the point to click
213
+ # y - a Fixnum representing the y component of the point to click
214
+ # is_double - (optional) Boolean determining if should be a double click
215
+ #
216
+ # Returns nothing
217
+ #
218
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
219
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
220
+ def click_point(x, y, is_double = false)
221
+ if is_double
222
+ @java_obj.doubleClick(offset_location(x, y))
223
+ else
224
+ @java_obj.click(offset_location(x, y))
225
+ end
226
+ end
227
+
228
+ # Private: hovers on a matched Region based on an image based search
229
+ #
230
+ # filename - A String representation of the filename of the region to
231
+ # match against
232
+ #
233
+ # Returns nothing
234
+ #
235
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
236
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
237
+ def hover_image(filename)
238
+ begin
239
+ @java_obj.hover(filename)
240
+ rescue NativeException => e
241
+ raise_exception e, filename
242
+ end
243
+ end
244
+
245
+ # Private: hovers on a point relative to a Region's top left corner
246
+ #
247
+ # x - a Fixnum representing the x component of the point to hover
248
+ # y - a Fixnum representing the y component of the point to hover
249
+ #
250
+ # Returns nothing
251
+ #
252
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
253
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
254
+ def hover_point(x, y)
255
+ @java_obj.hover(offset_location(x, y))
256
+ end
257
+
258
+ # Private: create a new instance of Location
259
+ #
260
+ # x - a Fixnum representing the x component of the point to hover
261
+ # y - a Fixnum representing the y component of the point to hover
262
+ #
263
+ # Return location class instance
264
+ def location(x, y)
265
+ org.sikuli.script::Location.new(x, y)
266
+ end
267
+
268
+ # Private: location with offset
269
+ #
270
+ # x - a Fixnum representing the x component of the point to hover
271
+ # y - a Fixnum representing the y component of the point to hover
272
+ #
273
+ # Return new location
274
+ def offset_location(x, y)
275
+ location(x, y).offset(x(), y())
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,67 @@
1
+ # Config variables for the Sikuli driver
2
+ #
3
+ module Rukuli
4
+ class Config
5
+ class << self
6
+
7
+ # Public: the Boolean representing whether or not to perform a 1 second
8
+ # highlight when an image is matched through Searchable#find,
9
+ # Searchable#find_all. Defaults to false.
10
+ attr_accessor :highlight_on_find
11
+
12
+ # Public: the absolute file path where Sikuli will look for images when
13
+ # a just a filename is passed to a search or click method
14
+ #
15
+ # Returns the String representation of the path
16
+ def image_path
17
+ java.lang.System.getProperty("SIKULI_IMAGE_PATH")
18
+ end
19
+
20
+ # Public: the setter for the absolute file path where Sikuli will search
21
+ # for images with given a filename as an image
22
+ #
23
+ # Examples
24
+ #
25
+ # Rukuli::Config.image_path = "/Users/andreanastacio/rukuli/images/"
26
+ #
27
+ # Returns nothing
28
+ def image_path=(path)
29
+ java.lang.System.setProperty("SIKULI_IMAGE_PATH", path)
30
+ end
31
+
32
+ # Public: turns stdout logging on and off for the Sikuli java classes.
33
+ # Defaults to true.
34
+ #
35
+ # Examples
36
+ #
37
+ # Rukuli::Config.logging = false
38
+ #
39
+ # Returns nothing
40
+ def logging=(boolean)
41
+ return unless [TrueClass, FalseClass].include? boolean.class
42
+ org.sikuli.basics::Settings.InfoLogs = boolean
43
+ org.sikuli.basics::Settings.ActionLogs = boolean
44
+ org.sikuli.basics::Settings.DebugLogs = boolean
45
+ end
46
+
47
+ # Public: convienence method for grouping the setting of config
48
+ # variables
49
+ #
50
+ # Examples
51
+ #
52
+ # Rukuli::Config.run do |config|
53
+ # config.logging = true
54
+ # config.image_path = "/User/andreanastacio/images"
55
+ # config.highlight_on_find = true
56
+ # end
57
+ #
58
+ # Returns nothing
59
+ def run(*args)
60
+ if block_given?
61
+ yield self
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ # Exception classes for Sikuli image searching and matching
2
+ #
3
+ module Rukuli
4
+
5
+ # Thrown when Sikuli is unable to find a match within the region for the
6
+ # file given.
7
+ #
8
+ class ImageNotFound < StandardError; end
9
+
10
+ # Thrown when a filename is given that is not found on disk in the image
11
+ # path. Image path can be configured using Rukuli::Config.image_path
12
+ #
13
+ class FileDoesNotExist < StandardError; end
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'java'
2
+ java_import 'org.sikuli.script.Key'
3
+ java_import 'org.sikuli.script.KeyModifier'
4
+
5
+ #
6
+ # These constants represent keyboard codes for interacting with the keyboard.
7
+ # Keyboard interaction is defined in the Rukuli::Typeable module.
8
+ #
9
+ module Rukuli
10
+ KEY_CMD = KeyModifier::META
11
+ KEY_SHIFT = KeyModifier::SHIFT
12
+ KEY_CTRL = KeyModifier::CTRL
13
+ KEY_ALT = KeyModifier::ALT
14
+
15
+ KEY_BACKSPACE = Key::BACKSPACE
16
+ KEY_RETURN = Key::ENTER
17
+ LEFT_ARROW = Key::LEFT
18
+ RIGHT_ARROW = Key::RIGHT
19
+ UP_ARROW = Key::UP
20
+ DOWN_ARROW = Key::DOWN
21
+ end
@@ -0,0 +1,12 @@
1
+ module Rukuli
2
+ class Platform
3
+
4
+ def self.sikuli_script_path
5
+ path = "#{ENV['SIKULI_HOME']}"
6
+ if ENV['SIKULI_HOME'].nil?
7
+ raise LoadError, "Failed to load 'sikuli-script.jar'\nMake sure SIKULI_HOME is set!"
8
+ end
9
+ path
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,85 @@
1
+ # A Region represents a rectangle on screen. Regions are the main point of
2
+ # interaction for Sikuli actions. Regions can receive actions from the mouse,
3
+ # keyboard, and image search.
4
+ #
5
+ require "rukuli/clickable"
6
+ require "rukuli/typeable"
7
+ require "rukuli/searchable"
8
+
9
+ module Rukuli
10
+ class Region
11
+ include Clickable
12
+ include Typeable
13
+ include Searchable
14
+
15
+ # Public: creates a new Region object
16
+ #
17
+ # args - Array representing x (left bound), y (top), width, height
18
+ # 4 Fixnums left, top, width, height
19
+ # An instance of an org.sikuli.script::Region
20
+ #
21
+ # Examples
22
+ #
23
+ # Region.new([10, 10, 200, 300])
24
+ # Region.new(10, 10, 200, 300)
25
+ # Region.new(another_region)
26
+ #
27
+ # Returns the newly initialized object
28
+ def initialize(*args)
29
+ @java_obj = org.sikuli.script::Region.new(*args)
30
+ end
31
+
32
+ # Public: highlight the region with a ~ 5 pixel red border
33
+ #
34
+ # seconds - Fixnum length of time to show border
35
+ #
36
+ # Returns nothing
37
+ def highlight(seconds = 1)
38
+ @java_obj.java_send(:highlight, [Java::int], seconds)
39
+ end
40
+
41
+ # Public: the x component of the top, left corner of the Region
42
+ def x
43
+ @java_obj.x()
44
+ end
45
+
46
+ # Public: the y component of the top, left corner of the Region
47
+ def y
48
+ @java_obj.y()
49
+ end
50
+
51
+ # Public: the width in pixels of the Region
52
+ def width
53
+ @java_obj.w()
54
+ end
55
+
56
+ # Public: the height in pixels of the Region
57
+ def height
58
+ @java_obj.h()
59
+ end
60
+
61
+ # Public: provide access to all region methods provided by the SikuliScript API
62
+ # See http://sikuli.org/doc/java/edu/mit/csail/uid/Region.html
63
+ def method_missing method_name, *args, &block
64
+ @java_obj.send method_name, *args, &block
65
+ end
66
+
67
+ private
68
+
69
+ # Private: interpret a java NativeException and raises a more descriptive
70
+ # exception
71
+ #
72
+ # exception - The original java exception thrown by the sikuli java_obj
73
+ # filename - A string representing the filename to include in the
74
+ # exception message
75
+ #
76
+ # Returns nothing
77
+ def raise_exception(exception, filename)
78
+ if exception.message == "org.sikuli.script.FindFailed: ImageFile null not found on disk"
79
+ raise Rukuli::FileDoesNotExist, "The file '#{filename}' does not exist."
80
+ else
81
+ raise Rukuli::ImageNotFound, "The image '#{filename}' did not match in this region."
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,20 @@
1
+ # A Screen object defines a special type of Rukuli::Region that represents
2
+ # the entire screen.
3
+ #
4
+ # TODO: Test the Screen object with multiple monitors attached.
5
+ #
6
+ module Rukuli
7
+ class Screen < Region
8
+
9
+ # Public: creates a new Screen object
10
+ #
11
+ # Examples
12
+ #
13
+ # screen = Rukuli::Screen.new
14
+ #
15
+ # Returns the newly initialized Screen object
16
+ def initialize
17
+ @java_obj = org.sikuli.script::Screen.new()
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,134 @@
1
+ # The Rukuli::Searchable module is the heart of Sikuli. It defines the
2
+ # wrapper around Sikuli's on screen image searching and matching capability
3
+ # It is implemented by the Region class.
4
+ #
5
+ module Rukuli
6
+ module Searchable
7
+
8
+ # Public: search for an image within a Region
9
+ #
10
+ # filename - A String representation of the filename to match against
11
+ # similarity - A Float between 0 and 1 representing the threshold for
12
+ # matching an image. Passing 1 corresponds to a 100% pixel for pixel
13
+ # match. Defaults to 0.9 (90% match)
14
+ #
15
+ # Examples
16
+ #
17
+ # region.find('needle.png')
18
+ # region.find('needle.png', 0.5)
19
+ #
20
+ # Returns an instance of Region representing the best match
21
+ #
22
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
23
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
24
+ def find(filename, similarity = 0.9)
25
+ begin
26
+ pattern = build_pattern(filename, similarity)
27
+ match = Region.new(@java_obj.find(pattern))
28
+ match.highlight if Rukuli::Config.highlight_on_find
29
+ match
30
+ rescue NativeException => e
31
+ raise_exception e, filename
32
+ end
33
+ end
34
+
35
+ # Public: search for an image within a region (does not raise ImageNotFound exceptions)
36
+ #
37
+ # filename - A String representation of the filename to match against
38
+ # similarity - A Float between 0 and 1 representing the threshold for
39
+ # matching an image. Passing 1 corresponds to a 100% pixel for pixel
40
+ # match. Defaults to 0.9 (90% match)
41
+ #
42
+ # Examples
43
+ #
44
+ # region.find!('needle.png')
45
+ # region.find!('needle.png', 0.5)
46
+ #
47
+ # Returns the match or nil if no match is found
48
+ def find!(filename, similarity = 0.9)
49
+ begin
50
+ find(filename, similarity)
51
+ rescue Rukuli::ImageNotFound => e
52
+ nil
53
+ end
54
+ end
55
+
56
+ # Public: search for an image within a Region and return all matches
57
+ #
58
+ # TODO: Sort return results so they are always returned in the same order
59
+ # (top left to bottom right)
60
+ #
61
+ # filename - A String representation of the filename to match against
62
+ # similarity - A Float between 0 and 1 representing the threshold for
63
+ # matching an image. Passing 1 corresponds to a 100% pixel for pixel
64
+ # match. Defaults to 0.9 (90% match)
65
+ #
66
+ # Examples
67
+ #
68
+ # region.find_all('needle.png')
69
+ # region.find_all('needle.png', 0.5)
70
+ #
71
+ # Returns an array of Region objects that match the given file and
72
+ # threshold
73
+ #
74
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
75
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
76
+ def find_all(filename, similarity = 0.9)
77
+ begin
78
+ pattern = build_pattern(filename, similarity)
79
+ matches = @java_obj.findAll(pattern)
80
+ regions = matches.collect do |r|
81
+ match = Region.new(r)
82
+ match.highlight if Rukuli::Config.highlight_on_find
83
+ match
84
+ end
85
+ regions
86
+ rescue NativeException => e
87
+ raise_exception e, filename
88
+ end
89
+ end
90
+
91
+ # Public: wait for a match to appear within a region
92
+ #
93
+ # filename - A String representation of the filename to match against
94
+ # time - A Fixnum representing the amount of time to wait defaults
95
+ # to 2 seconds
96
+ # similarity - A Float between 0 and 1 representing the threshold for
97
+ # matching an image. Passing 1 corresponds to a 100% pixel for pixel
98
+ # match. Defaults to 0.9 (90% match)
99
+ #
100
+ # Examples
101
+ #
102
+ # region.wait('needle.png') # wait for needle.png to appear for up to 1 second
103
+ # region.wait('needle.png', 10) # wait for needle.png to appear for 10 seconds
104
+ #
105
+ # Returns nothing
106
+ #
107
+ # Throws Rukuli::FileNotFound if the file could not be found on the system
108
+ # Throws Rukuli::ImageNotMatched if no matches are found within the region
109
+ def wait(filename, time = 2, similarity = 0.9)
110
+ begin
111
+ pattern = build_pattern(filename, similarity)
112
+ match = Region.new(@java_obj.wait(pattern, time))
113
+ match.highlight if Rukuli::Config.highlight_on_find
114
+ match
115
+ rescue NativeException => e
116
+ raise_exception e, filename
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ # Private: builds a java Pattern to check
123
+ #
124
+ # filename - A String representation of the filename to match against
125
+ # similarity - A Float between 0 and 1 representing the threshold for
126
+ # matching an image. Passing 1 corresponds to a 100% pixel for pixel
127
+ # match. Defaults to 0.9 (90% match)
128
+ #
129
+ # Returns a org.sikuli.script::Pattern object to match against
130
+ def build_pattern(filename, similarity)
131
+ org.sikuli.script::Pattern.new(filename).similar(similarity)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,32 @@
1
+ # Defines interactions with the keyboard. Implemented in the Region class.
2
+ #
3
+ module Rukuli
4
+ module Typeable
5
+
6
+ # Public: Types text as if it was being typed on the keyboard with an
7
+ # optional key modifier
8
+ #
9
+ # text - String representing text to be typed on keyboard
10
+ # modifier - (optional) Sikilu constant (defined in key_code.rb)
11
+ # representing key to hold while typing text
12
+ #
13
+ # Examples
14
+ #
15
+ # region.type("Hello World")
16
+ # region.type("s", Rukuli::KEY_CMD) # saves a file
17
+ #
18
+ # Returns nothing
19
+ def type(text, modifier = 0)
20
+ @java_obj.type(nil, text, modifier)
21
+ end
22
+
23
+ # Public: Types text then presses the return/enter key on the keyboard
24
+ #
25
+ # text - String
26
+ #
27
+ # Returns nothing
28
+ def enter(text)
29
+ @java_obj.type(text + Sikuli::KEY_RETURN)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Rukuli
2
+ VERSION = "1.0.0"
3
+ end
data/rukuli.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rukuli/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.add_development_dependency "rspec"
7
+ s.name = "rukuli"
8
+ s.version = Rukuli::VERSION
9
+ s.authors = ["Chas Lemley"]
10
+ s.email = ["chas.lemley@gmail.com"]
11
+ s.homepage = "https://github.com/andreanastacio/rukuli"
12
+ s.summary = %q{Ruby wrapper for Sikuli GUI automation library}
13
+ s.description = %q{Sikuli allows you to interact with your application's user interface using image based search to automate user actions.}
14
+
15
+ s.rubyforge_project = "rukuli"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::App do
4
+ subject { Rukuli::App.new(application "FILE_EXPLORER") }
5
+
6
+ its(:window) { should be_an_instance_of Rukuli::Region }
7
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Region, "#Clickable" do
4
+ before(:all) do
5
+ @region = setup_test_area
6
+ end
7
+
8
+ it "should perform a click at 10, 10" do
9
+ expect { @region.click(12, 12) }.not_to raise_error
10
+ end
11
+
12
+ it "should perform a double click 10, 1040" do
13
+ expect { @region.double_click(12, 491) }.not_to raise_error
14
+ end
15
+
16
+ it "should perform a double click on an image" do
17
+ expect { @region.double_click("smiley_face.png") }.not_to raise_error
18
+ end
19
+
20
+ it "should perform a drag and drop" do
21
+ expect { @region.drag_drop(12, 12, 491, 491) }.not_to raise_error
22
+ end
23
+
24
+ it "should perform a click on an image" do
25
+ expect { @region.click("smiley_face.png") }.not_to raise_error
26
+ end
27
+
28
+ it "should not perform a click on an image that is outside of the region" do
29
+ expect { @region.click("apple.png") }.to raise_error(Rukuli::ImageNotFound, "The image 'apple.png' did not match in this region.")
30
+ end
31
+
32
+ it "should perform a click and hold on an image" do
33
+ expect { @region.click_and_hold(2, "green_apple.png") }.not_to raise_error
34
+ end
35
+
36
+ it "should perform a click and hold on a point" do
37
+ expect { @region.click_and_hold(2, 100, 100) }.not_to raise_error
38
+ end
39
+
40
+ it "should simulate a mouse scroll wheel up event" do
41
+ expect { @region.wheel_up(10) }.not_to raise_error
42
+ end
43
+
44
+ it "should simulate a mouse scroll wheel down event" do
45
+ expect { @region.wheel_down(10) }.not_to raise_error
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Config do
4
+ it "should retain a list of directories to search in for images" do
5
+ Rukuli::Config.image_path = ""
6
+ java.lang.System.getProperty("SIKULI_IMAGE_PATH").should == ""
7
+ end
8
+
9
+ it "should allow a path to be added" do
10
+ Rukuli::Config.run do |config|
11
+ config.image_path = '/Users/images/'
12
+ end
13
+
14
+ java.lang.System.getProperty("SIKULI_IMAGE_PATH").should == '/Users/images/'
15
+ Rukuli::Config.image_path.should == '/Users/images/'
16
+ end
17
+
18
+ it "should allow logging to be turned on" do
19
+ Rukuli::Config.run do |config|
20
+ config.logging = true
21
+ end
22
+
23
+ org.sikuli.basics::Settings.InfoLogs.should be_true
24
+ org.sikuli.basics::Settings.ActionLogs.should be_true
25
+ org.sikuli.basics::Settings.DebugLogs.should be_true
26
+ end
27
+
28
+ it "should allow logging to be turned off" do
29
+ Rukuli::Config.run do |config|
30
+ config.logging = false
31
+ end
32
+
33
+ org.sikuli.basics::Settings.InfoLogs.should be_false
34
+ org.sikuli.basics::Settings.ActionLogs.should be_false
35
+ org.sikuli.basics::Settings.DebugLogs.should be_false
36
+ end
37
+
38
+ it "should allow us to turn off highlighting on find" do
39
+ Rukuli::Config.highlight_on_find = false
40
+ Rukuli::Config.highlight_on_find.should be_false
41
+ end
42
+
43
+ it "should allow us to turn on highlighting on find" do
44
+ Rukuli::Config.highlight_on_find = true
45
+ Rukuli::Config.highlight_on_find.should be_true
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Region, 'when initialized with a rectangle' do
4
+ subject { Rukuli::Region.new(10, 15, 250, 400) }
5
+ its(:x) { should == 10 }
6
+ its(:y) { should == 15 }
7
+ its(:width) { should == 250 }
8
+ its(:height) { should == 400 }
9
+ end
10
+
11
+ describe Rukuli::Region, 'when initialzed with a sikuli region' do
12
+ subject { Rukuli::Region.new org.sikuli.script::Region.new(11, 16, 251, 401) }
13
+ its(:x) { should == 11 }
14
+ its(:y) { should == 16 }
15
+ its(:width) { should == 251 }
16
+ its(:height) { should == 401 }
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Screen, 'when initialized' do
4
+ subject { Rukuli::Screen.new }
5
+
6
+ its(:x) { should == 0 }
7
+ its(:y) { should == 0 }
8
+ its(:width) { should > 0 } # screen's resolution
9
+ its(:width) { should be_instance_of Fixnum }
10
+ its(:height) { should > 0 } # screen's resolution
11
+ its(:height) { should be_instance_of Fixnum }
12
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Region, "#Searchable" do
4
+ before(:all) do
5
+ @region = setup_test_area
6
+ end
7
+
8
+ it "should find an image within the region" do
9
+ @region.find("smiley_face.png").should_not be_nil
10
+ end
11
+
12
+ it "should return a region containing the found image" do
13
+ @region.find("smiley_face.png").should be_an_instance_of Rukuli::Region
14
+ end
15
+
16
+ it "should raise an error if a file can not be found" do
17
+ expect { @region.find("no_photo.png") }.to raise_error(Rukuli::FileDoesNotExist, "The file 'no_photo.png' does not exist.")
18
+ end
19
+
20
+ it "should not find an image that is not in the region" do
21
+ expect { @region.find("apple.png") }.to raise_error(Rukuli::ImageNotFound, "The image 'apple.png' did not match in this region.")
22
+ end
23
+
24
+ it "should return true if the image is found" do
25
+ @region.find!("smiley_face.png").should be_an_instance_of Rukuli::Region
26
+ end
27
+
28
+ it "should return nil if the image is not found" do
29
+ @region.find!("apple.png").should be_nil
30
+ end
31
+
32
+ it "should raise an exception if the file does not exist" do
33
+ expect { @region.find!("no_photo.png") }.to raise_error(Rukuli::FileDoesNotExist, "The file 'no_photo.png' does not exist.")
34
+ end
35
+
36
+ context "#wait" do
37
+ it "should raise an error if no match is found after a given time" do
38
+ expect { @region.wait('apple.png') }.to raise_error(Rukuli::ImageNotFound, "The image 'apple.png' did not match in this region.")
39
+ end
40
+
41
+ it "should return a Region object when a match is found" do
42
+ match = @region.wait('green_apple.png', 5)
43
+ match.should be_an_instance_of Rukuli::Region
44
+ end
45
+ end
46
+
47
+ context "#find_all" do
48
+ before(:all) do
49
+ @matches = @region.find_all("green_apple.png")
50
+ end
51
+
52
+ it "should raise an error if no matches are found" do
53
+ expect { @region.find_all("apple.png") }.to raise_error(Rukuli::ImageNotFound, "The image 'apple.png' did not match in this region.")
54
+ end
55
+
56
+ it "should return an array" do
57
+ @matches.should be_an_instance_of Array
58
+ end
59
+
60
+ it "should contain 4 matches" do
61
+ @matches.length.should == 4
62
+ end
63
+
64
+ it "should contain regions" do
65
+ @matches.each do |m|
66
+ m.should be_an_instance_of Rukuli::Region
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rukuli::Region, "#Typeable" do
4
+ before(:all) do
5
+ app = Rukuli::App.new(application "TEXT_EDIT")
6
+ app.focus
7
+ @region = app.window
8
+ end
9
+
10
+ context "modifying text input with" do
11
+ it "apple key" do
12
+ @region.type("n", Rukuli::KEY_CMD)
13
+ end
14
+
15
+ it "shift key" do
16
+ @region.type("this should be lower case")
17
+ @region.type("this should be upper case", Rukuli::KEY_SHIFT)
18
+ end
19
+ end
20
+
21
+ context "unicode characters" do
22
+ it("backspace") { @region.type(Rukuli::KEY_BACKSPACE * 50) }
23
+ it("return") { @region.type(Rukuli::KEY_RETURN) }
24
+ it("left arrow") { @region.type(Rukuli::LEFT_ARROW) }
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require 'java'
2
+
3
+ require 'yaml'
4
+ require 'rspec'
5
+ require 'rukuli'
6
+
7
+ APPLICATIONS = YAML.load(File.open("#{Dir.pwd}/spec/support/applications.yml"))
8
+
9
+ Rukuli::Config.logging = false
10
+
11
+ def application(app)
12
+ APPLICATIONS[app].select { |k, _| k =~ /#{RbConfig::CONFIG['host_os']}/ }.shift[1]
13
+ end
14
+
15
+ def setup_test_area
16
+ Rukuli::Config.image_path = "#{Dir.pwd}/spec/support/images/"
17
+ screen = Rukuli::Screen.new
18
+ app = Rukuli::App.new(application "IMAGE_VIEWER")
19
+ app.focus()
20
+ center = screen.find("#{Dir.pwd}/spec/support/images/smiley_face.png")
21
+
22
+ test_area = Rukuli::Region.new(center.x - 212, center.y - 212, 503, 503)
23
+ test_area.highlight
24
+
25
+ test_area
26
+ end
@@ -0,0 +1,15 @@
1
+ IMAGE_VIEWER:
2
+ darwin|mac os:
3
+ 'Preview'
4
+ cygwin|mswin|mingw|bccwin|wince|emx|mswin32:
5
+ "explorer"
6
+ FILE_EXPLORER:
7
+ darwin|mac os:
8
+ 'Finder'
9
+ cygwin|mswin|mingw|bccwin|wince|emx|mswin32:
10
+ "explorer"
11
+ TEXT_EDIT:
12
+ darwin|mac os:
13
+ 'TextEdit'
14
+ cygwin|mswin|mingw|bccwin|wince|emx|mswin32:
15
+ "notepad"
Binary file
Binary file
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rukuli
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chas Lemley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
25
+ prerelease: false
26
+ type: :development
27
+ description: Sikuli allows you to interact with your application's user interface using image based search to automate user actions.
28
+ email:
29
+ - chas.lemley@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - License.txt
37
+ - README.md
38
+ - Rakefile
39
+ - lib/rukuli.rb
40
+ - lib/rukuli/app.rb
41
+ - lib/rukuli/clickable.rb
42
+ - lib/rukuli/config.rb
43
+ - lib/rukuli/exception.rb
44
+ - lib/rukuli/key_code.rb
45
+ - lib/rukuli/platform.rb
46
+ - lib/rukuli/region.rb
47
+ - lib/rukuli/screen.rb
48
+ - lib/rukuli/searchable.rb
49
+ - lib/rukuli/typeable.rb
50
+ - lib/rukuli/version.rb
51
+ - rukuli.gemspec
52
+ - spec/rukuli/app_spec.rb
53
+ - spec/rukuli/clickable_spec.rb
54
+ - spec/rukuli/config_spec.rb
55
+ - spec/rukuli/region_spec.rb
56
+ - spec/rukuli/screen_spec.rb
57
+ - spec/rukuli/searchable_spec.rb
58
+ - spec/rukuli/typeable_spec.rb
59
+ - spec/spec_helper.rb
60
+ - spec/support/applications.yml
61
+ - spec/support/images/apple.png
62
+ - spec/support/images/green_apple.png
63
+ - spec/support/images/smiley_face.png
64
+ - spec/support/images/test_area.jpg
65
+ homepage: https://github.com/andreanastacio/rukuli
66
+ licenses: []
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project: rukuli
84
+ rubygems_version: 2.1.9
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Ruby wrapper for Sikuli GUI automation library
88
+ test_files:
89
+ - spec/rukuli/app_spec.rb
90
+ - spec/rukuli/clickable_spec.rb
91
+ - spec/rukuli/config_spec.rb
92
+ - spec/rukuli/region_spec.rb
93
+ - spec/rukuli/screen_spec.rb
94
+ - spec/rukuli/searchable_spec.rb
95
+ - spec/rukuli/typeable_spec.rb
96
+ - spec/spec_helper.rb
97
+ - spec/support/applications.yml
98
+ - spec/support/images/apple.png
99
+ - spec/support/images/green_apple.png
100
+ - spec/support/images/smiley_face.png
101
+ - spec/support/images/test_area.jpg