insite 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/insite.rb +13 -7
  3. data/lib/insite/component/component.rb +454 -0
  4. data/lib/insite/component/component_collection.rb +112 -0
  5. data/lib/insite/component/component_instance_methods.rb +4 -0
  6. data/lib/insite/component/component_methods.rb +4 -0
  7. data/lib/insite/constants.rb +323 -31
  8. data/lib/insite/element/element.rb +147 -0
  9. data/lib/insite/element/element_collection.rb +102 -0
  10. data/lib/insite/element/generated/class_map.rb +244 -0
  11. data/lib/insite/element/generated/element_classes.rb +721 -0
  12. data/lib/insite/element/generated/element_instance_methods.rb +1594 -0
  13. data/lib/insite/errors.rb +2 -0
  14. data/lib/insite/examples/material_angular_io/components/angular_material_component.rb +13 -0
  15. data/lib/insite/examples/material_angular_io/components/example_viewer.rb +5 -0
  16. data/lib/insite/examples/material_angular_io/components/mat_chip.rb +29 -0
  17. data/lib/insite/examples/material_angular_io/components/mat_chip_list.rb +21 -0
  18. data/lib/insite/examples/material_angular_io/components/mat_form_field.rb +5 -0
  19. data/lib/insite/examples/material_angular_io/components/mat_icon.rb +5 -0
  20. data/lib/insite/examples/material_angular_io/components/mat_input.rb +5 -0
  21. data/lib/insite/examples/material_angular_io/components/mat_option.rb +3 -0
  22. data/lib/insite/examples/material_angular_io/components/mat_select.rb +15 -0
  23. data/lib/insite/examples/material_angular_io/components/mat_select_content.rb +13 -0
  24. data/lib/insite/examples/material_angular_io/components/no_selector.rb +3 -0
  25. data/lib/insite/examples/material_angular_io/pages.rb +20 -0
  26. data/lib/insite/examples/material_angular_io/site.rb +5 -0
  27. data/lib/insite/examples/material_angular_io/utils.rb +6 -0
  28. data/lib/insite/examples/material_angular_io/watir_mods.rb +54 -0
  29. data/lib/insite/examples/material_angular_io_site.rb +54 -0
  30. data/lib/insite/insite.rb +96 -11
  31. data/lib/insite/methods/common_methods.rb +26 -37
  32. data/lib/insite/methods/dom_methods.rb +73 -46
  33. data/lib/insite/page/defined_page.rb +37 -50
  34. data/lib/insite/page/undefined_page.rb +12 -1
  35. data/lib/insite/version.rb +1 -1
  36. metadata +69 -29
  37. data/lib/insite/element_container/element_container.rb +0 -42
  38. data/lib/insite/feature/feature.rb +0 -30
  39. data/lib/insite/widget/widget.rb +0 -346
  40. data/lib/insite/widget/widget_methods.rb +0 -4
