insite 0.0.2 → 0.0.5

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 (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