page_record 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|