page_magic 1.2.6 → 2.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +19 -3
  3. data/.simplecov +5 -3
  4. data/.zsh_config +6 -0
  5. data/Dockerfile +10 -0
  6. data/Gemfile +12 -12
  7. data/Gemfile.lock +133 -126
  8. data/Makefile +13 -0
  9. data/README.md +78 -14
  10. data/Rakefile +12 -2
  11. data/VERSION +1 -1
  12. data/circle.yml +4 -2
  13. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  14. data/lib/page_magic.rb +7 -4
  15. data/lib/page_magic/class_methods.rb +4 -1
  16. data/lib/page_magic/driver.rb +3 -0
  17. data/lib/page_magic/drivers.rb +6 -5
  18. data/lib/page_magic/drivers/poltergeist.rb +2 -0
  19. data/lib/page_magic/drivers/rack_test.rb +3 -1
  20. data/lib/page_magic/drivers/selenium.rb +4 -2
  21. data/lib/page_magic/element.rb +19 -10
  22. data/lib/page_magic/element/locators.rb +4 -1
  23. data/lib/page_magic/element/query.rb +33 -33
  24. data/lib/page_magic/element/query_builder.rb +61 -0
  25. data/lib/page_magic/element/selector.rb +3 -0
  26. data/lib/page_magic/element/selector_methods.rb +3 -0
  27. data/lib/page_magic/element_context.rb +9 -12
  28. data/lib/page_magic/element_definition_builder.rb +10 -4
  29. data/lib/page_magic/elements.rb +51 -8
  30. data/lib/page_magic/exceptions.rb +4 -33
  31. data/lib/page_magic/instance_methods.rb +6 -1
  32. data/lib/page_magic/matcher.rb +12 -3
  33. data/lib/page_magic/session.rb +7 -5
  34. data/lib/page_magic/session_methods.rb +2 -0
  35. data/lib/page_magic/utils/string.rb +14 -0
  36. data/lib/page_magic/wait_methods.rb +3 -0
  37. data/lib/page_magic/watcher.rb +2 -0
  38. data/lib/page_magic/watchers.rb +4 -1
  39. data/page_magic.gemspec +19 -13
  40. data/spec/element_spec.rb +2 -0
  41. data/spec/lib/active_support/core_ext/object/to_query_test.rb +78 -0
  42. data/spec/page_magic/class_methods_spec.rb +2 -0
  43. data/spec/page_magic/driver_spec.rb +2 -0
  44. data/spec/page_magic/drivers/poltergeist_spec.rb +2 -0
  45. data/spec/page_magic/drivers/rack_test_spec.rb +2 -0
  46. data/spec/page_magic/drivers/selenium_spec.rb +2 -0
  47. data/spec/page_magic/drivers_spec.rb +2 -0
  48. data/spec/page_magic/element/locators_spec.rb +2 -0
  49. data/spec/page_magic/element/query_builder_spec.rb +110 -0
  50. data/spec/page_magic/element/query_spec.rb +35 -77
  51. data/spec/page_magic/element/selector_spec.rb +14 -7
  52. data/spec/page_magic/element_context_spec.rb +5 -10
  53. data/spec/page_magic/element_definition_builder_spec.rb +3 -1
  54. data/spec/page_magic/elements_spec.rb +19 -5
  55. data/spec/page_magic/instance_methods_spec.rb +2 -0
  56. data/spec/page_magic/matcher_spec.rb +3 -1
  57. data/spec/page_magic/session_methods_spec.rb +2 -0
  58. data/spec/page_magic/session_spec.rb +3 -4
  59. data/spec/page_magic/utils/string_spec.rb +36 -0
  60. data/spec/page_magic/wait_methods_spec.rb +5 -3
  61. data/spec/page_magic/watchers_spec.rb +2 -0
  62. data/spec/page_magic_spec.rb +5 -7
  63. data/spec/spec_helper.rb +3 -0
  64. data/spec/support/shared_contexts.rb +3 -1
  65. data/spec/support/shared_contexts/files_context.rb +2 -0
  66. data/spec/support/shared_contexts/nested_elements_html_context.rb +2 -0
  67. data/spec/support/shared_contexts/rack_application_context.rb +2 -0
  68. data/spec/support/shared_contexts/webapp_fixture_context.rb +3 -1
  69. data/spec/support/shared_examples.rb +2 -0
  70. data/spec/watcher_spec.rb +3 -0
  71. metadata +62 -40
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  class Element
3
5
  # class Selector - models the selection criteria understood by Capybara
