site_prism 2.13 → 2.14
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.
- checksums.yaml +4 -4
- data/README.md +86 -5
- data/lib/site_prism.rb +2 -1
- data/lib/site_prism/addressable_url_matcher.rb +45 -38
- data/lib/site_prism/element_checker.rb +20 -4
- data/lib/site_prism/element_container.rb +209 -156
- data/lib/site_prism/exceptions.rb +4 -1
- data/lib/site_prism/loadable.rb +31 -18
- data/lib/site_prism/page.rb +12 -6
- data/lib/site_prism/section.rb +19 -5
- data/lib/site_prism/version.rb +1 -1
- data/lib/site_prism/waiter.rb +0 -5
- metadata +26 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b74604cbe9c1815149c4bce7c747d9087098ade
|
4
|
+
data.tar.gz: 5043802657e6d0f88d6f52ea8790b49b8e634dd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4683666aa25142921dfab489841ef22eed10768978cd6c0ba69d768517a28cfcd7ef236fa5291c9a951836ea3bf3f762e2c25451ff5ad30940ec7db071c204cb
|
7
|
+
data.tar.gz: 5ade19c3956fbf314b096831c72f9c0d9f572ab5995384d389cfd795ada012b4e63e3036ca612fafa3b78658bb037391bddc19f09a1e6fa97f27772901a0a1c1
|
data/README.md
CHANGED
@@ -746,7 +746,6 @@ present in the browser, false if they're not all there.
|
|
746
746
|
Then /^the friends page contains all the expected elements$/ do
|
747
747
|
expect(@friends_page).to be_all_there
|
748
748
|
end
|
749
|
-
|
750
749
|
```
|
751
750
|
|
752
751
|
You may wish to have elements declared in a page object class that are not always guaranteed to be present (success or error messages, etc.). If you'd still like to test such a page with `all_there?` you can declare `expected_elements` on your page object class that narrows the elements included in `all_there?` check to those that definitely should be present.
|
@@ -761,6 +760,20 @@ class TestPage < SitePrism::Page
|
|
761
760
|
end
|
762
761
|
```
|
763
762
|
|
763
|
+
And if you aren't sure which elements are present and which are, Then ask SitePrism to tell you!
|
764
|
+
|
765
|
+
```ruby
|
766
|
+
class TestPage < SitePrism::Page
|
767
|
+
element :name_field, '#name'
|
768
|
+
element :address_field, '#address'
|
769
|
+
element :success_msg, 'span.alert-success'
|
770
|
+
end
|
771
|
+
|
772
|
+
# and... Only `address_field` is on the page
|
773
|
+
|
774
|
+
@test_page.elements_present #=> [:address_field]
|
775
|
+
```
|
776
|
+
|
764
777
|
## Sections
|
765
778
|
|
766
779
|
SitePrism allows you to model sections of a page that appear on multiple
|
@@ -804,12 +817,44 @@ SitePrism allows adding sections to sections) is to call the `section`
|
|
804
817
|
method. It takes 3 arguments: the first is the name of the section as
|
805
818
|
referred to on the page (sections that appear on multiple pages can be
|
806
819
|
named differently). The second argument is the class of which an
|
807
|
-
instance will be created to represent the page section, and the
|
808
|
-
|
820
|
+
instance will be created to represent the page section, and the following
|
821
|
+
arguments are [Capybara::Node::Finders](https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders).
|
822
|
+
These identify the root node of the section
|
809
823
|
on this page (note that the css selector can be different for different
|
810
824
|
pages as the whole point of sections is that they can appear in
|
811
825
|
different places on different pages).
|
812
826
|
|
827
|
+
If you define a section as a class and as an Anonymous section,
|
828
|
+
you can have some handy constructs like the one below
|
829
|
+
|
830
|
+
```ruby
|
831
|
+
class People < SitePrism::Section
|
832
|
+
element :headline, 'h2'
|
833
|
+
end
|
834
|
+
|
835
|
+
class HomePage < SitePrism::Page
|
836
|
+
# section people_with_block will have headline and
|
837
|
+
# headline_clone elements in it
|
838
|
+
section :people_with_block, People do
|
839
|
+
element :headline_clone, 'h2'
|
840
|
+
end
|
841
|
+
end
|
842
|
+
```
|
843
|
+
|
844
|
+
The 3rd argument (Locators), can be omitted if you are re-using the same locator for all
|
845
|
+
references to the section Class. In order to do this, simply tell SitePrism that
|
846
|
+
you want to use a default search argument.
|
847
|
+
|
848
|
+
```ruby
|
849
|
+
class People < SitePrism::Section
|
850
|
+
set_default_search_arguments '.people'
|
851
|
+
end
|
852
|
+
|
853
|
+
class Home < SitePrism::Page
|
854
|
+
section :people, People
|
855
|
+
end
|
856
|
+
```
|
857
|
+
|
813
858
|
#### Accessing a page's section
|
814
859
|
|
815
860
|
The `section` method (like the `element` method) adds a few methods to
|
@@ -1265,7 +1310,7 @@ end
|
|
1265
1310
|
|
1266
1311
|
```ruby
|
1267
1312
|
Then /^there are search results on the page$/ do
|
1268
|
-
expect(@
|
1313
|
+
expect(@results_page).to have_search_results
|
1269
1314
|
end
|
1270
1315
|
```
|
1271
1316
|
|
@@ -1493,7 +1538,7 @@ into our page and section classes:
|
|
1493
1538
|
|
1494
1539
|
```ruby
|
1495
1540
|
Then /^there are search results on the page$/ do
|
1496
|
-
expect(@
|
1541
|
+
expect(@results_page).to have_search_results(count: 25)
|
1497
1542
|
end
|
1498
1543
|
```
|
1499
1544
|
|
@@ -1688,6 +1733,42 @@ with this:
|
|
1688
1733
|
@search_page.search_results
|
1689
1734
|
```
|
1690
1735
|
|
1736
|
+
## Raising Errors on wait_for
|
1737
|
+
|
1738
|
+
By default, when using `wait_for_*` and `wait_for_no_*` methods, SitePrism will not raise
|
1739
|
+
an error if an element does not appear or disappear within the timeout period and will
|
1740
|
+
simply return `false` and allow the test to continue. This is different from
|
1741
|
+
the other methods such as `wait_until_*_visible` which do raise errors.
|
1742
|
+
|
1743
|
+
Add the following code your spec_helper file to enable errors to be
|
1744
|
+
raised immediately when a `wait_for_*` method does not find the element it is
|
1745
|
+
waiting for and when a `wait_for_no_*` method continues to find an element it is
|
1746
|
+
waiting to not exist:
|
1747
|
+
|
1748
|
+
```ruby
|
1749
|
+
SitePrism.configure do |config|
|
1750
|
+
config.raise_on_wait_fors = true
|
1751
|
+
end
|
1752
|
+
```
|
1753
|
+
|
1754
|
+
This enables you to replace this:
|
1755
|
+
|
1756
|
+
```ruby
|
1757
|
+
raise unless @search_page.wait_for_search_results
|
1758
|
+
# or...
|
1759
|
+
raise unless @search_page.wait_for_no_search_results
|
1760
|
+
```
|
1761
|
+
|
1762
|
+
with this:
|
1763
|
+
|
1764
|
+
```
|
1765
|
+
# With raise on wait_fors enabled, this will automatically raise
|
1766
|
+
# if no search results are found
|
1767
|
+
@search_page.wait_for_search_results
|
1768
|
+
# or automatically raise if search results are still found
|
1769
|
+
@search_page.wait_for_no_search_results
|
1770
|
+
```
|
1771
|
+
|
1691
1772
|
## Using SitePrism with VCR
|
1692
1773
|
|
1693
1774
|
There's a SitePrism plugin called `site_prism.vcr` that lets you use
|
data/lib/site_prism.rb
CHANGED
@@ -12,7 +12,7 @@ module SitePrism
|
|
12
12
|
autoload :AddressableUrlMatcher, 'site_prism/addressable_url_matcher'
|
13
13
|
|
14
14
|
class << self
|
15
|
-
attr_accessor :use_implicit_waits
|
15
|
+
attr_accessor :use_implicit_waits, :raise_on_wait_fors
|
16
16
|
|
17
17
|
def configure
|
18
18
|
yield self
|
@@ -20,4 +20,5 @@ module SitePrism
|
|
20
20
|
end
|
21
21
|
|
22
22
|
@use_implicit_waits = false
|
23
|
+
@raise_on_wait_fors = false
|
23
24
|
end
|
@@ -5,11 +5,10 @@ require 'base64'
|
|
5
5
|
|
6
6
|
module SitePrism
|
7
7
|
class AddressableUrlMatcher
|
8
|
-
COMPONENT_NAMES = %i[scheme user password host
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
}.freeze
|
8
|
+
COMPONENT_NAMES = %i[scheme user password host
|
9
|
+
port path query fragment ].freeze
|
10
|
+
|
11
|
+
COMPONENT_PREFIXES = { query: '?', fragment: '#' }.freeze
|
13
12
|
|
14
13
|
attr_reader :pattern
|
15
14
|
|
@@ -17,7 +16,8 @@ module SitePrism
|
|
17
16
|
@pattern = pattern
|
18
17
|
end
|
19
18
|
|
20
|
-
# @return the hash of extracted mappings from
|
19
|
+
# @return the hash of extracted mappings from
|
20
|
+
# parsing the provided URL according to our pattern,
|
21
21
|
# or nil if the URL doesn't conform to the matcher template.
|
22
22
|
def mappings(url)
|
23
23
|
uri = Addressable::URI.parse(url)
|
@@ -31,15 +31,15 @@ module SitePrism
|
|
31
31
|
result
|
32
32
|
end
|
33
33
|
|
34
|
-
# Determine whether URL matches our pattern, and
|
35
|
-
#
|
34
|
+
# Determine whether URL matches our pattern, and
|
35
|
+
# optionally whether the extracted mappings match
|
36
|
+
# a hash of expected values. You can specify values
|
37
|
+
# as strings, numbers or regular expressions.
|
36
38
|
def matches?(url, expected_mappings = {})
|
37
39
|
actual_mappings = mappings(url)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
false
|
42
|
-
end
|
40
|
+
return false unless actual_mappings
|
41
|
+
expected_mappings.empty? ||
|
42
|
+
all_expected_mappings_match?(expected_mappings, actual_mappings)
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
@@ -72,29 +72,30 @@ module SitePrism
|
|
72
72
|
component_url = component_url.sub(substituted_value, template_value)
|
73
73
|
end
|
74
74
|
|
75
|
-
component_templates[component] =
|
75
|
+
component_templates[component] =
|
76
|
+
Addressable::Template.new(component_url.to_s)
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
79
|
-
# Returns empty hash if the template omits the component,
|
80
|
-
#
|
80
|
+
# Returns empty hash if the template omits the component,
|
81
|
+
# a set of substitutions if the
|
82
|
+
# provided URI component matches the template component,
|
83
|
+
# or nil if the match fails.
|
81
84
|
def component_matches(component, uri)
|
82
|
-
extracted_mappings = {}
|
83
85
|
component_template = component_templates[component]
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
extracted_mappings
|
86
|
+
return {} unless component_template
|
87
|
+
component_url = uri.public_send(component).to_s
|
88
|
+
mappings = component_template.extract(component_url)
|
89
|
+
return mappings if mappings
|
90
|
+
# to support Addressable's expansion of queries
|
91
|
+
# ensure it's parsing the fragment as appropriate (e.g. {?params*})
|
92
|
+
prefix = COMPONENT_PREFIXES[component]
|
93
|
+
return nil unless prefix
|
94
|
+
component_template.extract(prefix + component_url)
|
95
95
|
end
|
96
96
|
|
97
|
-
# Convert the pattern into an Addressable URI by substituting
|
97
|
+
# Convert the pattern into an Addressable URI by substituting
|
98
|
+
# the template slugs with nonsense strings.
|
98
99
|
def to_substituted_uri
|
99
100
|
url = pattern
|
100
101
|
substitutions.each_pair do |slug, value|
|
@@ -115,28 +116,34 @@ module SitePrism
|
|
115
116
|
end
|
116
117
|
|
117
118
|
def reverse_substitutions
|
118
|
-
@reverse_substitutions ||=
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
@reverse_substitutions ||=
|
120
|
+
slugs.each_with_index.reduce({}) do |memo, slug_index|
|
121
|
+
slug, index = slug_index
|
122
|
+
memo.merge(
|
123
|
+
slug_prefix(slug) + substitution_value(index) => slug,
|
124
|
+
substitution_value(index) => slug
|
125
|
+
)
|
126
|
+
end
|
122
127
|
end
|
123
128
|
|
124
129
|
def slugs
|
125
130
|
pattern.scan(/{[^}]+}/)
|
126
131
|
end
|
127
132
|
|
128
|
-
# If a slug begins with non-alpha characters,
|
129
|
-
# (e.g. query or fragment).
|
133
|
+
# If a slug begins with non-alpha characters,
|
134
|
+
# it may denote the start of a new component (e.g. query or fragment).
|
135
|
+
# We emit this prefix as part of the substituted slug
|
130
136
|
# so that Addressable's URI parser can see it as such.
|
131
137
|
def slug_prefix(slug)
|
132
|
-
|
133
|
-
|
138
|
+
prefix = slug.match(/\A{([^A-Za-z]+)/)
|
139
|
+
prefix && prefix[1] || ''
|
134
140
|
end
|
135
141
|
|
136
142
|
# Generate a repeatable 5 character uniform alphabetical nonsense string
|
137
143
|
# to allow parsing as a URI
|
138
144
|
def substitution_value(index)
|
139
|
-
|
145
|
+
sha = Digest::SHA1.digest(index.to_s)
|
146
|
+
Base64.urlsafe_encode64(sha).gsub(/[^A-Za-z]/, '')[0..5]
|
140
147
|
end
|
141
148
|
end
|
142
149
|
end
|
@@ -3,15 +3,31 @@
|
|
3
3
|
module SitePrism
|
4
4
|
module ElementChecker
|
5
5
|
def all_there?
|
6
|
-
elements_to_check.all? { |element|
|
6
|
+
elements_to_check.all? { |element| present?(element) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def elements_present
|
10
|
+
mapped_items.select { |item_name| present?(item_name) }
|
7
11
|
end
|
8
12
|
|
9
13
|
private
|
10
14
|
|
11
15
|
def elements_to_check
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
if self.class.expected_items
|
17
|
+
mapped_items.select do |el|
|
18
|
+
self.class.expected_items.include?(el)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
mapped_items
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def mapped_items
|
26
|
+
self.class.mapped_items.uniq
|
27
|
+
end
|
28
|
+
|
29
|
+
def present?(element)
|
30
|
+
send("has_#{element}?")
|
15
31
|
end
|
16
32
|
end
|
17
33
|
end
|
@@ -2,222 +2,275 @@
|
|
2
2
|
|
3
3
|
module SitePrism
|
4
4
|
module ElementContainer
|
5
|
-
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend ClassMethods
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
9
|
+
private
|
10
|
+
|
11
|
+
def raise_if_block(obj, name, has_block)
|
12
|
+
return unless has_block
|
13
|
+
|
14
|
+
raise SitePrism::UnsupportedBlock, "#{obj.class}##{name}"
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
17
|
+
def raise_wait_for_if_failed(obj, name, timeout, failed)
|
18
|
+
return unless SitePrism.raise_on_wait_fors && failed
|
19
|
+
|
20
|
+
raise SitePrism::TimeOutWaitingForExistenceError, \
|
21
|
+
"Timed out after #{timeout}s waiting for #{obj.class}##{name}"
|
23
22
|
end
|
24
|
-
alias collection elements
|
25
23
|
|
26
|
-
def
|
27
|
-
|
24
|
+
def raise_wait_for_no_if_failed(obj, name, timeout, failed)
|
25
|
+
return unless SitePrism.raise_on_wait_fors && failed
|
26
|
+
|
27
|
+
raise SitePrism::TimeOutWaitingForNonExistenceError, \
|
28
|
+
"Timed out after #{timeout}s waiting for no #{obj.class}##{name}"
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def merge_args(find_args, runtime_args, override_options = {})
|
32
|
+
find_args = find_args.dup
|
33
|
+
runtime_args = runtime_args.dup
|
34
|
+
options = {}
|
35
|
+
options.merge!(find_args.pop) if find_args.last.is_a? Hash
|
36
|
+
options.merge!(runtime_args.pop) if runtime_args.last.is_a? Hash
|
37
|
+
options.merge!(override_options)
|
38
|
+
[*find_args, *runtime_args, options]
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
attr_reader :mapped_items, :expected_items
|
43
|
+
|
44
|
+
def element(element_name, *find_args)
|
45
|
+
build(element_name, *find_args) do
|
46
|
+
define_method(element_name.to_s) do |*runtime_args, &element_block|
|
47
|
+
raise_if_block(self, element_name.to_s, !element_block.nil?)
|
48
|
+
find_first(*merge_args(find_args, runtime_args))
|
49
|
+
end
|
35
50
|
end
|
36
51
|
end
|
37
|
-
end
|
38
52
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
find_all(*find_args, *runtime_args).map do |element|
|
45
|
-
section_class.new(self, element)
|
53
|
+
def elements(collection_name, *find_args)
|
54
|
+
build(collection_name, *find_args) do
|
55
|
+
define_method(collection_name.to_s) do |*runtime_args, &element_block|
|
56
|
+
raise_if_block(self, collection_name.to_s, !element_block.nil?)
|
57
|
+
find_all(*merge_args(find_args, runtime_args))
|
46
58
|
end
|
47
59
|
end
|
48
60
|
end
|
49
|
-
|
61
|
+
alias collection elements
|
62
|
+
|
63
|
+
def expected_elements(*elements)
|
64
|
+
@expected_items = elements
|
65
|
+
end
|
50
66
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
within_frame(*scope_find_args) do
|
58
|
-
block.call iframe_page_class.new
|
67
|
+
def section(section_name, *args, &block)
|
68
|
+
section_class, find_args = extract_section_options(args, &block)
|
69
|
+
build(section_name, *find_args) do
|
70
|
+
define_method section_name do |*runtime_args, &runtime_block|
|
71
|
+
section_class.new self, find_first(*merge_args(find_args, runtime_args)), &runtime_block
|
72
|
+
end
|
59
73
|
end
|
60
74
|
end
|
61
|
-
end
|
62
75
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
76
|
+
def sections(section_collection_name, *args, &block)
|
77
|
+
section_class, find_args = extract_section_options(args, &block)
|
78
|
+
build(section_collection_name, *find_args) do
|
79
|
+
define_method(section_collection_name) do |*runtime_args, &element_block|
|
80
|
+
raise_if_block(self, section_collection_name.to_s, !element_block.nil?)
|
81
|
+
find_all(*merge_args(find_args, runtime_args)).map do |element|
|
82
|
+
section_class.new(self, element)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
67
87
|
|
68
|
-
|
69
|
-
|
88
|
+
def iframe(iframe_name, iframe_page_class, *args)
|
89
|
+
element_find_args = deduce_iframe_element_find_args(args)
|
90
|
+
scope_find_args = deduce_iframe_scope_find_args(args)
|
91
|
+
add_to_mapped_items(iframe_name)
|
92
|
+
add_iframe_helper_methods(iframe_name, *element_find_args)
|
93
|
+
define_method(iframe_name) do |&block|
|
94
|
+
within_frame(*scope_find_args) do
|
95
|
+
block.call iframe_page_class.new
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
70
99
|
|
71
|
-
|
72
|
-
|
100
|
+
def add_to_mapped_items(item)
|
101
|
+
@mapped_items ||= []
|
102
|
+
@mapped_items << item
|
103
|
+
end
|
73
104
|
|
74
|
-
|
105
|
+
private
|
75
106
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
107
|
+
def build(name, *find_args)
|
108
|
+
if find_args.empty?
|
109
|
+
create_no_selector(name)
|
110
|
+
else
|
111
|
+
add_to_mapped_items(name)
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
add_helper_methods(name, *find_args)
|
82
115
|
end
|
83
|
-
add_helper_methods(name, *find_args)
|
84
|
-
end
|
85
116
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
117
|
+
def add_helper_methods(name, *find_args)
|
118
|
+
create_existence_checker(name, *find_args)
|
119
|
+
create_nonexistence_checker(name, *find_args)
|
120
|
+
create_waiter(name, *find_args)
|
121
|
+
create_nonexistence_waiter(name, *find_args)
|
122
|
+
create_visibility_waiter(name, *find_args)
|
123
|
+
create_invisibility_waiter(name, *find_args)
|
124
|
+
end
|
94
125
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
126
|
+
def add_iframe_helper_methods(name, *find_args)
|
127
|
+
create_existence_checker(name, *find_args)
|
128
|
+
create_nonexistence_checker(name, *find_args)
|
129
|
+
create_waiter(name, *find_args)
|
130
|
+
create_nonexistence_waiter(name, *find_args)
|
131
|
+
end
|
101
132
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
133
|
+
def create_helper_method(proposed_method_name, *find_args)
|
134
|
+
if find_args.empty?
|
135
|
+
create_no_selector(proposed_method_name)
|
136
|
+
else
|
137
|
+
yield
|
138
|
+
end
|
107
139
|
end
|
108
|
-
end
|
109
140
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
141
|
+
def create_existence_checker(element_name, *find_args)
|
142
|
+
method_name = "has_#{element_name}?"
|
143
|
+
create_helper_method(method_name, *find_args) do
|
144
|
+
define_method(method_name) do |*runtime_args|
|
145
|
+
wait_time = SitePrism.use_implicit_waits ? Capybara.default_max_wait_time : 0
|
146
|
+
Capybara.using_wait_time(wait_time) do
|
147
|
+
element_exists?(*merge_args(find_args, runtime_args))
|
148
|
+
end
|
117
149
|
end
|
118
150
|
end
|
119
151
|
end
|
120
|
-
end
|
121
152
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
153
|
+
def create_nonexistence_checker(element_name, *find_args)
|
154
|
+
method_name = "has_no_#{element_name}?"
|
155
|
+
create_helper_method(method_name, *find_args) do
|
156
|
+
define_method(method_name) do |*runtime_args|
|
157
|
+
wait_time = SitePrism.use_implicit_waits ? Capybara.default_max_wait_time : 0
|
158
|
+
Capybara.using_wait_time(wait_time) do
|
159
|
+
element_does_not_exist?(*merge_args(find_args, runtime_args))
|
160
|
+
end
|
129
161
|
end
|
130
162
|
end
|
131
163
|
end
|
132
|
-
end
|
133
164
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
165
|
+
def create_waiter(element_name, *find_args)
|
166
|
+
method_name = "wait_for_#{element_name}"
|
167
|
+
create_helper_method(method_name, *find_args) do
|
168
|
+
define_method(method_name) do |timeout = Capybara.default_max_wait_time, *runtime_args|
|
169
|
+
result = Capybara.using_wait_time(timeout) do
|
170
|
+
element_exists?(*merge_args(find_args, runtime_args))
|
171
|
+
end
|
172
|
+
raise_wait_for_if_failed(self, element_name.to_s, timeout, !result)
|
173
|
+
result
|
141
174
|
end
|
142
175
|
end
|
143
176
|
end
|
144
|
-
end
|
145
177
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
178
|
+
def create_nonexistence_waiter(element_name, *find_args)
|
179
|
+
method_name = "wait_for_no_#{element_name}"
|
180
|
+
create_helper_method(method_name, *find_args) do
|
181
|
+
define_method(method_name) do |timeout = Capybara.default_max_wait_time, *runtime_args|
|
182
|
+
result = Capybara.using_wait_time(timeout) do
|
183
|
+
element_does_not_exist?(*merge_args(find_args, runtime_args))
|
184
|
+
end
|
185
|
+
raise_wait_for_no_if_failed(self, element_name.to_s, timeout, !result)
|
186
|
+
result
|
153
187
|
end
|
154
188
|
end
|
155
189
|
end
|
156
|
-
end
|
157
190
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
sleep 0.05 until element_exists?(*find_args, *runtime_args, visible: true)
|
191
|
+
def create_visibility_waiter(element_name, *find_args)
|
192
|
+
method_name = "wait_until_#{element_name}_visible"
|
193
|
+
create_helper_method(method_name, *find_args) do
|
194
|
+
define_method(method_name) do |timeout = Capybara.default_max_wait_time, *runtime_args|
|
195
|
+
unless element_exists?(*merge_args(find_args, runtime_args, visible: true, wait: timeout))
|
196
|
+
raise SitePrism::TimeOutWaitingForElementVisibility
|
165
197
|
end
|
166
198
|
end
|
167
199
|
end
|
168
200
|
end
|
169
|
-
end
|
170
201
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
sleep 0.05 while element_exists?(*find_args, *runtime_args, visible: true)
|
202
|
+
def create_invisibility_waiter(element_name, *find_args)
|
203
|
+
method_name = "wait_until_#{element_name}_invisible"
|
204
|
+
create_helper_method(method_name, *find_args) do
|
205
|
+
define_method(method_name) do |timeout = Capybara.default_max_wait_time, *runtime_args|
|
206
|
+
unless element_does_not_exist?(*merge_args(find_args, runtime_args, visible: true, wait: timeout))
|
207
|
+
raise SitePrism::TimeOutWaitingForElementInvisibility
|
178
208
|
end
|
179
209
|
end
|
180
210
|
end
|
181
211
|
end
|
182
|
-
end
|
183
212
|
|
184
|
-
|
185
|
-
|
186
|
-
|
213
|
+
def create_no_selector(method_name)
|
214
|
+
define_method(method_name) do
|
215
|
+
raise SitePrism::NoSelectorForElement.new, "#{self.class.name} => :#{method_name} needs a selector"
|
216
|
+
end
|
187
217
|
end
|
188
|
-
end
|
189
218
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
219
|
+
def deduce_iframe_scope_find_args(args)
|
220
|
+
case args[0]
|
221
|
+
when Integer
|
222
|
+
[args[0]]
|
223
|
+
when String
|
224
|
+
[:css, args[0]]
|
225
|
+
else
|
226
|
+
args
|
227
|
+
end
|
198
228
|
end
|
199
|
-
end
|
200
229
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
230
|
+
def deduce_iframe_element_find_args(args)
|
231
|
+
case args[0]
|
232
|
+
when Integer
|
233
|
+
"iframe:nth-of-type(#{args[0] + 1})"
|
234
|
+
when String
|
235
|
+
[:css, args[0]]
|
236
|
+
else
|
237
|
+
args
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def extract_section_options(args, &block)
|
242
|
+
if args.first.is_a?(Class)
|
243
|
+
klass = args.shift
|
244
|
+
section_class = klass if klass.ancestors.include?(SitePrism::Section)
|
245
|
+
end
|
246
|
+
|
247
|
+
section_class = deduce_section_class(section_class, &block)
|
248
|
+
arguments = deduce_search_arguments(section_class, args)
|
249
|
+
[section_class, arguments]
|
250
|
+
end
|
251
|
+
|
252
|
+
def deduce_section_class(base_class, &block)
|
253
|
+
klass = base_class
|
254
|
+
|
255
|
+
klass = Class.new(klass || SitePrism::Section, &block) if block_given?
|
256
|
+
|
257
|
+
unless klass
|
258
|
+
raise ArgumentError, "You should provide descendant of SitePrism::Section \
|
259
|
+
class or/and a block as the second argument."
|
260
|
+
end
|
261
|
+
klass
|
262
|
+
end
|
263
|
+
|
264
|
+
def deduce_search_arguments(section_class, args)
|
265
|
+
extract_search_arguments(args) ||
|
266
|
+
extract_search_arguments(section_class.default_search_arguments) ||
|
267
|
+
raise(ArgumentError, "You should provide search arguments \
|
268
|
+
in section creation or set_default_search_arguments within section class")
|
209
269
|
end
|
210
|
-
end
|
211
270
|
|
212
|
-
|
213
|
-
|
214
|
-
section_class = args.shift
|
215
|
-
elsif block_given?
|
216
|
-
section_class = Class.new(SitePrism::Section, &block)
|
217
|
-
else
|
218
|
-
raise ArgumentError, 'You should provide section class either as a block, or as the second argument.'
|
271
|
+
def extract_search_arguments(args)
|
272
|
+
args if args && !args.empty?
|
219
273
|
end
|
220
|
-
[section_class, args]
|
221
274
|
end
|
222
275
|
end
|
223
276
|
end
|
@@ -6,7 +6,8 @@ module SitePrism
|
|
6
6
|
|
7
7
|
class InvalidUrlMatcher < StandardError
|
8
8
|
def message
|
9
|
-
|
9
|
+
"Could not automatically match your URL. \
|
10
|
+
Templated port numbers are unsupported."
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -18,6 +19,8 @@ module SitePrism
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
class TimeOutWaitingForExistenceError < StandardError; end
|
23
|
+
class TimeOutWaitingForNonExistenceError < StandardError; end
|
21
24
|
class TimeOutWaitingForElementVisibility < StandardError; end
|
22
25
|
class TimeOutWaitingForElementInvisibility < StandardError; end
|
23
26
|
|
data/lib/site_prism/loadable.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
module SitePrism
|
4
4
|
module Loadable
|
5
5
|
module ClassMethods
|
6
|
-
# The list of load_validations.
|
6
|
+
# The list of load_validations.
|
7
|
+
# They will be executed in the order they are defined.
|
7
8
|
#
|
8
9
|
# @return [Array]
|
9
10
|
def load_validations
|
@@ -16,44 +17,53 @@ module SitePrism
|
|
16
17
|
|
17
18
|
# Appends a load validation block to the page class.
|
18
19
|
#
|
19
|
-
# When `loaded?` is called, these blocks are instance_eval'd
|
20
|
-
#
|
21
|
-
# to
|
20
|
+
# When `loaded?` is called, these blocks are instance_eval'd
|
21
|
+
# against the current page instance.
|
22
|
+
# This allows users to wait for specific events to occur on
|
23
|
+
# the page or certain elements to be loaded before performing
|
24
|
+
# any actions on the page.
|
22
25
|
#
|
23
|
-
# @param block [&block] A block which returns true if the page
|
26
|
+
# @param block [&block] A block which returns true if the page
|
27
|
+
# loaded successfully, or false if it did not.
|
24
28
|
def load_validation(&block)
|
25
29
|
_load_validations << block
|
26
30
|
end
|
27
31
|
|
32
|
+
private
|
33
|
+
|
28
34
|
def _load_validations
|
29
35
|
@_load_validations ||= []
|
30
36
|
end
|
31
|
-
private :_load_validations
|
32
37
|
end
|
33
38
|
|
34
39
|
def self.included(base)
|
35
40
|
base.extend(ClassMethods)
|
36
41
|
end
|
37
42
|
|
38
|
-
# In certain circumstances, we cache that the page has already
|
39
|
-
#
|
43
|
+
# In certain circumstances, we cache that the page has already
|
44
|
+
# been confirmed to be loaded so that actions which
|
45
|
+
# call `loaded?` a second time do not need to perform
|
46
|
+
# the load_validation queries against the page a second time.
|
40
47
|
attr_accessor :loaded, :load_error
|
41
48
|
|
42
49
|
# Executes the given block after the page is loaded.
|
43
50
|
#
|
44
51
|
# The loadable object instance is yielded into the block.
|
45
52
|
#
|
46
|
-
# @param block [&block] The block to be executed once the page
|
53
|
+
# @param block [&block] The block to be executed once the page
|
54
|
+
# has finished loading.
|
47
55
|
def when_loaded(&_block)
|
48
|
-
|
49
|
-
|
56
|
+
# Get original loaded value, in case we are nested
|
57
|
+
# inside another when_loaded block.
|
58
|
+
previously_loaded = loaded
|
59
|
+
message = 'A block was expected, but none received.'
|
60
|
+
raise ArgumentError, message unless block_given?
|
50
61
|
|
51
62
|
# Within the block, cache loaded? to optimize performance.
|
52
63
|
self.loaded = loaded?
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
64
|
+
|
65
|
+
message = "Failed to load because: #{load_error || 'no reason given'}"
|
66
|
+
raise ::SitePrism::NotLoadedError, message unless loaded
|
57
67
|
|
58
68
|
yield self
|
59
69
|
ensure
|
@@ -62,7 +72,8 @@ module SitePrism
|
|
62
72
|
|
63
73
|
# Check if the page is loaded.
|
64
74
|
#
|
65
|
-
# On failure, if an error was reported by a failing validation,
|
75
|
+
# On failure, if an error was reported by a failing validation,
|
76
|
+
# it will be available via the `load_error` accessor.
|
66
77
|
#
|
67
78
|
# @return [Boolean] True if the page loaded successfully; otherwise false.
|
68
79
|
def loaded?
|
@@ -73,7 +84,10 @@ module SitePrism
|
|
73
84
|
load_validations_pass?
|
74
85
|
end
|
75
86
|
|
76
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
# If any load validations from page subclasses returns false,
|
90
|
+
# immediately return false.
|
77
91
|
def load_validations_pass?
|
78
92
|
self.class.load_validations.all? do |validation|
|
79
93
|
passed, message = instance_eval(&validation)
|
@@ -82,6 +96,5 @@ module SitePrism
|
|
82
96
|
passed
|
83
97
|
end
|
84
98
|
end
|
85
|
-
private :load_validations_pass?
|
86
99
|
end
|
87
100
|
end
|
data/lib/site_prism/page.rb
CHANGED
@@ -3,14 +3,18 @@
|
|
3
3
|
require 'site_prism/loadable'
|
4
4
|
|
5
5
|
module SitePrism
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
6
7
|
class Page
|
7
8
|
include Capybara::DSL
|
8
9
|
include ElementChecker
|
9
10
|
include Loadable
|
10
|
-
|
11
|
+
include ElementContainer
|
11
12
|
|
12
13
|
load_validation do
|
13
|
-
[
|
14
|
+
[
|
15
|
+
displayed?,
|
16
|
+
"Expected #{current_url} to match #{url_matcher} but it did not."
|
17
|
+
]
|
14
18
|
end
|
15
19
|
|
16
20
|
def page
|
@@ -40,7 +44,8 @@ module SitePrism
|
|
40
44
|
|
41
45
|
def displayed?(*args)
|
42
46
|
expected_mappings = args.last.is_a?(::Hash) ? args.pop : {}
|
43
|
-
seconds = !args.empty? ? args.first :
|
47
|
+
seconds = !args.empty? ? args.first : Capybara.default_max_wait_time
|
48
|
+
|
44
49
|
raise SitePrism::NoUrlMatcherForPage if url_matcher.nil?
|
45
50
|
begin
|
46
51
|
Waiter.wait_until_true(seconds) { url_matches?(expected_mappings) }
|
@@ -49,7 +54,7 @@ module SitePrism
|
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
52
|
-
def url_matches(seconds =
|
57
|
+
def url_matches(seconds = Capybara.default_max_wait_time)
|
53
58
|
return unless displayed?(seconds)
|
54
59
|
|
55
60
|
if url_matcher.is_a?(Regexp)
|
@@ -93,7 +98,7 @@ module SitePrism
|
|
93
98
|
end
|
94
99
|
|
95
100
|
def secure?
|
96
|
-
page.current_url.start_with?
|
101
|
+
page.current_url.start_with?('https')
|
97
102
|
end
|
98
103
|
|
99
104
|
private
|
@@ -133,7 +138,8 @@ module SitePrism
|
|
133
138
|
end
|
134
139
|
|
135
140
|
def matcher_template
|
136
|
-
@
|
141
|
+
@matcher_template ||= AddressableUrlMatcher.new(url_matcher)
|
137
142
|
end
|
138
143
|
end
|
144
|
+
# rubocop:enable Metrics/ClassLength
|
139
145
|
end
|
data/lib/site_prism/section.rb
CHANGED
@@ -7,10 +7,23 @@ module SitePrism
|
|
7
7
|
include Capybara::DSL
|
8
8
|
include ElementChecker
|
9
9
|
include Loadable
|
10
|
-
|
10
|
+
include ElementContainer
|
11
11
|
|
12
12
|
attr_reader :root_element, :parent
|
13
13
|
|
14
|
+
def self.set_default_search_arguments(*args)
|
15
|
+
@default_search_arguments = args
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.default_search_arguments
|
19
|
+
@default_search_arguments ||
|
20
|
+
(
|
21
|
+
superclass.respond_to?(:default_search_arguments) &&
|
22
|
+
superclass.default_search_arguments
|
23
|
+
) ||
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
14
27
|
def initialize(parent, root_element)
|
15
28
|
@parent = parent
|
16
29
|
@root_element = root_element
|
@@ -18,8 +31,11 @@ module SitePrism
|
|
18
31
|
end
|
19
32
|
|
20
33
|
# Capybara::DSL module "delegates" Capybara methods to the "page" method
|
34
|
+
# as such we need to overload this method so that the correct scoping
|
35
|
+
# occurs and calls within a section (For example section.find(element))
|
36
|
+
# correctly scope to look within the section only
|
21
37
|
def page
|
22
|
-
root_element ||
|
38
|
+
root_element || super
|
23
39
|
end
|
24
40
|
|
25
41
|
def visible?
|
@@ -36,9 +52,7 @@ module SitePrism
|
|
36
52
|
|
37
53
|
def parent_page
|
38
54
|
candidate_page = parent
|
39
|
-
until candidate_page.is_a?(SitePrism::Page)
|
40
|
-
candidate_page = candidate_page.parent
|
41
|
-
end
|
55
|
+
candidate_page = candidate_page.parent until candidate_page.is_a?(SitePrism::Page)
|
42
56
|
candidate_page
|
43
57
|
end
|
44
58
|
|
data/lib/site_prism/version.rb
CHANGED
data/lib/site_prism/waiter.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: site_prism
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.14'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nat Ritmeyer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
@@ -54,95 +54,89 @@ dependencies:
|
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 3.0.1
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: dotenv
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
62
|
+
version: '2.2'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
69
|
+
version: '2.2'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: rake
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '12.0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '12.0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
85
|
+
name: rspec
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- -
|
88
|
+
- - "~>"
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
90
|
+
version: '3.5'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- -
|
95
|
+
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version:
|
97
|
+
version: '3.5'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
99
|
+
name: rubocop
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
101
101
|
requirements:
|
102
|
-
- - "
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
version: 3.4.0
|
105
|
-
- - "<="
|
102
|
+
- - "~>"
|
106
103
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
104
|
+
version: '0.50'
|
108
105
|
type: :development
|
109
106
|
prerelease: false
|
110
107
|
version_requirements: !ruby/object:Gem::Requirement
|
111
108
|
requirements:
|
112
|
-
- - "
|
113
|
-
- !ruby/object:Gem::Version
|
114
|
-
version: 3.4.0
|
115
|
-
- - "<="
|
109
|
+
- - "~>"
|
116
110
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
111
|
+
version: '0.50'
|
118
112
|
- !ruby/object:Gem::Dependency
|
119
|
-
name:
|
113
|
+
name: selenium-webdriver
|
120
114
|
requirement: !ruby/object:Gem::Requirement
|
121
115
|
requirements:
|
122
|
-
- - "
|
116
|
+
- - "~>"
|
123
117
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
118
|
+
version: '3.4'
|
125
119
|
type: :development
|
126
120
|
prerelease: false
|
127
121
|
version_requirements: !ruby/object:Gem::Requirement
|
128
122
|
requirements:
|
129
|
-
- - "
|
123
|
+
- - "~>"
|
130
124
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
125
|
+
version: '3.4'
|
132
126
|
- !ruby/object:Gem::Dependency
|
133
|
-
name:
|
127
|
+
name: simplecov
|
134
128
|
requirement: !ruby/object:Gem::Requirement
|
135
129
|
requirements:
|
136
130
|
- - "~>"
|
137
131
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
132
|
+
version: '0.12'
|
139
133
|
type: :development
|
140
134
|
prerelease: false
|
141
135
|
version_requirements: !ruby/object:Gem::Requirement
|
142
136
|
requirements:
|
143
137
|
- - "~>"
|
144
138
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
139
|
+
version: '0.12'
|
146
140
|
description: |-
|
147
141
|
SitePrism gives you a simple, clean and semantic DSL for describing your site.
|
148
142
|
SitePrism implements the Page Object Model pattern on top of Capybara.
|