site_prism 4.0.3 → 5.0.beta

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: 919e215261486aca371a708b7b06826e54941a29cf1580ebca68e2dea4cfbc0a
4
+ data.tar.gz: ecd938940c5546287ffbbf182aece2d6aacf96b454738f33a73b114c09803f19
5
5
  SHA512:
6
- metadata.gz: 9eda5c088201fe6055d67d92cdd3a5f08a90ffe9ccbd2788ac1b4da6e6a0dff32e000c59819e113b4091fc33d2c38d875c9b3d36bd4d2dc549d42d4e1f071ac3
7
- data.tar.gz: 5f926bac135af87939fc52ce5d110dd806ed7e3708bad8b885670a71e75065057d58de506547389ac11f2216ffcc043031262c4eeb5d11697ff38f114d1685f9
6
+ metadata.gz: ebb6eb3adbe1cb9663160d4347a5c3189c333937b4cb49649bf998a397cd634be4d4b557d5dc6ad5e5415d7eee8f297a8b05e8b894d19fe98cb3a9ff7a602657
7
+ data.tar.gz: e63ee2fd016759f69c943de5ea7e477151c1333af5c25aef8a10834426db5e14fa1790accf5069726c405ae9ec33dd2a96d9877ad75cfb558b85b284e4e1875d
@@ -0,0 +1,157 @@
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
+ # If legacy is set to false (Default) -> @return [Hash]
19
+ # If legacy is set to true (Old behaviour) -> @return [Array]
20
+ def mapped_items(legacy: false)
21
+ return legacy_mapped_items if legacy
22
+
23
+ @mapped_items ||= { element: [], elements: [], section: [], sections: [], iframe: [] }
24
+ end
25
+
26
+ private
27
+
28
+ def build(type, name, *find_args)
29
+ raise InvalidDSLNameError if ENV.fetch('SITEPRISM_DSL_VALIDATION_ENABLED', 'true') == 'true' && invalid?(name)
30
+
31
+ if find_args.empty?
32
+ create_error_method(name)
33
+ else
34
+ map_item(type, name)
35
+ yield
36
+ end
37
+ add_helper_methods(name, type, *find_args)
38
+ end
39
+
40
+ def create_error_method(name)
41
+ SitePrism::Deprecator.deprecate(
42
+ 'DSL definition with no find_args',
43
+ 'DSL definition with at least 1 find_arg'
44
+ )
45
+ SitePrism.logger.error("#{name} has come from an item with no locators.")
46
+ define_method(name) { raise SitePrism::InvalidElementError }
47
+ end
48
+
49
+ def add_helper_methods(name, _type, *find_args)
50
+ create_existence_checker(name, *find_args)
51
+ create_nonexistence_checker(name, *find_args)
52
+ SitePrism::RSpecMatchers.new(name)._create_rspec_existence_matchers if defined?(RSpec)
53
+ create_visibility_waiter(name, *find_args)
54
+ create_invisibility_waiter(name, *find_args)
55
+ end
56
+
57
+ def create_existence_checker(element_name, *find_args)
58
+ method_name = "has_#{element_name}?"
59
+ create_helper_method(method_name, *find_args) do
60
+ define_method(method_name) do |*runtime_args|
61
+ args = merge_args(find_args, runtime_args)
62
+ element_exists?(*args)
63
+ end
64
+ end
65
+ end
66
+
67
+ def create_nonexistence_checker(element_name, *find_args)
68
+ method_name = "has_no_#{element_name}?"
69
+ create_helper_method(method_name, *find_args) do
70
+ define_method(method_name) do |*runtime_args|
71
+ args = merge_args(find_args, runtime_args)
72
+ element_does_not_exist?(*args)
73
+ end
74
+ end
75
+ end
76
+
77
+ def create_visibility_waiter(element_name, *find_args)
78
+ method_name = "wait_until_#{element_name}_visible"
79
+ create_helper_method(method_name, *find_args) do
80
+ define_method(method_name) do |*runtime_args|
81
+ args = merge_args(find_args, runtime_args, visible: true)
82
+ return true if element_exists?(*args)
83
+
84
+ raise SitePrism::ElementVisibilityTimeoutError
85
+ end
86
+ end
87
+ end
88
+
89
+ def create_invisibility_waiter(element_name, *find_args)
90
+ method_name = "wait_until_#{element_name}_invisible"
91
+ create_helper_method(method_name, *find_args) do
92
+ define_method(method_name) do |*runtime_args|
93
+ args = merge_args(find_args, runtime_args, visible: true)
94
+ return true if element_does_not_exist?(*args)
95
+
96
+ raise SitePrism::ElementInvisibilityTimeoutError
97
+ end
98
+ end
99
+ end
100
+
101
+ def create_helper_method(proposed_method_name, *find_args)
102
+ return create_error_method(proposed_method_name) if find_args.empty?
103
+
104
+ yield
105
+ end
106
+
107
+ def legacy_mapped_items
108
+ @legacy_mapped_items ||= begin
109
+ SitePrism::Deprecator.deprecate(
110
+ '.mapped_items structure (internally), on a class',
111
+ 'the new .mapped_items structure'
112
+ )
113
+ []
114
+ end
115
+ end
116
+
117
+ def map_item(type, name)
118
+ mapped_items(legacy: true) << { type => name }
119
+ mapped_items[type] << name.to_sym
120
+ end
121
+
122
+ def extract_section_options(args, &block)
123
+ if args.first.is_a?(Class)
124
+ klass = args.shift
125
+ section_class = klass if klass <= SitePrism::Section
126
+ end
127
+
128
+ section_class = deduce_section_class(section_class, &block)
129
+ arguments = deduce_search_arguments(section_class, args)
130
+ [section_class, arguments]
131
+ end
132
+
133
+ def deduce_section_class(base_class, &block)
134
+ klass = base_class
135
+ klass = Class.new(klass || SitePrism::Section, &block) if block
136
+ return klass if klass
137
+
138
+ raise ArgumentError, 'You should provide descendant of SitePrism::Section class or/and a block as the second argument.'
139
+ end
140
+
141
+ def deduce_search_arguments(section_class, args)
142
+ extract_search_arguments(args) ||
143
+ extract_search_arguments(section_class.default_search_arguments) ||
144
+ invalidate_search_arguments!
145
+ end
146
+
147
+ def extract_search_arguments(args)
148
+ args if args && !args.empty?
149
+ end
150
+
151
+ def invalidate_search_arguments!
152
+ SitePrism.logger.error('Could not deduce search_arguments')
153
+ raise(ArgumentError, 'search arguments are needed in `section` definition or alternatively use `set_default_search_arguments`')
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,68 @@
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
+ to_capybara_node.find(*find_args, **kwargs)
17
+ end
18
+
19
+ def _all(*find_args)
20
+ kwargs = find_args.pop
21
+ to_capybara_node.all(*find_args, **kwargs)
22
+ end
23
+
24
+ def element_exists?(*find_args)
25
+ kwargs = find_args.pop
26
+ to_capybara_node.has_selector?(*find_args, **kwargs)
27
+ end
28
+
29
+ def element_does_not_exist?(*find_args)
30
+ kwargs = find_args.pop
31
+ to_capybara_node.has_no_selector?(*find_args, **kwargs)
32
+ end
33
+
34
+ # Sanitize method called before calling any SitePrism DSL method or
35
+ # meta-programmed method. This ensures that the Capybara query is correct.
36
+ #
37
+ # Accepts any combination of arguments sent at DSL definition or runtime
38
+ # and combines them in such a way that Capybara can operate with them.
39
+ def merge_args(find_args, runtime_args, visibility_args = {})
40
+ find_args = find_args.dup
41
+ runtime_args = runtime_args.dup
42
+ options = visibility_args.dup
43
+ SitePrism.logger.debug("Initial args: #{find_args}, #{runtime_args}.")
44
+
45
+ recombine_args(find_args, runtime_args, options)
46
+
47
+ return [*find_args, *runtime_args, {}] if options.empty?
48
+
49
+ [*find_args, *runtime_args, options]
50
+ end
51
+
52
+ # Options re-combiner. This takes the original inputs and combines
53
+ # them such that there is only one hash passed as a final argument
54
+ # to Capybara.
55
+ #
56
+ # If the hash is empty, then the hash is omitted from the payload sent
57
+ # to Capybara, and the find / runtime arguments are sent alone.
58
+ #
59
+ # NB: If the +wait+ key is present in the options hash, even as false or 0, It will
60
+ # be set as the user-supplied value (So user error can be the cause for issues).
61
+ def recombine_args(find_args, runtime_args, options)
62
+ options.merge!(find_args.pop) if find_args.last.is_a? Hash
63
+ options.merge!(runtime_args.pop) if runtime_args.last.is_a? Hash
64
+ options[:wait] = Capybara.default_max_wait_time unless options.key?(:wait)
65
+ end
66
+ end
67
+ end
68
+ 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
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SitePrism
4
+ module DSL
5
+ # [SitePrism::DSL::Validators]
6
+ #
7
+ # This is the new validator module which will check all DSL items against a whitelist
8
+ # for any entries which break specific rules
9
+ #
10
+ # @api private
11
+ #
12
+ module Validator
13
+ def invalid?(name)
14
+ prefix_invalid?(name) ||
15
+ suffix_invalid?(name) ||
16
+ characters_invalid?(name) ||
17
+ blacklisted?(name)
18
+ end
19
+
20
+ private
21
+
22
+ def prefix_invalid?(name)
23
+ return unless prefix_blacklist.any? { |prefix| name.start_with?(prefix) }
24
+
25
+ log_failure(name, 'prefix')
26
+ end
27
+
28
+ def suffix_invalid?(name)
29
+ return unless suffix_blacklist.any? { |prefix| name.end_with?(prefix) }
30
+
31
+ log_failure(name, 'suffix')
32
+ end
33
+
34
+ def characters_invalid?(name)
35
+ return if name.match?(regex_permission)
36
+
37
+ log_failure(name, 'character(s)')
38
+ end
39
+
40
+ def blacklisted?(name)
41
+ return unless blacklisted_names.include?(name)
42
+
43
+ log_failure(name, 'name (blacklisted entry)')
44
+ end
45
+
46
+ def regex_permission
47
+ /^[a-z]\w+$/
48
+ end
49
+
50
+ def prefix_blacklist
51
+ %w[
52
+ no_
53
+ _
54
+ ]
55
+ end
56
+
57
+ def suffix_blacklist
58
+ %w[
59
+ _
60
+ ?
61
+ ]
62
+ end
63
+
64
+ def blacklisted_names
65
+ %w[
66
+ attributes
67
+ html
68
+ no
69
+ title
70
+ element
71
+ elements
72
+ section
73
+ sections
74
+ iframe
75
+ ]
76
+ end
77
+
78
+ def log_failure(name, type)
79
+ SitePrism.logger.error("DSL item: #{name} has an invalid #{type}")
80
+ SitePrism.logger.debug(debug_error(type))
81
+ end
82
+
83
+ def debug_error(type)
84
+ case type
85
+ when 'prefix'; then "Invalid Prefixes: #{prefix_blacklist.join(', ')}."
86
+ when 'suffix'; then "Invalid Suffixes: #{suffix_blacklist.join(', ')}"
87
+ when 'character(s)'; then "Invalid DSL Names: #{blacklisted_names.join(', ')}"
88
+ else "DSL Charset REGEX: #{regex_permission.inspect}"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,17 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'site_prism/dsl/builder'
4
+ require 'site_prism/dsl/methods'
5
+ require 'site_prism/dsl/locators'
6
+ require 'site_prism/dsl/validator'
7
+
3
8
  module SitePrism