@@ -10,6 +12,7 @@ module PageMagic
10
12
  def find(name)
11
13
  selector = constants.find { |constant| constant.to_s.casecmp(name.to_s).zero? }
12
14
  raise UnsupportedCriteriaException unless selector
15
+
13
16
  const_get(selector)
14
17
  end
15
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  class Element
3
5
  # module SelectorMethods - adds method for getting and setting an element selector
@@ -6,6 +8,7 @@ module PageMagic
6
8
  # @param [Hash] selector method for locating the browser element. E.g. text: 'the text'
7
9
  def selector(selector = nil)
8
10
  return @selector unless selector
11
+
9
12
  @selector = selector
10
13
  end
11
14
  end
@@ -1,9 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # class ElementContext - resolves which element definition to use when accessing the browser.
3
5
  class ElementContext
4
- # Message template for execptions raised as a result of calling method_missing
5
- ELEMENT_NOT_FOUND_MSG = 'Unable to find %s'.freeze
6
-
7
6
  attr_reader :page_element
8
7
 
9
8
  def initialize(page_element)
@@ -24,8 +23,7 @@ module PageMagic
24
23
  prefecteched_element = builder.element
25
24
  return builder.build(prefecteched_element) if prefecteched_element
26
25
 
27
- elements = find(builder)
28
- elements.size == 1 ? elements.first : elements
26
+ find(builder)
29
27
  end
30
28
 
31
29
  def respond_to?(*args)
@@ -35,15 +33,14 @@ module PageMagic
35
33
  private
36
34
 
37
35
  def find(builder)
38
- query_args = builder.build_query
39
- result = page_element.browser_element.all(*query_args)
36
+ query = builder.build_query
37
+ result = query.execute(page_element.browser_element)
40
38
 
41
- if result.empty?
42
- query = Capybara::Query.new(*query_args)
43
- raise ElementMissingException, ELEMENT_NOT_FOUND_MSG % query.description
39
+ if query.multiple_results?
40
+ result.collect { |e| builder.build(e) }
41
+ else
42
+ builder.build(result)
44
43
  end
45
-
46
- result.to_a.collect { |e| builder.build(e) }
47
44
  end
48
45
  end
49
46
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # Builder for creating ElementDefinitions
3
5
  class ElementDefinitionBuilder
4
- INVALID_SELECTOR_MSG = 'Pass a locator/define one on the class'.freeze
5
- attr_reader :definition_class, :options, :selector, :type, :element
6
+ INVALID_SELECTOR_MSG = 'Pass a locator/define one on the class'
7
+ attr_reader :definition_class, :options, :selector, :type, :element, :query_builder
6
8
 
7
9
  def initialize(definition_class:, selector:, type:, options: {}, element: nil)
8
10
  unless element
@@ -13,13 +15,16 @@ module PageMagic
13
15
  @definition_class = definition_class
14
16
  @selector = selector
15
17
  @type = type
16
- @options = options
18
+ @query_builder = Element::QueryBuilder.find(type)
19
+
20
+ @options = { multiple_results: false }.merge(options)
17
21
  @element = element
18
22
  end
19
23
 
20
24
  # @return [Capybara::Query] query to find this element in the browser
21
25
  def build_query
22
- Element::Query.find(type).build(selector, options)
26
+ multiple_results = options.delete(:multiple_results)
27
+ query_builder.build(selector, options, multiple_results: multiple_results)
23
28
  end
24
29
 
25
30
  # Create new instance of the ElementDefinition modeled by this builder
