page_magic 1.0.0.alpha13 → 1.0.0.alpha17

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.simplecov +3 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +15 -10
  6. data/VERSION +1 -1
  7. data/lib/page_magic/drivers.rb +2 -2
  8. data/lib/page_magic/element/method_observer.rb +13 -0
  9. data/lib/page_magic/element/query.rb +38 -0
  10. data/lib/page_magic/element/selector.rb +40 -0
  11. data/lib/page_magic/element/selector_methods.rb +10 -0
  12. data/lib/page_magic/element.rb +65 -104
  13. data/lib/page_magic/element_context.rb +3 -8
  14. data/lib/page_magic/elements.rb +31 -31
  15. data/lib/page_magic/exceptions.rb +9 -0
  16. data/lib/page_magic/page_magic.rb +7 -8
  17. data/lib/page_magic/session.rb +30 -35
  18. data/lib/page_magic.rb +14 -20
  19. data/spec/element_spec.rb +53 -37
  20. data/spec/page_magic/driver_spec.rb +3 -4
  21. data/spec/page_magic/element/query_spec.rb +76 -0
  22. data/spec/page_magic/element/selector_spec.rb +109 -0
  23. data/spec/page_magic/element_context_spec.rb +5 -9
  24. data/spec/page_magic/elements_spec.rb +150 -147
  25. data/spec/page_magic/page_magic_spec.rb +51 -8
  26. data/spec/page_magic/session_spec.rb +69 -51
  27. data/spec/page_magic_spec.rb +64 -6
  28. data/spec/spec_helper.rb +4 -96
  29. data/spec/support/shared_contexts/files_context.rb +7 -0
  30. data/spec/support/shared_contexts/rack_application_context.rb +9 -0
  31. data/spec/support/shared_contexts/webapp_context.rb +37 -0
  32. data/spec/support/shared_contexts.rb +3 -0
  33. metadata +13 -8
  34. data/lib/ext/string.rb +0 -9
  35. data/spec/helpers/capybara.rb +0 -10
  36. data/spec/page_magic/usage/defining_pages_spec.rb +0 -81
  37. data/spec/page_magic/usage/include_page_magic_spec.rb +0 -18
  38. data/spec/page_magic/usage/interacting_with_pages_spec.rb +0 -54
  39. data/spec/page_magic/usage/starting_a_session_spec.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3bd5d9cee944d0fe84e178901c4db272f56cab69
4
- data.tar.gz: f0a9352162e73a5dbffa7b835043e6da5d77f52b
3
+ metadata.gz: b6aee6a0833a50dbf849f13b2c7a4c3668feef1d
4
+ data.tar.gz: b4d0aecfbba7509a3ffb9e2bf08b38cb30f4cb64
5
5
  SHA512:
6
- metadata.gz: e1721e6c1e3db25d8374e08f0a0f6f17e680c3cc84735a8b02bc9afac1370236da7e14cb61265d90b3643178d6ab221b8ed44bca06cee6f0bdd8bbd5fa88ac0f
7
- data.tar.gz: f4bf17178033131e4656a2564a0a752a9638a2e36bd9215e054a63993dcba1ce1708a15f00a26573f7f9b0fad122aa518fef14b5c4038920eef80d54948fa770
6
+ metadata.gz: 4437e58a2107ffcec212ee3778450b73f5384bc46581c78b53c8f28e2c69d3a5c42731c1a8356ff1c42e2ccdb7ba5b4af0c68c6de62477e34349729b9fbc13fd
7
+ data.tar.gz: 27e427b3fa2bc153836c545b24ef9ea3cf14e360eba3acc3f7fb932ff2c694a6262746735d60eb4752749f0c9cb335bf55c8ff82d4a4ea0f0fe0517f91e5df2d
data/.rubocop.yml CHANGED
@@ -1,9 +1,15 @@
1
1
  Documentation:
2
2
  Enabled: false
3
3
 
4
+ Metrics/LineLength:
5
+ Max: 120
6
+
4
7
  AllCops:
5
8
  Exclude:
6
9
  - 'pkg/**/*'
