domkey 0.1.0 → 0.2.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 +4 -4
- data/.gitmodules +3 -0
- data/README.md +2 -4
- data/Rakefile +4 -1
- data/lib/domkey/version.rb +1 -1
- data/lib/domkey/view.rb +42 -4
- data/lib/domkey/view/cargo.rb +92 -0
- data/lib/domkey/view/checkbox_group.rb +75 -0
- data/lib/domkey/view/label_mapper.rb +18 -0
- data/lib/domkey/view/labeled_group.rb +50 -0
- data/lib/domkey/view/page_object.rb +85 -85
- data/lib/domkey/view/page_object_collection.rb +31 -49
- data/lib/domkey/view/radio_group.rb +70 -0
- data/lib/domkey/view/watir_widget.rb +106 -0
- data/lib/domkey/view/widgetry_package.rb +48 -0
- data/spec/checkbox_group_spec.rb +81 -0
- data/spec/html/test.html +61 -5
- data/spec/labeled_group_spec.rb +118 -0
- data/spec/page_object_collection_spec.rb +14 -14
- data/spec/page_object_decorators_spec.rb +41 -52
- data/spec/page_object_spec.rb +23 -18
- data/spec/page_spec.rb +12 -12
- data/spec/radio_group_spec.rb +85 -0
- data/spec/selenium_webdriver_api_example.rb +42 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/view_cargo_spec.rb +106 -0
- data/spec/view_dom_spec.rb +50 -16
- data/spec/view_doms_spec.rb +7 -7
- data/spec/watir_widget_select_spec.rb +176 -0
- data/spec/watirspec_pageobjects/watirspec_spec.rb +12 -0
- metadata +24 -2
@@ -2,45 +2,36 @@ module Domkey
|
|
2
2
|
|
3
3
|
module View
|
4
4
|
|
5
|
+
# PageObjectCollection see PageObject for detailes.
|
6
|
+
# Compose PageObjectCollection with package and container
|
7
|
+
#
|
8
|
+
# What is a container? see PageObject container
|
9
|
+
#
|
10
|
+
# What is package? see PageObject package except the following:
|
11
|
+
# package can be one of the following:
|
12
|
+
# - definition of watir elements collection i.e. `-> { text_fields(:class, /^foo/)}`
|
13
|
+
# - a pageobject i.e. previously instantiated definition watir elements collection
|
14
|
+
# - hash where key defines subelement and value a definition or pageobject
|
15
|
+
# Usage:
|
16
|
+
# Clients would not usually instantate this class.
|
17
|
+
# A client class which acts as a View would use a :doms factory method to create PageObjectCollection
|
18
|
+
# TODO Example:
|
5
19
|
class PageObjectCollection
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
attr_accessor :watirproc, :container
|
9
|
-
|
10
|
-
# PageObjectCollection see PageObject for detailes.
|
11
|
-
# Compose PageObjectCollection with watirproc and container
|
12
|
-
#
|
13
|
-
# What is a container? see PageObject container
|
14
|
-
#
|
15
|
-
# What is watirproc? see PageObject watirproc except the following:
|
16
|
-
# watirproc can be one of the following:
|
17
|
-
# - definition of watir elements collection i.e. `-> { text_fields(:class, /^foo/)}`
|
18
|
-
# - a pageobject i.e. previously instantiated definition watir elements collection
|
19
|
-
# - hash where key defines subelement and value a definition or pageobject
|
20
|
-
# Usage:
|
21
|
-
# Clients would not usually instantate this class.
|
22
|
-
# A client class which acts as a View would use a :doms factory method to create PageObjectCollection
|
23
|
-
# Example:
|
24
|
-
#
|
25
|
-
def initialize watirproc, container=lambda { Domkey.browser }
|
26
|
-
@container = container
|
27
|
-
@watirproc = initialize_this watirproc
|
28
|
-
end
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
return watirproc.fetch(key).element if key
|
33
|
-
Hash[watirproc.map { |key, watirproc| [key, watirproc.element] }]
|
34
|
-
end
|
21
|
+
include WidgetryPackage
|
22
|
+
include Enumerable
|
35
23
|
|
24
|
+
# @return [PageObject, Hash{Symbol => PageObjectCollection}]
|
36
25
|
def each(&blk)
|
37
|
-
if
|
38
|
-
|
26
|
+
if package.respond_to?(:each_pair)
|
27
|
+
package.map { |k, v| [k, PageObjectCollection.new(lambda { v }, @container)] }.each { |k, v| yield Hash[k, v] }
|
39
28
|
else
|
40
29
|
instantiator.each { |e| yield PageObject.new(lambda { e }, @container) }
|
41
30
|
end
|
42
31
|
end
|
43
32
|
|
33
|
+
# @param [Fixnum]
|
34
|
+
# @return [PageObject, Hash{Symbol => PageObjectCollection}]
|
44
35
|
def [] idx
|
45
36
|
to_a[idx]
|
46
37
|
end
|
@@ -49,30 +40,21 @@ module Domkey
|
|
49
40
|
|
50
41
|
private
|
51
42
|
|
52
|
-
#
|
53
|
-
#
|
54
|
-
def initialize_this
|
55
|
-
if
|
56
|
-
Hash[
|
43
|
+
# @api private
|
44
|
+
# Recursive. Examines each packages and turns each Proc into PageObject
|
45
|
+
def initialize_this package
|
46
|
+
if package.respond_to?(:each_pair) #hash
|
47
|
+
Hash[package.map { |key, package| [key, PageObjectCollection.new(package, container)] }]
|
57
48
|
else
|
58
|
-
if
|
59
|
-
|
60
|
-
elsif
|
61
|
-
|
49
|
+
if package.respond_to?(:call) #proc
|
50
|
+
package
|
51
|
+
elsif package.respond_to?(:package)
|
52
|
+
package.package
|
62
53
|
else
|
63
|
-
fail Exception::Error, "
|
54
|
+
fail Exception::Error, "package must be kind of hash, watirelement or pageobject but I got this: #{package}"
|
64
55
|
end
|
65
56
|
end
|
66
57
|
end
|
67
|
-
|
68
|
-
def instantiator
|
69
|
-
container_at_runtime.instance_exec(&watirproc)
|
70
|
-
end
|
71
|
-
|
72
|
-
def container_at_runtime
|
73
|
-
container.respond_to?(:call) ? container.call : container.send(:instantiator)
|
74
|
-
end
|
75
|
-
|
76
58
|
end
|
77
59
|
end
|
78
60
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'domkey/view/labeled_group'
|
2
|
+
module Domkey
|
3
|
+
|
4
|
+
module View
|
5
|
+
|
6
|
+
# RadioGroup allows you to interact with PageObjectCollection of radios as a single PageObject
|
7
|
+
# Radios collection is constrained by the same name attribute and behaves like one object.
|
8
|
+
# It behaves like a single Select list.
|
9
|
+
# When one radio is selected in the collection the others become unselected.
|
10
|
+
class RadioGroup < PageObjectCollection
|
11
|
+
|
12
|
+
# @param [String] match text in value attribute and set that radio
|
13
|
+
def set value
|
14
|
+
validate_scope
|
15
|
+
return unless value
|
16
|
+
[*value].each do |v|
|
17
|
+
e = case v
|
18
|
+
when String
|
19
|
+
element.find { |e| e.value == v }
|
20
|
+
when Regexp
|
21
|
+
element.find { |e| e.value.match(v) }
|
22
|
+
end
|
23
|
+
e ? e.set : fail(Exception::Error, "RadioGroup value not found: #{v.inspect}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] text in value attribute of currently set
|
28
|
+
def value
|
29
|
+
validate_scope
|
30
|
+
element.find_all { |r| r.set? }.map { |e| e.value }
|
31
|
+
end
|
32
|
+
|
33
|
+
def options
|
34
|
+
validate_scope
|
35
|
+
element.map { |e| e.value }
|
36
|
+
end
|
37
|
+
|
38
|
+
# convert to LabeledGroup settable by corresponding label text
|
39
|
+
def to_labeled
|
40
|
+
LabeledGroup.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @yield [PageObject]
|
44
|
+
def each(&blk)
|
45
|
+
validate_scope
|
46
|
+
super(&blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Array<PageObject>]
|
50
|
+
def to_a
|
51
|
+
validate_scope
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# precondition on acting on this collection
|
58
|
+
# @return [true] when all radios in collection share the same name attribute
|
59
|
+
# @raise [Exception::Error] when where is more than one unique name attribute
|
60
|
+
# --
|
61
|
+
# returns true on subsequent unless magically more radios show up after initial validation
|
62
|
+
def validate_scope
|
63
|
+
return if @validated
|
64
|
+
groups = element.map { |e| e.name }.uniq
|
65
|
+
fail(Exception::Error, "RadioGroup definition scope too broad: Found #{groups.count} radio groups with names: #{groups}") unless (groups.size == 1)
|
66
|
+
@validated = true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Domkey
|
2
|
+
|
3
|
+
module View
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
class WatirWidget
|
7
|
+
|
8
|
+
def initialize(object)
|
9
|
+
@object = object
|
10
|
+
object_class_name = @object.class.name.split('::').last
|
11
|
+
@set_method = "set_%s" % object_class_name
|
12
|
+
@value_method = "value_%s" % object_class_name
|
13
|
+
@options_method = "options_%s" % object_class_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def set value
|
17
|
+
return __send__(@set_method, value) if respond_to?(@set_method, true)
|
18
|
+
@object.set value
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
return __send__(@value_method) if respond_to?(@value_method, true)
|
23
|
+
@object.value
|
24
|
+
end
|
25
|
+
|
26
|
+
def options
|
27
|
+
return __send__(@options_method) if respond_to?(@options_method, true)
|
28
|
+
opts = @object.options
|
29
|
+
opts.count == 0 ? [] : opts
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def options_Select
|
35
|
+
@object.options.map do |o|
|
36
|
+
{text: o.text,
|
37
|
+
value: o.value}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# for Watir::Select
|
42
|
+
# @param [String] text or label to be selected. Text visible to the user on the page
|
43
|
+
# @param [Array<String>] collection for multiselect to select
|
44
|
+
def set_Select value
|
45
|
+
@object.clear if @object.multiple?
|
46
|
+
set_Select_strategy value
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_Select_strategy value
|
50
|
+
case value
|
51
|
+
when String
|
52
|
+
@object.select value
|
53
|
+
when Array
|
54
|
+
value.each { |v| set_Select_strategy v }
|
55
|
+
when Hash
|
56
|
+
value.each_pair do |how, what|
|
57
|
+
|
58
|
+
#-- select by option position: can be one or many index: 0, index: [0,1,2,3]
|
59
|
+
if how == :index
|
60
|
+
case what
|
61
|
+
when Fixnum
|
62
|
+
@object.options[what].select
|
63
|
+
when Array
|
64
|
+
what.each do |i|
|
65
|
+
@object.options[i].select
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#-- select by option value: attribute (invisible to the user)
|
71
|
+
if how == :value
|
72
|
+
case what
|
73
|
+
when String
|
74
|
+
@object.select_value what
|
75
|
+
when Array
|
76
|
+
what.each { |v| @object.select_value v }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#-- select by text visible to the user. This is the same as default set 'Text' behavior
|
81
|
+
if how == :text
|
82
|
+
case what
|
83
|
+
when String
|
84
|
+
set_Select_strategy what
|
85
|
+
when Array
|
86
|
+
what.each { |v| set_Select_strategy v }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] text or label from Select, not actual 'value' attribute?
|
97
|
+
# @return [Array<String>] collection for multiselect list
|
98
|
+
# @return [False] when nothing selected in multiselect list
|
99
|
+
def value_Select
|
100
|
+
@object.selected_options.map { |o| o.text }
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Domkey
|
2
|
+
|
3
|
+
module View
|
4
|
+
|
5
|
+
module WidgetryPackage
|
6
|
+
|
7
|
+
attr_accessor :package, :container
|
8
|
+
|
9
|
+
# initialize PageObject or PageObjectCollection
|
10
|
+
# for PageObject expects WebdriverElement a single element definition i.e text_field, checkbox
|
11
|
+
# for PageObjectCollection expects WebdriverElement a collection definition i.e. text_fields, checkboxes
|
12
|
+
# @param package [Proc(WebdriverElement)]
|
13
|
+
# @param package [PageObject]
|
14
|
+
# @param package [Hash{Symbol => Proc(WebdriverElement)]
|
15
|
+
# @param package [Hash{Symbol => PageObject]
|
16
|
+
def initialize package, container=lambda { Domkey.browser }
|
17
|
+
@container = container
|
18
|
+
@package = initialize_this package
|
19
|
+
end
|
20
|
+
|
21
|
+
# access widgetry of watir elements composing this page object
|
22
|
+
# @param [Symbol] (false)
|
23
|
+
# @return [Hash{Symbol => WebdriverElement}]
|
24
|
+
# @return [WebdriverElement]
|
25
|
+
def element(key=false)
|
26
|
+
return instantiator unless package.respond_to?(:each_pair)
|
27
|
+
return package.fetch(key).element if key
|
28
|
+
Hash[package.map { |key, package| [key, package.element] }]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# talks to the browser
|
34
|
+
# returns runtime element in a specified container
|
35
|
+
# @return [WebdriverElement]
|
36
|
+
def instantiator
|
37
|
+
container_instantiator.instance_exec(&package)
|
38
|
+
end
|
39
|
+
|
40
|
+
# talks to the browser
|
41
|
+
# returns runtime container element in a browser/driver
|
42
|
+
# @return [WebdriverElement]
|
43
|
+
def container_instantiator
|
44
|
+
container.respond_to?(:call) ? container.call : container.send(:instantiator)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Domkey::View::CheckboxGroup do
|
4
|
+
|
5
|
+
class CollectionAsPageObjectCheckboxGroupView
|
6
|
+
include Domkey::View
|
7
|
+
|
8
|
+
def group
|
9
|
+
CheckboxGroup.new -> { checkboxes(name: 'fruit') }
|
10
|
+
end
|
11
|
+
|
12
|
+
def groups
|
13
|
+
CheckboxGroup.new -> { checkboxes(name: /^fruit/) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before :all do
|
18
|
+
goto_html("test.html")
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'two groups example' do
|
22
|
+
v = CollectionAsPageObjectCheckboxGroupView.new
|
23
|
+
expect { v.groups.to_a.size }.to raise_error
|
24
|
+
expect { v.groups.map { |e| e } }.to raise_error
|
25
|
+
expect { v.groups.count }.to raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
context "OptionSelectable object multi" do
|
29
|
+
# OptionSelectable object is an object that responds to options and is selectable by its optioins;
|
30
|
+
# RadioGroup, Select, CheckboxGroup. CheckboxGroup acts like Multi Select, RadioGroup acts like Single Select
|
31
|
+
|
32
|
+
before :each do
|
33
|
+
goto_html("test.html")
|
34
|
+
|
35
|
+
@v = CollectionAsPageObjectCheckboxGroupView.new
|
36
|
+
@v.group.count.should == 3
|
37
|
+
@v.group.to_a.each { |e| e.should be_kind_of(Domkey::View::PageObject) }
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'initial value on test page' do
|
41
|
+
@v.group.value.should eql ['other']
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'set value attribute by default. value returns array of value attribute' do
|
45
|
+
@v.group.set 'tomato'
|
46
|
+
@v.group.value.should eql ['tomato']
|
47
|
+
@v.group.set /^othe/
|
48
|
+
@v.group.value.should eql ['other']
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'set array of value attribute. value returns array of value attribute' do
|
52
|
+
@v.group.set ['tomato']
|
53
|
+
@v.group.value.should eql ['tomato']
|
54
|
+
|
55
|
+
@v.group.set ['other', /tomat/]
|
56
|
+
@v.group.value.should eql ['tomato', 'other']
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'set false clears all. value is empty array' do
|
60
|
+
@v.group.set false
|
61
|
+
@v.group.value.should eql []
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'set empty array clears all. value is empty array' do
|
65
|
+
@v.group.set []
|
66
|
+
@v.group.value.should eql []
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'set value string not found error' do
|
70
|
+
expect { @v.group.set 'toma' }.to raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'set value regexp not found error' do
|
74
|
+
expect { @v.group.set /balaba/ }.to raise_error
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'options' do
|
78
|
+
@v.group.options.should eql ["cucumber", "tomato", "other"]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/spec/html/test.html
CHANGED
@@ -17,15 +17,15 @@
|
|
17
17
|
<br/>
|
18
18
|
|
19
19
|
<div id='feature_1' class='pageobject'>
|
20
|
-
<label for='feature_checkbox1'>
|
20
|
+
<label for='feature_checkbox1'>Enable Checkbox for TextArea</label>
|
21
21
|
<input id="feature_checkbox1" type="checkbox" class="check" onclick="featureEnabler('feature_checkbox1','feature_textarea1');"/><br/>
|
22
|
-
<textarea id='feature_textarea1' readonly disabled>CheckboxTextField 1</textarea>
|
22
|
+
<textarea id='feature_textarea1' cols="30" readonly disabled style="background-color:#eee">CheckboxTextField 1</textarea>
|
23
23
|
</div>
|
24
24
|
|
25
25
|
<div id='feature_2' class='pageobject'>
|
26
26
|
<label for='feature_checkbox2'>Golf Course</label>
|
27
27
|
<input id='feature_checkbox2' type='checkbox' class="check" onclick="featureEnabler('feature_checkbox2','feature_textarea2');"/><br/>
|
28
|
-
<textarea id='feature_textarea2' readonly disabled>CheckboxTextField 2</textarea>
|
28
|
+
<textarea id='feature_textarea2' cols="30" readonly disabled style="background-color:#eee">CheckboxTextField 2</textarea>
|
29
29
|
</div>
|
30
30
|
|
31
31
|
<script type="text/javascript">
|
@@ -33,14 +33,18 @@
|
|
33
33
|
var cb = document.getElementById(cbe);
|
34
34
|
var txt = document.getElementById(txte);
|
35
35
|
if (cb.checked == true) {
|
36
|
-
txt.readOnly =
|
37
|
-
txt.disabled =
|
36
|
+
txt.readOnly = '';
|
37
|
+
txt.disabled = '';
|
38
|
+
txt.style = 'background-color: #fff';
|
38
39
|
} else {
|
39
40
|
txt.readOnly = 'readonly';
|
40
41
|
txt.disabled = 'disabled';
|
42
|
+
txt.value = '';
|
43
|
+
txt.style = 'background-color: #eee';
|
41
44
|
}
|
42
45
|
}
|
43
46
|
</script>
|
47
|
+
<br/>
|
44
48
|
|
45
49
|
<div>
|
46
50
|
<p>DateSelector</p>
|
@@ -49,5 +53,57 @@
|
|
49
53
|
<input type='text' id="year_field" class="city"/>
|
50
54
|
</div>
|
51
55
|
|
56
|
+
<div>
|
57
|
+
<p>RadioGroup named tool</p>
|
58
|
+
<label for='tool1'>Cucumber</label>
|
59
|
+
<input type='radio' name="tool" value="cucumber" id="tool1"/>
|
60
|
+
<label for='tool2'>Tomato</label>
|
61
|
+
<input type='radio' name="tool" value="tomato" id="tool2"/>
|
62
|
+
<label for='tool3'>Other</label>
|
63
|
+
<input type='radio' name="tool" value="other" id="tool3" checked/>
|
64
|
+
</div>
|
65
|
+
|
66
|
+
<div>
|
67
|
+
<p>RadioGroup named toolkey</p>
|
68
|
+
<input type='radio' name="toolkey" value="a cucumber"/><label for=""
|
69
|
+
<input type='radio' name="toolkey" value="a tomato"/>
|
70
|
+
<input type='radio' name="toolkey" value="some other" checked/>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div>
|
74
|
+
<p>CheckboxGroup named fruit</p>
|
75
|
+
<label for='fruit1'>Cucumberama</label>
|
76
|
+
<input type='checkbox' name="fruit" value="cucumber" id="fruit1"/>
|
77
|
+
<label for='fruit2'>Tomatorama</label>
|
78
|
+
<input type='checkbox' name="fruit" value="tomato" id="fruit2"/>
|
79
|
+
<label for='fruit3'>Other</label>
|
80
|
+
<input type='checkbox' name="fruit" value="other" id="fruit3" checked/>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<div>
|
84
|
+
<p>CheckboxGroup named fruitkey</p>
|
85
|
+
<input type='checkbox' name="fruitkey" value="a cucumberama"/>
|
86
|
+
<input type='checkbox' name="fruitkey" value="a tomatorama"/>
|
87
|
+
<input type='checkbox' name="fruitkey" value="some otherama" checked/>
|
88
|
+
</div>
|
89
|
+
|
90
|
+
<div>
|
91
|
+
<select id="fruit_list">
|
92
|
+
<option value="tomato">Tomato</option>
|
93
|
+
<option value="gurken">Cucumber</option>
|
94
|
+
<option label="Other"/>
|
95
|
+
<option selected>Default</option>
|
96
|
+
</select>
|
97
|
+
</div>
|
98
|
+
|
99
|
+
<div>
|
100
|
+
<select multiple size="5" name="multiselect" id="multiselect">
|
101
|
+
<option value="1">Danish</option>
|
102
|
+
<option selected="selected" value="2">English</option>
|
103
|
+
<option selected value="3">Norwegian</option>
|
104
|
+
<option label='Polish'/>
|
105
|
+
<option>Swedish</option>
|
106
|
+
</select>
|
107
|
+
</div>
|
52
108
|
</body>
|
53
109
|
</html>
|