site_prism 4.0.3 → 5.0.beta

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