page_magic 1.2.8 → 2.0.2

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.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +23 -4
  3. data/.simplecov +5 -3
  4. data/.zsh_config +6 -0
  5. data/Dockerfile +11 -0
  6. data/Gemfile +13 -13
  7. data/Gemfile.lock +136 -148
  8. data/Makefile +17 -0
  9. data/README.md +26 -5
  10. data/Rakefile +12 -2
  11. data/VERSION +1 -1
  12. data/circle.yml +3 -1
  13. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  14. data/lib/page_magic.rb +31 -24
  15. data/lib/page_magic/class_methods.rb +5 -2
  16. data/lib/page_magic/comparator.rb +37 -0
  17. data/lib/page_magic/comparator/fuzzy.rb +23 -0
  18. data/lib/page_magic/comparator/literal.rb +22 -0
  19. data/lib/page_magic/comparator/null.rb +26 -0
  20. data/lib/page_magic/comparator/parameter_map.rb +52 -0
  21. data/lib/page_magic/driver.rb +3 -0
  22. data/lib/page_magic/drivers.rb +6 -5
  23. data/lib/page_magic/drivers/poltergeist.rb +2 -0
  24. data/lib/page_magic/drivers/rack_test.rb +3 -1
  25. data/lib/page_magic/drivers/selenium.rb +4 -2
  26. data/lib/page_magic/element.rb +35 -15
  27. data/lib/page_magic/element/locators.rb +8 -5
  28. data/lib/page_magic/element/not_found.rb +38 -0
  29. data/lib/page_magic/element/query.rb +21 -20
  30. data/lib/page_magic/element/query/multiple_results.rb +21 -0
  31. data/lib/page_magic/element/query/prefetched_result.rb +26 -0
  32. data/lib/page_magic/element/query/single_result.rb +20 -0
  33. data/lib/page_magic/element/selector.rb +41 -16
  34. data/lib/page_magic/element/selector/methods.rb +18 -0
  35. data/lib/page_magic/element/selector/model.rb +21 -0
  36. data/lib/page_magic/element_context.rb +7 -21
  37. data/lib/page_magic/element_definition_builder.rb +20 -24
  38. data/lib/page_magic/elements.rb +65 -69
  39. data/lib/page_magic/elements/config.rb +103 -0
  40. data/lib/page_magic/elements/inheritance_hooks.rb +15 -0
  41. data/lib/page_magic/elements/types.rb +25 -0
  42. data/lib/page_magic/exceptions.rb +6 -1
  43. data/lib/page_magic/instance_methods.rb +8 -3
  44. data/lib/page_magic/mapping.rb +79 -0
  45. data/lib/page_magic/session.rb +15 -35
  46. data/lib/page_magic/session_methods.rb +3 -1
  47. data/lib/page_magic/transitions.rb +49 -0
  48. data/lib/page_magic/utils/string.rb +18 -0
  49. data/lib/page_magic/utils/url.rb +20 -0
  50. data/lib/page_magic/wait_methods.rb +3 -0
  51. data/lib/page_magic/watcher.rb +12 -17
  52. data/lib/page_magic/watchers.rb +31 -15
  53. data/page_magic.gemspec +15 -11
  54. data/spec/lib/active_support/core_ext/object/to_query_test.rb +78 -0
  55. data/spec/page_magic/class_methods_spec.rb +66 -37
  56. data/spec/page_magic/comparator/fuzzy_spec.rb +44 -0
  57. data/spec/page_magic/comparator/literal_spec.rb +41 -0
  58. data/spec/page_magic/comparator/null_spec.rb +35 -0
  59. data/spec/page_magic/comparator/parameter_map_spec.rb +75 -0
  60. data/spec/page_magic/driver_spec.rb +26 -28
  61. data/spec/page_magic/drivers/poltergeist_spec.rb +6 -7
  62. data/spec/page_magic/drivers/rack_test_spec.rb +6 -9
  63. data/spec/page_magic/drivers/selenium_spec.rb +11 -12
  64. data/spec/page_magic/drivers_spec.rb +38 -29
  65. data/spec/page_magic/element/locators_spec.rb +28 -25
  66. data/spec/page_magic/element/not_found_spec.rb +24 -0
  67. data/spec/page_magic/element/query/multiple_results_spec.rb +14 -0
  68. data/spec/page_magic/element/query/single_result_spec.rb +21 -0
  69. data/spec/page_magic/element/query_spec.rb +26 -45
  70. data/spec/page_magic/element/selector_spec.rb +120 -110
  71. data/spec/page_magic/element_context_spec.rb +47 -87
  72. data/spec/page_magic/element_definition_builder_spec.rb +14 -71
  73. data/spec/page_magic/element_spec.rb +256 -0
  74. data/spec/page_magic/elements/config_spec.rb +203 -0
  75. data/spec/page_magic/elements_spec.rb +90 -134
  76. data/spec/page_magic/instance_methods_spec.rb +65 -63
  77. data/spec/page_magic/mapping_spec.rb +181 -0
  78. data/spec/page_magic/session_methods_spec.rb +29 -25
  79. data/spec/page_magic/session_spec.rb +109 -199
  80. data/spec/page_magic/transitions_spec.rb +43 -0
  81. data/spec/page_magic/utils/string_spec.rb +29 -0
  82. data/spec/page_magic/utils/url_spec.rb +9 -0
  83. data/spec/page_magic/wait_methods_spec.rb +16 -22
  84. data/spec/page_magic/watcher_spec.rb +22 -0
  85. data/spec/page_magic/watchers_spec.rb +58 -62
  86. data/spec/page_magic_spec.rb +37 -29
  87. data/spec/spec_helper.rb +9 -2
  88. data/spec/support/shared_contexts.rb +3 -1
  89. data/spec/support/shared_examples.rb +17 -17
  90. metadata +101 -48
  91. data/lib/page_magic/element/query_builder.rb +0 -48
  92. data/lib/page_magic/element/selector_methods.rb +0 -13
  93. data/lib/page_magic/matcher.rb +0 -121
  94. data/spec/element_spec.rb +0 -249
  95. data/spec/page_magic/element/query_builder_spec.rb +0 -108
  96. data/spec/page_magic/matcher_spec.rb +0 -336
  97. data/spec/support/shared_contexts/files_context.rb +0 -7
  98. data/spec/support/shared_contexts/nested_elements_html_context.rb +0 -16
  99. data/spec/support/shared_contexts/rack_application_context.rb +0 -9
  100. data/spec/support/shared_contexts/webapp_fixture_context.rb +0 -39
  101. data/spec/watcher_spec.rb +0 -61
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Comparator
5
+ # class Map - used to model parameter matching requirements
6
+ class ParameterMap < Comparator
7
+ def initialize(map)
8
+ comparator = normalise(map).keys.each_with_object({}) do |key, params|
9
+ params[key] = Comparator.for(map[key])
10
+ end
11
+
12
+ fuzzy = comparator.values.any?(&:fuzzy?)
13
+ super(comparator, fuzzy)
14
+ end
15
+
16
+ def <=>(other)
17
+ return 0 if empty? && other.empty?
18
+ return 1 if other.empty?
19
+ if (comparator.keys.size <=> other.comparator.keys.size).zero?
20
+ return literal_matchers.size <=> other.literal_matchers.size
21
+ end
22
+
23
+ 0
24
+ end
25
+
26
+ def empty?
27
+ comparator.empty?
28
+ end
29
+
30
+ def literal_matchers
31
+ comparator.values.find_all { |matcher| !matcher.fuzzy? }
32
+ end
33
+
34
+ def match?(params)
35
+ params_copy = normalise(params)
36
+ comparator.each do |key, value|
37
+ param = params_copy[key]
38
+ return false unless value&.match?(param)
39
+ end
40
+ true
41
+ end
42
+
43
+ private
44
+
45
+ def normalise(hash)
46
+ hash.keys.each_with_object({}) do |key, map|
47
+ map[key.to_sym] = hash[key]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # class Driver - instances are factories for drivers used by PageMagic
3
5
  class Driver
