page_record 0.4.0 → 0.5.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.
- data/.rubocop.yml +13 -0
- data/CHANGES.md +5 -0
- data/Gemfile.lock +46 -14
- data/Guardfile +24 -0
- data/README.md +27 -1
- data/bin/autospec +16 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/lib/page_record/attribute_accessors.rb +57 -54
- data/lib/page_record/base.rb +27 -22
- data/lib/page_record/class_actions.rb +47 -50
- data/lib/page_record/class_methods.rb +252 -261
- data/lib/page_record/errors.rb +81 -82
- data/lib/page_record/finders.rb +129 -131
- data/lib/page_record/form_builder.rb +4 -4
- data/lib/page_record/formtastic.rb +20 -9
- data/lib/page_record/helpers.rb +192 -131
- data/lib/page_record/inspector.rb +36 -0
- data/lib/page_record/instance_actions.rb +44 -46
- data/lib/page_record/rspec.rb +1 -1
- data/lib/page_record/validation.rb +46 -0
- data/lib/page_record/version.rb +1 -1
- data/lib/page_record.rb +13 -15
- data/page_record.gemspec +4 -1
- data/spec/.rubocop.yml +4 -0
- data/spec/helpers_spec.rb +109 -100
- data/spec/inspector_spec.rb +70 -0
- data/spec/page_record_spec.rb +357 -388
- data/spec/spec_helper.rb +1 -3
- data/spec/support/shared_contexts.rb +24 -2
- data/spec/support/shared_examples.rb +41 -45
- data/spec/support/team.rb +4 -4
- data/spec/support/test_app.rb +10 -13
- data/spec/support/views/page-with-1-error.erb +5 -0
- data/spec/support/views/page-with-2-errors-on-different-attributes.erb +9 -0
- data/spec/support/views/page-with-2-errors-on-same-attribute.erb +6 -0
- data/spec/support/views/page-without-errors.erb +4 -0
- data/spec/validation_spec.rb +142 -0
- data/tmp/rspec_guard_result +1 -0
- metadata +80 -5
data/lib/page_record/errors.rb
CHANGED
@@ -1,88 +1,87 @@
|
|
1
1
|
module PageRecord
|
2
2
|
|
3
|
+
##
|
4
|
+
# This {::Exception} is raised when the specified record is not found
|
5
|
+
# on the page. Check your selector, filter and HTML code for details.
|
6
|
+
#
|
7
|
+
# ```html
|
8
|
+
# <div data-team-id='10'>
|
9
|
+
# <div data-attribute-for='name'>Ajax</div>
|
10
|
+
# </div>
|
11
|
+
# ```
|
12
|
+
# When the following code is executed, the {RecordNotFound} exception is raised.
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# TeamPage.find(11)
|
16
|
+
# ```
|
17
|
+
#
|
18
|
+
class RecordNotFound < Exception
|
19
|
+
end
|
3
20
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
##
|
22
|
+
# This {::Exception} is raised when the specfied attribute is not found
|
23
|
+
# on the page. Check your selector, filter and HTML code for details.
|
24
|
+
#
|
25
|
+
# ```html
|
26
|
+
# <div data-team-id='10'>
|
27
|
+
# <div data-attribute-for='name'>Ajax</div>
|
28
|
+
# </div>
|
29
|
+
# ```
|
30
|
+
# When the following code is executed, the {AttributeNotFound} exception is raised.
|
31
|
+
#
|
32
|
+
# ```ruby
|
33
|
+
# TeamPage.find(10).ranking
|
34
|
+
# ```
|
35
|
+
#
|
36
|
+
class AttributeNotFound < Exception
|
37
|
+
end
|
21
38
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# <div data-attribute-for='name'>Ajax</div>
|
29
|
-
# </div>
|
30
|
-
# ```
|
31
|
-
# When the following code is executed, the {AttributeNotFound} exception is raised.
|
32
|
-
#
|
33
|
-
# ```ruby
|
34
|
-
# TeamPage.find(10).ranking
|
35
|
-
#```
|
36
|
-
#
|
37
|
-
class AttributeNotFound < Exception
|
38
|
-
end
|
39
|
+
##
|
40
|
+
# This {::Exception} is raised when you have not set the page variable
|
41
|
+
# of the class. Check {PageRecord::Base.page} for details
|
42
|
+
#
|
43
|
+
class PageNotSet < Exception
|
44
|
+
end
|
39
45
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
+
##
|
47
|
+
# This {::Exception} is raised when the page contains multiple instances
|
48
|
+
# of the specfied record type. Use a selector to narrow the search.
|
49
|
+
#
|
50
|
+
# ```html
|
51
|
+
# <div id='first-table' data-team-id='10'>
|
52
|
+
# <div data-attribute-for='name'>Ajax</div>
|
53
|
+
# </div>
|
54
|
+
# <div id='second-table' data-team-id='10'>
|
55
|
+
# <div data-attribute-for='name'>Ajax</div>
|
56
|
+
# </div>
|
57
|
+
# ```
|
58
|
+
# When the following code is executed, the {MultipleRecords} exception is raised.
|
59
|
+
#
|
60
|
+
# ```ruby
|
61
|
+
# TeamPage.find(10)
|
62
|
+
# ```
|
63
|
+
#
|
64
|
+
# To fix this, use the `#first-table` in the selector
|
65
|
+
#
|
66
|
+
# ```ruby
|
67
|
+
# TeamPage.find(10, '#first-table')
|
68
|
+
# ```
|
69
|
+
#
|
70
|
+
class MultipleRecords < Exception
|
71
|
+
end
|
46
72
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# TeamPage.find(10)
|
63
|
-
#```
|
64
|
-
#
|
65
|
-
# To fix this, use the `#first-table` in the selector
|
66
|
-
#
|
67
|
-
# ```ruby
|
68
|
-
# TeamPage.find(10, '#first-table')
|
69
|
-
#```
|
70
|
-
#
|
71
|
-
class MultipleRecords < Exception
|
72
|
-
end
|
73
|
-
|
74
|
-
# This {::Exception} is raised when you try to set a non input field.
|
75
|
-
#
|
76
|
-
# ```ruby
|
77
|
-
# <div data-team-id=1>
|
78
|
-
# <div data-attribute-for='name'>PSV</div>
|
79
|
-
# </div>
|
80
|
-
# ```
|
81
|
-
# When the following code is executed, the {NotInputField} exception is raised.
|
82
|
-
#
|
83
|
-
# ```ruby
|
84
|
-
# team_on_page.name = 'Ajax'
|
85
|
-
# ```
|
86
|
-
class NotInputField < Exception
|
87
|
-
end
|
88
|
-
end
|
73
|
+
# This {::Exception} is raised when you try to set a non input field.
|
74
|
+
#
|
75
|
+
# ```ruby
|
76
|
+
# <div data-team-id=1>
|
77
|
+
# <div data-attribute-for='name'>PSV</div>
|
78
|
+
# </div>
|
79
|
+
# ```
|
80
|
+
# When the following code is executed, the {NotInputField} exception is raised.
|
81
|
+
#
|
82
|
+
# ```ruby
|
83
|
+
# team_on_page.name = 'Ajax'
|
84
|
+
# ```
|
85
|
+
class NotInputField < Exception
|
86
|
+
end
|
87
|
+
end
|
data/lib/page_record/finders.rb
CHANGED
@@ -1,135 +1,133 @@
|
|
1
1
|
module PageRecord
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
2
|
+
##
|
3
|
+
# PageRecord is a specific sort of {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject pattern}. Where a "normal"
|
4
|
+
# {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject} tries to make the page accessible with business like functions.
|
5
|
+
# We have taken a different approach. We've noticed that a lot of WebPage are
|
6
|
+
# mainly luxury CRUD pages. This means that almost every page shows one or more
|
7
|
+
# records of a certain type and has the ability to create, read update and delete
|
8
|
+
# one or more records. Sounds familiar? Yes, it is the same as an ActiveRecord
|
9
|
+
# pattern. So we tried to make accessing a page as close as possible to accessing
|
10
|
+
# an ActiveRecord.
|
11
|
+
#
|
12
|
+
# To make this work, however, we need to add extra information to the HTML page.
|
13
|
+
# With HTML5, , you can do this easily. HTML-5 supports the data- attributes on any
|
14
|
+
# tag. We use these tags to identify the records on the page.
|
15
|
+
#
|
16
|
+
#
|
17
|
+
class Base
|
18
|
+
##
|
19
|
+
#
|
20
|
+
# Searches the page and returns an {::Array} of {PageRecord::Base} of instances of
|
21
|
+
# that match the selector and the filter. See {file:README.md#markup markup} for more
|
22
|
+
# details about formatting the page.
|
23
|
+
#
|
24
|
+
# example:
|
25
|
+
#
|
26
|
+
# ```ruby
|
27
|
+
# TeamPage.all
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# returns all records on the page
|
31
|
+
#
|
32
|
+
# @param selector [String] selector to use for searching the table on the page
|
33
|
+
# @param filter [String] filter to use on the records on the page
|
34
|
+
#
|
35
|
+
# @return [Array Pagerecord] The Array containing instances of [PageRecord::Base]
|
36
|
+
# with records that fit the selector and the filter
|
37
|
+
#
|
38
|
+
# @raise [MultipleRecords] if the page contains more then on set of records
|
39
|
+
# @raise [RecordNotFound] if the page does not contain any of the specified records
|
40
|
+
#
|
41
|
+
def self.all(selector = nil, filter = nil)
|
42
|
+
selector ||= @selector
|
43
|
+
filter ||= @filter
|
44
|
+
records = []
|
45
|
+
context = context_for_selector(selector)
|
46
|
+
context.all("[data-#{@type}-id]#{filter}").each do | record|
|
47
|
+
id = record["data-#{@type}-id"]
|
48
|
+
records << new(id, selector)
|
49
|
+
end
|
50
|
+
records
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
53
|
+
##
|
54
|
+
#
|
55
|
+
# Searches the page and returns an instance of {PageRecord::Base} of instances of
|
56
|
+
# that matches the given id, selector and the filter. See {file:README.md#markup markup} for more
|
57
|
+
# details about formatting the page.
|
58
|
+
#
|
59
|
+
# example:
|
60
|
+
#
|
61
|
+
# ```ruby
|
62
|
+
# TeamPage.find(1)
|
63
|
+
# ```
|
64
|
+
#
|
65
|
+
# returns the record with id
|
66
|
+
#
|
67
|
+
# When you don't specify an id, `find` returns the only record on the page.
|
68
|
+
# If you have more than one record on the page, `find` raises {MultipleRecords}.
|
69
|
+
#
|
70
|
+
# example:
|
71
|
+
#
|
72
|
+
# ```ruby
|
73
|
+
# TeamPage.find()
|
74
|
+
# ```
|
75
|
+
#
|
76
|
+
# @param selector [String] selector to use for searching the table on the page
|
77
|
+
# @param filter [String] filter to use on the records on the page
|
78
|
+
#
|
79
|
+
# @return [Pagerecord] An instance of [PageRecord::Base]
|
80
|
+
#
|
81
|
+
# @raise [MultipleRecords] if the page contains more then on set of records
|
82
|
+
# @raise [RecordNotFound] if the page does not contain any of the specified records
|
83
|
+
#
|
84
|
+
def self.find(id = nil, selector = nil, filter = nil)
|
85
|
+
selector ||= @selector
|
86
|
+
filter ||= @filter
|
87
|
+
new(id, selector, filter)
|
88
|
+
end
|
89
89
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
end
|
134
|
-
end
|
90
|
+
##
|
91
|
+
#
|
92
|
+
# Searches the page and returns an instance of {PageRecord::Base} of instances of
|
93
|
+
# that matches the given attribute. See {file:README.md#markup markup} for more
|
94
|
+
# details about formatting the page.
|
95
|
+
#
|
96
|
+
# Although you can call this yourself, {PageRecord::Base} uses this method for defining a
|
97
|
+
# finder for all attributes when you define your page class, {PageRecord::Base}
|
98
|
+
# See {PageRecord::Base.attributes} for more details.
|
99
|
+
#
|
100
|
+
# example:
|
101
|
+
#
|
102
|
+
# ```ruby
|
103
|
+
# TeamPage.find_by_name('Ajax')
|
104
|
+
# ```
|
105
|
+
#
|
106
|
+
# returns the record where the name is set to Ajax
|
107
|
+
#
|
108
|
+
# @param attribute [String] The attribute name
|
109
|
+
# @param value [String] The value to search for
|
110
|
+
# @param selector [String] selector to use for searching the table on the page
|
111
|
+
# @param filter [String] filter to use on the records on the page
|
112
|
+
#
|
113
|
+
# @return [Pagerecord] An instance of [PageRecord::Base].
|
114
|
+
#
|
115
|
+
# @raise [MultipleRecords] if the page contains more then on set of records
|
116
|
+
# @raise [RecordNotFound] if the page does not contain any of the specified records
|
117
|
+
#
|
118
|
+
def self.find_by_attribute(attribute, value, selector, filter)
|
119
|
+
selector ||= @selector
|
120
|
+
filter ||= @filter
|
121
|
+
|
122
|
+
context = context_for_selector(selector)
|
123
|
+
record = context.find("[data-#{@type}-id]#{filter} > [data-attribute-for='#{attribute}']", text: value)
|
124
|
+
parent = record.find(:xpath, '..')
|
125
|
+
id = parent["data-#{@type}-id"]
|
126
|
+
new(id, selector)
|
127
|
+
rescue Capybara::Ambiguous
|
128
|
+
raise MultipleRecords, "Found multiple #{@type} record with #{attribute} #{value} on page"
|
129
|
+
rescue Capybara::ElementNotFound
|
130
|
+
raise RecordNotFound, "#{@type} record with #{attribute} #{value} not found on page"
|
131
|
+
end
|
132
|
+
end
|
135
133
|
end
|
@@ -3,18 +3,18 @@ module PageRecord
|
|
3
3
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
4
4
|
include PageRecord::Helpers
|
5
5
|
|
6
|
-
helpers = field_helpers +
|
6
|
+
helpers = field_helpers + [:time_zone_select, :date_select] - [:hidden_field, :fields_for, :label]
|
7
7
|
helpers.each do |helper|
|
8
8
|
define_method helper do |field, *args|
|
9
|
-
options = args.
|
9
|
+
options = args.find { |a| a.is_a?(Hash) } || {}
|
10
10
|
super field, options.merge(attribute_for(field))
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
actions =
|
14
|
+
actions = %w(submit button)
|
15
15
|
actions.each do |helper|
|
16
16
|
define_method helper do |field, *args|
|
17
|
-
options = args.
|
17
|
+
options = args.find { |a| a.is_a?(Hash) } || {}
|
18
18
|
super field, options.merge(action_for(field.downcase))
|
19
19
|
end
|
20
20
|
end
|
@@ -2,6 +2,18 @@ module Formtastic
|
|
2
2
|
|
3
3
|
module Inputs
|
4
4
|
module Base
|
5
|
+
|
6
|
+
module Errors
|
7
|
+
|
8
|
+
def error_sentence_html
|
9
|
+
extend(PageRecord::Helpers)
|
10
|
+
error_class = options[:error_class] || builder.default_inline_error_class
|
11
|
+
options = { :class => error_class }.merge(error_for(@method))
|
12
|
+
template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.html_safe), options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
5
17
|
module Html
|
6
18
|
|
7
19
|
alias_method :input_html_options_org, :input_html_options
|
@@ -13,14 +25,13 @@ module Formtastic
|
|
13
25
|
#
|
14
26
|
|
15
27
|
def input_html_options
|
16
|
-
|
17
|
-
input_html_options_org.merge(attribute_for(
|
28
|
+
extend(PageRecord::Helpers)
|
29
|
+
input_html_options_org.merge(attribute_for(input_name))
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
23
|
-
|
24
35
|
module Actions
|
25
36
|
module Buttonish
|
26
37
|
|
@@ -32,7 +43,7 @@ module Formtastic
|
|
32
43
|
# for all action methods
|
33
44
|
#
|
34
45
|
def extra_button_html_options
|
35
|
-
|
46
|
+
extend(PageRecord::Helpers)
|
36
47
|
extra_button_html_options_org.merge(action_for(@method))
|
37
48
|
end
|
38
49
|
|
@@ -46,21 +57,21 @@ module Formtastic
|
|
46
57
|
|
47
58
|
#
|
48
59
|
# This is a replacement method for the original `semantic_form_for` method from `Formtastic`.
|
49
|
-
# The only thing it does, is merge the `form_record_for` into the output.
|
60
|
+
# The only thing it does, is merge the `form_record_for` into the output.
|
50
61
|
#
|
51
62
|
def semantic_form_for(record_or_name_or_array, *args, &proc)
|
52
|
-
|
63
|
+
extend(PageRecord::Helpers)
|
53
64
|
options = args.extract_options!
|
54
|
-
options||= {}
|
65
|
+
options ||= {}
|
55
66
|
case record_or_name_or_array
|
56
67
|
when String, Symbol
|
57
68
|
object_name = record_or_name_or_array
|
58
69
|
else
|
59
70
|
object = record_or_name_or_array.is_a?(Array) ? record_or_name_or_array.last : record_or_name_or_array
|
60
|
-
raise ArgumentError,
|
71
|
+
raise ArgumentError, 'First argument in form cannot contain nil or be empty' unless object
|
61
72
|
object_name = options[:as] || model_name_from_record_or_class(object).param_key
|
62
73
|
end
|
63
|
-
options = options.merge(html:form_record_for(object_name))
|
74
|
+
options = options.merge(html: form_record_for(object_name))
|
64
75
|
args << options
|
65
76
|
semantic_form_for_org(record_or_name_or_array, *args, &proc)
|
66
77
|
end
|