4
9
  # [SitePrism::DSL]
5
10
  #
6
- # This is the core Module Namespace for all of the public-facing DSL methods
7
- # such as `element`. The code here is designed to be used through the defining
8
- # of said items, and not to be instantiated directly.
11
+ # This is the core internal workings of SitePrism. It consists of four moving parts - plus some generic methods included here
12
+ # Builder -> The way in which the .build method generates lots of instance-methods on a SitePrism::Page or SitePrism::Section instance
13
+ # Methods -> The public DSL metaprogram methods, such as `element` or `section`
14
+ # Locators -> Our locator scoping logic within capybara. By and large leaning on `#to_capybara_node`
15
+ # Validators -> EXPERIMENTAL: A new module that ensures names of all DSL items conform to certain rules
9
16
  #
10
- # The whole package here can be thought of as [@api private]
11
17
  module DSL
12
18
  def self.included(klass)
13
- klass.extend ClassMethods
14
- klass.extend DSLValidator
19
+ klass.extend Builder
20
+ klass.extend Methods
21
+ klass.include Locators
22
+ klass.extend Validator
15
23
  end
16
24
 
17
25
  private
@@ -23,326 +31,5 @@ module SitePrism
23
31
  SitePrism.logger.error("#{object.class}##{name} cannot accept runtime blocks")