4
6
  attr_reader :supported_browsers, :handler
7
+
5
8
  # Creates a driver definition
6
9
  # @example
7
10
  # Driver.new do |rack_application, options|
@@ -1,4 +1,7 @@
1
- require 'page_magic/driver'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'driver'
4
+ require_relative 'utils/string'
2
5
  module PageMagic
3
6
  # class Drivers - creates an object that can be used to hold driver definitions
4
7
  # These PageMagic gets the user's chosen driver from this object.
@@ -16,12 +19,10 @@ module PageMagic
16
19
  # Loads drivers defined in files at the given path
17
20
  # @param [String] path where the drivers are located
18
21
  def load(path = "#{__dir__}/drivers")
19
- require 'active_support/inflector'
20
-
21
- Dir["#{path}/*.rb"].each do |driver_file|
22
+ Dir["#{path}/*.rb"].sort.each do |driver_file|
22
23
  require driver_file
23
24
  driver_name = File.basename(driver_file)[/(.*)\.rb$/, 1]
24
- register self.class.const_get(driver_name.classify)
25
+ register self.class.const_get(Utils::String.classify(driver_name))
25
26
  end
26
27
  end
27
28
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  PageMagic::Drivers::Poltergeist = PageMagic::Driver.new(:poltergeist) do |app, options|
2
4
  require 'capybara/poltergeist'