7
10
  - 'vendor/**/*'
8
11
  - 'coverage/**/*'
9
12
  - '.idea/**/*'
13
+
14
+ Metrics/ParameterLists:
15
+ CountKeywordArgs: false
data/.simplecov ADDED
@@ -0,0 +1,3 @@
1
+ SimpleCov.start do
2
+ add_filter '/spec/'
3
+ end
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gem 'capybara'
4
4
  gem 'activesupport'
data/Gemfile.lock CHANGED
@@ -1,5 +1,5 @@
1
1
  GEM
2
- remote: http://rubygems.org/
2
+ remote: https://rubygems.org/
3
3
  specs:
4
4
  activesupport (4.2.3)
5
5
  i18n (~> 0.7)
@@ -21,7 +21,7 @@ GEM
21
21
  childprocess (0.3.9)
22
22
  ffi (~> 1.0, >= 1.0.11)
23
23
  cliver (0.2.2)
24
- diff-lcs (1.2.4)
24
+ diff-lcs (1.2.5)
25
25
  faraday (0.8.9)
26
26
  multipart-post (~> 1.2.0)
27
27
  ffi (1.9.3)
@@ -79,14 +79,19 @@ GEM
79
79
  rake (10.2.2)
80
80
  rdoc (4.1.1)
81
81
  json (~> 1.4)
82
- rspec (2.14.1)
83
- rspec-core (~> 2.14.0)
84
- rspec-expectations (~> 2.14.0)
85
- rspec-mocks (~> 2.14.0)
86
- rspec-core (2.14.6)
87
- rspec-expectations (2.14.3)
88
- diff-lcs (>= 1.1.3, < 2.0)
89
- rspec-mocks (2.14.4)
82
+ rspec (3.3.0)
83
+ rspec-core (~> 3.3.0)
84
+ rspec-expectations (~> 3.3.0)
85
+ rspec-mocks (~> 3.3.0)
86
+ rspec-core (3.3.2)
87
+ rspec-support (~> 3.3.0)
88
+ rspec-expectations (3.3.1)
89
+ diff-lcs (>= 1.2.0, < 2.0)
90
+ rspec-support (~> 3.3.0)
91
+ rspec-mocks (3.3.2)
92
+ diff-lcs (>= 1.2.0, < 2.0)
93
+ rspec-support (~> 3.3.0)
94
+ rspec-support (3.3.0)
90
95
  rubocop (0.34.0)
91
96
  astrolabe (~> 1.3)