24
32
  raise SitePrism::UnsupportedBlockError
25
33
  end
26
-
27
- def _find(*find_args)
28
- kwargs = find_args.pop
29
- to_capybara_node.find(*find_args, **kwargs)
30
- end
31
-
32
- def _all(*find_args)
33
- kwargs = find_args.pop
34
- to_capybara_node.all(*find_args, **kwargs)
35
- end
36
-
37
- def element_exists?(*find_args)
38
- kwargs = find_args.pop
39
- to_capybara_node.has_selector?(*find_args, **kwargs)
40
- end
41
-
42
- def element_does_not_exist?(*find_args)
43
- kwargs = find_args.pop
44
- to_capybara_node.has_no_selector?(*find_args, **kwargs)
45
- end
46
-
47
- # Sanitize method called before calling any SitePrism DSL method or
48
- # meta-programmed method. This ensures that the Capybara query is correct.
49
- #
50
- # Accepts any combination of arguments sent at DSL definition or runtime
51
- # and combines them in such a way that Capybara can operate with them.
52
- def merge_args(find_args, runtime_args, visibility_args = {})
53
- find_args = find_args.dup
54
- runtime_args = runtime_args.dup
55
- options = visibility_args.dup
56
- SitePrism.logger.debug("Initial args: #{find_args}, #{runtime_args}.")
57
-
58
- recombine_args(find_args, runtime_args, options)
59
-
60
- return [*find_args, *runtime_args, {}] if options.empty?
61
-
62
- [*find_args, *runtime_args, options]
63
- end
64
-
65
- # Options re-combiner. This takes the original inputs and combines
66
- # them such that there is only one hash passed as a final argument
67
- # to Capybara.
68
- #
69
- # If the hash is empty, then the hash is omitted from the payload sent
70
- # to Capybara, and the find / runtime arguments are sent alone.
71
- #
72
- # NB: If the +wait+ key is present in the options hash, even as false or 0, It will
73
- # be set as the user-supplied value (So user error can be the cause for issues).
74
- def recombine_args(find_args, runtime_args, options)
75
- options.merge!(find_args.pop) if find_args.last.is_a? Hash
76
- options.merge!(runtime_args.pop) if runtime_args.last.is_a? Hash
77
- options[:wait] = Capybara.default_max_wait_time unless options.key?(:wait)
78
- end
79
-
80
- # [SitePrism::DSL::ClassMethods]
81
- # This exposes all of the DSL definitions users will use when generating
82
- # their POM classes.
83
- #
84
- # Many of these methods will be used in-line to allow users to generate a multitude of
85
- # methods and locators for finding elements / sections on a page or section of a page
86
- module ClassMethods
87
- attr_reader :expected_items
88
-
89
- # Sets the `expected_items` iVar on a class. This property is used in conjunction with
90
- # `all_there?` to provide a way of granularising the check made to only interrogate a sub-set
91
- # of DSL defined items
92
- def expected_elements(*elements)
93
- @expected_items = elements
94
- end
95
-
96
- # Creates an instance of a SitePrism Element - This will create several methods designed to
97
- # Locate the element -> @return [Capybara::Node::Element]
98
- # Check the elements presence or non-presence -> @return [Boolean]
99
- # Wait for the elements to be present or not -> @return [TrueClass, SitePrism::Error]
100
- # Validate certain properties about the element
101
- def element(name, *find_args)
102
- raise_if_build_time_block_supplied(self, name, block_given?, :element)
103
- build(:element, name, *find_args) do
104
- define_method(name) do |*runtime_args, &runtime_block|
105
- raise_if_runtime_block_supplied(self, name, runtime_block, :element)
106
- _find(*merge_args(find_args, runtime_args))
107
- end
108
- end
109
- end
110
-
111
- # Creates a enumerable instance of a SitePrism Element - This will create several methods designed to
112
- # Locate the enumerable element -> @return [Capybara::Result]
113
- # Check the elements presence or non-presence -> @return [Boolean]
114
- # Wait for the elements to be present or not -> @return [TrueClass, SitePrism::Error]
115
- # Validate certain properties about the elements
116
- def elements(name, *find_args)
117
- raise_if_build_time_block_supplied(self, name, block_given?, :elements)
118
- build(:elements, name, *find_args) do
119
- define_method(name) do |*runtime_args, &runtime_block|
120
- raise_if_runtime_block_supplied(self, name, runtime_block, :elements)
121
- _all(*merge_args(find_args, runtime_args))
122
- end
123
- end
124
- end
125
-
126
- # Creates an instance of a SitePrism Section - This will create several methods designed to
127
- # Locate the section -> @return [SitePrism::Section]
128
- # Check the section presence or non-presence -> @return [Boolean]
129
- # Wait for the section to be present or not -> @return [TrueClass, SitePrism::Error]
130
- # Validate certain properties about the section
131
- def section(name, *args, &block)
132
- section_class, find_args = extract_section_options(args, &block)
133
- build(:section, name, *find_args) do
134
- define_method(name) do |*runtime_args, &runtime_block|
135
- section_element = _find(*merge_args(find_args, runtime_args))
136
- section_class.new(self, section_element, &runtime_block)
137
- end
138
- end
139
- end
140
-
141
- # Creates an enumerable instance of a SitePrism Section - This will create several methods designed to
142
- # Locate the sections -> @return [Array]
143
- # Check the sections presence or non-presence -> @return [Boolean]
144
- # Wait for the sections to be present or not -> @return [TrueClass, SitePrism::Error]
145
- # Validate certain properties about the section
146
- def sections(name, *args, &block)
147
- section_class, find_args = extract_section_options(args, &block)
148
- build(:sections, name, *find_args) do
149
- define_method(name) do |*runtime_args, &runtime_block|
150
- raise_if_runtime_block_supplied(self, name, runtime_block, :sections)
151
- _all(*merge_args(find_args, runtime_args)).map do |element|
152
- section_class.new(self, element)
153
- end
154
- end
155
- end
156
- end
157
-
158
- def iframe(name, klass, *args)
159
- raise_if_build_time_block_supplied(self, name, block_given?, :elements)
160
- element_find_args = deduce_iframe_element_find_args(args)
161
- scope_find_args = deduce_iframe_scope_find_args(args)
162
- build(:iframe, name, *element_find_args) do
163
- define_method(name) do |&block|
164
- raise MissingBlockError unless block
165
-
166
- within_frame(*scope_find_args) { block.call(klass.new) }
167
- end
168
- end
169
- end
170
-
171
- # Return a list of all mapped items on a SitePrism class instance (Page or Section)
172
- # If legacy is set to true (Default) -> @return [Array]
173
- # If legacy is set to false (New behaviour) -> @return [Hash]
174
- def mapped_items(legacy: false)
175
- return legacy_mapped_items if legacy
176
-
177
- @mapped_items ||= { element: [], elements: [], section: [], sections: [], iframe: [] }
178
- end
179
-
180
- private
181
-
182
- def build(type, name, *find_args)
183
- raise InvalidDSLNameError if ENV.fetch('SITEPRISM_DSL_VALIDATION_ENABLED', nil) && invalid?(name)
184
-
185
- if find_args.empty?
186
- create_error_method(name)
187
- else
188
- map_item(type, name)
189
- yield
190
- end
191
- add_helper_methods(name, type, *find_args)
192
- end
193
-
194
- def add_helper_methods(name, _type, *find_args)
195
- create_existence_checker(name, *find_args)
196
- create_nonexistence_checker(name, *find_args)
197
- SitePrism::RSpecMatchers.new(name)._create_rspec_existence_matchers if defined?(RSpec)
198
- create_visibility_waiter(name, *find_args)
199
- create_invisibility_waiter(name, *find_args)
200
- end
201
-
202
- def create_helper_method(proposed_method_name, *find_args)
203
- return create_error_method(proposed_method_name) if find_args.empty?
204
-
205
- yield
206
- end
207
-
208
- def create_existence_checker(element_name, *find_args)
209
- method_name = "has_#{element_name}?"
210
- create_helper_method(method_name, *find_args) do
211
- define_method(method_name) do |*runtime_args|
212
- args = merge_args(find_args, runtime_args)
213
- element_exists?(*args)
214
- end
215
- end
216
- end
217
-
218
- def create_nonexistence_checker(element_name, *find_args)
219
- method_name = "has_no_#{element_name}?"
220
- create_helper_method(method_name, *find_args) do
221
- define_method(method_name) do |*runtime_args|
222
- args = merge_args(find_args, runtime_args)
223
- element_does_not_exist?(*args)
224
- end
225
- end
226
- end
227
-
228
- def create_visibility_waiter(element_name, *find_args)
229
- method_name = "wait_until_#{element_name}_visible"
230
- create_helper_method(method_name, *find_args) do
231
- define_method(method_name) do |*runtime_args|
232
- args = merge_args(find_args, runtime_args, visible: true)
233
- return true if element_exists?(*args)
234
-
235
- raise SitePrism::ElementVisibilityTimeoutError
236
- end
237
- end
238
- end
239
-
240
- def create_invisibility_waiter(element_name, *find_args)
241
- method_name = "wait_until_#{element_name}_invisible"
242
- create_helper_method(method_name, *find_args) do
243
- define_method(method_name) do |*runtime_args|
244
- args = merge_args(find_args, runtime_args, visible: true)
245
- return true if element_does_not_exist?(*args)
246
-
247
- raise SitePrism::ElementInvisibilityTimeoutError
248
- end
249
- end
250
- end
251
-
252
- def create_error_method(name)
253
- SitePrism::Deprecator.deprecate(
254
- 'DSL definition with no find_args',
255
- 'DSL definition with at least 1 find_arg'
256
- )
257
- SitePrism.logger.error("#{name} has come from an item with no locators.")
258
- define_method(name) { raise SitePrism::InvalidElementError }
259
- end
260
-
261
- def raise_if_build_time_block_supplied(parent_object, name, has_block, type)
262
- return unless has_block
263
-
264
- SitePrism.logger.debug("Type passed in: #{type}")
265
- SitePrism.logger.error("#{name} has been defined as a '#{type}' item in #{parent_object}. It does not accept build-time blocks.")
266
- raise SitePrism::UnsupportedBlockError
267
- end
268
-
269
- def legacy_mapped_items
270
- @legacy_mapped_items ||= begin
271
- SitePrism::Deprecator.deprecate(
272
- '.mapped_items structure (internally), on a class',
273
- 'the new .mapped_items structure'
274
- )
275
- []
276
- end
277
- end
278
-
279
- def map_item(type, name)
280
- mapped_items(legacy: true) << { type => name }
281
- mapped_items[type] << name.to_sym
282
- end
283
-
284
- def deduce_iframe_scope_find_args(args)
285
- warn_on_invalid_selector_input(args)
286
- case args[0]
287
- when Integer then [args[0]]
288
- when String then [:css, args[0]]
289
- else args
290
- end
291
- end
292
-
293
- def deduce_iframe_element_find_args(args)
294
- warn_on_invalid_selector_input(args)
295
- case args[0]
296
- when Integer then "iframe:nth-of-type(#{args[0] + 1})"
297
- when String then [:css, args[0]]
298
- else args
299
- end
300
- end
301
-
302
- def warn_on_invalid_selector_input(args)
303
- return unless looks_like_xpath?(args[0])
304
-
305
- SitePrism.logger.warn('The arguments passed in look like xpath. Check your locators.')
306
- SitePrism.logger.debug("Default locator strategy: #{Capybara.default_selector}")
307
- end
308
-
309
- def looks_like_xpath?(arg)
310
- arg.is_a?(String) && arg.start_with?('/', './')
311
- end
312
-
313
- def extract_section_options(args, &block)
314
- if args.first.is_a?(Class)
315
- klass = args.shift
316
- section_class = klass if klass <= SitePrism::Section
317
- end
318
-
319
- section_class = deduce_section_class(section_class, &block)
320
- arguments = deduce_search_arguments(section_class, args)
321
- [section_class, arguments]
322
- end
323
-
324
- def deduce_section_class(base_class, &block)
325
- klass = base_class
326
- klass = Class.new(klass || SitePrism::Section, &block) if block
327
- return klass if klass
328
-
329
- raise ArgumentError, 'You should provide descendant of SitePrism::Section class or/and a block as the second argument.'
330
- end
331
-
332
- def deduce_search_arguments(section_class, args)
333
- extract_search_arguments(args) ||
334
- extract_search_arguments(section_class.default_search_arguments) ||
335
- invalidate_search_arguments!
336
- end
337
-
338
- def extract_search_arguments(args)
339
- args if args && !args.empty?
340
- end
341
-
342
- def invalidate_search_arguments!
343
- SitePrism.logger.error('Could not deduce search_arguments')
344
- raise(ArgumentError, 'search arguments are needed in `section` definition or alternatively use `set_default_search_arguments`')
345
- end
346
- end
347
34
  end