3
5
  Capybara::Poltergeist::Driver.new(app, options)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  PageMagic::Drivers::RackTest = PageMagic::Driver.new(:rack_test) do |app, options|
2
- Capybara::RackTest::Driver.new(app, options)
4
+ Capybara::RackTest::Driver.new(app, **options)
3
5
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  PageMagic::Drivers::Selenium = PageMagic::Driver.new(:chrome, :firefox) do |app, options, browser|
2
- require 'watir-webdriver'
3
- Capybara::Selenium::Driver.new(app, options.dup.merge(browser: browser))
4
+ require 'watir'
5
+ Capybara::Selenium::Driver.new(app, **options.dup.merge(browser: browser))
4
6
  end
@@ -1,17 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
- require 'page_magic/element/selector_methods'
3
- require 'page_magic/element/locators'
4
- require 'page_magic/element/selector'
5
- require 'page_magic/element/query_builder'
4
+ require_relative 'element/not_found'
5
+ require_relative 'element/selector/methods'
6
+ require_relative 'element/locators'
7
+ require_relative 'element/selector'
8
+ require_relative 'element/query'
6
9
  module PageMagic
7
10
  # class Element - represents an element in a html page.
8
11
  class Element
9
- EVENT_TYPES = [:set, :select, :select_option, :unselect_option, :click].freeze
12
+ EVENT_TYPES = %i[set select select_option unselect_option click].freeze
10
13
  DEFAULT_HOOK = proc {}.freeze
11
- EVENT_NOT_SUPPORTED_MSG = '%s event not supported'.freeze
14
+ EVENT_NOT_SUPPORTED_MSG = '%s event not supported'
12
15
 
13
- include SelectorMethods, Watchers, SessionMethods, WaitMethods, Locators
14
- extend Elements, SelectorMethods, Forwardable
16
+ include Locators
17
+ include WaitMethods
18
+ include SessionMethods
19
+ include Watchers
20
+ include Selector::Methods
21
+ extend Forwardable
22
+ extend Selector::Methods
23
+ extend Elements
15
24
 
16
25
  attr_reader :type, :name, :parent_element, :browser_element, :before_events, :after_events
17
26
 
@@ -24,7 +33,7 @@ module PageMagic
24
33
  # @!method before_events
25
34
  # If a block is passed in, it adds it to be run before an event is triggered on an element.
26
35
  # @see .after_events
27
- %i(after_events before_events).each do |method|
36
+ %i[after_events before_events].each do |method|
28
37
  define_method method do |&block|
29
38
  instance_variable_name = "@#{method}".to_sym