@@ -31,6 +36,7 @@ module PageMagic
31
36
 
32
37
  def ==(other)
33
38
  return false unless other.is_a?(ElementDefinitionBuilder)
39
+
34
40
  this = [options, selector, type, element, definition_class]
35
41
  this == [other.options, other.selector, other.type, other.element, other.definition_class]
36
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/inflector'
2
4
  require 'page_magic/element_definition_builder'
3
5
  module PageMagic
@@ -12,9 +14,44 @@ module PageMagic
12
14
  end
13
15
  end
14
16
 
15
- INVALID_METHOD_NAME_MSG = 'a method already exists with this method name'.freeze
16
-
17
- TYPES = [:text_field, :button, :link, :checkbox, :select_list, :radios, :textarea].freeze
17
+ INVALID_METHOD_NAME_MSG = 'a method already exists with this method name'
18
+
19
+ # css
20
+ # datalist_input
21
+ # datalist_option
22
+ # field
23
+ # fieldset
24
+ # file_field
25
+ # fillable_field
26
+ # frame
27
+ # link_or_button
28
+ # option
29
+ # radio_button
30
+ # select
31
+ # table
32
+ # table_row
33
+ # xpath
34
+ #
35
+
36
+ TYPES = %i[field
37
+ fieldset
38
+ file_field
39
+ fillable_field
40
+ frame
41
+ link_or_button
42
+ option
43
+ radio_button
44
+ select
45
+ table
46
+ table_row
47
+ text_field
48
+ button
49
+ link
50
+ checkbox
51
+ select_list
52
+ radio
53
+ textarea
54
+ label].collect{ |type| [type, :"#{type}s"]}.flatten.freeze
18
55
 
19
56
  class << self
20
57
  def extended(clazz)
@@ -52,8 +89,8 @@ module PageMagic
52
89
  # @param [Hash] selector a key value pair defining the method for locating this element. See above for details
53
90
  def element(*args, &block)
54
91
  block ||= proc {}
55
- options = compute_options(args.dup)
56
- options[:type] = __callee__
92
+ options = compute_options(args.dup, __callee__)
93
+
57
94
  section_class = options.delete(:section_class)
58
95
 
59
96
  add_element_definition(options.delete(:name)) do |parent_element, *e_args|
@@ -61,10 +98,12 @@ module PageMagic
61
98
  parent_element(parent_element)
62
99
  class_exec(*e_args, &block)
63
100
  end
64
- ElementDefinitionBuilder.new(options.merge(definition_class: definition_class))
101
+
102
+ ElementDefinitionBuilder.new(**options.merge(definition_class: definition_class))
65
103
  end
66
104
  end
67
105
 
106
+ alias elements element
68
107
  TYPES.each { |type| alias_method type, :element }
69
108
 
70
109
  # @return [Hash] element definition names mapped to blocks that can be used to create unique instances of
@@ -75,13 +114,17 @@ module PageMagic
75
114
 
76
115
  private
77
116
 
78
- def compute_options(args)
117
+ def compute_options(args, type)
79
118
  section_class = remove_argument(args, Class) || Element
119
+
80
120
  { name: compute_name(args, section_class),
121
+ type: type,
81
122
  selector: compute_selector(args, section_class),
82
123
  options: compute_argument(args, Hash),
83
124
  element: args.delete_at(0),
84
- section_class: section_class }
125
+ section_class: section_class }.tap do |hash|
126
+ hash[:options][:multiple_results] = type.to_s.end_with?('s')
127
+ end
85
128
  end
86
129
 
87
130
  def add_element_definition(name, &block)
@@ -1,39 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  class ElementMissingException < RuntimeError
3
5
  end
4
6
 