348
35
  end
@@ -48,6 +48,6 @@ module SitePrism
48
48
  # Generic Attribute validation family of errors inherit from this error
49
49
  class AttributeValidationError < SitePrismError; end
50
50
 
51
- # DSL items are not permitted to start with certain prefixes
51
+ # DSL items are not permitted to be named in certain ways
52
52
  class InvalidDSLNameError < AttributeValidationError; end
53
53
  end
@@ -42,18 +42,6 @@ module SitePrism
42
42
  end
43
43
  end
44
44
 
45
- # Where a Capybara HTML fragment has been directly injected into `#load` as a block return this loaded fragment
46
- # Where a page has been directly navigated to through traditional means (i.e. Selenium), return an instance of the
47
- # current Capybara session (With all applicable methods)
48
- #
49
- # @return [Capybara::Node::Simple || Capybara::Session]
50
- def page
51
- @_page ||= begin
52
- SitePrism::Deprecator.deprecate('Calling #page on a SitePrism::Page instance')
53
- to_capybara_node
54
- end
55
- end
56
-
57
45
  # This scopes our calls inside Page correctly to the `Capybara::Session`
58
46
  #
59
47
  # @return [Capybara::Node::Simple || Capybara::Session]
@@ -183,7 +171,7 @@ module SitePrism
183
171
 
