capybara-page-object 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/Gemfile +8 -9
- data/Gemfile.lock +27 -17
- data/README.markdown +32 -12
- data/Rakefile +0 -7
- data/VERSION +1 -1
- data/capybara-page-object.gemspec +41 -34
- data/lib/capybara-page-object.rb +12 -11
- data/lib/capybara-page-object/collections.rb +4 -0
- data/lib/capybara-page-object/component.rb +5 -0
- data/lib/{delegators.rb → capybara-page-object/delegators.rb} +0 -0
- data/lib/capybara-page-object/element.rb +6 -0
- data/lib/capybara-page-object/elements.rb +15 -0
- data/lib/capybara-page-object/elements/anchor.rb +6 -0
- data/lib/{element → capybara-page-object/elements}/form.rb +8 -16
- data/lib/capybara-page-object/elements/form_field.rb +10 -0
- data/lib/capybara-page-object/elements/head.rb +7 -0
- data/lib/capybara-page-object/elements/image.rb +7 -0
- data/lib/{element → capybara-page-object/elements}/input.rb +5 -2
- data/lib/{element → capybara-page-object/elements}/list.rb +1 -1
- data/lib/{element → capybara-page-object/elements}/list_item.rb +1 -1
- data/lib/capybara-page-object/elements/meta.rb +6 -0
- data/lib/{element → capybara-page-object/elements}/select.rb +0 -0
- data/lib/{element → capybara-page-object/elements}/table.rb +1 -1
- data/lib/capybara-page-object/elements/table_header.rb +4 -0
- data/lib/{element → capybara-page-object/elements}/table_row.rb +1 -1
- data/lib/{element → capybara-page-object/elements}/text_based_input.rb +0 -0
- data/lib/{element → capybara-page-object/elements}/textarea.rb +1 -3
- data/lib/capybara-page-object/extensions/capybara.rb +7 -0
- data/lib/capybara-page-object/extensions/enumerable.rb +10 -0
- data/lib/{html5_data.rb → capybara-page-object/html5_data.rb} +0 -0
- data/lib/{key_value.rb → capybara-page-object/key_value.rb} +0 -0
- data/lib/{node.rb → capybara-page-object/node.rb} +14 -10
- data/lib/capybara-page-object/page.rb +77 -0
- data/spec/common_spec.rb +6 -6
- data/spec/element/anchor_spec.rb +8 -3
- data/spec/element/form_field_spec.rb +7 -1
- data/spec/element/form_spec.rb +27 -17
- data/spec/helper.rb +13 -0
- data/spec/node_spec.rb +9 -0
- data/spec/page_spec.rb +90 -30
- metadata +58 -43
- data/lib/collections.rb +0 -17
- data/lib/element.rb +0 -14
- data/lib/element/TODO.md +0 -11
- data/lib/element/anchor.rb +0 -7
- data/lib/element/form_field.rb +0 -15
- data/lib/element/head.rb +0 -15
- data/lib/element/image.rb +0 -15
- data/lib/element/meta.rb +0 -11
- data/lib/element/table_header.rb +0 -4
- data/lib/page.rb +0 -40
@@ -0,0 +1,7 @@
|
|
1
|
+
module CapybaraPageObject
|
2
|
+
class Head < CapybaraPageObject::Element
|
3
|
+
field(:title) { find('title').text }
|
4
|
+
field(:meta_description) { find('meta[@name="description"]')['content'] }
|
5
|
+
field(:meta_keywords) { find('meta[@name="keywords"]')['content'].split(',').collect(&:strip) }
|
6
|
+
end
|
7
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'element/text_based_input'
|
2
|
-
|
3
1
|
module CapybaraPageObject
|
4
2
|
class Input < CapybaraPageObject::FormField
|
5
3
|
include TextBasedInput
|
@@ -7,6 +5,11 @@ module CapybaraPageObject
|
|
7
5
|
BUTTON_TYPES = ['submit', 'reset', 'button']
|
8
6
|
CHECKABLE_TYPES = ['radio', 'checkbox']
|
9
7
|
|
8
|
+
# TODO this doesn't belong here
|
9
|
+
def checked?
|
10
|
+
source.checked?
|
11
|
+
end
|
12
|
+
|
10
13
|
def untyped?
|
11
14
|
source[:type].nil?
|
12
15
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,18 +1,13 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require 'html5_data'
|
4
|
-
require 'delegators'
|
1
|
+
require 'capybara-page-object/delegators'
|
2
|
+
|
5
3
|
|
6
4
|
module CapybaraPageObject
|
7
|
-
class Node
|
5
|
+
class Node
|
8
6
|
include Delegators
|
9
|
-
include CapybaraPageObject::Collections
|
10
|
-
include CapybaraPageObject::HTML5Data
|
11
7
|
|
12
8
|
attr_accessor :source
|
13
9
|
|
14
10
|
def initialize(source=nil)
|
15
|
-
source ||= Capybara.current_session
|
16
11
|
@source = source
|
17
12
|
end
|
18
13
|
|
@@ -24,8 +19,17 @@ module CapybaraPageObject
|
|
24
19
|
classes_list = source[:class] or return []
|
25
20
|
classes_list.split(' ')
|
26
21
|
end
|
27
|
-
end
|
28
22
|
|
29
|
-
|
23
|
+
def source
|
24
|
+
@source ||= Capybara.current_session
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.element(name, &block)
|
28
|
+
define_method(name, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.field(name, &block)
|
32
|
+
define_method(name, &block)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'capybara-page-object/node'
|
2
|
+
require 'capybara-page-object/collections'
|
3
|
+
|
4
|
+
module CapybaraPageObject
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def path(*args)
|
8
|
+
raise MissingPath, "You need to override #path in #{self.class}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def prefix
|
12
|
+
'/'
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_path(attr)
|
16
|
+
target = prefix + path
|
17
|
+
if attr.kind_of?(String)
|
18
|
+
target += attr
|
19
|
+
elsif attr.kind_of?(Hash)
|
20
|
+
pairs = attr.map { |k, v| "#{k}=#{v}" }
|
21
|
+
target += '?' + pairs.join('&') if pairs.any?
|
22
|
+
elsif attr != nil
|
23
|
+
raise ArgumentError
|
24
|
+
end
|
25
|
+
source.visit target
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
def from_string(string, target)
|
31
|
+
new(Capybara.string(string).find(target))
|
32
|
+
end
|
33
|
+
|
34
|
+
def current?
|
35
|
+
page = new
|
36
|
+
page.source.current_path == page.prefix + page.path
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit(attr={})
|
40
|
+
page = new
|
41
|
+
page.visit_path(attr)
|
42
|
+
page
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Page < CapybaraPageObject::Node
|
47
|
+
# TODO why doesn't this work when in the module?
|
48
|
+
def tables
|
49
|
+
all('table').each_with_object({}) do |e, hash|
|
50
|
+
t = CapybaraPageObject::Table.new(e)
|
51
|
+
hash[t.key] = t
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def forms
|
56
|
+
all('form').each_with_object({}) do |e, hash|
|
57
|
+
f = CapybaraPageObject::Form.new(e)
|
58
|
+
hash[f.key] = f
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.path(p)
|
63
|
+
define_method(:path) { p }
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.component(name, &block)
|
67
|
+
define_method(name, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
include CapybaraPageObject::Collections
|
71
|
+
include CapybaraPageObject::InstanceMethods
|
72
|
+
extend CapybaraPageObject::ClassMethods
|
73
|
+
end
|
74
|
+
|
75
|
+
class MissingPath < RuntimeError
|
76
|
+
end
|
77
|
+
end
|
data/spec/common_spec.rb
CHANGED
@@ -3,20 +3,20 @@ require 'helper'
|
|
3
3
|
describe "Page" do
|
4
4
|
before do
|
5
5
|
html = File.open(File.dirname(__FILE__) + '/fixtures/node.html').read
|
6
|
-
@page = CapybaraPageObject::
|
6
|
+
@page = CapybaraPageObject::Page.from_string html, 'body'
|
7
7
|
end
|
8
8
|
|
9
9
|
context "#tables" do
|
10
10
|
it "returns a hash of Tables keyed by ID" do
|
11
11
|
tables = @page.tables
|
12
|
-
tables.keys.should
|
12
|
+
tables.keys.should =~ ['table_1', 'table_2']
|
13
13
|
tables['table_1'].class.should == CapybaraPageObject::Table
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
context "#forms" do
|
18
|
-
|
19
|
-
@page.forms.should
|
18
|
+
it "returns a hash of Forms keyed by ID" do
|
19
|
+
@page.forms.keys.should =~ ['form_1', 'form_2']
|
20
20
|
@page.forms['form_1'].class.should == CapybaraPageObject::Form
|
21
21
|
end
|
22
22
|
end
|
@@ -24,13 +24,13 @@ describe "Page" do
|
|
24
24
|
context "#data" do
|
25
25
|
it "returns an empty hash if the element has no HTML5 data attributes" do
|
26
26
|
html = '<div foo="bar"/>'
|
27
|
-
@fragment = CapybaraPageObject::
|
27
|
+
@fragment = CapybaraPageObject::Element.from_string html, 'div'
|
28
28
|
@fragment.data.should == {}
|
29
29
|
end
|
30
30
|
|
31
31
|
it "returns a hash of the elements HTML5 data attributes" do
|
32
32
|
html = '<div id="data" data-foo="a" data-bar="b" data-cat>Some data</div>'
|
33
|
-
@fragment = CapybaraPageObject::
|
33
|
+
@fragment = CapybaraPageObject::Element.from_string html, 'div'
|
34
34
|
h = {'foo' => 'a', 'bar' => 'b', 'cat' => ''}
|
35
35
|
@fragment.data.should == h
|
36
36
|
end
|
data/spec/element/anchor_spec.rb
CHANGED
@@ -7,9 +7,14 @@ describe "Anchor" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
context "#link" do
|
10
|
-
it "return
|
11
|
-
@anchor.link.
|
12
|
-
|
10
|
+
it "return the link" do
|
11
|
+
@anchor.link.should == 'hello.html'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#uri" do
|
16
|
+
it "return the link as a URI" do
|
17
|
+
@anchor.uri.should == URI('hello.html')
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -2,11 +2,17 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe "FormField" do
|
4
4
|
before do
|
5
|
-
@input = CapybaraPageObject::FormField.from_string '<input value="hello">', 'input'
|
5
|
+
@input = CapybaraPageObject::FormField.from_string '<input name="greeting" value="hello">', 'input'
|
6
6
|
@textarea = CapybaraPageObject::FormField.from_string '<textarea>foo</textarea>', 'textarea'
|
7
7
|
@blank_textarea = CapybaraPageObject::FormField.from_string '<textarea></textarea>', 'textarea'
|
8
8
|
end
|
9
9
|
|
10
|
+
context "#key" do
|
11
|
+
it "returns the name of the form field" do
|
12
|
+
@input.key.should == 'greeting'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
10
16
|
context "#value" do
|
11
17
|
it "delegates" do
|
12
18
|
@input.value.should == 'hello'
|
data/spec/element/form_spec.rb
CHANGED
@@ -42,23 +42,33 @@ describe "Form" do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
45
|
+
context "#inputs" do
|
46
|
+
it "returns the inputs on the page (excluding buttons)" do
|
47
|
+
@form.inputs.keys.should == ["input_1", "text_input", "password_input", "checkbox_1",
|
48
|
+
"checkbox_2", "radio_button_1", "radio_button_2"]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#textareas" do
|
53
|
+
it "returns the textareas on the page" do
|
54
|
+
@form.textareas.keys.should == ["essay"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "#selects" do
|
59
|
+
it "returns the selects on the page" do
|
60
|
+
@form.selects.keys.should == ["colour", "countries"]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#fields" do
|
65
|
+
it "merges inputs, textareas and selects" do
|
66
|
+
@form.stub!(:inputs => {'a' => 1})
|
67
|
+
@form.stub!(:textareas => {'b' => 2})
|
68
|
+
@form.stub!(:selects => {'c' => 3})
|
69
|
+
@form.fields.should == {'a' => 1, 'b' => 2, 'c' => 3}
|
70
|
+
end
|
71
|
+
end
|
62
72
|
|
63
73
|
# it "provides direct read access to form's fields" do
|
64
74
|
# @form.essay.should == 'hello world'
|
data/spec/helper.rb
CHANGED
@@ -12,4 +12,17 @@ require 'rspec'
|
|
12
12
|
|
13
13
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
14
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
|
16
|
+
unless ENV["SKIP_COVERAGE"]
|
17
|
+
require 'simplecov'
|
18
|
+
|
19
|
+
SimpleCov.start
|
20
|
+
SimpleCov.at_exit do
|
21
|
+
SimpleCov.result.format!
|
22
|
+
if SimpleCov.result.covered_percent < 100
|
23
|
+
`open coverage/index.html`
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
15
28
|
require 'capybara-page-object'
|
data/spec/node_spec.rb
CHANGED
@@ -18,5 +18,14 @@ describe "Page" do
|
|
18
18
|
anchor = CapybaraPageObject::Anchor.from_string '<a/>', 'a'
|
19
19
|
anchor.classes.should be_empty
|
20
20
|
end
|
21
|
+
|
22
|
+
it "has a nice DSL" do
|
23
|
+
source = mock
|
24
|
+
source.should_receive(:find_button).with('place_order')
|
25
|
+
class Bar < CapybaraPageObject::Node
|
26
|
+
element(:place_order) { source.find_button('place_order') }
|
27
|
+
end
|
28
|
+
Bar.new(source).place_order
|
29
|
+
end
|
21
30
|
end
|
22
31
|
end
|
data/spec/page_spec.rb
CHANGED
@@ -1,37 +1,97 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'active_support/ordered_hash'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/capybara-page-object/page'
|
3
|
+
|
4
|
+
describe "a class which extends CapybaraPageObject::Page" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
class FooShow < CapybaraPageObject::Page
|
8
|
+
def path
|
9
|
+
'foos/'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class FooIndex < CapybaraPageObject::Page
|
13
|
+
def path
|
14
|
+
'foos'
|
15
|
+
end
|
16
|
+
end
|
4
17
|
end
|
5
|
-
end
|
6
18
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
it "supports key-value pairs" do
|
17
|
-
mock_source = mock()
|
18
|
-
mock_source.should_receive(:current_path)
|
19
|
-
mock_source.should_receive(:visit).with('/foo?a=1&b=2')
|
20
|
-
# intential mix of string and symbol keys below
|
21
|
-
@page = FooPage.new({'a' => 1, :b => 2}, mock_source)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "raises an exception if revisiting" do
|
25
|
-
s = stub('Capybara', :current_path => '/foo', :visit => nil)
|
26
|
-
lambda do
|
27
|
-
@page = FooPage.new(nil, s)
|
28
|
-
end.should raise_error(RuntimeError, 'Detected repeat of page load - use #reload instead')
|
19
|
+
context ".current?" do
|
20
|
+
it "is true if the path matches the current session path" do
|
21
|
+
Capybara.stub :current_session => mock(:current_path => '/foos')
|
22
|
+
FooIndex.should be_current
|
23
|
+
end
|
24
|
+
|
25
|
+
it "is false if the path doesn't match the current session path" do
|
26
|
+
Capybara.stub :current_session => mock(:current_path => '/foos/1')
|
27
|
+
FooIndex.should_not be_current
|
29
28
|
end
|
30
29
|
end
|
31
|
-
|
32
|
-
context "
|
33
|
-
|
34
|
-
|
30
|
+
|
31
|
+
context ".visit" do
|
32
|
+
it "visits the appropriate path" do
|
33
|
+
session = mock
|
34
|
+
session.should_receive(:visit).with('/foos')
|
35
|
+
Capybara.stub :current_session => session
|
36
|
+
FooIndex.visit
|
37
|
+
end
|
38
|
+
|
39
|
+
it "throws an error if path hasn't been overridden" do
|
40
|
+
lambda do
|
41
|
+
CapybaraPageObject::Page.visit
|
42
|
+
end.should raise_error(CapybaraPageObject::MissingPath, 'You need to override #path in CapybaraPageObject::Page')
|
43
|
+
end
|
44
|
+
|
45
|
+
it "converts a supplied hash into querystring paramaters" do
|
46
|
+
session = mock
|
47
|
+
session.should_receive(:visit).with('/foos?a=1&b=2')
|
48
|
+
Capybara.stub :current_session => session
|
49
|
+
# using an OrderedHash so we can guarantee the order on Ruby 1.8
|
50
|
+
# also, the mix of string and symbol keys is intentional
|
51
|
+
# for some reason, passing in the hash to the initializer fails when
|
52
|
+
# running via Rake
|
53
|
+
key_values = ActiveSupport::OrderedHash.new
|
54
|
+
key_values['a'] = 1
|
55
|
+
key_values[:b] = 2
|
56
|
+
@page = FooIndex.visit key_values
|
57
|
+
end
|
58
|
+
|
59
|
+
it "converts a supplied string into a resource identifier" do
|
60
|
+
session = mock()
|
61
|
+
session.should_receive(:visit).with('/foos/bar')
|
62
|
+
Capybara.stub :current_session => session
|
63
|
+
@page = FooShow.visit 'bar'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns an instance of the page object" do
|
67
|
+
Capybara.stub :current_session => stub.as_null_object
|
68
|
+
FooIndex.visit.class.should == FooIndex
|
69
|
+
end
|
70
|
+
|
71
|
+
it "throws an error if the supplied argument is something other a string or hash" do
|
72
|
+
lambda do
|
73
|
+
FooIndex.visit Object
|
74
|
+
end.should raise_error ArgumentError
|
75
|
+
end
|
76
|
+
|
77
|
+
it "has a DSL for setting the path" do
|
78
|
+
class Blah < CapybaraPageObject::Page
|
79
|
+
path 'xyz'
|
80
|
+
end
|
81
|
+
session = mock()
|
82
|
+
session.should_receive(:visit).with('/xyz')
|
83
|
+
Capybara.stub :current_session => session
|
84
|
+
@page = Blah.visit
|
85
|
+
end
|
86
|
+
|
87
|
+
it "has a DSL for setting components" do
|
88
|
+
class Blah < CapybaraPageObject::Page
|
89
|
+
component(:navigation) { source.find('#navigation') }
|
90
|
+
end
|
91
|
+
session = mock
|
92
|
+
session.should_receive(:find).with('#navigation')
|
93
|
+
Capybara.stub :current_session => session
|
94
|
+
Blah.new.navigation
|
35
95
|
end
|
36
96
|
end
|
37
97
|
end
|