rukuli 1.0.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.
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