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 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