92
97
  parser (>= 2.2.2.5, < 3.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.alpha13
1
+ 1.0.0.alpha17
@@ -19,12 +19,12 @@ module PageMagic
19
19
  Dir["#{path}/*.rb"].each do |driver_file|
20
20
  require driver_file
21
21
  driver_name = File.basename(driver_file)[/(.*)\.rb$/, 1]
22
- register eval(driver_name.classify)
22
+ register self.class.const_get(driver_name.classify)
23
23
  end
24
24
  end
25
25
 
26
26
  def ==(other)
27
- other.is_a?(Drivers) && other.all == other.all
27
+ other.is_a?(Drivers) && all == other.all
28
28
  end
29
29
  end
30
30
  end
@@ -0,0 +1,13 @@
1
+ module PageMagic
2
+ class Element
3
+ module MethodObserver
4
+ def singleton_method_added(arg)
5
+ @singleton_methods_added = true unless arg == :singleton_method_added
6
+ end
7
+
8
+ def singleton_methods_added?
9
+ @singleton_methods_added == true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module PageMagic
2
+ class Element
3
+ # class Query - models overall queries for Capybara, queries can include:
4
+ # - requirements on element type
5
+ # - selection criteria, modeled through the Selector class
6
+ # - options
7
+
8
+ class Query
9
+ class << self
10
+ def find(type)
11
+ query = constants.find { |constant| constant.to_s.downcase == type.to_s.downcase }
12
+ return ELEMENT unless query
13
+ const_get(query)
14
+ end
15
+ end
16
+
17
+ attr_reader :type
18
+
19
+ # @param type -
20
+ def initialize(type = nil)
21
+ @type = type
22
+ end
23
+
24
+ def build(locator, options = {})
25
+ [].tap do |array|
26
+ selector = Selector.find(locator.keys.first)
27
+ array << selector.build(type, locator.values.first)
28
+ array << options unless options.empty?
29
+ end.flatten
30
+ end
31
+
32
+ ELEMENT = Query.new
33
+ TEXT_FIELD = CHECKBOX = SELECT_LIST = RADIOS = TEXTAREA = Query.new(:field)
34
+ LINK = Query.new(:link)
35
+ BUTTON = Query.new(:button)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ module PageMagic
2
+ class Element
3
+ # class Selector - models the selection criteria understood by Capybara
4
+ class Selector
5
+ class << self
6
+ def find(name)
7
+ selector = constants.find { |constant| constant.to_s.downcase == name.to_s.downcase }
8
+ fail UnsupportedCriteriaException unless selector
9
+ const_get(selector)
10
+ end
11
+ end
12
+
13
+ def build(element_type, locator)
14
+ [].tap do |array|
15
+ array << element_type if supports_type
16
+ array << name if name
17
+ array << formatter.call(locator)
18
+ end
19
+ end
20
+
21
+ attr_reader :name, :formatter, :supports_type
22
+
23
+ def initialize(selector = nil, supports_type: false, &formatter)
24
+ @name = selector
25
+ @formatter = formatter || proc { |arg| arg }
26
+ @supports_type = supports_type
27
+ end
28
+
29
+ XPATH = Selector.new(:xpath, supports_type: false)
30
+ ID = Selector.new(:id, supports_type: false)
31
+ LABEL = Selector.new(:field, supports_type: false)
32
+
33
+ CSS = Selector.new(supports_type: false)
34
+ TEXT = Selector.new(supports_type: true)
35
+ NAME = Selector.new(supports_type: false) do |arg|
36
+ "*[name='#{arg}']"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ module PageMagic
2
+ class Element
3
+ module SelectorMethods
4
+ def selector(selector = nil)
5
+ return @selector unless selector
6
+ @selector = selector
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,53 +1,37 @@
1
+ require 'page_magic/element/method_observer'
2
+ require 'page_magic/element/selector_methods'
3
+ require 'page_magic/element/selector'
4
+ require 'page_magic/element/query'
1
5
  module PageMagic
2
- module MethodObserver
3
- def singleton_method_added(arg)
4
- @singleton_methods_added = true unless arg == :singleton_method_added
5
- end
6
-
7
- def singleton_methods_added?
8
- @singleton_methods_added == true
9
- end
10
- end
11
-
12
6
  class Element
13
7
  EVENT_TYPES = [:set, :select, :select_option, :unselect_option, :click]
14
- attr_reader :type, :name, :selector, :browser_element
8
+ DEFAULT_HOOK = proc {}.freeze
9
+ attr_reader :type, :name, :parent_page_element, :browser_element
15
10
 
16
- include Elements
11
+ include Elements, MethodObserver, SelectorMethods
12
+ extend SelectorMethods
17
13
 
18
14
  class << self
19
15
  def inherited(clazz)
20
16
  clazz.extend(Elements)
21
-
22
- def clazz.selector(selector = nil)
23
- return @selector unless selector
24
- @selector = selector
25
- end
26
17
  end
27
18
  end
28
19
 
29
- def initialize(name, parent_page_element, options, &block)
30
- options = {type: :element, selector: {}, browser_element: nil}.merge(options)
31
- @browser_element = options[:browser_element]
32
- @selector = options[:selector]
20
+ def initialize(name, parent_page_element, type: :element, selector: {}, browser_element: nil, &block)
21
+ @browser_element = browser_element
22
+ @selector = selector
33
23
 
34
- @before_hook = proc {}
35
- @after_hook = proc {}
24
+ @before_hook = DEFAULT_HOOK
25
+ @after_hook = DEFAULT_HOOK
36
26
  @parent_page_element = parent_page_element
37
- @type = options[:type]
27
+ @type = type
38
28
  @name = name.to_s.downcase.to_sym
39
-
40
- extend MethodObserver
41
- expand &block if block
29
+ expand(&block) if block
42
30
  end
43
31
 
44
32
  def expand(*args, &block)
45
- instance_exec *args, &block
46
- end
47
-
48
- def selector(selector = nil)
49
- return @selector unless selector
50
- @selector = selector
33
+ instance_exec(*args, &block)
34
+ self
51
35
  end
52
36
 
53
37
  def section?
@@ -68,19 +52,14 @@ module PageMagic
68
52
  @after_hook = block
69
53
  end
70
54
 
71
- def method_missing method, *args, &block
55
+ def method_missing(method, *args, &block)
56
+ ElementContext.new(self, browser_element, self, *args).send(method, args.first, &block)
57
+ rescue ElementMissingException
72
58
  begin
73
- ElementContext.new(self, browser_element, self, *args).send(method, args.first, &block)
74
- rescue ElementMissingException
75
- begin
76
- if browser_element.respond_to?(method)
77
- browser_element.send(method, *args, &block)
78
- else
79
- @parent_page_element.send(method, *args, &block)
80
- end
81
- rescue ElementMissingException
82
- super
83
- end
59
+ return browser_element.send(method, *args, &block) if browser_element.respond_to?(method)
60
+ return parent_page_element.send(method, *args, &block)
61
+ rescue NoMethodError, ElementMissingException
62
+ super
84
63
  end
85
64
  end
86
65
 
@@ -90,77 +69,59 @@ module PageMagic
90
69
 
91
70
  def browser_element(*_args)
92
71
  return @browser_element if @browser_element
93
- fail UndefinedSelectorException, 'Pass a selector/define one on the class' if @selector.empty?
94
- if @selector
95
- selector_copy = @selector.dup
96
- method = selector.keys.first
97
- selector = selector_copy.delete(method)
98
- options = selector_copy
99
-
100
- finder_method, selector_type, selector_arg = case method
101
- when :id
102
- [:find, "##{selector}"]
103
- when :xpath
104
- [:find, :xpath, selector]
105
- when :name
106
- [:find, "*[name='#{selector}']"]
107
- when :css
108
- [:find, :css, selector]
109
- when :label
110
- [:find_field, selector]
111
- when :text
112
- if @type == :link
113
- [:find_link, selector]
114
- elsif @type == :button
115
- [:find_button, selector]
116
- else
117
- fail UnsupportedSelectorException
118
- end
119
-
120
- else
121
- fail UnsupportedSelectorException
122
- end
123
-
124
- finder_args = [selector_type, selector_arg].compact
125
- finder_args << options unless options.empty?
126
- @browser_element = @parent_page_element.browser_element.send(finder_method, *finder_args).tap do |browser_element|
127
- EVENT_TYPES.each do |action_method|
128
- apply_hooks(page_element: browser_element,
129
- capybara_method: action_method,
130
- before_hook: before,
131
- after_hook: after)
132
- end
133
- end
134
72
 
135
- end
136
- end
73
+ fail UndefinedSelectorException, 'Pass a locator/define one on the class' if selector.empty?
137
74
 
138
- def apply_hooks(options)
139
- _self = self
140
- page_element = options[:page_element]
141
- capybara_method = options[:capybara_method]
142
- if page_element.respond_to?(capybara_method)
143
- original_method = page_element.method(capybara_method)
75
+ query = Query.find(type).build(query_selector, query_options)
144
76
 
145
- page_element.define_singleton_method capybara_method do |*arguments, &block|
146
- _self.call_hook &options[:before_hook]
147
- original_method.call *arguments, &block
148
- _self.call_hook &options[:after_hook]
149
- end
77
+ @browser_element = parent_browser_element.send(:find, *query).tap do |raw_element|
78
+ wrap_events(raw_element)
150
79
  end
151
80
  end
152
81
 
153
- def call_hook(&block)
154
- @executing_hooks = true
155
- result = instance_exec @browser, &block
156
- @executing_hooks = false
157
- result
82
+ def ==(other)
83
+ return false unless other.is_a?(Element)
84
+ this = [type, name, selector, before, after]
85
+ this == [other.type, other.name, other.selector, other.before, other.after]
158
86
  end
159
87
 
160
88
  private
161
89
 
90
+ def query_selector
91
+ Hash[*selector.first]
92
+ end
93
+
94
+ def query_options
95
+ selector.dup.delete_if { |key, _value| key == selector.keys.first }
96
+ end
97
+
162
98
  def element_context(*args)
163
99
  ElementContext.new(self, @browser_element, self, *args)
164
100
  end
101
+
102
+ def wrap_events(raw_element)
103
+ EVENT_TYPES.each do |action_method|
104
+ next unless raw_element.respond_to?(action_method)
105
+ apply_hooks(raw_element: raw_element,
106
+ capybara_method: action_method,
107
+ before_hook: before,
108
+ after_hook: after)
109
+ end
110
+ end
111
+
112
+ def parent_browser_element
113
+ parent_page_element.browser_element
114
+ end
115
+
116
+ def apply_hooks(raw_element:, capybara_method:, before_hook:, after_hook:)
117
+ original_method = raw_element.method(capybara_method)
118
+ this = self
119
+
120
+ raw_element.define_singleton_method(capybara_method) do |*arguments, &block|
121
+ this.instance_exec(&before_hook)
122
+ original_method.call(*arguments, &block)
123
+ this.instance_exec(&after_hook)
124
+ end
125
+ end
165
126
  end
166
127
  end
@@ -7,9 +7,8 @@ module PageMagic
7
7
 
8
8
  attr_reader :caller, :page_element
9
9
 
10
- def initialize(page_element, browser, caller, *_args)
10
+ def initialize(page_element, caller, *_args)
11
11
  @page_element = page_element
12
- @browser = browser
13
12
  @caller = caller
14
13
  end
15
14
 
@@ -18,13 +17,9 @@ module PageMagic
18
17
 
19
18
  element_locator_factory = page_element.element_definitions[method]
20
19
 
21
- raise ElementMissingException, "Could not find: #{method}" unless element_locator_factory
20
+ fail ElementMissingException, "Could not find: #{method}" unless element_locator_factory
22
21
 
23
- if args.empty?
24
- element_locator = element_locator_factory.call(page_element, nil)
25
- else
26
- element_locator = element_locator_factory.call(page_element, *args)
27
- end
22
+ element_locator = element_locator_factory.call(page_element, *args)
28
23
 
29
24
  element_locator.section? ? element_locator : element_locator.browser_element
30
25
  end
@@ -1,22 +1,17 @@
1
- require 'ext/string'
2
1
  require 'active_support/inflector'
3
2
  module PageMagic
4
3
  module Elements
5
- class InvalidElementNameException < Exception
6
- end
4
+ INVALID_METHOD_NAME_MSG = 'a method already exists with this method name'
7
5
 
8
- class InvalidMethodNameException < Exception
6
+ module InstanceOnlyMethods
7
+ def element_definitions
8
+ self.class.element_definitions
9
+ end
9
10
  end
10
11
 
11
12
  def self.extended(clazz)
12
13
  clazz.class_eval do
13
- unless instance_methods.include?(:browser_element)
14
- attr_reader :browser_element
15
- end
16
-
17
- def element_definitions
18
- self.class.element_definitions
19
- end
14
+ include InstanceOnlyMethods
20
15
  end
21
16
  end
22
17
 
@@ -28,36 +23,30 @@ module PageMagic
28
23
  element_definitions.values.collect { |definition| definition.call(browser_element, *args) }
29
24
  end
30
25
 
31
- TYPES = [:element, :text_field, :button, :link, :checkbox, :select_list, :radios, :textarea, :section]
32
-
33
-
34
- TYPES.each do |type|
35
- define_method type do |*args, &block|
36
-
37
- section_class = remove_argument(args, Class) || Element
38
-
39
- selector = remove_argument(args, Hash)
40
- selector ||= section_class.selector if section_class.respond_to?(:selector)
41
-
42
- name = remove_argument(args, Symbol)
43
- name ||= section_class.name.demodulize.to_snake_case.to_sym unless section_class.is_a?(Element)
26
+ def element(*args, &block)
27
+ block ||= proc {}
44
28
 
45
- options = selector ? {selector: selector} : {browser_element: args.delete_at(0)}
29
+ section_class = remove_argument(args, Class) || Element
30
+ selector = compute_selector(args, section_class)
31
+ name = compute_name(args, section_class)
46
32
 
47
- add_element_definition(name) do |parent_browser_element, *e_args|
48
- section_class.new(name, parent_browser_element, options.merge(type: type)).tap do |section|
49
- section.expand(*e_args, &(block || proc {}))
50
- end
51
- end
33
+ options = { type: __callee__ }
34
+ selector ? options[:selector] = selector : options[:browser_element] = args.delete_at(0)
52
35
 
36
+ add_element_definition(name) do |parent_browser_element, *e_args|
37
+ section_class.new(name, parent_browser_element, options).expand(*e_args, &block)
53
38
  end
54
39
  end
55
40
 
41
+ TYPES = [:text_field, :button, :link, :checkbox, :select_list, :radios, :textarea]
42
+
43
+ TYPES.each { |type| alias_method type, :element }
44
+
56
45
  def add_element_definition(name, &block)
57
46
  fail InvalidElementNameException, 'duplicate page element defined' if element_definitions[name]
58
47
 
59
48
  methods = respond_to?(:instance_methods) ? instance_methods : methods()
60
- fail InvalidElementNameException, 'a method already exists with this method name' if methods.find { |method| method == name }
49
+ fail InvalidElementNameException, INVALID_METHOD_NAME_MSG if methods.find { |method| method == name }
61
50
 
62
51
  element_definitions[name] = block
63
52
  end
@@ -67,9 +56,20 @@ module PageMagic
67
56
  end
68
57
 
69
58
  private
59
+
70
60
  def remove_argument(args, clazz)
71
61
  argument = args.find { |arg| arg.is_a?(clazz) }
72
62
  args.delete(argument)
73
63
  end
64
+
65
+ def compute_name(args, section_class)
66
+ name = remove_argument(args, Symbol)
67
+ name || section_class.name.demodulize.underscore.to_sym unless section_class.is_a?(Element)
68
+ end
69
+
70
+ def compute_selector(args, section_class)
71
+ selector = remove_argument(args, Hash)
72
+ selector || section_class.selector if section_class.respond_to?(:selector)
73
+ end
74
74
  end
75
75
  end
@@ -6,4 +6,13 @@ module PageMagic
6
6
 
7
7
  class MissingLocatorOrSelector < Exception
8
8
  end
9
+
10
+ class InvalidElementNameException < Exception
11
+ end
12
+
13
+ class InvalidMethodNameException < Exception
14
+ end
15
+
16
+ class UnsupportedCriteriaException < Exception
17
+ end
9
18
  end
@@ -1,21 +1,20 @@
1
1
  module PageMagic
2
- attr_reader :browser, :session
2
+ attr_reader :browser, :session, :browser_element
3
3
 
4
- def initialize(session = Session.new(Capybara.current_session), options = {}, &block)
4
+ def initialize(session = Session.new(Capybara.current_session), &block)
5
5
  @browser = session.raw_session
6
6
  @session = session
7
7
 
8
- @browser_element = @browser
9
- navigate if options[:navigate_to_page]
10
- block.call @browser if block
8
+ @browser_element = browser
9
+ block.call browser if block
11
10
  end
12
11
 
13
12
  def title
14
13
  browser.title
15
14
  end
16
15
 
17
- def text_on_page?(text)
18
- text().downcase.include?(text.downcase)
16
+ def text_on_page?(string)
17
+ text.downcase.include?(string.downcase)
19
18
  end
20
19
 
21
20
  def visit
@@ -36,6 +35,6 @@ module PageMagic
36
35
  end
37
36
 
38
37
  def element_context
39
- ElementContext.new(self, @browser, self)
38
+ ElementContext.new(self, self)
40
39
  end
41
40
  end