insite 0.0.2 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|