5
- class InvalidElementNameException < RuntimeError
6
- end
7
-
8
- class InvalidMethodNameException < RuntimeError
9
- end
10
-
11
- class InvalidURLException < RuntimeError
12
- end
13
-
14
- class MatcherInvalidException < RuntimeError
15
- end
16
-
17
- class TimeoutException < RuntimeError
18
- end
19
-
20
- class UnspportedBrowserException < RuntimeError
21
- end
22
-
23
- class UnsupportedCriteriaException < RuntimeError
24
- end
25
-
26
- class UnsupportedSelectorException < RuntimeError
27
- end
28
-
29
- class UndefinedSelectorException < RuntimeError
30
- end
31
-
32
- class NotSupportedException < RuntimeError
33
- end
34
- end
35
- module PageMagic
36
- class ElementMissingException < RuntimeError
7
+ class AmbiguousQueryException < RuntimeError
37
8
  end
38
9
 
39
10
  class InvalidElementNameException < RuntimeError
@@ -51,7 +22,7 @@ module PageMagic
51
22
  class TimeoutException < RuntimeError
52
23
  end
53
24
 
54
- class UnspportedBrowserException < RuntimeError
25
+ class UnsupportedBrowserException < RuntimeError
55
26
  end
56
27
 
57
28
  class UnsupportedCriteriaException < RuntimeError
@@ -1,9 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # module InstanceMethods - provides instance level methods for page objects
3
5
  module InstanceMethods
4
6
  attr_reader :browser, :session, :browser_element
5
7
 
6
- include Watchers, SessionMethods, WaitMethods, Element::Locators
8
+ include Element::Locators
9
+ include WaitMethods
10
+ include SessionMethods
11
+ include Watchers
7
12
 
8
13
  # Creates a new instance
9
14
  # @param [Session] session session that provides gateway to the browser throw the users chosen browser
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/object/to_query'
2
4
  module PageMagic
3
5
  # models mapping used to relate pages to uris
@@ -10,6 +12,7 @@ module PageMagic
10
12
  # @raise [MatcherInvalidException] if at least one component is not specified
11
13
  def initialize(path = nil, parameters: nil, fragment: nil)
12
14
  raise MatcherInvalidException unless path || parameters || fragment
15
+
13
16
  @path = path
14
17
  @parameters = parameters
15
18
  @fragment = fragment
@@ -22,7 +25,7 @@ module PageMagic
22
25
 
23
26
  # @return [String] uri represented by this mapping
24
27
  def compute_uri
25
- path.to_s.tap do |uri|
28
+ path.to_s.dup.tap do |uri|
26
29
  uri << "?#{parameters.to_query}" if parameters
27
30
  uri << "##{fragment}" if fragment
28
31
  end
@@ -44,7 +47,7 @@ module PageMagic
44
47
  # @param [Matcher] other
45
48
  # @return [Fixnum] -1 = smaller, 0 = equal to, 1 = greater than
46
49
  def <=>(other)
47
- results = [:path, :parameters, :fragment].collect do |component|
50
+ results = %i[path parameters fragment].collect do |component|
48
51
  compare(send(component), other.send(component))
49
52
  end
50
53
  results.find { |result| !result.zero? } || 0
@@ -55,6 +58,7 @@ module PageMagic
55
58
  # @return [Boolean]
56
59
  def ==(other)
57
60
  return false unless other.is_a?(Matcher)
61
+
58
62
  path == other.path && parameters == other.parameters && fragment == other.fragment
59
63
  end
60
64
 
@@ -64,11 +68,13 @@ module PageMagic
64
68
 
65
69
  def compare(this, other)
66
70
  return presence_comparison(this, other) unless this && other
71
+
67
72
  fuzzy_comparison(this, other)
68
73
  end
69
74
 
70
75
  def compatible?(string, comparitor)
71
76
  return true if comparitor.nil?
77
+
72
78
  if fuzzy?(comparitor)
73
79
  string =~ comparitor ? true : false
74
80
  else
@@ -82,6 +88,7 @@ module PageMagic
82
88
 
83
89
  def fuzzy?(component)
84
90
  return false unless component
91
+
85
92
  if component.is_a?(Hash)
86
93
  component.values.any? { |o| fuzzy?(o) }
87
94
  else
