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.
- checksums.yaml +4 -4
- data/lib/insite.rb +13 -7
- data/lib/insite/component/component.rb +454 -0
- data/lib/insite/component/component_collection.rb +112 -0
- data/lib/insite/component/component_instance_methods.rb +4 -0
- data/lib/insite/component/component_methods.rb +4 -0
- data/lib/insite/constants.rb +323 -31
- data/lib/insite/element/element.rb +147 -0
- data/lib/insite/element/element_collection.rb +102 -0
- data/lib/insite/element/generated/class_map.rb +244 -0
- data/lib/insite/element/generated/element_classes.rb +721 -0
- data/lib/insite/element/generated/element_instance_methods.rb +1594 -0
- data/lib/insite/errors.rb +2 -0
- data/lib/insite/examples/material_angular_io/components/angular_material_component.rb +13 -0
- data/lib/insite/examples/material_angular_io/components/example_viewer.rb +5 -0
- data/lib/insite/examples/material_angular_io/components/mat_chip.rb +29 -0
- data/lib/insite/examples/material_angular_io/components/mat_chip_list.rb +21 -0
- data/lib/insite/examples/material_angular_io/components/mat_form_field.rb +5 -0
- data/lib/insite/examples/material_angular_io/components/mat_icon.rb +5 -0
- data/lib/insite/examples/material_angular_io/components/mat_input.rb +5 -0
- data/lib/insite/examples/material_angular_io/components/mat_option.rb +3 -0
- data/lib/insite/examples/material_angular_io/components/mat_select.rb +15 -0
- data/lib/insite/examples/material_angular_io/components/mat_select_content.rb +13 -0
- data/lib/insite/examples/material_angular_io/components/no_selector.rb +3 -0
- data/lib/insite/examples/material_angular_io/pages.rb +20 -0
- data/lib/insite/examples/material_angular_io/site.rb +5 -0
- data/lib/insite/examples/material_angular_io/utils.rb +6 -0
- data/lib/insite/examples/material_angular_io/watir_mods.rb +54 -0
- data/lib/insite/examples/material_angular_io_site.rb +54 -0
- data/lib/insite/insite.rb +96 -11
- data/lib/insite/methods/common_methods.rb +26 -37
- data/lib/insite/methods/dom_methods.rb +73 -46
- data/lib/insite/page/defined_page.rb +37 -50
- data/lib/insite/page/undefined_page.rb +12 -1
- data/lib/insite/version.rb +1 -1
- metadata +69 -29
- data/lib/insite/element_container/element_container.rb +0 -42
- data/lib/insite/feature/feature.rb +0 -30
- data/lib/insite/widget/widget.rb +0 -346
- 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
|
data/lib/insite/widget/widget.rb
DELETED
@@ -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
|