30
39
  instance_variable_value = instance_variable_get(instance_variable_name) || [DEFAULT_HOOK]
@@ -38,6 +47,7 @@ module PageMagic
38
47
  # @return [Element]
39
48
  def parent_element(page_element = nil)
40
49
  return @parent_page_element unless page_element
50
+
41
51
  @parent_page_element = page_element
42
52
  end
43
53
 
@@ -49,10 +59,16 @@ module PageMagic
49
59
  clazz.after_events.replace(after_events)
50
60
  end
51
61
 
62
+ def load(source)
63
+ new(Capybara::Node::Simple.new(source))
64
+ end
65
+
52
66
  # Defines watchers to be used by instances
53
67
  # @see Watchers#watch
54
68
  def watch(name, method = nil, &block)
55
- before_events { watch(name, method, &block) }
69
+ before_events do
70
+ watch(name, method: method, &block)
71
+ end
56
72
  end
57
73
 
58
74
  def ==(other)
@@ -78,9 +94,7 @@ module PageMagic
78
94
  # @raise [NotSupportedException] if the wrapped Capybara element does not support the method
79
95
  EVENT_TYPES.each do |method|
80
96
  define_method method do |*args|
81
- unless browser_element.respond_to?(method)
82
- raise NotSupportedException, EVENT_NOT_SUPPORTED_MSG % method
83
- end
97
+ raise NotSupportedException, EVENT_NOT_SUPPORTED_MSG % method unless browser_element.respond_to?(method)
84
98
 
85
99
  browser_element.send(method, *args)
86
100
  end
@@ -91,16 +105,21 @@ module PageMagic
91
105
  rescue ElementMissingException
92
106
  begin
93
107
  return browser_element.send(method, *args, &block) if browser_element.respond_to?(method)
94
- return parent_element.send(method, *args, &block)
108
+
109
+ parent_element.send(method, *args, &block)
95
110
  rescue NoMethodError
96
111
  super
97
112
  end
98
113
  end
99
114
 
100
- def respond_to?(*args)
115
+ def respond_to_missing?(*args)
101
116
  super || contains_element?(args.first) || browser_element.respond_to?(*args) || parent_element.respond_to?(*args)
102
117
  end
103
118
 
119
+ # def respond_to_missing?(*args)
120
+ # respond_to?(*args)
121
+ # end
122
+
104
123
  # @!method session
105
124
  # get the current session
106
125
  # @return [Session] returns the session of the parent page element.
@@ -131,6 +150,7 @@ module PageMagic
131
150
  def wrap_events(raw_element)
132
151
  EVENT_TYPES.each do |action_method|
133
152
  next unless raw_element.respond_to?(action_method)
