page_magic 1.2.6 → 2.0.0.alpha1
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 +5 -5
- data/.rubocop.yml +19 -3
- data/.simplecov +5 -3
- data/.zsh_config +6 -0
- data/Dockerfile +10 -0
- data/Gemfile +12 -12
- data/Gemfile.lock +133 -126
- data/Makefile +13 -0
- data/README.md +78 -14
- data/Rakefile +12 -2
- data/VERSION +1 -1
- data/circle.yml +4 -2
- data/lib/active_support/core_ext/object/to_query.rb +84 -0
- data/lib/page_magic.rb +7 -4
- data/lib/page_magic/class_methods.rb +4 -1
- data/lib/page_magic/driver.rb +3 -0
- data/lib/page_magic/drivers.rb +6 -5
- data/lib/page_magic/drivers/poltergeist.rb +2 -0
- data/lib/page_magic/drivers/rack_test.rb +3 -1
- data/lib/page_magic/drivers/selenium.rb +4 -2
- data/lib/page_magic/element.rb +19 -10
- data/lib/page_magic/element/locators.rb +4 -1
- data/lib/page_magic/element/query.rb +33 -33
- data/lib/page_magic/element/query_builder.rb +61 -0
- data/lib/page_magic/element/selector.rb +3 -0
- data/lib/page_magic/element/selector_methods.rb +3 -0
- data/lib/page_magic/element_context.rb +9 -12
- data/lib/page_magic/element_definition_builder.rb +10 -4
- data/lib/page_magic/elements.rb +51 -8
- data/lib/page_magic/exceptions.rb +4 -33
- data/lib/page_magic/instance_methods.rb +6 -1
- data/lib/page_magic/matcher.rb +12 -3
- data/lib/page_magic/session.rb +7 -5
- data/lib/page_magic/session_methods.rb +2 -0
- data/lib/page_magic/utils/string.rb +14 -0
- data/lib/page_magic/wait_methods.rb +3 -0
- data/lib/page_magic/watcher.rb +2 -0
- data/lib/page_magic/watchers.rb +4 -1
- data/page_magic.gemspec +19 -13
- data/spec/element_spec.rb +2 -0
- data/spec/lib/active_support/core_ext/object/to_query_test.rb +78 -0
- data/spec/page_magic/class_methods_spec.rb +2 -0
- data/spec/page_magic/driver_spec.rb +2 -0
- data/spec/page_magic/drivers/poltergeist_spec.rb +2 -0
- data/spec/page_magic/drivers/rack_test_spec.rb +2 -0
- data/spec/page_magic/drivers/selenium_spec.rb +2 -0
- data/spec/page_magic/drivers_spec.rb +2 -0
- data/spec/page_magic/element/locators_spec.rb +2 -0
- data/spec/page_magic/element/query_builder_spec.rb +110 -0
- data/spec/page_magic/element/query_spec.rb +35 -77
- data/spec/page_magic/element/selector_spec.rb +14 -7
- data/spec/page_magic/element_context_spec.rb +5 -10
- data/spec/page_magic/element_definition_builder_spec.rb +3 -1
- data/spec/page_magic/elements_spec.rb +19 -5
- data/spec/page_magic/instance_methods_spec.rb +2 -0
- data/spec/page_magic/matcher_spec.rb +3 -1
- data/spec/page_magic/session_methods_spec.rb +2 -0
- data/spec/page_magic/session_spec.rb +3 -4
- data/spec/page_magic/utils/string_spec.rb +36 -0
- data/spec/page_magic/wait_methods_spec.rb +5 -3
- data/spec/page_magic/watchers_spec.rb +2 -0
- data/spec/page_magic_spec.rb +5 -7
- data/spec/spec_helper.rb +3 -0
- data/spec/support/shared_contexts.rb +3 -1
- data/spec/support/shared_contexts/files_context.rb +2 -0
- data/spec/support/shared_contexts/nested_elements_html_context.rb +2 -0
- data/spec/support/shared_contexts/rack_application_context.rb +2 -0
- data/spec/support/shared_contexts/webapp_fixture_context.rb +3 -1
- data/spec/support/shared_examples.rb +2 -0
- data/spec/watcher_spec.rb +3 -0
- 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
|
-
|
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
|
-
|
39
|
-
result = page_element.browser_element
|
36
|
+
query = builder.build_query
|
37
|
+
result = query.execute(page_element.browser_element)
|
40
38
|
|
41
|
-
if
|
42
|
-
|
43
|
-
|
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'
|
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
|
-
@
|
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
|
-
|
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
|
data/lib/page_magic/elements.rb
CHANGED
@@ -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'
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
data/lib/page_magic/matcher.rb
CHANGED
@@ -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 = [
|
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
|
-
|
124
|
+
|
125
|
+
parameters.none? do |key, value|
|
117
126
|
!compatible?(parameters_hash(string)[key.downcase.to_s], value)
|
118
127
|
end
|
119
128
|
end
|
data/lib/page_magic/session.rb
CHANGED
@@ -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'
|
7
|
-
REGEXP_MAPPING_MSG = 'URL could not be derived because mapping contains Regexps'
|
8
|
-
INVALID_MAPPING_MSG = 'mapping must be a string or regexp'
|
9
|
-
UNSUPPORTED_OPERATION_MSG = 'execute_script not supported by driver'
|
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
|
@@ -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'
|
data/lib/page_magic/watcher.rb
CHANGED
data/lib/page_magic/watchers.rb
CHANGED
@@ -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'
|
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)
|