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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31d906cda08dd376f6e81fe3ed760b954cf172d2037dcee25d91c01b3b8ace24
4
- data.tar.gz: aefc0ed612cf57548101505e46c92845ef9fa2f7482b23e5d6b38cc7c37cf9f7
3
+ metadata.gz: 3036f6b2266855a0a4d08a3c72b44b08d1ce6f423ca72e5dbe5b539cb90b1216
4
+ data.tar.gz: 1314d67df7aeb3c9d70478e5b884aec2cfb7b4216c58cf9841d4d6dc8b7bc9f5
5
5
  SHA512:
6
- metadata.gz: 9eda5c088201fe6055d67d92cdd3a5f08a90ffe9ccbd2788ac1b4da6e6a0dff32e000c59819e113b4091fc33d2c38d875c9b3d36bd4d2dc549d42d4e1f071ac3
7
- data.tar.gz: 5f926bac135af87939fc52ce5d110dd806ed7e3708bad8b885670a71e75065057d58de506547389ac11f2216ffcc043031262c4eeb5d11697ff38f114d1685f9
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.6 - 3.1.
33
- If you are using SitePrism with Ruby 2.5-2.7 it is highly advisable to upgrade to a more modern
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
- # define sections used on multiple pages or multiple times on one page
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
- # now for some tests
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
- an abstraction of your site's user interface that can be used in tests.
171
- The most common way to do this is to model each page as a class, and
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
- represented by a method that, when called, returns a reference to that
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
- below by also allowing modelling of repeated sections that appear on
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
- navigate directly to that page. It makes sense to set the URL for a page model of a
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
- displayed. SitePrism can automatically parse your URL template
286
- and verify that whatever components your template specifies match the
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
- matches the page's template and false if it doesn't. It will wait for
307
- `Capybara.default_max_wait_time` seconds or you can pass an explicit
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
- you can override and use SitePrism's previous support for regular expression-based
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 URL begins with 'https' or not. SitePrism provides the
399
- `secure?` method that will return true if the current url begins with
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
- either individual elements or groups of them. Examples of individual
415
- elements would be a search field or a company logo image; examples of
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
- to become visible. You can customise the wait time by supplying a number
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
- wait time for the element to become invisible. You can as with the visibility
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
- possible to use XPath expressions too. In SitePrism, everywhere that you
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 v5. You have been warned!")
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