153
+
134
154
  apply_hooks(raw_element: raw_element,
135
155
  capybara_method: action_method,
136
156
  before_events: before_events,
@@ -1,22 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # for the benefit of pull review :@
3
5
  class Element
4
6
  # contains method for finding element definitions
5
7
  module Locators
6
8
  # message used when raising {ElementMissingException} from methods within this module
7
- ELEMENT_NOT_DEFINED_MSG = 'Element not defined: %s'.freeze
9
+ ELEMENT_NOT_DEFINED_MSG = 'Element not defined: %s'
8
10
 
9
11
  # find an element definition based on its name
10
12
  # @param [Symbol] name name of the element
11
13
  # @return [Element] element definition with the given name
12
14
  # @raise [ElementMissingException] raised when element with the given name is not found
13
15
  def element_by_name(name, *args)
14
- defintion = element_definitions[name]
15
- raise ElementMissingException, (ELEMENT_NOT_DEFINED_MSG % name) unless defintion
16
- defintion.call(self, *args)
16
+ definition = element_definitions[name]
17
+ raise ElementMissingException, (ELEMENT_NOT_DEFINED_MSG % name) unless definition
18
+
19
+ definition.call(self, *args)
17
20
  end
18
21
 
19
- # @return [Array] class level defined element definitions
22
+ # @return [Array<Element>] class level defined element definitions
20
23
  def element_definitions
21
24
  self.class.element_definitions
22
25
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Element
5
+ # class NotFound - Used to represent elements which are missing. All method calls other than
6
+ # to those that check visibility thrown a {PageMagic::ElementMissingException} exception
7
+ class NotFound
8
+ # @private [Capybara::ElementNotFound] exception
9
+ def initialize(exception)
10
+ @exception = exception
11
+ end
12
+
13
+ # @return [Boolean] - always false
14
+ def visible?
15
+ false
16
+ end
17
+
18
+ # @return [Boolean] - always false
19
+ def present?
20
+ false
21
+ end
22
+
23
+ # @raise [PageMagic::ElementMissingException]
24
+ def method_missing(*_args)
25
+ raise ElementMissingException, exception.message
26
+ end
27
+
28
+ # @return [Boolean] - always true
29
+ def respond_to_missing?(*_args)
30
+ true
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :exception
36
+ end
37
+ end
38
+ end
@@ -1,35 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query/multiple_results'
4
+ require_relative 'query/single_result'
5
+ require_relative 'query/prefetched_result'
1
6
  module PageMagic
2
7
  class Element
3
8
  # class Query - executes query on capybara driver
4
9
  class Query
5
- # Message template for execptions raised as a result of calling method_missing
6
- ELEMENT_NOT_FOUND_MSG = 'Unable to find %s'.freeze
7
-
8
- attr_reader :args, :multiple_results
10
+ attr_reader :selector_args, :options
9
11
 
10
- alias multiple_results? multiple_results
12
+ DEFAULT_DECORATOR = proc { |arg| arg }.freeze
11
13
 
12
- def initialize(args, multiple_results: false)
13
- @args = args
14
- @multiple_results = multiple_results
14
+ def initialize(*selector_args, options: {})
15
+ @selector_args = selector_args
16
+ @options = options
15
17
  end
16
18
 
17
- def execute(capybara_element)
18
- if multiple_results
19
- capybara_element.all(*args).to_a.tap do |result|
20
- raise Capybara::ElementNotFound if result.empty?
21
- end
22
- else
23
- capybara_element.find(*args)
24
- end
25
- rescue Capybara::Ambiguous => e
26
- raise AmbiguousQueryException, e.message
19
+ # TODO: - test for decoration?
20
+ # Run query against the scope of the given element
21
+ # The supplied block will be used to decorate the results
22
+ # @param [Capybara::Node::Element] capybara_element the element to be searched within
23
+ # @return [Array<Capybara::Node::Element>] the results
24
+ # @return [NullElement] when the element is not found
25
+ def execute(capybara_element, &block)
26
+ find(capybara_element, &(block || DEFAULT_DECORATOR))
27
27
  rescue Capybara::ElementNotFound => e
28
- raise ElementMissingException, e.message
28
+ NotFound.new(e)
29
29
  end
30
30
 
31
31
  def ==(other)
32
- other.respond_to?(:args) && args == other.args
32
+ other.respond_to?(:selector_args) && selector_args == other.selector_args &&
33
+ other.respond_to?(:options) && options == other.options
33
34
  end
34
35
  end
35
36
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Element
5
+ class Query
6
+ # class MultipleResults - use to query for multiple results
7
+ class MultipleResults < Query
8
+ # Find multiple elements
9
+ # The supplied block will be used to decorate the results
10
+ # @param [Capybara::Node::Element] capybara_element the element to be searched within
11
+ # @return [Array<Capybara::Node::Element>] the results
12
+ def find(capybara_element, &block)
13
+ results = capybara_element.all(*selector_args, **options).to_a.tap do |result|
14
+ raise Capybara::ElementNotFound if result.empty?
15
+ end
16
+ results.collect { |result| block.call(result) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Element
5
+ class Query
6
+ # class PrefetchedResult - used to return element that has already been retrieved
7
+ class PrefetchedResult < Query
8
+ def initialize(prefetched_element)
9
+ super
10
+ @prefetched_element = prefetched_element
11
+ end
12
+
13
+ # Returns the object provided to `initialize`
14
+ # The supplied block will be used to decorate the results
15
+ # @return [Capybara::Node::Element] the object supplied to `initialize`
16
+ def find(_capybara_element, &block)
17
+ block.call(prefetched_element)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :prefetched_element
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Element
5
+ class Query
6
+ # class SingleResult - use to query when only one result should be expected
7
+ class SingleResult < Query
8
+ # Find an element
9
+ # The supplied block will be used to decorate the results
10
+ # @param [Capybara::Node::Element] capybara_element the element to be searched within
11
+ # @return [Object] the results
12
+ def find(capybara_element, &block)
13
+ block.call capybara_element.find(*selector_args, **options)
14
+ rescue Capybara::Ambiguous => e
15
+ raise AmbiguousQueryException, e.message
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,40 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector/model'
1
4
  module PageMagic
5
+ # Capybara::Finder
2
6
  class Element
3
7
  # class Selector - models the selection criteria understood by Capybara
4
8
  class Selector
5
9
  class << self
6
- # Find a Selecor using it's name
10
+ # Find a Selector using it's name
7
11
  # @param [Symbol] name the name of the required Selector in snakecase format. See class constants for available
8
12
  # selectors
9
13
  # @return [Selector] returns the predefined selector with the given name
10
14
  def find(name)
11
- selector = constants.find { |constant| constant.to_s.casecmp(name.to_s).zero? }
12
- raise UnsupportedCriteriaException unless selector
13
- const_get(selector)
15
+ selector_name = selector_constant_name(name)
16
+ raise UnsupportedCriteriaException unless selector_name
17
+
18
+ const_get(selector_name)
14
19
  end
15
- end
16
20
 
17
- attr_reader :name, :formatter, :exact, :supports_type
21
+ private
22
+
23
+ def selector_constant_name(name)
24
+ constants.find { |constant| constant.to_s.casecmp(name.to_s).zero? }
25
+ end
26
+ end
18
27
 
28
+ # Initialize a new selector
29
+ # a block can be supplied to decorate the query. E.g.
30
+ # @example
31
+ # Selector.new(supports_type: false) do |arg|
32
+ # "*[name='#{arg}']"
33
+ # end
34
+ #
35
+ # @param [Symbol] selector the identifier for the selector
36
+ # @param [Boolean] supports_type whether the element type being searched for can be used as part of the query
37
+ # @param [Boolean] exact whether an exact match is required. E.g. element should include exactly the same text
19
38
  def initialize(selector = nil, supports_type: false, exact: false, &formatter)
20
- @name = selector
39
+ @selector = selector
21
40
  @formatter = formatter || proc { |arg| arg }
22
41
  @supports_type = supports_type
23
- @exact = exact
42
+ @options = {}.tap do |hash|
43
+ hash[:exact] = true if exact
44
+ end
24
45
  end
25
46
 
26
47
  # Build selector query parameters for Capybara's find method
27
48
  # @param [Symbol] element_type the type of browser element being found. e.g :link
28
- # @param [Hash] locator the selection method and its parameter. E.g. text: 'click me'
29
- def build(element_type, locator)
30
- [].tap do |array|
31
- array << element_type if supports_type
32
- array << name if name
33
- array << formatter.call(locator)
34
- array << { exact: true } if exact
35
- end
49
+ # @param [Hash<Symbol,String>] locator the selection method and its parameter. E.g. text: 'click me'
50
+ def build(element_type, locator, options: {})
51
+ array = [type(element_type), selector, formatter.call(locator)].compact
52
+ Model.new(array, self.options.merge(options))
53
+ end
54
+
55
+ private
56
+
57
+ def type(element_type)
58
+ supports_type ? element_type : nil
36
59
  end
37
60
 
61
+ attr_reader :supports_type, :options, :selector, :formatter
62
+
38
63
  XPATH = Selector.new(:xpath, supports_type: false)
39
64
  ID = Selector.new(:id, supports_type: false)
40
65
  LABEL = Selector.new(:field, supports_type: false, exact: true)