site_prism 4.0.3 → 5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|