184
172
  def load_html_string(string)
185
173
  @page = Capybara.string(string)
186
- yield self if block_given?
174
+ yield to_capybara_node if block_given?
187
175
  end
188
176
 
189
177
  def load_html_website(html, &block)
@@ -195,7 +183,7 @@ module SitePrism
195
183
  if with_validations
196
184
  when_loaded(&block)
197
185
  elsif block
198
- yield self
186
+ yield to_capybara_node
199
187
  end
200
188
  end
201
189
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SitePrism
4
- VERSION = '4.0.3'
4
+ VERSION = '5.0.beta'
5
5
  end
data/lib/site_prism.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'site_prism/error'
4
3
  require 'site_prism/all_there'
5
4
 
6
5
  require 'addressable/template'
@@ -8,10 +7,10 @@ require 'capybara/dsl'
8
7
  require 'forwardable'
9
8
 
10
9
  require 'site_prism/addressable_url_matcher'
11
- require 'site_prism/dsl'
12
- require 'site_prism/dsl_validator'
13
10
  require 'site_prism/deprecator'
11
+ require 'site_prism/dsl'
14
12
  require 'site_prism/element_checker'
13
+ require 'site_prism/error'
15
14
  require 'site_prism/loadable'
16
15
  require 'site_prism/logger'