@@ -1,42 +0,0 @@
1
- class ElementContainer
2
- attr_reader :target, :site
3
-
4
- include Insite::CommonMethods
5
- extend Forwardable
6
-
7
- class << self
8
- include Insite::DOMMethods
9
- end # self
10
-
11
- def initialize(site, element)
12
- @site = site
13
- @browser = @site.browser
14
- @target = element
15
-
16
- # Temporary replacement for custom wait_until.
17
- # TODO: Continue looking at scolling solutions.
18
- if @target.present?
19
- @target.scroll.to
20
- t = Time.now + 2
21
- while Time.now <= t do
22
- break if @target.present?
23
- sleep 0.1
24
- end
25
- end
26
-
27
- @target
28
- end
29
-
30
- # For page widget code.
31
- def method_missing(sym, *args, &block)
32
- if @target.respond_to? sym
33
- if @target.is_a? Watir::ElementCollection
34
- @target.map { |x| self.class.new(x) }.send(sym, *args, &block)
35
- else
36
- @target.send(sym, *args, &block)
37
- end
38
- else
39
- super
40
- end
41
- end
42
- end
@@ -1,30 +0,0 @@
1
- module Insite
2
- class Feature
3
- attr_reader :args, :page, :page_elements, :browser
4
-
5
- include Insite::CommonMethods
6
-
7
- class << self
8
- attr_accessor :alias, :page_elements
9
-
10
- include Insite::DOMMethods
11
- include Insite::WidgetMethods
12
- end # Self.
13
-
14
- def initialize(site, **args)
15
- if self.class.ancestors.include?(Insite::DefinedPage)
16
- # if site.is_a? Insite:DefinedPage # TODO: Bandaid.
17
- @site = site.site
18
- @page = site
19
- elsif site.class.ancestors.include?(Insite)
20
- @site = site
21
- @page = site.page
22
- end
23
-
24
- @args = args
25
- @page = page
26
- @browser = @site.browser
27
- @page_elements = @site.page_elements
28
- end
29
- end
30
- end
@@ -1,346 +0,0 @@
1
- require_relative 'widget_methods'
2
- require_relative '../methods/common_methods'
3
-
4
- # Allows the page object developer to encapsulate common web application features
5
- # into a "widget" that can be reused across multiple pages. Let's say that a
6
- # web application has a search widget that is used in 11 of the application's pages.
7
- # With a modern web app all of those search widgets will likely be implemented
8
- # in a common way, with a similar or identical structure in the HTML. The widget
9
- # would look something like this:
10
- #
11
- # class SearchWidget < Widget
12
- # text_field :query, id: 'q'
13
- # button :search_button, name: 'Search'
14
- #
15
- # def search(search_query)
16
- # query.set search_query
17
- # search_button.click
18
- # end
19
- #
20
- # def clear
21
- # query.set ''
22
- # search_button.click
23
- # end
24
- # end
25
- #
26
- # Once the widget has been defined, it can be included in a page object definition
27
- # like this:
28
- #
29
- # class SomePage < SomeSite::Page
30
- # set_url 'some_page'
31
- # search_widget :search_for_foo, :div, class: 'search-div'
32
- # end
33
- #
34
- # The search widget can then be accessed like this when working with the site:
35
- # site.some_page.search_for_foo 'some search term'
36
- # site.search_for_foo.clear
37
- #
38
- # Widgets can be embedded in other widgets, but in that case, the arguments for
39
- # accessing the child widget need to be RELATIVE to the parent widget. For example:
40
- #
41
- # # Generic link menu, you hover over it and one or more links are displayed.
42
- # class LinkMenu < Widget
43
- # end
44
- #
45
- # # Card widget that uses the link_menu widget. In this case, link_menu widget
46
- # # arguments will be used to find a div a div with class == 'card-action-links'
47
- # # WITHIN the card itself. This ensures that, if there are multiple cards
48
- # # on the page that have link_menus, the CORRECT link_menu will be accessed
49
- # # rather than one for some other card widget.
50
- # class Card < Widget
51
- # link_menu :card_menu, :div, class: 'card-action-links'
52
- # end
53
- module Insite
54
- class Widget
55
- attr_reader :site, :browser, :type, :args, :target
56
-
57
- include CommonMethods
58
- alias_method :update_widget, :update_object
59
-
60
- class << self
61
- attr_reader :widget_elements
62
-
63
- include DOMMethods
64
- include WidgetMethods
65
-
66
- # - Don't allow the user to create a widget with a name that matches a DOM
67
- # element.
68
- #
69
- # - Don't allow the user to create a widget method that references a
70
- # collection (because this will be done automatically.)
71
- tmp = name.to_s.underscore.to_sym
72
- if DOM_METHODS.include?(name.to_s.underscore.to_sym)
73
- raise "#{name} cannot be used as a widget name, as the methodized version of the class name (#{name.to_s.underscore} conflicts with a Watir DOM method.)"
74
- elsif Watir::Browser.methods.include?(name.to_s.underscore.to_sym)
75
- raise "#{name} cannot be used as a widget name, as the methodized version of the class name (#{name.to_s.underscore} conflicts with a Watir::Browser method.)"
76
- end
77
-
78
- if tmp =~ /.*s+/
79
- raise "Invalid widget type :#{tmp}. You can create a widget for the DOM object but it must be for :#{tmp.singularize} (:#{tmp} will be created automatically.)"
80
- end
81
- end # Self.
82
-
83
- extend Forwardable
84
-
85
- def self.inherited(subclass)
86
- name_string = subclass.name.demodulize.underscore
87
- pluralized_name_string = name_string.pluralize
88
-
89
- if name_string == pluralized_name_string
90
- raise ArgumentError, "When defining a new widget, define the singular version only (Plural case will be handled automatically.)"
91
- end
92
-
93
- # Creates an accessor method for a new widget when one gets defined via inheritance.
94
- # In this case the method is for a single instance of the widget. The top-level block
95
- # defines the widget accessor in the Insite::Widget module. The methods in that module
96
- # automatically get included in page classes and are used to define widget accessors.
97
- #
98
- # In this case an accessor for an individual widget is being defined.
99
- WidgetMethods.send(:define_method, name_string) do |method_name, dom_type, *args, &block|
100
- unless name_string == 'Widget'
101
- @widget_elements ||= []
102
- @widget_elements << method_name.to_sym unless @widget_elements.include?(method_name.to_sym)
103
-
104
- define_method(method_name) do
105
- if is_a? Widget
106
- elem = send(dom_type, *args, &block)
107
- else
108
- elem = @browser.send(dom_type, *args, &block)
109
- end
110
-
111
- # TODO: Bandaid.
112
- if dom_type.to_s == dom_type.to_s.pluralize
113
- raise ArgumentError, "Individual widget method :#{method_name} cannot initialize a widget using an element collection (#{elem.class}.) Use :#{method_name.pluralize} rather than :#{method_name} if you want to define a widget collection."
114
- else
115
- subclass.new(self, dom_type, *args, &block)
116
- end
117
- end
118
- end
119
- end
120
-
121
- # Creates an accessor method for a new widget when one gets defined via inheritance.
122
- # In this case the method is for a single instance of the widget. The top-level block
123
- # defines the widget accessor in the Insite::Widget module. The methods in that module
124
- # automatically get included in page classes and are used to define widget accessors.
125
- #
126
- # In this case an accessor is being defined for a widget collection.
127
- #
128
- # TODO: The current implementation for widget collections isn't ideal and should be
129
- # replaced at some point. It'd be much better to use a (lazy) custom collection for this.
130
- WidgetMethods.send(:define_method, pluralized_name_string) do |method_name, dom_type, *args, &block|
131
- unless name_string == 'Widget'
132
- @widget_elements ||= []
133
- @widget_elements << method_name.to_sym unless @widget_elements.include?(method_name.to_sym)
134
-
135
- define_method(method_name) do
136
- if is_a?(Widget) && present?
137
- elem = send(dom_type, *args, &block)
138
- elsif is_a?(Widget) && !present?
139
- return []
140
- else
141
- elem = @browser.send(dom_type, *args, &block)
142
- end
143
-
144
- # TODO: Bandaid.
145
- if dom_type.to_s == dom_type.to_s.singularize
146
- raise ArgumentError, "Widget collection method :#{method_name} cannot initialize a widget collection using an individual element (#{elem.class}.) Use :#{method_name.to_s.singularize} rather than :#{method_name} if you want to define a widget for an individual element."
147
- else
148
- # TODO: Revisit the whole .to_a thing, need a custom collection or
149
- # somesuch (don't bypass watir wait logic.)
150
- t = Time.now
151
- loop do
152
- elem = @browser.send(dom_type, *args, &block)
153
- break if elem.present? && elem.length > 0
154
- break if Time.now > t + 4
155
- end
156
-
157
- if elem.present?
158
- elem.to_a.map! { |x| subclass.new(self, x, [], &block) }
159
- else
160
- []
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end # self.
167
-
168
- # This method gets used 2 different ways. Most of the time, dom_type and args
169
- # will be a symbol and a set of hash arguments that will be used to locate an
170
- # element.
171
- #
172
- # In some cases, dom_type can be a Watir DOM object, and in this case, the
173
- # args are ignored and the widget is initialized using the Watir object.
174
- #
175
- # TODO: Needs a rewrite, lines between individual and collection are blurred
176
- # here and that makes the code more confusing. And there should be a proper
177
- # collection class for element collections, with possibly some AR-like accessors.
178
- def initialize(parent, dom_type, *args)
179
- @parent = parent
180
- @site = parent.class.ancestors.include?(Insite) ? parent : parent.site
181
- @browser = @site.browser
182
- @widget_elements = self.class.widget_elements
183
-
184
- if dom_type.is_a?(Watir::HTMLElement) || dom_type.is_a?(Watir::Element)
185
- @dom_type = nil
186
- @args = nil
187
- @target = dom_type
188
- elsif [String, Symbol].include? dom_type.class
189
- @dom_type = dom_type
190
- @args = args
191
-
192
- if @parent.is_a? Widget
193
- @target = @parent.send(dom_type, *args)
194
- else
195
- @target = @browser.send(dom_type, *args)
196
- end
197
-
198
- # New webdriver approach.
199
- begin
200
- @target.scroll.to
201
- sleep 0.1
202
- rescue => e
203
- t = Time.now + 2
204
- while Time.now <= t do
205
- break if @target.present?
206
- sleep 0.1
207
- end
208
- end
209
- elsif dom_type.is_a? Watir::ElementCollection
210
- @dom_type = nil
211
- @args = nil
212
- if @parent.is_a? Widget
213
- @target = dom_type.map { |x| self.class.new(@parent, x.to_subtype) }
214
- else
215
- @target = dom_type.map { |x| self.class.new(@site, x.to_subtype) }
216
- end
217
- else
218
- raise "Unhandled exception."
219
- end
220
- end
221
-
222
- # Delegates method calls down to the widget's wrapped element if the element supports the method.
223
- #
224
- # Supports dynamic link methods. Examples:
225
- # s.accounts_page account
226
- #
227
- # # Nav to linked page only.
228
- # s.account_actions.edit_account_info
229
- #
230
- # # Update linked page after nav:
231
- # s.account_actions.edit_account_info username: 'foo'
232
- #
233
- # # Link with modal (if the modal requires args they should be passed as hash keys):
234
- # # s.hosted_pages.refresh_urls
235
- def method_missing(mth, *args, &block)
236
- if @target.respond_to? mth
237
- @target.send(mth, *args, &block)
238
- else
239
- if args[0].is_a? Hash
240
- page_arguments = args[0]#.with_indifferent_access
241
- elsif args.empty?
242
- # Do nothing.
243
- elsif args[0].nil?
244
- raise ArgumentError, "Optional argument for :#{mth} must be a hash. Got NilClass."
245
- else
246
- raise ArgumentError, "Optional argument must be a hash (got #{args[0].class}.)"
247
- end
248
-
249
- if present?
250
- # If it's a widget we want to hover over it to ensure links are visible
251
- # before trying to find them.
252
- if self.is_a?(Widget)
253
- t = Time.now
254
- loop do
255
- begin
256
- scroll.to
257
- hover
258
- sleep 0.2
259
- break
260
- rescue => e
261
- break if Time.now > t + 10
262
- sleep 0.2
263
- end
264
- end
265
- end
266
-
267
- # Dynamic helper method, returns DOM object for link (no validation).
268
- if mth.to_s =~ /_link$/
269
- return a(text: /^#{mth.to_s.sub(/_link$/, '').gsub('_', '.*')}/i)
270
- # Dynamic helper method, returns DOM object for button (no validation).
271
- elsif mth.to_s =~ /_button$/
272
- return button(value: /^#{mth.to_s.sub(/_button$/, '').gsub('_', '.*')}/i)
273
- # Dynamic helper method for links. If a match is found, clicks on the link and performs follow up actions.
274
- elsif elem = as.find { |x| x.text =~ /^#{mth.to_s.gsub('_', '.*')}/i } # See if there's a matching button and treat it as a method call if so.
275
- elem.click
276
- sleep 1
277
-
278
- current_page = @site.page
279
-
280
- if page_arguments.present?
281
-
282
- if current_page.respond_to?(:submit)
283
- current_page.submit page_arguments
284
- elsif @browser.input(xpath: "//div[starts-with(@class,'Row') and last()]//input[@type='submit' and last()]").present?
285
- current_page.update_page page_arguments
286
- @browser.input(xpath: "//div[starts-with(@class,'Row') and last()]//input[@type='submit' and last()]").click
287
- end
288
- current_page = @site.page
289
- end
290
- # Dynamic helper method for buttons. If a match is found, clicks on the link and performs follow up actions.
291
- elsif elem = buttons.find { |x| x.text =~ /^#{mth.to_s.gsub('_', '.*')}/i } # See if there's a matching button and treat it as a method call if so.
292
- elem.click
293
- sleep 1
294
-
295
- if @site.modal.present?
296
- @site.modal.continue(page_arguments)
297
- else
298
- current_page = @site.page
299
-
300
- if page_arguments.present?
301
- if current_page.respond_to?(:submit)
302
- current_page.submit page_arguments
303
- elsif @browser.input(xpath: "//div[starts-with(@class,'Row') and last()]//input[@type='submit' and last()]").present?
304
- current_page.update_page page_arguments
305
- @browser.input(xpath: "//div[starts-with(@class,'Row') and last()]//input[@type='submit' and last()]").click
306
- end
307
- current_page = @site.page
308
- end
309
- end
310
- else
311
- raise NoMethodError, "undefined method `#{mth}' for #{self.class}."
312
- end
313
- else
314
- raise NoMethodError, "Unhandled method call `#{mth}' for #{self.class} (The widget was not present in the DOM at the point that the method was called.)"
315
- end
316
-
317
- page_arguments.present? ? page_arguments : current_page
318
- end
319
- end
320
-
321
- def present?
322
- sleep 0.1
323
- begin
324
- if @parent
325
- if @parent.present? && @target.present?
326
- true
327
- else
328
- false
329
- end
330
- else
331
- if @target.present?
332
- true
333
- else
334
- false
335
- end
336
- end
337
- rescue => e
338
- false
339
- end
340
- end
341
- end
342
- end
343
-
344
- # TODO: For legacy code, should be removed.
345
- class Widget < Insite::Widget
346
- end
@@ -1,4 +0,0 @@
1
- module Insite
2
- module WidgetMethods
3
- end
4
- end