@@ -108,12 +115,14 @@ module PageMagic
108
115
  def presence_comparison(this, other)
109
116
  return 0 if this.nil? && other.nil?
110
117
  return 1 if this.nil? && other
118
+
111
119
  -1
112
120
  end
113
121
 
114
122
  def query_string_valid?(string)
115
123
  return true unless parameters
116
- !parameters.any? do |key, value|
124
+
125
+ parameters.none? do |key, value|
117
126
  !compatible?(parameters_hash(string)[key.downcase.to_s], value)
118
127
  end
119
128
  end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'page_magic/matcher'
3
5
  module PageMagic
4
6
  # class Session - coordinates access to the browser though page objects.
5
7
  class Session
6
- URL_MISSING_MSG = 'a path must be mapped or a url supplied'.freeze
7
- REGEXP_MAPPING_MSG = 'URL could not be derived because mapping contains Regexps'.freeze
8
- INVALID_MAPPING_MSG = 'mapping must be a string or regexp'.freeze
9
- UNSUPPORTED_OPERATION_MSG = 'execute_script not supported by driver'.freeze
8
+ URL_MISSING_MSG = 'a path must be mapped or a url supplied'
9
+ REGEXP_MAPPING_MSG = 'URL could not be derived because mapping contains Regexps'
10
+ INVALID_MAPPING_MSG = 'mapping must be a string or regexp'
11
+ UNSUPPORTED_OPERATION_MSG = 'execute_script not supported by driver'
10
12
 
11
13
  extend Forwardable
12
14
 
@@ -18,7 +20,6 @@ module PageMagic
18
20
  def initialize(capybara_session, base_url = nil)
19
21
  @raw_session = capybara_session
20
22
  @base_url = base_url
21
- visit(url: base_url) if base_url
22
23
  @transitions = {}
23
24
  end
24
25
 
@@ -94,6 +95,7 @@ module PageMagic
94
95
  target_url = url || begin
95
96
  if (mapping = transitions.key(page))
96
97
  raise InvalidURLException, REGEXP_MAPPING_MSG unless mapping.can_compute_uri?
98
+
97
99
  url(base_url, mapping.compute_uri)
98
100
  end
99
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  module PageMagic
3
5
  # module SessionMethods - contains methods for interacting with the {Session}
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ module Utils
5
+ # module String - contains methods for manipulating strings
6
+ module String
7
+ class << self
8
+ def classify(string_or_symbol)
9
+ string_or_symbol.to_s.split('_').collect(&:capitalize).reduce(:+)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # module WaitMethods - contains methods for waiting
3
5
  module WaitMethods
@@ -10,6 +12,7 @@ module PageMagic
10
12
  start_time = Time.now
11
13
  until Time.now > start_time + timeout_after
12
14
  return true if yield == true
15
+
13
16
  sleep retry_every
14
17
  end
15
18
  raise TimeoutException, 'Action took to long'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # class WatchedElementDefinition - Contains the specification the for checking if an subject has changed
3
5
  class Watcher
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'page_magic/watcher'
2
4
 
3
5
  module PageMagic
4
6
  # module Watchers - contains methods for adding watchers and checking them
5
7
  module Watchers
6
- ELEMENT_MISSING_MSG = 'Unable to defined watcher: Element or method with the name %s can not be found'.freeze
8
+ ELEMENT_MISSING_MSG = 'Unable to defined watcher: Element or method with the name %s can not be found'
7
9
 
8
10
  # @param [Symbol] name - the name of the watcher
9
11
  # @return [Boolean] true if a change is detected
@@ -24,6 +26,7 @@ module PageMagic
24
26
  # end
25
27
  def watch(name, method = nil, &block)
26
28
  raise ElementMissingException, (ELEMENT_MISSING_MSG % name) unless block || respond_to?(name)
29
+
27
30
  watched_element = block ? Watcher.new(name, &block) : Watcher.new(name, method)
28
31
  watchers.delete_if { |w| w.name == name }
29
32
  watchers << watched_element.check(self)