site_prism 4.0.3 → 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.
- checksums.yaml +4 -4
- data/README.md +60 -54
- data/lib/site_prism/deprecator.rb +2 -21
- data/lib/site_prism/dsl/builder.rb +144 -0
- data/lib/site_prism/dsl/locators.rb +85 -0
- data/lib/site_prism/dsl/methods.rb +135 -0
- data/lib/site_prism/dsl/validator.rb +95 -0
- data/lib/site_prism/dsl.rb +14 -327
- data/lib/site_prism/element_checker.rb +2 -2
- data/lib/site_prism/error.rb +4 -1
- data/lib/site_prism/loadable.rb +13 -23
- data/lib/site_prism/logger.rb +1 -1
- data/lib/site_prism/page.rb +4 -15
- data/lib/site_prism/version.rb +1 -1
- data/lib/site_prism.rb +7 -3
- metadata +51 -36
- data/lib/site_prism/dsl_validator.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3036f6b2266855a0a4d08a3c72b44b08d1ce6f423ca72e5dbe5b539cb90b1216
|
4
|
+
data.tar.gz: 1314d67df7aeb3c9d70478e5b884aec2cfb7b4216c58cf9841d4d6dc8b7bc9f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 057576ee2b44eb40824344eeef80243e4fbbe8b30df7ab8e6b011053f652b0257065472103b16ed6a4ce094ed3ad608cf8c72dcb8e11c0e3e4f3daa2aaa9ef38
|
7
|
+
data.tar.gz: c9ef4c0ac315e0664f0089cd13a75e8566e7be3f553e85a7f2d17ef1bce7943762074b478767790bb44497968fc6ee0919440a1e613ed6a27f0efde585e19f24
|
data/README.md
CHANGED
@@ -29,8 +29,8 @@ We have a brief set of setup docs [HERE](https://github.com/site-prism/site_pris
|
|
29
29
|
|
30
30
|
## Supported Rubies / Browsers
|
31
31
|
|
32
|
-
SitePrism is built and tested to work on Ruby 2.
|
33
|
-
If you are using SitePrism with Ruby 2.
|
32
|
+
SitePrism is built and tested to work on Ruby 2.7 - 3.2.
|
33
|
+
If you are using SitePrism with Ruby 2.7 it is highly advisable to upgrade to a more modern
|
34
34
|
Ruby (v3+), if for any other reason, to get a performance improvement!
|
35
35
|
|
36
36
|
SitePrism should run on all major browsers. The gem's integration tests are run on Chrome and Firefox.
|
@@ -66,7 +66,7 @@ class SearchResults < SitePrism::Page
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
#
|
69
|
+
# Define sections that are used on multiple pages or multiple times on one page
|
70
70
|
|
71
71
|
class Menu < SitePrism::Section
|
72
72
|
element :search, 'a.search'
|
@@ -79,7 +79,7 @@ class SearchResults < SitePrism::Section
|
|
79
79
|
element :blurb, 'span.result-description'
|
80
80
|
end
|
81
81
|
|
82
|
-
#
|
82
|
+
# Then we can write some tests
|
83
83
|
|
84
84
|
When('I navigate to the google home page') do
|
85
85
|
@home = Home.new
|
@@ -88,6 +88,7 @@ end
|
|
88
88
|
|
89
89
|
Then('the home page should contain the menu and the search form') do
|
90
90
|
@home.wait_until_menu_visible(wait: 5)
|
91
|
+
|
91
92
|
expect(@home).to have_menu
|
92
93
|
expect(@home).to have_search_field
|
93
94
|
expect(@home).to have_search_button
|
@@ -100,11 +101,13 @@ end
|
|
100
101
|
|
101
102
|
Then('the search results page is displayed') do
|
102
103
|
@results_page = SearchResults.new
|
104
|
+
|
103
105
|
expect(@results_page).to be_displayed
|
104
106
|
end
|
105
107
|
|
106
108
|
Then('the search results page contains 10 individual search results') do
|
107
109
|
@results_page.wait_until_search_results_visible(wait: 5)
|
110
|
+
|
108
111
|
expect(@results_page).to have_search_results(count: 10)
|
109
112
|
end
|
110
113
|
|
@@ -113,8 +116,6 @@ Then('the search results contain a link to the wikipedia sausages page') do
|
|
113
116
|
end
|
114
117
|
```
|
115
118
|
|
116
|
-
Now for the details...
|
117
|
-
|
118
119
|
## Setup
|
119
120
|
|
120
121
|
### Installation
|
@@ -166,19 +167,15 @@ And again, as above, a sample driver is no different to a normal driver instanti
|
|
166
167
|
|
167
168
|
## Introduction to the Page Object Model
|
168
169
|
|
169
|
-
The Page Object Model is a test automation pattern that aims to create
|
170
|
-
|
171
|
-
|
172
|
-
to then use instances of those classes in your tests.
|
170
|
+
The Page Object Model is a test automation pattern that aims to create an abstraction of your sites user
|
171
|
+
interface that can be used in tests. The most common way to do this is to model each page as a class and
|
172
|
+
then to use instances of those classes in your tests.
|
173
173
|
|
174
|
-
If a class represents a page then each element of the page is
|
175
|
-
|
176
|
-
element that can then be acted upon (clicked, type in some text), or
|
177
|
-
queried (is it enabled? / visible?).
|
174
|
+
If a class represents a page then each element of the page is represented by a method that, when called, returns a
|
175
|
+
reference to that element that can then be acted upon (clicked, type in some text), or queried (is it enabled? / visible?).
|
178
176
|
|
179
|
-
SitePrism is based around this concept, but goes further as you'll see
|
180
|
-
|
181
|
-
multiple pages, or many times on a page using the concept of sections.
|
177
|
+
SitePrism is based around this concept, but goes further as you'll see below by also allowing modelling of
|
178
|
+
repeated sections that appear on multiple pages, or many times on a page using the concept of sections.
|
182
179
|
|
183
180
|
## Pages
|
184
181
|
|
@@ -216,9 +213,8 @@ class Home < SitePrism::Page
|
|
216
213
|
end
|
217
214
|
```
|
218
215
|
|
219
|
-
Note that setting a URL is **optional** - you only need to set a url if you want to be able to
|
220
|
-
|
221
|
-
home page or a login page, but probably not a search results page.
|
216
|
+
Note that setting a URL is **optional** - you only need to set a url if you want to be able to navigate directly to that page.
|
217
|
+
It makes sense to set the URL for a page model of a home page or a login page, but probably not a search results page.
|
222
218
|
|
223
219
|
#### Parametrized URLs
|
224
220
|
|
@@ -281,10 +277,9 @@ navigate to the URL set against that page's class.
|
|
281
277
|
|
282
278
|
### Verifying that a particular page is displayed
|
283
279
|
|
284
|
-
Automated tests often need to verify that a particular page is
|
285
|
-
|
286
|
-
|
287
|
-
currently viewed page. For example, with the following URL template:
|
280
|
+
Automated tests often need to verify that a particular page is displayed. SitePrism can automatically parse
|
281
|
+
your templated URL and verify that whatever components your template specifies match the currently viewed page.
|
282
|
+
For example, with the following URL template:
|
288
283
|
|
289
284
|
```ruby
|
290
285
|
class Account < SitePrism::Page
|
@@ -302,10 +297,9 @@ expect(@account_page.current_url).to end_with('/accounts/22?token=ca2786616a4285
|
|
302
297
|
expect(@account_page).to be_displayed
|
303
298
|
```
|
304
299
|
|
305
|
-
Calling `#displayed?` will return true if the browser's current URL
|
306
|
-
|
307
|
-
|
308
|
-
wait time in seconds as the first argument like this:
|
300
|
+
Calling `#displayed?` will return true if the browser's current URL matches the page's template and false if
|
301
|
+
it doesn't. It will wait for `Capybara.default_max_wait_time` seconds or you can pass an explicit wait time in
|
302
|
+
seconds as the first argument like this:
|
309
303
|
|
310
304
|
```ruby
|
311
305
|
@account_page.displayed?(10) # wait up to 10 seconds for display
|
@@ -346,9 +340,8 @@ expect(@account_page.url_matches.dig('query', 'token')).to eq('ca2786616a4285bc'
|
|
346
340
|
|
347
341
|
#### Falling back to basic regexp matchers
|
348
342
|
|
349
|
-
If SitePrism's built-in URL matching is not sufficient for your needs
|
350
|
-
|
351
|
-
URL matchers by it by calling `set_url_matcher`:
|
343
|
+
If SitePrism's built-in URL matching is not sufficient for your needs you can override and use SitePrism's
|
344
|
+
support for regular expression-based URL matchers by it by calling `set_url_matcher`:
|
352
345
|
|
353
346
|
```ruby
|
354
347
|
class Account < SitePrism::Page
|
@@ -377,6 +370,7 @@ end
|
|
377
370
|
|
378
371
|
@account = Account.new
|
379
372
|
@account.current_url #=> "http://www.example.com/account/123"
|
373
|
+
|
380
374
|
expect(@account.current_url).to include('example.com/account/')
|
381
375
|
```
|
382
376
|
|
@@ -394,10 +388,9 @@ end
|
|
394
388
|
|
395
389
|
### HTTP vs. HTTPS
|
396
390
|
|
397
|
-
You can easily tell if the page is secure or not by checking to see if
|
398
|
-
the current
|
399
|
-
|
400
|
-
'https' and false if it doesn't. For example:
|
391
|
+
You can easily tell if the page is secure or not by checking to see if the current URL begins with 'https' or not.
|
392
|
+
SitePrism provides the `#secure?` method that will return true if the current url begins with 'https' and false if it doesn't.
|
393
|
+
For example:
|
401
394
|
|
402
395
|
```ruby
|
403
396
|
class Account < SitePrism::Page
|
@@ -405,16 +398,15 @@ end
|
|
405
398
|
|
406
399
|
@account = Account.new
|
407
400
|
@account.secure? #=> true/false
|
401
|
+
|
408
402
|
expect(@account).to be_secure
|
409
403
|
```
|
410
404
|
|
411
405
|
## Elements
|
412
406
|
|
413
|
-
Pages are made up of elements (text fields, buttons, combo boxes, etc),
|
414
|
-
|
415
|
-
|
416
|
-
element collections would be items in any sort of list, eg: menu items,
|
417
|
-
images in a carousel, etc.
|
407
|
+
Pages are made up of elements (text fields, buttons, combo boxes, etc), either individual elements or groups of
|
408
|
+
them. Examples of individual elements would be a search field or a company logo image; examples of element collections would
|
409
|
+
be items in any sort of list, eg: menu items,images in a carousel, etc.
|
418
410
|
|
419
411
|
### Individual Elements
|
420
412
|
|
@@ -508,18 +500,16 @@ Using the above example:
|
|
508
500
|
|
509
501
|
```ruby
|
510
502
|
Then('the search field exists')do
|
511
|
-
expect(@home).to have_no_search_field #NB: NOT => expect(@home).not_to have_search_field
|
503
|
+
expect(@home).to have_no_search_field #NB: NOT THE SAME AS => expect(@home).not_to have_search_field
|
512
504
|
end
|
513
505
|
```
|
514
506
|
|
515
507
|
#### Waiting for an element to become visible
|
516
508
|
|
517
|
-
A method that gets added by calling `element` is the
|
518
|
-
`wait_until_<element_name>_visible` method.
|
509
|
+
A method that gets added by calling `element` is the `wait_until_<element_name>_visible` method.
|
519
510
|
This method delegates to [Capybara::Node::Matchers#has_selector?](https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Matchers#has_selector%3F-instance_method).
|
520
|
-
Calling this method will cause the test to wait for Capybara's default wait time for the element
|
521
|
-
|
522
|
-
of seconds to wait in-line or configuring the default wait time.
|
511
|
+
Calling this method will cause the test to wait for Capybara's default wait time for the element to become visible. You can customise
|
512
|
+
the wait time by supplying a number of seconds to wait in-line or configuring the default wait time.
|
523
513
|
|
524
514
|
```ruby
|
525
515
|
@home.wait_until_search_field_visible # using the default wait time set
|
@@ -529,12 +519,10 @@ of seconds to wait in-line or configuring the default wait time.
|
|
529
519
|
|
530
520
|
#### Waiting for an element to become invisible
|
531
521
|
|
532
|
-
Another method added by calling `element` is the
|
533
|
-
`wait_until_<element_name>_invisible` method.
|
522
|
+
Another method added by calling `element` is the `wait_until_<element_name>_invisible` method.
|
534
523
|
This method delegates to [Capybara::Node::Matchers#has_no_selector?](https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Matchers#has_no_selector%3F-instance_method).
|
535
|
-
Calling this method will cause the test to wait for Capybara's default
|
536
|
-
|
537
|
-
waiter, customise the wait time in the same way.
|
524
|
+
Calling this method will cause the test to wait for Capybara's default wait time for the element to become invisible.
|
525
|
+
You can (as with the visibility waiter), customise the wait time in the same way.
|
538
526
|
|
539
527
|
```ruby
|
540
528
|
@home.wait_until_search_field_invisible # using the default wait time set
|
@@ -544,9 +532,8 @@ waiter, customise the wait time in the same way.
|
|
544
532
|
|
545
533
|
#### CSS Selectors vs. XPath Expressions
|
546
534
|
|
547
|
-
While the above examples all use CSS selectors to find elements, it is
|
548
|
-
|
549
|
-
can use a CSS selector, you can use an XPath expression.
|
535
|
+
While the above examples all use CSS selectors to find elements, it is possible to use XPath expressions too.
|
536
|
+
In SitePrism, everywhere that you can use a CSS selector, you can use an XPath expression (Standard Capybara logic).
|
550
537
|
|
551
538
|
An example:
|
552
539
|
|
@@ -1686,6 +1673,25 @@ When('I log in') do
|
|
1686
1673
|
end
|
1687
1674
|
```
|
1688
1675
|
|
1676
|
+
## Shadow Root
|
1677
|
+
|
1678
|
+
SitePrism allows you to interact with Shadow Roots too.
|
1679
|
+
|
1680
|
+
### Creating an Shadow Root
|
1681
|
+
|
1682
|
+
You can use the `section` methods and provide the arguments. Specify the reference name for the Shadow Root,
|
1683
|
+
the CSS selector to locate it, and add the `:shadow_root` option. For example:
|
1684
|
+
|
1685
|
+
```ruby
|
1686
|
+
class Home < SitePrism::Page
|
1687
|
+
section :foo, '.foo', shadow_root: true do
|
1688
|
+
element :bar, '.bar'
|
1689
|
+
end
|
1690
|
+
end
|
1691
|
+
```
|
1692
|
+
|
1693
|
+
NB: By default `shadow_root` will be set to false
|
1694
|
+
|
1689
1695
|
## SitePrism Configuration
|
1690
1696
|
|
1691
1697
|
SitePrism can be configured to change its behaviour.
|
@@ -4,7 +4,7 @@ module SitePrism
|
|
4
4
|
# [SitePrism::Deprecator]
|
5
5
|
class Deprecator
|
6
6
|
class << self
|
7
|
-
# @return SitePrism.logger.warn(msg)
|
7
|
+
# @return [SitePrism.logger.warn(msg)]
|
8
8
|
#
|
9
9
|
# Tells the user that they are using old functionality, which needs removing in the
|
10
10
|
# next major version
|
@@ -15,22 +15,7 @@ module SitePrism
|
|
15
15
|
warn("#{old} is being deprecated and should no longer be used.")
|
16
16
|
end
|
17
17
|
|
18
|
-
warn("#{old} will be removed in SitePrism
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return SitePrism.logger.debug(msg)
|
22
|
-
#
|
23
|
-
# Tells the user that they are using functionality which is non-optimal
|
24
|
-
# The functionality should usually provide a reason for it being poor, as well as an
|
25
|
-
# optional way of upgrading to something different
|
26
|
-
#
|
27
|
-
# NB: As this is bubbled up at debug level, often users will not see this. So it will
|
28
|
-
# never be a candidate for removal directly
|
29
|
-
def soft_deprecate(old, reason, new = nil)
|
30
|
-
debug("The #{old} method is changing, as is SitePrism, and is now advised to be changed.")
|
31
|
-
debug("REASON: #{reason}.")
|
32
|
-
debug('Moving forwards into SitePrism v5, the default behaviour will change.')
|
33
|
-
debug("We advise you change to using #{new}") if new
|
18
|
+
warn("#{old} will be removed in SitePrism v6. You have been warned!")
|
34
19
|
end
|
35
20
|
|
36
21
|
private
|
@@ -38,10 +23,6 @@ module SitePrism
|
|
38
23
|
def warn(msg)
|
39
24
|
SitePrism.logger.warn(msg)
|
40
25
|
end
|
41
|
-
|
42
|
-
def debug(msg)
|
43
|
-
SitePrism.logger.debug(msg)
|
44
|
-
end
|
45
26
|
end
|
46
27
|
end
|
47
28
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SitePrism
|
4
|
+
module DSL
|
5
|
+
# [SitePrism::DSL::Builder]
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# The Building logic - Initially coming from `.build`
|
10
|
+
# This will take a request to build from a DSL invocation such as `element` and generate a series of
|
11
|
+
# helper methods and waiters. It will also generate the correct classes for `SitePrism::Section` objects
|
12
|
+
#
|
13
|
+
# Whilst doing all of this, it will also build up a "map" of objects in memory which can be used for
|
14
|
+
# future interrogation. There are 2 ways of this being stored currently (Default as a hash, Legacy as an array)
|
15
|
+
#
|
16
|
+
module Builder
|
17
|
+
# Return a list of all mapped items on a SitePrism class instance (Page or Section)
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
def mapped_items
|
21
|
+
@mapped_items ||= { element: [], elements: [], section: [], sections: [], iframe: [] }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build(type, name, *find_args)
|
27
|
+
invalid_element_name if invalid_element_name?(name)
|
28
|
+
blank_element(name) if find_args.empty?
|
29
|
+
|
30
|
+
mapped_items[type] << name.to_sym
|
31
|
+
yield
|
32
|
+
add_helper_methods(name, type, *find_args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def invalid_element_name
|
36
|
+
raise InvalidDSLNameError, dsl_name_error
|
37
|
+
end
|
38
|
+
|
39
|
+
def invalid_element_name?(name)
|
40
|
+
!dsl_validation_disabled? && name_invalid?(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def dsl_validation_disabled?
|
44
|
+
SitePrism.dsl_validation_disabled || ENV.key?('SITEPRISM_DSL_VALIDATION_DISABLED')
|
45
|
+
end
|
46
|
+
|
47
|
+
def blank_element(name)
|
48
|
+
raise SitePrism::InvalidElementError, "#{name} has come from an item with no locators."
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_helper_methods(name, _type, *find_args)
|
52
|
+
create_existence_checker(name, *find_args)
|
53
|
+
create_nonexistence_checker(name, *find_args)
|
54
|
+
SitePrism::RSpecMatchers.new(name)._create_rspec_existence_matchers if defined?(RSpec)
|
55
|
+
create_visibility_waiter(name, *find_args)
|
56
|
+
create_invisibility_waiter(name, *find_args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_existence_checker(element_name, *find_args)
|
60
|
+
method_name = "has_#{element_name}?"
|
61
|
+
create_helper_method(method_name, *find_args) do
|
62
|
+
define_method(method_name) do |*runtime_args|
|
63
|
+
args = merge_args(find_args, runtime_args)
|
64
|
+
element_exists?(*args)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_nonexistence_checker(element_name, *find_args)
|
70
|
+
method_name = "has_no_#{element_name}?"
|
71
|
+
create_helper_method(method_name, *find_args) do
|
72
|
+
define_method(method_name) do |*runtime_args|
|
73
|
+
args = merge_args(find_args, runtime_args)
|
74
|
+
element_does_not_exist?(*args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_visibility_waiter(element_name, *find_args)
|
80
|
+
method_name = "wait_until_#{element_name}_visible"
|
81
|
+
create_helper_method(method_name, *find_args) do
|
82
|
+
define_method(method_name) do |*runtime_args|
|
83
|
+
args = merge_args(find_args, runtime_args, visible: true)
|
84
|
+
return true if element_exists?(*args)
|
85
|
+
|
86
|
+
raise SitePrism::ElementVisibilityTimeoutError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_invisibility_waiter(element_name, *find_args)
|
92
|
+
method_name = "wait_until_#{element_name}_invisible"
|
93
|
+
create_helper_method(method_name, *find_args) do
|
94
|
+
define_method(method_name) do |*runtime_args|
|
95
|
+
args = merge_args(find_args, runtime_args, visible: true)
|
96
|
+
return true if element_does_not_exist?(*args)
|
97
|
+
|
98
|
+
raise SitePrism::ElementInvisibilityTimeoutError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_helper_method(proposed_method_name, *find_args)
|
104
|
+
return blank_element(proposed_method_name) if find_args.empty?
|
105
|
+
|
106
|
+
yield
|
107
|
+
end
|
108
|
+
|
109
|
+
def extract_section_options(args, &block)
|
110
|
+
if args.first.is_a?(Class)
|
111
|
+
klass = args.shift
|
112
|
+
section_class = klass if klass <= SitePrism::Section
|
113
|
+
end
|
114
|
+
|
115
|
+
section_class = deduce_section_class(section_class, &block)
|
116
|
+
arguments = deduce_search_arguments(section_class, args)
|
117
|
+
[section_class, arguments]
|
118
|
+
end
|
119
|
+
|
120
|
+
def deduce_section_class(base_class, &block)
|
121
|
+
klass = base_class
|
122
|
+
klass = Class.new(klass || SitePrism::Section, &block) if block
|
123
|
+
return klass if klass
|
124
|
+
|
125
|
+
raise ArgumentError, 'You should provide descendant of SitePrism::Section class or/and a block as the second argument.'
|
126
|
+
end
|
127
|
+
|
128
|
+
def deduce_search_arguments(section_class, args)
|
129
|
+
extract_search_arguments(args) ||
|
130
|
+
extract_search_arguments(section_class.default_search_arguments) ||
|
131
|
+
invalidate_search_arguments!
|
132
|
+
end
|
133
|
+
|
134
|
+
def extract_search_arguments(args)
|
135
|
+
args if args && !args.empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
def invalidate_search_arguments!
|
139
|
+
SitePrism.logger.error('Could not deduce search_arguments')
|
140
|
+
raise(ArgumentError, 'search arguments are needed in `section` definition or alternatively use `set_default_search_arguments`')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SitePrism
|
4
|
+
module DSL
|
5
|
+
# [SitePrism::DSL::Locators]
|
6
|
+
#
|
7
|
+
# The locator logic for scoping all items - for use in locators, boolean tests and waiters
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
module Locators
|
12
|
+
private
|
13
|
+
|
14
|
+
def _find(*find_args)
|
15
|
+
kwargs = find_args.pop
|
16
|
+
shadow_root = kwargs.delete(:shadow_root) { false }
|
17
|
+
check_capybara_version_if_creating_shadow_root if shadow_root
|
18
|
+
to_capybara_node.find(*find_args, **kwargs).tap do |element|
|
19
|
+
break element.shadow_root if shadow_root
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def _all(*find_args)
|
24
|
+
kwargs = find_args.pop
|
25
|
+
shadow_root = kwargs.delete(:shadow_root) { false }
|
26
|
+
check_capybara_version_if_creating_shadow_root if shadow_root
|
27
|
+
to_capybara_node.all(*find_args, **kwargs).tap do |element|
|
28
|
+
break element.map(&:shadow_root) if shadow_root
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def element_exists?(*find_args)
|
33
|
+
kwargs = find_args.pop
|
34
|
+
kwargs.delete(:shadow_root)
|
35
|
+
to_capybara_node.has_selector?(*find_args, **kwargs)
|
36
|
+
end
|
37
|
+
|
38
|
+
def element_does_not_exist?(*find_args)
|
39
|
+
kwargs = find_args.pop
|
40
|
+
kwargs.delete(:shadow_root)
|
41
|
+
to_capybara_node.has_no_selector?(*find_args, **kwargs)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sanitize method called before calling any SitePrism DSL method or
|
45
|
+
# meta-programmed method. This ensures that the Capybara query is correct.
|
46
|
+
#
|
47
|
+
# Accepts any combination of arguments sent at DSL definition or runtime
|
48
|
+
# and combines them in such a way that Capybara can operate with them.
|
49
|
+
def merge_args(find_args, runtime_args, visibility_args = {})
|
50
|
+
find_args = find_args.dup
|
51
|
+
runtime_args = runtime_args.dup
|
52
|
+
options = visibility_args.dup
|
53
|
+
SitePrism.logger.debug("Initial args: #{find_args}, #{runtime_args}.")
|
54
|
+
|
55
|
+
recombine_args(find_args, runtime_args, options)
|
56
|
+
|
57
|
+
return [*find_args, *runtime_args, {}] if options.empty?
|
58
|
+
|
59
|
+
[*find_args, *runtime_args, options]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Options re-combiner. This takes the original inputs and combines
|
63
|
+
# them such that there is only one hash passed as a final argument
|
64
|
+
# to Capybara.
|
65
|
+
#
|
66
|
+
# If the hash is empty, then the hash is omitted from the payload sent
|
67
|
+
# to Capybara, and the find / runtime arguments are sent alone.
|
68
|
+
#
|
69
|
+
# NB: If the +wait+ key is present in the options hash, even as false or 0, It will
|
70
|
+
# be set as the user-supplied value (So user error can be the cause for issues).
|
71
|
+
def recombine_args(find_args, runtime_args, options)
|
72
|
+
options.merge!(find_args.pop) if find_args.last.is_a? Hash
|
73
|
+
options.merge!(runtime_args.pop) if runtime_args.last.is_a? Hash
|
74
|
+
options[:wait] = Capybara.default_max_wait_time unless options.key?(:wait)
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_capybara_version_if_creating_shadow_root
|
78
|
+
minimum_version = '3.37.0'
|
79
|
+
raise SitePrism::UnsupportedGemVersionError unless Capybara::VERSION >= minimum_version
|
80
|
+
|
81
|
+
SitePrism.logger.error("Shadow root support requires Capybara version >= #{minimum_version}. You are using #{Capybara::VERSION}.")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SitePrism
|
4
|
+
module DSL
|
5
|
+
# [SitePrism::DSL::Methods]
|
6
|
+
#
|
7
|
+
# The meta-programmed methods for using SitePrism during runtime. This public DSL contains all the methods
|
8
|
+
# you will use on `SitePrism::Page` or `SitePrism::Section` classes
|
9
|
+
#
|
10
|
+
module Methods
|
11
|
+
attr_reader :expected_items
|
12
|
+
|
13
|
+
# Sets the `expected_items` iVar on a class. This property is used in conjunction with
|
14
|
+
# `all_there?` to provide a way of granularising the check made to only interrogate a sub-set
|
15
|
+
# of DSL defined items
|
16
|
+
def expected_elements(*elements)
|
17
|
+
@expected_items = elements
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates an instance of a SitePrism Element - This will create several methods designed to
|
21
|
+
# Locate the element -> @return [Capybara::Node::Element]
|
22
|
+
# Check the elements presence or non-presence -> @return [Boolean]
|
23
|
+
# Wait for the elements to be present or not -> @return [TrueClass, SitePrism::Error]
|
24
|
+
# Validate certain properties about the element
|
25
|
+
def element(name, *find_args)
|
26
|
+
raise_if_build_time_block_supplied(self, name, block_given?, :element)
|
27
|
+
build(:element, name, *find_args) do
|
28
|
+
define_method(name) do |*runtime_args, &runtime_block|
|
29
|
+
raise_if_runtime_block_supplied(self, name, runtime_block, :element)
|
30
|
+
_find(*merge_args(find_args, runtime_args))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a enumerable instance of a SitePrism Element - This will create several methods designed to
|
36
|
+
# Locate the enumerable element -> @return [Capybara::Result]
|
37
|
+
# Check the elements presence or non-presence -> @return [Boolean]
|
38
|
+
# Wait for the elements to be present or not -> @return [TrueClass, SitePrism::Error]
|
39
|
+
# Validate certain properties about the elements
|
40
|
+
def elements(name, *find_args)
|
41
|
+
raise_if_build_time_block_supplied(self, name, block_given?, :elements)
|
42
|
+
build(:elements, name, *find_args) do
|
43
|
+
define_method(name) do |*runtime_args, &runtime_block|
|
44
|
+
raise_if_runtime_block_supplied(self, name, runtime_block, :elements)
|
45
|
+
_all(*merge_args(find_args, runtime_args))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates an instance of a SitePrism Section - This will create several methods designed to
|
51
|
+
# Locate the section -> @return [SitePrism::Section]
|
52
|
+
# Check the section presence or non-presence -> @return [Boolean]
|
53
|
+
# Wait for the section to be present or not -> @return [TrueClass, SitePrism::Error]
|
54
|
+
# Validate certain properties about the section
|
55
|
+
def section(name, *args, &block)
|
56
|
+
section_class, find_args = extract_section_options(args, &block)
|
57
|
+
build(:section, name, *find_args) do
|
58
|
+
define_method(name) do |*runtime_args, &runtime_block|
|
59
|
+
section_element = _find(*merge_args(find_args, runtime_args))
|
60
|
+
section_class.new(self, section_element, &runtime_block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates an enumerable instance of a SitePrism Section - This will create several methods designed to
|
66
|
+
# Locate the sections -> @return [Array]
|
67
|
+
# Check the sections presence or non-presence -> @return [Boolean]
|
68
|
+
# Wait for the sections to be present or not -> @return [TrueClass, SitePrism::Error]
|
69
|
+
# Validate certain properties about the section
|
70
|
+
def sections(name, *args, &block)
|
71
|
+
section_class, find_args = extract_section_options(args, &block)
|
72
|
+
build(:sections, name, *find_args) do
|
73
|
+
define_method(name) do |*runtime_args, &runtime_block|
|
74
|
+
raise_if_runtime_block_supplied(self, name, runtime_block, :sections)
|
75
|
+
_all(*merge_args(find_args, runtime_args)).map do |element|
|
76
|
+
section_class.new(self, element)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def iframe(name, klass, *args)
|
83
|
+
raise_if_build_time_block_supplied(self, name, block_given?, :elements)
|
84
|
+
element_find_args = deduce_iframe_element_find_args(args)
|
85
|
+
scope_find_args = deduce_iframe_scope_find_args(args)
|
86
|
+
build(:iframe, name, *element_find_args) do
|
87
|
+
define_method(name) do |&block|
|
88
|
+
raise MissingBlockError unless block
|
89
|
+
|
90
|
+
within_frame(*scope_find_args) { block.call(klass.new) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def raise_if_build_time_block_supplied(parent_object, name, has_block, type)
|
98
|
+
return unless has_block
|
99
|
+
|
100
|
+
SitePrism.logger.debug("Type passed in: #{type}")
|
101
|
+
SitePrism.logger.error("#{name} has been defined as a '#{type}' item in #{parent_object}. It does not accept build-time blocks.")
|
102
|
+
raise SitePrism::UnsupportedBlockError
|
103
|
+
end
|
104
|
+
|
105
|
+
def deduce_iframe_element_find_args(args)
|
106
|
+
warn_on_invalid_selector_input(args)
|
107
|
+
case args[0]
|
108
|
+
when Integer then "iframe:nth-of-type(#{args[0] + 1})"
|
109
|
+
when String then [:css, args[0]]
|
110
|
+
else args
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def deduce_iframe_scope_find_args(args)
|
115
|
+
warn_on_invalid_selector_input(args)
|
116
|
+
case args[0]
|
117
|
+
when Integer then [args[0]]
|
118
|
+
when String then [:css, args[0]]
|
119
|
+
else args
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def warn_on_invalid_selector_input(args)
|
124
|
+
return unless looks_like_xpath?(args[0])
|
125
|
+
|
126
|
+
SitePrism.logger.warn('The arguments passed in look like xpath. Check your locators.')
|
127
|
+
SitePrism.logger.debug("Default locator strategy: #{Capybara.default_selector}")
|
128
|
+
end
|
129
|
+
|
130
|
+
def looks_like_xpath?(arg)
|
131
|
+
arg.is_a?(String) && arg.start_with?('/', './')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|