17
16
  require 'site_prism/page'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: site_prism
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 5.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Hill
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-07-07 00:00:00.000000000 Z
12
+ date: 2023-10-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: addressable
@@ -18,6 +18,9 @@ dependencies:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
20
  version: '2.8'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.8.1
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -25,54 +28,77 @@ dependencies:
25
28
  - - "~>"
26
29
  - !ruby/object:Gem::Version
27
30
  version: '2.8'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.8.1
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: capybara
30
36
  requirement: !ruby/object:Gem::Requirement
31
37
  requirements:
32
38
  - - "~>"
33
39
  - !ruby/object:Gem::Version
34
- version: '3.27'
40
+ version: '3.31'
35
41
  type: :runtime
36
42
  prerelease: false
37
43
  version_requirements: !ruby/object:Gem::Requirement
38
44
  requirements:
39
45
  - - "~>"
40
46
  - !ruby/object:Gem::Version
41
- version: '3.27'
47
+ version: '3.31'
42
48
  - !ruby/object:Gem::Dependency
43
49
  name: site_prism-all_there
44
50
  requirement: !ruby/object:Gem::Requirement
45
51
  requirements:
46
- - - "~>"
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
55
+ - - "<"
47
56
  - !ruby/object:Gem::Version
48
- version: '2.0'
57
+ version: '4'
49
58
  type: :runtime
