domkey 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|