mohawk 0.0.4 → 0.0.5
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/Changelog +7 -0
- data/LICENSE.txt +16 -18
- data/features/control.feature +4 -0
- data/features/mohawk.feature +4 -0
- data/features/step_definitions/control_steps.rb +8 -0
- data/features/step_definitions/mohawk_steps.rb +11 -0
- data/features/step_definitions/navigation_steps.rb +1 -1
- data/features/step_definitions/table_steps.rb +6 -1
- data/features/support/WindowsForms.exe +0 -0
- data/features/support/env.rb +4 -4
- data/features/support/screens/main_screen.rb +2 -0
- data/features/support/screens/screen_scoped_to_children.rb +6 -0
- data/features/table.feature +45 -39
- data/lib/mohawk/accessors/control.rb +3 -2
- data/lib/mohawk/accessors/table.rb +17 -1
- data/lib/mohawk/accessors/table_row.rb +6 -0
- data/lib/mohawk/accessors.rb +6 -3
- data/lib/mohawk/adapters/uia_adapter.rb +19 -11
- data/lib/mohawk/version.rb +1 -1
- data/lib/mohawk.rb +33 -9
- data/mohawk.gemspec +3 -1
- data/spec/lib/mohawk/accessors/control_spec.rb +10 -1
- data/spec/lib/mohawk/accessors/table_spec.rb +79 -59
- data/spec/lib/mohawk/adapters/uia_adapter_spec.rb +22 -1
- data/spec/lib/mohawk_spec.rb +64 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/table_stubber.rb +49 -0
- metadata +28 -7
data/Changelog
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== Version 0.0.5 / 2013-07-23
|
2
|
+
* Enhancements
|
3
|
+
* can tell a screen to only consider child controls when searching (can
|
4
|
+
boost speed, especially on windows with data grid views)
|
5
|
+
* added a simplified way to start and stop an application
|
6
|
+
* added the ability to select a table row based on a hash
|
7
|
+
|
1
8
|
=== Version 0.0.4 / 2013-05-24
|
2
9
|
* Enhancements
|
3
10
|
* sped up table access when there are many rows
|
data/LICENSE.txt
CHANGED
@@ -1,22 +1,20 @@
|
|
1
|
-
|
1
|
+
The MIT License (MIT)
|
2
2
|
|
3
|
-
|
3
|
+
Copyright (c) 2013 Levi Wilson
|
4
4
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
the following conditions:
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
12
11
|
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
15
14
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/features/control.feature
CHANGED
@@ -3,3 +3,7 @@ Feature: Working with controls
|
|
3
3
|
Scenario: Getting and setting the value of value controls
|
4
4
|
When I set the "value control field" to the value "12/25/2012"
|
5
5
|
Then the value of the "value control field" control should be "12/25/2012 12:00:00 AM"
|
6
|
+
|
7
|
+
Scenario: Clicking controls
|
8
|
+
When I click the control identified by "about_control"
|
9
|
+
Then the "About" screen should be shown
|
data/features/mohawk.feature
CHANGED
@@ -19,3 +19,7 @@ Scenario: Waiting for a particular control
|
|
19
19
|
Scenario: Specifying a parent container
|
20
20
|
When we are using the "ScreenWithContainer"
|
21
21
|
Then our parent is the container, not the main window
|
22
|
+
|
23
|
+
Scenario: Forcing the search scope to children only
|
24
|
+
When we tell the screen to limit searches to children only
|
25
|
+
Then we notice a performance increase, especially when data grid views are involved
|
@@ -1,3 +1,11 @@
|
|
1
1
|
Then /^the value of the "([^"]*)" control should be "(.*?)"$/ do |which, what|
|
2
2
|
on(MainScreen).send("#{which.to_method}_value").should eq(what)
|
3
|
+
end
|
4
|
+
|
5
|
+
When(/^I click the control identified by "([^"]*)"$/) do |what|
|
6
|
+
on(MainScreen).send("click_#{what}")
|
7
|
+
end
|
8
|
+
|
9
|
+
Then(/^the "([^"]*)" screen should be shown$/) do |screen|
|
10
|
+
on(AboutScreen).should be_active
|
3
11
|
end
|
@@ -21,4 +21,15 @@ end
|
|
21
21
|
Then /^our parent is the container, not the main window$/ do
|
22
22
|
@screen.adapter.window.title.should_not match(/MainForm/)
|
23
23
|
@screen.up_view.control_name.should eq('Forward')
|
24
|
+
end
|
25
|
+
|
26
|
+
When(/^we tell the screen to limit searches to children only$/) do
|
27
|
+
on(MainScreen).data_grid
|
28
|
+
on(ScreenScopedToChildren).should be_active
|
29
|
+
end
|
30
|
+
|
31
|
+
Then(/^we notice a performance increase, especially when data grid views are involved$/) do
|
32
|
+
start = Time.now
|
33
|
+
on(ScreenScopedToChildren).close
|
34
|
+
(Time.now - start).should be < 2
|
24
35
|
end
|
@@ -7,7 +7,7 @@ Then /^we should still arrive at our destination$/ do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
When /^we give extra navigational information to a screen$/ do
|
10
|
-
@which_screen = on(MainScreen, :pid =>
|
10
|
+
@which_screen = on(MainScreen, :pid => Mohawk.app.pid)
|
11
11
|
end
|
12
12
|
|
13
13
|
Then /^it should listen to every word that we say$/ do
|
@@ -17,6 +17,10 @@ When /^we select the table row with the value "([^"]*)"$/ do |row_value|
|
|
17
17
|
on(DataEntryForm).people = row_value
|
18
18
|
end
|
19
19
|
|
20
|
+
When(/^we select the table row with the following information:$/) do |table|
|
21
|
+
on(DataEntryForm).select_people table.hashes.first
|
22
|
+
end
|
23
|
+
|
20
24
|
When /^we select the "(.*?)"th table row$/ do |index|
|
21
25
|
on(DataEntryForm).people[index.to_i].select
|
22
26
|
end
|
@@ -50,4 +54,5 @@ end
|
|
50
54
|
|
51
55
|
Then(/^accessing the values in row "([^"]*)" should be snappy$/) do |which_row|
|
52
56
|
Timeout.timeout(5.0) { on(DataEntryForm).people[which_row.to_i].cells.should_not be_empty }
|
53
|
-
end
|
57
|
+
end
|
58
|
+
|
Binary file
|
data/features/support/env.rb
CHANGED
@@ -8,6 +8,8 @@ World(Mohawk::Navigation)
|
|
8
8
|
|
9
9
|
require_rel 'screens'
|
10
10
|
|
11
|
+
Mohawk.app_path = 'features/support/WindowsForms.exe'
|
12
|
+
|
11
13
|
Mohawk::Navigation.routes = {
|
12
14
|
:default => [
|
13
15
|
[MainScreen, :about],
|
@@ -18,11 +20,9 @@ Mohawk::Navigation.routes = {
|
|
18
20
|
}
|
19
21
|
|
20
22
|
Before do
|
21
|
-
|
22
|
-
@process.start
|
23
|
-
RAutomation::WaitHelper.wait_until {RAutomation::Window.new(:pid => @process.pid).present?}
|
23
|
+
Mohawk.start
|
24
24
|
end
|
25
25
|
|
26
26
|
After do
|
27
|
-
|
27
|
+
Mohawk.stop
|
28
28
|
end
|
@@ -6,6 +6,8 @@ class MainScreen
|
|
6
6
|
text(:masked_text_field, :id => "maskedTextBox")
|
7
7
|
button(:data_entry_form_button, :value => "Data Entry Form")
|
8
8
|
button(:about, :value => "About")
|
9
|
+
control(:about_control, :value => 'About')
|
10
|
+
button(:data_grid, :value => "Data Grid View")
|
9
11
|
combo_box(:fruits, :id => "FruitsComboBox")
|
10
12
|
checkbox(:first_checkbox, :id => "checkBox")
|
11
13
|
radio(:first_radio, :id => "radioButton1")
|
data/features/table.feature
CHANGED
@@ -1,41 +1,47 @@
|
|
1
1
|
Feature: Working with tables
|
2
2
|
|
3
|
-
Background:
|
4
|
-
|
5
|
-
|
6
|
-
Scenario: Selecting a row by index
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Scenario: Selecting a row by value
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Scenario: Selecting a row
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Scenario: Retrieving the
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
Scenario:
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
3
|
+
Background:
|
4
|
+
Given we are observing the people table
|
5
|
+
|
6
|
+
Scenario: Selecting a row by index
|
7
|
+
When we select the table row with index "1"
|
8
|
+
Then the row with index "1" should be selected
|
9
|
+
|
10
|
+
Scenario: Selecting a row by value
|
11
|
+
When we select the table row with the value "Anna Doe"
|
12
|
+
Then the row with index "1" should be selected
|
13
|
+
|
14
|
+
Scenario: Selecting a row by matching cell information
|
15
|
+
When we select the table row with the following information:
|
16
|
+
| name | date_of_birth |
|
17
|
+
| Anna Doe | 3/4/1975 |
|
18
|
+
Then the row with index "1" should be selected
|
19
|
+
|
20
|
+
Scenario: Selecting a row from a child item
|
21
|
+
When we select the "1"th table row
|
22
|
+
Then the row with index "1" should be selected
|
23
|
+
|
24
|
+
Scenario: Rows have cells
|
25
|
+
Then the row with index "0" should look like the following:
|
26
|
+
| Name | Date of Birth | State |
|
27
|
+
| John Doe | 12/15/1967 | FL |
|
28
|
+
|
29
|
+
Scenario: Retrieving the row values
|
30
|
+
Then the table row information should look like the following:
|
31
|
+
| text | row |
|
32
|
+
| John Doe | 0 |
|
33
|
+
| Anna Doe | 1 |
|
34
|
+
|
35
|
+
Scenario: Retrieving the headers
|
36
|
+
Then the table headers are "Name, Date of birth, State"
|
37
|
+
|
38
|
+
Scenario: Retrieving a row value by its header
|
39
|
+
Then the "date_of_birth" for the row at index "1" is "3/4/1975"
|
40
|
+
|
41
|
+
Scenario: Working with lots of records counts
|
42
|
+
When there are a lot of records in a table
|
43
|
+
Then the table count responds in a reasonable amount of time
|
44
|
+
|
45
|
+
Scenario: Working with a row when there are a lot of records
|
46
|
+
When there are a lot of records in a table
|
47
|
+
Then accessing the values in row "12" should be snappy
|
@@ -10,7 +10,18 @@ module Mohawk
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def select(which_item)
|
13
|
-
|
13
|
+
case which_item
|
14
|
+
when Hash
|
15
|
+
find_row_with(which_item).select
|
16
|
+
else
|
17
|
+
select_by_value(which_item)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_row_with(row_info)
|
22
|
+
found_row = find { |r| r.all_match? row_info }
|
23
|
+
raise "A row with #{row_info} was not found" unless found_row
|
24
|
+
found_row
|
14
25
|
end
|
15
26
|
|
16
27
|
def headers
|
@@ -26,6 +37,11 @@ module Mohawk
|
|
26
37
|
yield TableRow.new(self, row)
|
27
38
|
end
|
28
39
|
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def select_by_value(which_item)
|
43
|
+
view.select which_item
|
44
|
+
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
end
|
@@ -22,6 +22,12 @@ module Mohawk
|
|
22
22
|
row.cells.map &:text
|
23
23
|
end
|
24
24
|
|
25
|
+
def all_match?(hash)
|
26
|
+
hash.all? do |key, value|
|
27
|
+
send(key) == "#{value}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
25
31
|
def value_from_header(name)
|
26
32
|
which_column = header_methods.index(name)
|
27
33
|
raise ArgumentError, "#{name} column does not exist in #{header_methods}" if which_column.nil?
|
data/lib/mohawk/accessors.rb
CHANGED
@@ -223,7 +223,7 @@ module Mohawk
|
|
223
223
|
#
|
224
224
|
# @example
|
225
225
|
# table(:some_table, :id => "tableId")
|
226
|
-
# # will generate 'some_table', 'some_table=', 'some_table_headers'
|
226
|
+
# # will generate 'some_table', 'some_table=', 'some_table_headers', 'select_some_table'
|
227
227
|
# # and 'some_table_view' methods to get an Enumerable of table rows,
|
228
228
|
# # select a table item, return all of the headers and get the raw view
|
229
229
|
#
|
@@ -241,6 +241,9 @@ module Mohawk
|
|
241
241
|
define_method("#{name}=") do |which_item|
|
242
242
|
adapter.table(locator).select which_item
|
243
243
|
end
|
244
|
+
define_method("select_#{name}") do |hash_info|
|
245
|
+
adapter.table(locator).select hash_info
|
246
|
+
end
|
244
247
|
define_method("#{name}_headers") do
|
245
248
|
adapter.table(locator).headers
|
246
249
|
end
|
@@ -292,8 +295,8 @@ module Mohawk
|
|
292
295
|
define_method("#{name}=") do |value|
|
293
296
|
adapter.value_control(locator).set value
|
294
297
|
end
|
295
|
-
define_method("click_#{name}") do
|
296
|
-
adapter.value_control(locator).click
|
298
|
+
define_method("click_#{name}") do |&block|
|
299
|
+
adapter.value_control(locator).click &block
|
297
300
|
end
|
298
301
|
define_method("#{name}_view") do
|
299
302
|
adapter.value_control(locator).view
|
@@ -2,6 +2,7 @@ module Mohawk
|
|
2
2
|
module Adapters
|
3
3
|
class UiaAdapter
|
4
4
|
def initialize(locator, container=nil)
|
5
|
+
@only_search_children = locator.delete(:children_only)
|
5
6
|
@window = RAutomation::Window.new(locator.merge(:adapter => :ms_uia))
|
6
7
|
@container = container
|
7
8
|
end
|
@@ -19,56 +20,63 @@ module Mohawk
|
|
19
20
|
|
20
21
|
def combo(locator)
|
21
22
|
@combos ||= {}
|
22
|
-
@combos[locator] ||= Mohawk::Accessors::Combo.new(self, locator)
|
23
|
+
@combos[locator] ||= Mohawk::Accessors::Combo.new(self, merge(locator))
|
23
24
|
end
|
24
25
|
|
26
|
+
|
25
27
|
def checkbox(locator)
|
26
28
|
@checkbox ||= {}
|
27
|
-
@checkbox[locator] ||= Mohawk::Accessors::CheckBox.new(self, locator)
|
29
|
+
@checkbox[locator] ||= Mohawk::Accessors::CheckBox.new(self, merge(locator))
|
28
30
|
end
|
29
31
|
|
30
32
|
def text(locator)
|
31
33
|
@text_fields ||= {}
|
32
|
-
@text_fields[locator] ||= Mohawk::Accessors::Text.new(self, locator)
|
34
|
+
@text_fields[locator] ||= Mohawk::Accessors::Text.new(self, merge(locator))
|
33
35
|
end
|
34
36
|
|
35
37
|
def button(locator)
|
36
38
|
@buttons ||= {}
|
37
|
-
@buttons[locator] ||= Mohawk::Accessors::Button.new(self, locator)
|
39
|
+
@buttons[locator] ||= Mohawk::Accessors::Button.new(self, merge(locator))
|
38
40
|
end
|
39
41
|
|
40
42
|
def radio(locator)
|
41
43
|
@radios ||= {}
|
42
|
-
@radios[locator] ||= Mohawk::Accessors::Radio.new(self, locator)
|
44
|
+
@radios[locator] ||= Mohawk::Accessors::Radio.new(self, merge(locator))
|
43
45
|
end
|
44
46
|
|
45
47
|
def label(locator)
|
46
48
|
@labels ||= {}
|
47
|
-
@labels[locator] ||= Mohawk::Accessors::Label.new(self, locator)
|
49
|
+
@labels[locator] ||= Mohawk::Accessors::Label.new(self, merge(locator))
|
48
50
|
end
|
49
51
|
|
50
52
|
def link(locator)
|
51
53
|
@links ||= {}
|
52
|
-
@links[locator] ||= Mohawk::Accessors::Link.new(self, locator)
|
54
|
+
@links[locator] ||= Mohawk::Accessors::Link.new(self, merge(locator))
|
53
55
|
end
|
54
56
|
|
55
57
|
def menu_item(locator)
|
56
|
-
Mohawk::Accessors::MenuItem.new(self, locator)
|
58
|
+
Mohawk::Accessors::MenuItem.new(self, merge(locator))
|
57
59
|
end
|
58
60
|
|
59
61
|
def table(locator)
|
60
62
|
@tables ||= {}
|
61
|
-
@tables[locator] ||= Mohawk::Accessors::Table.new(self, locator)
|
63
|
+
@tables[locator] ||= Mohawk::Accessors::Table.new(self, merge(locator))
|
62
64
|
end
|
63
65
|
|
64
66
|
def tree_view(locator)
|
65
67
|
@trees ||= {}
|
66
|
-
@trees[locator] ||= Mohawk::Accessors::TreeView.new(self, locator)
|
68
|
+
@trees[locator] ||= Mohawk::Accessors::TreeView.new(self, merge(locator))
|
67
69
|
end
|
68
70
|
|
69
71
|
def value_control(locator)
|
70
72
|
@controls ||= {}
|
71
|
-
@controls[locator] ||= Mohawk::Accessors::Control.new(self, locator)
|
73
|
+
@controls[locator] ||= Mohawk::Accessors::Control.new(self, merge(locator))
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def merge(locator)
|
78
|
+
locator = locator.merge(:children_only => true) if @only_search_children
|
79
|
+
locator
|
72
80
|
end
|
73
81
|
end
|
74
82
|
end
|
data/lib/mohawk/version.rb
CHANGED
data/lib/mohawk.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "rautomation"
|
2
|
+
require 'childprocess'
|
2
3
|
require "mohawk/version"
|
3
4
|
require "require_all"
|
4
5
|
require "mohawk/accessors"
|
@@ -9,13 +10,43 @@ require "mohawk/core_ext/string"
|
|
9
10
|
require_rel "mohawk/accessors"
|
10
11
|
|
11
12
|
module Mohawk
|
13
|
+
include RAutomation::WaitHelper
|
14
|
+
extend RAutomation::WaitHelper
|
12
15
|
|
13
|
-
|
16
|
+
class InvalidApplicationPath < StandardError
|
17
|
+
def initialize(message='You must set the Mohawk.app_path to start an application')
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
14
21
|
|
15
22
|
def self.included(cls)
|
16
23
|
cls.extend Mohawk::Accessors
|
17
24
|
end
|
18
25
|
|
26
|
+
attr_reader :adapter
|
27
|
+
|
28
|
+
def self.start
|
29
|
+
raise InvalidApplicationPath.new unless @app_path
|
30
|
+
@app = ChildProcess.build(@app_path).start
|
31
|
+
|
32
|
+
app_window = RAutomation::Window.new :pid => @app.pid
|
33
|
+
wait_until { app_window.present? }
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.stop
|
37
|
+
raise 'An application was never started' unless @app
|
38
|
+
@app.stop unless @app.exited?
|
39
|
+
@app = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.app
|
43
|
+
@app
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.app_path=(path)
|
47
|
+
@app_path = path
|
48
|
+
end
|
49
|
+
|
19
50
|
def initialize(extra={})
|
20
51
|
locator = [which_window.merge(extra)]
|
21
52
|
locator << parent_container if respond_to?(:parent_container)
|
@@ -56,19 +87,12 @@ module Mohawk
|
|
56
87
|
def wait_for_control(locator)
|
57
88
|
control = adapter.window.control(locator)
|
58
89
|
begin
|
59
|
-
|
90
|
+
wait_until { control.exist? }
|
60
91
|
rescue
|
61
92
|
raise "A control with #{locator} was not found"
|
62
93
|
end
|
63
94
|
end
|
64
95
|
|
65
|
-
#
|
66
|
-
# Waits until the block returns true
|
67
|
-
#
|
68
|
-
def wait_until(timeout=RAutomation::Window.wait_timeout, &block)
|
69
|
-
RAutomation::WaitHelper.wait_until timeout, &block
|
70
|
-
end
|
71
|
-
|
72
96
|
#
|
73
97
|
# Indicates if the window has text or not
|
74
98
|
#
|
data/mohawk.gemspec
CHANGED
@@ -11,15 +11,17 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.description = %q{High level wrapper around windows applications}
|
12
12
|
gem.summary = %q{High level wrapper around windows applications}
|
13
13
|
gem.homepage = "http://github.com/leviwilson/mohawk"
|
14
|
+
gem.license = 'MIT'
|
14
15
|
|
15
16
|
gem.files = `git ls-files`.split($/)
|
16
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
19
|
gem.require_paths = ["lib"]
|
19
20
|
|
20
|
-
gem.add_dependency 'rautomation', '
|
21
|
+
gem.add_dependency 'rautomation', '~> 0.9.3'
|
21
22
|
gem.add_dependency 'require_all'
|
22
23
|
gem.add_dependency 'page_navigation', '>= 0.7'
|
24
|
+
gem.add_dependency 'childprocess', '~> 0.3.9'
|
23
25
|
|
24
26
|
gem.add_development_dependency 'cucumber'
|
25
27
|
gem.add_development_dependency 'rspec', '>= 2.12.0'
|
@@ -28,10 +28,19 @@ describe Mohawk::Accessors::Control do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "can click the control" do
|
31
|
-
control.should_receive(:click)
|
31
|
+
control.should_receive(:click).and_yield
|
32
32
|
screen.click_control_yourself
|
33
33
|
end
|
34
34
|
|
35
|
+
it "can give a custom block to the click method" do
|
36
|
+
control.should_receive(:click).and_yield
|
37
|
+
result = false
|
38
|
+
screen.click_control_yourself do
|
39
|
+
result = true
|
40
|
+
end
|
41
|
+
result.should be_true
|
42
|
+
end
|
43
|
+
|
35
44
|
it "can work with the raw view" do
|
36
45
|
screen.control_yourself_view.should be(control)
|
37
46
|
end
|
@@ -4,122 +4,142 @@ class TableScreen
|
|
4
4
|
include Mohawk
|
5
5
|
window(:id => nil)
|
6
6
|
|
7
|
-
table(:top, :id =>
|
7
|
+
table(:top, :id => 'tableId')
|
8
8
|
|
9
9
|
# aliases
|
10
|
-
table(:my_default, :id =>
|
11
|
-
listview(:my_listview, :id =>
|
12
|
-
list_view(:my_list_view, :id =>
|
10
|
+
table(:my_default, :id => 'defaultAliasId')
|
11
|
+
listview(:my_listview, :id => 'listviewAliasId')
|
12
|
+
list_view(:my_list_view, :id => 'list_viewAliasId')
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
attr_reader :text, :row
|
17
|
-
def initialize(text, row)
|
18
|
-
@text = text
|
19
|
-
@row = row
|
20
|
-
end
|
21
|
-
end
|
15
|
+
include RAutomation::Adapter::MsUia
|
22
16
|
|
23
17
|
describe Mohawk::Accessors::Table do
|
24
18
|
let(:screen) { TableScreen.new }
|
25
|
-
let(:window) { double(
|
26
|
-
let(:table) { double(
|
19
|
+
let(:window) { double('RAutomation Window') }
|
20
|
+
let(:table) { double('Table') }
|
27
21
|
|
28
22
|
before(:each) do
|
29
23
|
RAutomation::Window.stub(:new).and_return(window)
|
30
24
|
end
|
31
25
|
|
32
|
-
context
|
26
|
+
context 'working with table controls' do
|
33
27
|
before(:each) do
|
34
|
-
window.should_receive(:table).with(:id =>
|
28
|
+
window.should_receive(:table).with(:id => 'tableId').and_return(table)
|
35
29
|
end
|
36
30
|
|
37
|
-
it
|
31
|
+
it 'can select a row by index' do
|
38
32
|
table.should_receive(:select).with(1)
|
39
33
|
screen.top = 1
|
40
34
|
end
|
41
35
|
|
42
|
-
it
|
43
|
-
table.should_receive(:select).with
|
44
|
-
screen.top =
|
36
|
+
it 'can select a row by value' do
|
37
|
+
table.should_receive(:select).with 'John Elway'
|
38
|
+
screen.top = 'John Elway'
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'selecting a row by hash' do
|
42
|
+
it 'selects the row if all values match' do
|
43
|
+
TableStubber.stub(table)
|
44
|
+
.with_headers('Column One', 'Column Two', 'Column Three')
|
45
|
+
.and_row('first', 'something', 'foo')
|
46
|
+
.and_row('second', 'another', 'bar')
|
47
|
+
|
48
|
+
table.should_receive(:select).with(1)
|
49
|
+
screen.select_top :column_one => 'second', :column_three => 'bar'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can handle non-string values' do
|
53
|
+
TableStubber.stub(table)
|
54
|
+
.with_headers('name', 'age')
|
55
|
+
.and_row('Levi', '33')
|
56
|
+
|
57
|
+
table.should_receive(:select).with(0)
|
58
|
+
screen.select_top :age => 33
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'raises if no row is found' do
|
62
|
+
TableStubber.stub(table)
|
63
|
+
.with_headers('Column One', 'Column Two', 'Column Three')
|
64
|
+
.and_row('first', 'something', 'foo')
|
65
|
+
|
66
|
+
lambda { screen.select_top :column_one => 'not found' }.should raise_error "A row with {:column_one=>\"not found\"} was not found"
|
67
|
+
end
|
68
|
+
|
45
69
|
end
|
46
70
|
|
47
|
-
it
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
screen.top.map(&:to_hash).should eq(expected_rows)
|
71
|
+
it 'has rows' do
|
72
|
+
TableStubber.stub(table)
|
73
|
+
.with_headers('Column')
|
74
|
+
.and_row('First Row')
|
75
|
+
.and_row('Second Row')
|
76
|
+
|
77
|
+
screen.top.map(&:column).should eq(['First Row', 'Second Row'])
|
55
78
|
end
|
56
79
|
|
57
|
-
it
|
58
|
-
|
59
|
-
|
60
|
-
RAutomation::Adapter::MsUia::UiaDll.should_receive(:table_headers).with(1234).and_return(expected_headers)
|
61
|
-
screen.top_headers.should eq(expected_headers)
|
80
|
+
it 'has headers' do
|
81
|
+
TableStubber.stub(table).with_headers('first header', 'second header')
|
82
|
+
screen.top_headers.should eq(['first header', 'second header'])
|
62
83
|
end
|
63
84
|
|
64
|
-
it
|
85
|
+
it 'can return the raw view' do
|
65
86
|
screen.top_view.should_not be_nil
|
66
87
|
end
|
67
88
|
|
68
89
|
describe Mohawk::Accessors::TableRow do
|
69
|
-
let(:table_row) { double("RAutomation TableRow") }
|
70
|
-
|
71
90
|
before(:each) do
|
72
|
-
|
73
|
-
table_row.stub(:row).and_return 0
|
91
|
+
TableStubber.stub(table).with_headers('column').and_row('first row')
|
74
92
|
end
|
75
93
|
|
76
|
-
it
|
94
|
+
it 'can get an individual row' do
|
77
95
|
screen.top[0].should_not be_nil
|
78
96
|
end
|
79
97
|
|
80
|
-
it
|
98
|
+
it 'knows if it is selected' do
|
81
99
|
table.should_receive(:selected?).with(0).and_return(true)
|
82
100
|
screen.top[0].should be_selected
|
83
101
|
end
|
84
102
|
|
85
|
-
it
|
103
|
+
it 'can be selected' do
|
86
104
|
table.should_receive(:select).with(0)
|
87
105
|
screen.top[0].select
|
88
106
|
end
|
89
107
|
|
90
|
-
it
|
91
|
-
|
92
|
-
|
93
|
-
|
108
|
+
it 'has cells' do
|
109
|
+
TableStubber.stub(table)
|
110
|
+
.with_headers('first', 'second')
|
111
|
+
.and_row('Cell 1', 'Cell 2')
|
112
|
+
|
113
|
+
screen.top[0].cells.should eq(['Cell 1', 'Cell 2'])
|
94
114
|
end
|
95
115
|
|
96
|
-
it
|
97
|
-
|
98
|
-
|
116
|
+
it 'can get cell values by header name' do
|
117
|
+
TableStubber.stub(table)
|
118
|
+
.with_headers('First Header', 'Second Header')
|
119
|
+
.and_row('Item 1', 'Item 2')
|
99
120
|
|
100
|
-
|
101
|
-
expected_cell.should_receive(:text).and_return('Item 2')
|
102
|
-
RAutomation::Adapter::MsUia::Cell.should_receive(:new).with(table_row, :index => 1).and_return(expected_cell)
|
103
|
-
screen.top[0].second_header.should eq("Item 2")
|
121
|
+
screen.top[0].second_header.should eq('Item 2')
|
104
122
|
end
|
105
123
|
|
106
|
-
it
|
107
|
-
|
108
|
-
|
109
|
-
|
124
|
+
it 'clearly lets you know if a header is not there' do
|
125
|
+
TableStubber.stub(table)
|
126
|
+
.with_headers('First Header', 'Second Header')
|
127
|
+
.and_row('Item 1', 'Item 2')
|
128
|
+
|
129
|
+
lambda { screen.top[0].does_not_exist }.should raise_error ArgumentError, 'does_not_exist column does not exist in [:first_header, :second_header]'
|
110
130
|
end
|
111
131
|
end
|
112
132
|
end
|
113
133
|
|
114
|
-
context
|
115
|
-
let(:null_table) { double(
|
116
|
-
let(:table_aliases) { [
|
134
|
+
context 'aliases for table' do
|
135
|
+
let(:null_table) { double('Null ComboBox Field').as_null_object }
|
136
|
+
let(:table_aliases) { ['default', 'listview', 'list_view'] }
|
117
137
|
|
118
138
|
def expected_alias(id)
|
119
139
|
window.should_receive(:table).with(:id => "#{id}AliasId").ordered.and_return(null_table)
|
120
140
|
end
|
121
141
|
|
122
|
-
it
|
142
|
+
it 'has many aliases' do
|
123
143
|
table_aliases.each do |which_alias|
|
124
144
|
expected_alias which_alias
|
125
145
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
class AnyScreen
|
4
4
|
include Mohawk
|
5
|
-
window(:title => /Some Title
|
5
|
+
window(:title => /Some Title/, :children_only => true)
|
6
6
|
end
|
7
7
|
|
8
8
|
RSpec::Matchers.define(:use_the_cached) do |control_type, the_alias=nil|
|
@@ -13,12 +13,33 @@ RSpec::Matchers.define(:use_the_cached) do |control_type, the_alias=nil|
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
RSpec::Matchers.define(:only_search_children_for_a) do |control_type, the_alias=nil|
|
17
|
+
match do |screen|
|
18
|
+
screen.adapter.window.should_receive(the_alias || control_type).with(:id => 'id', :children_only => true).and_return(double('child control'))
|
19
|
+
screen.adapter.send(control_type, :id => 'id')
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
16
24
|
describe Mohawk::Adapters::UiaAdapter do
|
17
25
|
subject(:screen) { AnyScreen.new }
|
18
26
|
|
19
27
|
let(:window) { double('RAutomation Window') }
|
20
28
|
before(:each) { RAutomation::Window.stub(:new).and_return(window) }
|
21
29
|
|
30
|
+
context 'limiting searches to children only' do
|
31
|
+
it { should only_search_children_for_a(:combo, :select_list) }
|
32
|
+
it { should only_search_children_for_a(:checkbox) }
|
33
|
+
it { should only_search_children_for_a(:text, :text_field) }
|
34
|
+
it { should only_search_children_for_a(:button) }
|
35
|
+
it { should only_search_children_for_a(:radio) }
|
36
|
+
it { should only_search_children_for_a(:label) }
|
37
|
+
it { should only_search_children_for_a(:link, :label) }
|
38
|
+
it { should only_search_children_for_a(:table) }
|
39
|
+
it { should only_search_children_for_a(:tree_view, :select_list) }
|
40
|
+
it { should only_search_children_for_a(:value_control) }
|
41
|
+
end
|
42
|
+
|
22
43
|
context 'caching controls' do
|
23
44
|
it { should use_the_cached(:combo, :select_list) }
|
24
45
|
it { should use_the_cached(:checkbox) }
|
data/spec/lib/mohawk_spec.rb
CHANGED
@@ -11,6 +11,11 @@ describe Mohawk do
|
|
11
11
|
let(:screen) { TestScreen.new }
|
12
12
|
let(:window) { double('RAutomation Window') }
|
13
13
|
|
14
|
+
before(:each) do
|
15
|
+
Mohawk.app_path = nil
|
16
|
+
Mohawk.instance_variable_set(:@app, nil)
|
17
|
+
end
|
18
|
+
|
14
19
|
it "uses the uia adapter by default" do
|
15
20
|
RAutomation::Window.should_receive(:new).with(:title => "Some Window Title", :adapter => :ms_uia).and_return(window)
|
16
21
|
TestScreen.new
|
@@ -21,6 +26,64 @@ describe Mohawk do
|
|
21
26
|
TestScreen.new :extra => 'test'
|
22
27
|
end
|
23
28
|
|
29
|
+
context 'launching an application' do
|
30
|
+
let(:process) { double('ChildProcess::Process') }
|
31
|
+
|
32
|
+
before(:each) do
|
33
|
+
process.stub(:start).and_return(process)
|
34
|
+
[:exited?, :stop, :pid].each &process.method(:stub)
|
35
|
+
ChildProcess.stub(:build).with('./the/app/path.exe').and_return(process)
|
36
|
+
|
37
|
+
RAutomation::Window.stub(:new).and_return(window)
|
38
|
+
window.stub(:present?).and_return(true)
|
39
|
+
|
40
|
+
Mohawk.app_path = './the/app/path.exe'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'requires a path' do
|
44
|
+
Mohawk.app_path = nil
|
45
|
+
lambda { Mohawk.start }.should raise_error(Mohawk::InvalidApplicationPath, 'You must set the Mohawk.app_path to start an application')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can start an application' do
|
49
|
+
process.should_receive(:start)
|
50
|
+
Mohawk.start
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'waits on the application' do
|
54
|
+
process.should_receive(:pid).and_return(123)
|
55
|
+
RAutomation::Window.should_receive(:new).with(:pid => 123).and_return(window)
|
56
|
+
window.should_receive(:present?).and_return(false, false, true)
|
57
|
+
|
58
|
+
Mohawk.start
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'can stop an application' do
|
62
|
+
process.should_receive(:stop)
|
63
|
+
Mohawk.start
|
64
|
+
|
65
|
+
Mohawk.stop
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'knows if a process was never started' do
|
69
|
+
lambda { Mohawk.stop }.should raise_error('An application was never started')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'does nothing if the process has exited' do
|
73
|
+
process.should_receive(:exited?).and_return(true)
|
74
|
+
process.should_not_receive(:stop)
|
75
|
+
Mohawk.start
|
76
|
+
|
77
|
+
Mohawk.stop
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'has a clean slate if the application has been stopped' do
|
81
|
+
Mohawk.start
|
82
|
+
Mohawk.stop
|
83
|
+
Mohawk.app.should be_nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
24
87
|
context "using the UI Automation adapter" do
|
25
88
|
before(:each) do
|
26
89
|
RAutomation::Window.stub(:new).and_return(window)
|
@@ -60,7 +123,7 @@ describe Mohawk do
|
|
60
123
|
end
|
61
124
|
|
62
125
|
it "tells you what you were waiting for if it fails" do
|
63
|
-
|
126
|
+
screen.should_receive(:wait_until).and_raise("you should have caught me")
|
64
127
|
locator = {:id => "whatever", :index => 0}
|
65
128
|
window.should_receive(:control).with(locator)
|
66
129
|
lambda { screen.wait_for_control(:id => "whatever", :index => 0) }.should raise_error(Exception, "A control with #{locator} was not found")
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
class TableStubber
|
4
|
+
include RSpec::Mocks::ExampleMethods
|
5
|
+
|
6
|
+
attr_reader :table, :rows
|
7
|
+
|
8
|
+
def initialize(table)
|
9
|
+
@table = table
|
10
|
+
@id = SecureRandom.base64
|
11
|
+
@table.stub(:search_information).and_return(@id)
|
12
|
+
@rows = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.stub(table)
|
16
|
+
TableStubber.new(table)
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_headers(*headers)
|
20
|
+
UiaDll.stub(:table_headers).with(@id).and_return(headers.map(&:to_s))
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def and_row(*values)
|
25
|
+
stub_cells_for(add_row, values)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def add_row
|
31
|
+
row = double("table #{@id}, row #{rows.count}")
|
32
|
+
row.stub(:row).and_return(rows.count)
|
33
|
+
Row.stub(:new).with(table, :index => rows.count).and_return(row)
|
34
|
+
rows << row
|
35
|
+
table.stub(:row_count).and_return(rows.count)
|
36
|
+
row
|
37
|
+
end
|
38
|
+
|
39
|
+
def stub_cells_for(row, values)
|
40
|
+
cells = []
|
41
|
+
values.each_with_index do |value, index|
|
42
|
+
cell = double("Cell at #{row.row}, #{index}")
|
43
|
+
Cell.stub(:new).with(row, :index => index).and_return(cell)
|
44
|
+
cell.stub(:text).and_return(value)
|
45
|
+
cells << cell
|
46
|
+
end
|
47
|
+
row.stub(:cells).and_return(cells)
|
48
|
+
end
|
49
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mohawk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rautomation
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.9.
|
21
|
+
version: 0.9.3
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.9.
|
29
|
+
version: 0.9.3
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: require_all
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0.7'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: childprocess
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.3.9
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.3.9
|
62
78
|
- !ruby/object:Gem::Dependency
|
63
79
|
name: cucumber
|
64
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -274,6 +290,7 @@ files:
|
|
274
290
|
- features/support/screens/about_screen.rb
|
275
291
|
- features/support/screens/data_entry_form.rb
|
276
292
|
- features/support/screens/main_screen.rb
|
293
|
+
- features/support/screens/screen_scoped_to_children.rb
|
277
294
|
- features/support/screens/screen_with_container.rb
|
278
295
|
- features/support/string.rb
|
279
296
|
- features/table.feature
|
@@ -314,8 +331,10 @@ files:
|
|
314
331
|
- spec/lib/mohawk_spec.rb
|
315
332
|
- spec/lib/navigation_spec.rb
|
316
333
|
- spec/spec_helper.rb
|
334
|
+
- spec/table_stubber.rb
|
317
335
|
homepage: http://github.com/leviwilson/mohawk
|
318
|
-
licenses:
|
336
|
+
licenses:
|
337
|
+
- MIT
|
319
338
|
post_install_message:
|
320
339
|
rdoc_options: []
|
321
340
|
require_paths:
|
@@ -393,6 +412,7 @@ test_files:
|
|
393
412
|
- features/support/screens/about_screen.rb
|
394
413
|
- features/support/screens/data_entry_form.rb
|
395
414
|
- features/support/screens/main_screen.rb
|
415
|
+
- features/support/screens/screen_scoped_to_children.rb
|
396
416
|
- features/support/screens/screen_with_container.rb
|
397
417
|
- features/support/string.rb
|
398
418
|
- features/table.feature
|
@@ -414,4 +434,5 @@ test_files:
|
|
414
434
|
- spec/lib/mohawk_spec.rb
|
415
435
|
- spec/lib/navigation_spec.rb
|
416
436
|
- spec/spec_helper.rb
|
437
|
+
- spec/table_stubber.rb
|
417
438
|
has_rdoc:
|