page_record 0.0.1
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/.coveralls.yml +1 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +22 -0
- data/README.md +210 -0
- data/Rakefile +1 -0
- data/lib/page_record/attribute_accessors.rb +60 -0
- data/lib/page_record/class_actions.rb +55 -0
- data/lib/page_record/class_methods.rb +214 -0
- data/lib/page_record/cucumber.rb +5 -0
- data/lib/page_record/errors.rb +88 -0
- data/lib/page_record/finders.rb +135 -0
- data/lib/page_record/instance_actions.rb +52 -0
- data/lib/page_record/page_record.rb +29 -0
- data/lib/page_record/rspec.rb +7 -0
- data/lib/page_record/version.rb +3 -0
- data/lib/page_record.rb +9 -0
- data/page_record.gemspec +29 -0
- data/spec/page_record_spec.rb +434 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/shared_contexts.rb +37 -0
- data/spec/support/shared_examples.rb +71 -0
- data/spec/support/team.rb +6 -0
- data/spec/support/test_app.rb +24 -0
- data/spec/support/views/page-one-record-in-a-form.erb +4 -0
- data/spec/support/views/page-one-record.erb +3 -0
- data/spec/support/views/page-with-duplicate-records.erb +8 -0
- data/spec/support/views/page-with-single-table-with-3-records.erb +17 -0
- data/spec/support/views/page-with-two-tables-with-3-records.erb +38 -0
- data/spec/support/views/page-without-records.erb +2 -0
- metadata +190 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module PageRecord
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# This {::Exception} is raised when the specified record is not found
|
6
|
+
# on the page. Check your selector, filter and HTML code for details.
|
7
|
+
#
|
8
|
+
# ```html
|
9
|
+
# <div data-team-id='10'>
|
10
|
+
# <div data-attribute-for='name'>Ajax</div>
|
11
|
+
# </div>
|
12
|
+
# ```
|
13
|
+
# When the following code is executed, the {RecordNotFound} exception is raised.
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# TeamPage.find(11)
|
17
|
+
#```
|
18
|
+
#
|
19
|
+
class RecordNotFound < Exception
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# This {::Exception} is raised when the specfied attribute is not found
|
24
|
+
# on the page. Check your selector, filter and HTML code for details.
|
25
|
+
#
|
26
|
+
# ```html
|
27
|
+
# <div data-team-id='10'>
|
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
|
+
##
|
41
|
+
# This {::Exception} is raised when you have not set the page variable
|
42
|
+
# of the class. Check {PageRecord::PageRecord.page} for details
|
43
|
+
#
|
44
|
+
class PageNotSet < Exception
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# This {::Exception} is raised when the page contains multiple instances
|
49
|
+
# of the specfied record type. Use a selector to narrow the search.
|
50
|
+
#
|
51
|
+
# ```html
|
52
|
+
# <div id='first-table' data-team-id='10'>
|
53
|
+
# <div data-attribute-for='name'>Ajax</div>
|
54
|
+
# </div>
|
55
|
+
# <div id='second-table' data-team-id='10'>
|
56
|
+
# <div data-attribute-for='name'>Ajax</div>
|
57
|
+
# </div>
|
58
|
+
# ```
|
59
|
+
# When the following code is executed, the {MultipleRecords} exception is raised.
|
60
|
+
#
|
61
|
+
# ```ruby
|
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
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module PageRecord
|
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 PageRecord
|
18
|
+
##
|
19
|
+
#
|
20
|
+
# Searches the page and returns an {::Array} of {PageRecord::PageRecord} 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::PageRecord]
|
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 ||= self.instance_variable_get('@selector')
|
43
|
+
filter ||= self.instance_variable_get('@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 << self.new(id, selector)
|
49
|
+
end
|
50
|
+
records
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
#
|
55
|
+
# Searches the page and returns an instance of {PageRecord::PageRecord} 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::PageRecord]
|
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 ||= self.instance_variable_get('@selector')
|
86
|
+
filter ||= self.instance_variable_get('@filter')
|
87
|
+
self.new(id, selector, filter)
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
#
|
92
|
+
# Searches the page and returns an instance of {PageRecord::PageRecord} 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::PageRecord} uses this method for defining a
|
97
|
+
# finder for all attributes when you define your page class, {PageRecord::PageRecord}
|
98
|
+
# See {PageRecord::PageRecord.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::PageRecord].
|
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
|
+
begin
|
120
|
+
selector ||= self.instance_variable_get('@selector')
|
121
|
+
filter ||= self.instance_variable_get('@filter')
|
122
|
+
|
123
|
+
context = self.context_for_selector(selector)
|
124
|
+
record = context.find("[data-#{@type}-id]#{filter} > [data-attribute-for='#{attribute}']", :text => value)
|
125
|
+
parent = record.find(:xpath, "..")
|
126
|
+
id = parent["data-#{@type}-id"]
|
127
|
+
self.new(id, selector)
|
128
|
+
rescue Capybara::Ambiguous
|
129
|
+
raise MultipleRecords, "Found multiple #{@type} record with #{attribute} #{value} on page"
|
130
|
+
rescue Capybara::ElementNotFound
|
131
|
+
raise RecordNotFound, "#{@type} record with #{attribute} #{value} not found on page"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PageRecord
|
2
|
+
class PageRecord
|
3
|
+
|
4
|
+
##
|
5
|
+
# This is the implementation of the record action routine. It has two variants.
|
6
|
+
# it has a `?` variant and a `normal` variant.
|
7
|
+
#
|
8
|
+
# normal variant:
|
9
|
+
# It checks the page for a data-action-for='action' tag somewhere on the page.
|
10
|
+
# If it finds it, it clicks it.
|
11
|
+
#
|
12
|
+
# `?` variant:
|
13
|
+
# It checks the page for a data-action-for='action' tag somewhere on the page.
|
14
|
+
# If it finds it, returns the Capybara element.
|
15
|
+
#
|
16
|
+
# @param action [Symbol] this is the name of the action
|
17
|
+
#
|
18
|
+
# @return [Capybara::Result]
|
19
|
+
#
|
20
|
+
# @raise [PageRecord::MultipleRecords] when there are more actions with
|
21
|
+
# this name on the page
|
22
|
+
#
|
23
|
+
def method_missing(action)
|
24
|
+
raw_action = /(.*)\?/.match(action)
|
25
|
+
begin
|
26
|
+
if raw_action
|
27
|
+
action_for?(raw_action[1])
|
28
|
+
else
|
29
|
+
action_for(action)
|
30
|
+
end
|
31
|
+
rescue Capybara::ElementNotFound
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def action_for(action)
|
41
|
+
element = action_for?(action)
|
42
|
+
element.click
|
43
|
+
element
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
47
|
+
def action_for?(action)
|
48
|
+
@record.find("[data-action-for='#{action}']")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module PageRecord
|
3
|
+
|
4
|
+
class PageRecord
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
alias :id? :id
|
8
|
+
|
9
|
+
def initialize(id=nil, selector=nil, filter=nil)
|
10
|
+
selector ||= self.instance_variable_get('@selector')
|
11
|
+
filter ||= self.instance_variable_get('@filter')
|
12
|
+
@page = self.class.page
|
13
|
+
# raise PageNotSet, "page variable not set" unless @page
|
14
|
+
@type = self.class.instance_variable_get('@type')
|
15
|
+
@id = id.to_s
|
16
|
+
id_text = @id.blank? ? "" : "='#{@id}'"
|
17
|
+
begin
|
18
|
+
context = self.class.context_for_selector(selector)
|
19
|
+
@record = context.find("[data-#{@type}-id#{id_text}]#{filter}")
|
20
|
+
@id = @record["data-#{@type}-id"] if @id.blank?
|
21
|
+
rescue Capybara::Ambiguous
|
22
|
+
raise MultipleRecords, "Found multiple #{@type} record with id #{@id} on page"
|
23
|
+
rescue Capybara::ElementNotFound
|
24
|
+
raise RecordNotFound, "#{@type} record with id #{@id} not found on page"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/page_record.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require "page_record/version"
|
3
|
+
require "page_record/page_record"
|
4
|
+
require "page_record/finders"
|
5
|
+
require "page_record/instance_actions"
|
6
|
+
require "page_record/attribute_accessors"
|
7
|
+
require "page_record/class_actions"
|
8
|
+
require "page_record/class_methods"
|
9
|
+
require "page_record/errors"
|
data/page_record.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'page_record/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "page_record"
|
8
|
+
spec.version = PageRecord::VERSION
|
9
|
+
spec.authors = ["Bert Hajee"]
|
10
|
+
spec.email = ["hajee@moretIA.com"]
|
11
|
+
spec.description = %q{ActiveRecord like reading from specialy formatted HTML-page}
|
12
|
+
spec.summary = %q{Using some specialy formatted 'data-...' tags you can read records from HTML pages like an ActiveRecord page}
|
13
|
+
spec.homepage = "https://github.com/appdrones/page_record"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "sinatra"
|
25
|
+
# spec.add_development_dependency "debugger"
|
26
|
+
|
27
|
+
spec.add_dependency "capybara" , '~>2.1.0'
|
28
|
+
spec.add_dependency "activesupport"
|
29
|
+
end
|