50
59
  prerelease: false
51
60
  version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '2'
65
+ - - "<"
66
+ - !ruby/object:Gem::Version
67
+ version: '4'
68
+ - !ruby/object:Gem::Dependency
69
+ name: automation_helpers
70
+ requirement: !ruby/object:Gem::Requirement
52
71
  requirements:
53
72
  - - "~>"
54
73
  - !ruby/object:Gem::Version
55
- version: '2.0'
74
+ version: '4.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '4.0'
56
82
  - !ruby/object:Gem::Dependency
57
83
  name: cucumber
58
84
  requirement: !ruby/object:Gem::Requirement
59
85
  requirements:
60
86
  - - ">"
61
87
  - !ruby/object:Gem::Version
62
- version: '6'
88
+ version: '7'
63
89
  - - "<"
64
90
  - !ruby/object:Gem::Version
65
- version: '9'
91
+ version: '10'
66
92
  type: :development
67
93
  prerelease: false
68
94
  version_requirements: !ruby/object:Gem::Requirement
69
95
  requirements:
70
96
  - - ">"
71
97
  - !ruby/object:Gem::Version
72
- version: '6'
98
+ version: '7'
73
99
  - - "<"
74
100
  - !ruby/object:Gem::Version
75
- version: '9'
101
+ version: '10'
76
102
  - !ruby/object:Gem::Dependency
77
103
  name: rspec
78
104
  requirement: !ruby/object:Gem::Requirement
@@ -93,56 +119,56 @@ dependencies:
93
119
  requirements:
94
120
  - - "~>"
95
121
  - !ruby/object:Gem::Version
96
- version: 1.49.0
122
+ version: 1.53.0
97
123
  type: :development
98
124
  prerelease: false
99
125
  version_requirements: !ruby/object:Gem::Requirement
100
126
  requirements:
101
127
  - - "~>"
102
128
  - !ruby/object:Gem::Version
103
- version: 1.49.0
129
+ version: 1.53.0
104
130
  - !ruby/object:Gem::Dependency
105
131
  name: rubocop-performance
106
132
  requirement: !ruby/object:Gem::Requirement
107
133
  requirements:
108
134
  - - "~>"
109
135
  - !ruby/object:Gem::Version
110
- version: 1.17.1
136
+ version: 1.19.0
111
137
  type: :development
112
138
  prerelease: false
113
139
  version_requirements: !ruby/object:Gem::Requirement
114
140
  requirements:
115
141
  - - "~>"
116
142
  - !ruby/object:Gem::Version
117
- version: 1.17.1
143
+ version: 1.19.0
118
144
  - !ruby/object:Gem::Dependency
119
145
  name: rubocop-rspec
120
146
  requirement: !ruby/object:Gem::Requirement
121
147
  requirements:
122
148
  - - "~>"
123
149
  - !ruby/object:Gem::Version
124
- version: 2.20.0
150
+ version: 2.23.2
125
151
  type: :development
126
152
  prerelease: false
127
153
  version_requirements: !ruby/object:Gem::Requirement
128
154
  requirements:
129
155
  - - "~>"
130
156
  - !ruby/object:Gem::Version
131
- version: 2.20.0
157
+ version: 2.23.2
132
158
  - !ruby/object:Gem::Dependency
133
159
  name: selenium-webdriver
134
160
  requirement: !ruby/object:Gem::Requirement
135
161
  requirements:
136
162
  - - "~>"
137
163
  - !ruby/object:Gem::Version
138
- version: '4.0'
164
+ version: '4.7'
139
165
  type: :development
140
166
  prerelease: false
141
167
  version_requirements: !ruby/object:Gem::Requirement
142
168
  requirements:
143
169
  - - "~>"
144
170
  - !ruby/object:Gem::Version
145
- version: '4.0'
171
+ version: '4.7'
146
172
  - !ruby/object:Gem::Dependency
147
173
  name: simplecov
148
174
  requirement: !ruby/object:Gem::Requirement
@@ -157,20 +183,6 @@ dependencies:
157
183
  - - "~>"
158
184
  - !ruby/object:Gem::Version
159
185
  version: '0.21'
160
- - !ruby/object:Gem::Dependency
161
- name: webdrivers
162
- requirement: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '5.0'
167
- type: :development
168
- prerelease: false
169
- version_requirements: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '5.0'
174
186
  description: SitePrism gives you a simple, clean and semantic DSL for describing your
175
187
  site. SitePrism implements the Page Object Model pattern on top of Capybara.
176
188
  email:
@@ -186,7 +198,10 @@ files:
186
198
  - lib/site_prism/addressable_url_matcher.rb
187
199
  - lib/site_prism/deprecator.rb
188
200
  - lib/site_prism/dsl.rb
189
- - lib/site_prism/dsl_validator.rb
201
+ - lib/site_prism/dsl/builder.rb
202
+ - lib/site_prism/dsl/locators.rb
203
+ - lib/site_prism/dsl/methods.rb
204
+ - lib/site_prism/dsl/validator.rb
190
205
  - lib/site_prism/element_checker.rb
191
206
  - lib/site_prism/error.rb
192
207
  - lib/site_prism/loadable.rb
@@ -212,14 +227,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
212
227
  requirements:
213
228
  - - ">="
214
229
  - !ruby/object:Gem::Version
215
- version: '2.6'
230
+ version: '2.7'
216
231
  required_rubygems_version: !ruby/object:Gem::Requirement
217
232
  requirements:
218
- - - ">="
233
+ - - ">"
219
234
  - !ruby/object:Gem::Version
220
- version: '0'
235
+ version: 1.3.1
221
236
  requirements: []
222
- rubygems_version: 3.4.13
237
+ rubygems_version: 3.2.3
223
238
  signing_key:
224
239
  specification_version: 4
225
240
  summary: A Page Object Model DSL for Capybara
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SitePrism
4
- # [SitePrism::DSLValidator]
5
- #
6
- # This is the new validator module which will check all DSL items against a whitelist
7
- # for any entries which are prohibited
8
- module DSLValidator
9
- def invalid?(name)
10
- prefix_invalid?(name) ||
11
- suffix_invalid?(name) ||
12
- characters_invalid?(name) ||
13
- blacklisted?(name)
14
- end
15
-
16
- private
17
-
18
- def prefix_invalid?(name)
19
- prefix_blacklist.any? { |prefix| name.start_with?(prefix) }.tap { |result| log_failure(name, 'prefix') unless result }
20
- end
21
-
22
- def suffix_invalid?(name)
23
- suffix_blacklist.any? { |prefix| name.end_with?(prefix) }.tap { |result| log_failure(name, 'suffix') unless result }
24
- end
25
-
26
- def characters_invalid?(name)
27
- !name.match?(regex_permission).tap { |result| log_failure(name, 'character(s)') unless result }
28
- end
29
-
30
- def blacklisted?(name)
31
- blacklisted_names.include?(name).tap { |result| log_failure(name, 'name (blacklisted entry)') unless result }
32
- end
33
-
34
- def regex_permission
35
- /^[a-z]\w+$/
36
- end
37
-
38
- def prefix_blacklist
39
- %w[
40
- no_
41
- _
42
- ]
43
- end
44
-
45
- def suffix_blacklist
46
- %w[
47
- _
48
- ?
49
- ]
50
- end
51
-
52
- def blacklisted_names
53
- %w[
54
- attributes
55
- html
56
- no
57
- title
58
- ]
59
- end
60
-
61
- def log_failure(name, type)
62
- SitePrism.logger.error("DSL item: #{name} has an invalid #{type}")
63
- SitePrism.logger.debug(debug_error(type))
64
- end
65
-
66
- def debug_error(type)
67
- case type
68
- when 'prefix'; then "Invalid Prefixes: #{prefix_blacklist.join(', ')}."
69
- when 'suffix'; then "Invalid Suffixes: #{suffix_blacklist.join(', ')}"
70
- when 'character(s)'; then "Invalid DSL Names: #{blacklisted_names.join(', ')}"
71
- else "DSL Charset REGEX: #{regex_permission.inspect}"
72
- end
73
- end
74
- end
75
- end