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
data/lib/insite/errors.rb
CHANGED
@@ -6,10 +6,12 @@ module Insite
|
|
6
6
|
class Insite::Errors::BrowserNotOpenError < StandardError; end
|
7
7
|
class Insite::Errors::BrowserNotValidError < StandardError; end
|
8
8
|
class Insite::Errors::BrowserResponseError < StandardError; end
|
9
|
+
class Insite::Errors::ComponentReferenceError < StandardError; end
|
9
10
|
class Insite::Errors::PageConfigError < StandardError; end
|
10
11
|
class Insite::Errors::PageInitError < StandardError; end
|
11
12
|
class Insite::Errors::PageNavigationError < StandardError; end
|
12
13
|
class Insite::Errors::PageNavigationNotAllowedError < StandardError; end
|
14
|
+
class Insite::Errors::ComponentSelectorError < StandardError; end
|
13
15
|
class Insite::Errors::SiteInitError < StandardError; end
|
14
16
|
class Insite::Errors::WrongPageError < StandardError; end
|
15
17
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AngularMaterialComponent < MaterialAngularIO::Component
|
2
|
+
attr_reader :ngcontent, :nghost
|
3
|
+
|
4
|
+
def ngcontent
|
5
|
+
@ngcontent ||=
|
6
|
+
attributes.keys.find { |k| k.match(/^_ngcontent-.*/) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def nghost
|
10
|
+
@nghost ||=
|
11
|
+
attributes.keys.find { |k| k.match(/^_nghost-.*/) }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'angular_material_component'
|
2
|
+
|
3
|
+
class MatChip < MaterialAngularIO::Component
|
4
|
+
select_by tag_name: 'mat-chip'
|
5
|
+
|
6
|
+
def disabled?
|
7
|
+
element(class: 'mat-chip-disabled').exist?
|
8
|
+
end
|
9
|
+
|
10
|
+
def label
|
11
|
+
nokogiri.xpath('//text()')[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove
|
15
|
+
element(class: 'mat-chip-remove').click
|
16
|
+
end
|
17
|
+
|
18
|
+
def removable?
|
19
|
+
element(class: 'mat-chip-remove').exist?
|
20
|
+
end
|
21
|
+
|
22
|
+
def selected?
|
23
|
+
aria_selected == 'true'
|
24
|
+
end
|
25
|
+
|
26
|
+
def selectable?
|
27
|
+
element(class: 'mat-chip-select').exist?
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'angular_material_component'
|
2
|
+
require 'pry'
|
3
|
+
class MatChipList < AngularMaterialComponent
|
4
|
+
select_by tag_name: 'mat-chip-list'
|
5
|
+
|
6
|
+
def add(value)
|
7
|
+
mat_input(ngcontent => true).set(value + "\n")
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear_input(value)
|
11
|
+
mat_input(ngcontent => true).clear
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove(value)
|
15
|
+
mat_input(ngcontent => true).mat_icon.click
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_input(value)
|
19
|
+
mat_input(ngcontent => true).set(value)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class MatSelect < MaterialAngularIO::Component
|
2
|
+
select_by tag_name: 'mat-select'
|
3
|
+
|
4
|
+
def options(*args)
|
5
|
+
mat_options(parse_args(args).merge!(css: "[#{ngcontent}='']"))
|
6
|
+
end
|
7
|
+
|
8
|
+
def option(*args)
|
9
|
+
mat_option(parse_args(args).merge!(css: "[#{ngcontent}='']"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def select(*values)
|
13
|
+
values.each { |val| option(text: process_value(val)) }.click
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class MatSelectContent < MaterialAngularIO::Component
|
2
|
+
select_by class: 'mat-select-content'
|
3
|
+
|
4
|
+
# def select(**values)
|
5
|
+
# values.each do |v|
|
6
|
+
# if tmp = options.find { |opt| opt.text =~ /#{v}/i}
|
7
|
+
# tmp.click
|
8
|
+
# else
|
9
|
+
# raise ArgumentError, ""
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'site'
|
2
|
+
|
3
|
+
# Auto-generates some of material.angular.io pages
|
4
|
+
%w(autocomplete checkbox datepicker form-field input radio
|
5
|
+
select slider slide-toggle menu sidenav toolbar card divider
|
6
|
+
expansion grid-list list stepper tabs tree button button-toggle
|
7
|
+
badge chips icon progress-spinner progress-bar bottom-sheet
|
8
|
+
dialog snackbar tooltip paginator sort table
|
9
|
+
).each do |component|
|
10
|
+
%w(overview api examples).each do |category|
|
11
|
+
klass = Class.new(MaterialAngularIO::Page) do
|
12
|
+
set_url "/components/#{component}/#{category}"
|
13
|
+
end
|
14
|
+
|
15
|
+
Object.const_set(
|
16
|
+
"#{component.gsub('-', '_')}_#{category}_page".camelcase,
|
17
|
+
klass
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# https://github.com/watir/watir/pull/737
|
2
|
+
# TODO: Already merged to master, can remove after next release.
|
3
|
+
module Watir
|
4
|
+
module Locators
|
5
|
+
class Element
|
6
|
+
class SelectorBuilder
|
7
|
+
class XPath
|
8
|
+
# @todo Get rid of building
|
9
|
+
def lhs_for(_building, key)
|
10
|
+
case key
|
11
|
+
when :text, 'text'
|
12
|
+
'normalize-space()'
|
13
|
+
when String
|
14
|
+
"@#{key}"
|
15
|
+
when :href
|
16
|
+
# TODO: change this behaviour?
|
17
|
+
'normalize-space(@href)'
|
18
|
+
when :type
|
19
|
+
# type attributes can be upper case - downcase them
|
20
|
+
# https://github.com/watir/watir/issues/72
|
21
|
+
XpathSupport.downcase('@type')
|
22
|
+
else
|
23
|
+
"@#{key.to_s.tr("_", "-")}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# https://github.com/watir/watir/blob/0943e7e6a88f58ad6bea0e33a9853e6f5676f31c/lib/watir/element_collection.rb
|
33
|
+
# TODO: Already merged to master, can remove after next release.
|
34
|
+
module Watir
|
35
|
+
class ElementCollection
|
36
|
+
def to_a
|
37
|
+
hash = {}
|
38
|
+
@to_a ||=
|
39
|
+
elements.map.with_index do |e, idx|
|
40
|
+
element = element_class.new(@query_scope, @selector.merge(element: e, index: idx))
|
41
|
+
if [Watir::HTMLElement, Watir::Input].include? element.class
|
42
|
+
tag_name = element.tag_name.to_sym
|
43
|
+
hash[tag_name] ||= 0
|
44
|
+
hash[tag_name] += 1
|
45
|
+
Watir.element_class_for(tag_name).new(@query_scope, @selector.merge(element: e,
|
46
|
+
tag_name: tag_name,
|
47
|
+
index: hash[tag_name] - 1))
|
48
|
+
else
|
49
|
+
element
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Require support files.
|
2
|
+
%w(watir_mods site pages utils).each do |f|
|
3
|
+
require "insite/examples/material_angular_io/#{f}"
|
4
|
+
end
|
5
|
+
|
6
|
+
# Require hand-crafted artisanal components.
|
7
|
+
%w(example_viewer mat_chip_list mat_chip mat_icon mat_form_field
|
8
|
+
mat_input mat_option mat_select_content mat_select no_selector).each do |c|
|
9
|
+
require "insite/examples/material_angular_io/components/#{c}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Most of the pages for material.angular.io are auto-generated because
|
13
|
+
# their URLs match a pattern. But some custom page definitions are included
|
14
|
+
# below. This first overwrites the auto-generated page definition for
|
15
|
+
# the /chips/overview page, adding some additional stuff that is used
|
16
|
+
# in some of the automated tests.
|
17
|
+
class ChipsOverviewPage < MaterialAngularIO::Page
|
18
|
+
set_url "/components/chips/overview"
|
19
|
+
|
20
|
+
no_selector :test_no_selector
|
21
|
+
no_selector :test_no_selector_with_args, tag_name: :div, index: 3
|
22
|
+
|
23
|
+
elements :mat_examples_collection, tag_name: 'example-viewer'
|
24
|
+
elements :modified_mat_examples_collection, tag_name: 'example-viewer' do
|
25
|
+
def test_method
|
26
|
+
'output'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
elements :examples_collection, tag_name: 'example-viewer'
|
31
|
+
elements :modified_examples_collection, tag_name: 'example-viewer' do
|
32
|
+
def test_method
|
33
|
+
'output'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
mat_chip :chip, tag_name: 'mat-chip'
|
38
|
+
element :modified_chip, tag_name: 'mat-chip' do
|
39
|
+
def test_method
|
40
|
+
text
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
element :chip_element, tag_name: 'mat-chip'
|
45
|
+
element :modified_chip_element, tag_name: 'mat-chip' do
|
46
|
+
def test_method
|
47
|
+
text
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
mat_chip_list :basic_chips, index: 0
|
52
|
+
mat_chip_list :chips_with_input, index: 1
|
53
|
+
mat_chip_list :chips_autocomplete, index: 2
|
54
|
+
end
|
data/lib/insite/insite.rb
CHANGED
@@ -10,19 +10,44 @@ module Insite
|
|
10
10
|
attr_reader :base_url, :unique_methods, :browser_type
|
11
11
|
attr_accessor :pages, :browser, :arguments, :most_recent_page
|
12
12
|
|
13
|
+
def self.class_to_tag(klass)
|
14
|
+
if klass.respond_to?(:collection) && klass.collection?
|
15
|
+
Watir.tag_to_class.key(klass) ||
|
16
|
+
Watir.tag_to_class.key(CLASS_MAP.key(klass)) ||
|
17
|
+
Watir.tag_to_class.key(CLASS_MAP.key(klass.collection_member_class))
|
18
|
+
else
|
19
|
+
Watir.tag_to_class.key(klass) ||
|
20
|
+
Watir.tag_to_class.key(CLASS_MAP.key(klass))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.tag_to_class(tag)
|
25
|
+
CLASS_MAP[Watir.tag_to_class[tag]] || Insite::HTMLElement
|
26
|
+
end
|
27
|
+
|
13
28
|
# Automatically sets up a Page class when Insite is included. Probably overkill
|
14
29
|
# but it protects against the case where two different sites are used at the
|
15
30
|
# same time: Each site will use its own page objects only.
|
16
31
|
def self.included(base)
|
17
32
|
mod = Module.new
|
18
|
-
base.const_set('
|
33
|
+
base.const_set('ComponentMethods', mod)
|
19
34
|
|
20
35
|
klass = Class.new(DefinedPage)
|
21
36
|
base.const_set('Page', klass)
|
22
|
-
base::send(:extend,
|
37
|
+
base::send(:extend, ComponentMethods)
|
23
38
|
|
24
39
|
klass = Class.new(UndefinedPage)
|
25
40
|
base.const_set('UndefinedPage', klass)
|
41
|
+
|
42
|
+
class << base
|
43
|
+
attr_reader :custom_tags
|
44
|
+
@custom_tags = []
|
45
|
+
end
|
46
|
+
|
47
|
+
base.define_singleton_method(:set_custom_tags) do |*tags|
|
48
|
+
@custom_tags ||= []
|
49
|
+
tags.sort.each { |t| @custom_tags << t.to_s.downcase.dasherize }
|
50
|
+
end
|
26
51
|
end
|
27
52
|
|
28
53
|
# Returns true if there's an open browser (that's also responding.) False if not.
|
@@ -36,7 +61,7 @@ module Insite
|
|
36
61
|
|
37
62
|
# Closes the site object's browser/driver.
|
38
63
|
def close
|
39
|
-
@browser.close
|
64
|
+
@browser.close if browser?
|
40
65
|
end
|
41
66
|
|
42
67
|
def describe
|
@@ -74,6 +99,23 @@ EOF
|
|
74
99
|
browser?
|
75
100
|
end
|
76
101
|
|
102
|
+
def generate_tag_classes
|
103
|
+
tags = []
|
104
|
+
cli = Highline.new
|
105
|
+
|
106
|
+
loop do
|
107
|
+
tags = (tags + find_non_standard_tags).uniq.sort
|
108
|
+
cli.choose do |menu|
|
109
|
+
menu.prompt "Found #{tags.length} non-standard tags. Choose one of the following options:"
|
110
|
+
menu.choice(:list_tags) { puts tags.join(",\n") + "\n" }
|
111
|
+
menu.choice(:continue) {}
|
112
|
+
menu.choice(:write_to_console) do
|
113
|
+
end
|
114
|
+
menu.choice(:exist_without_writing) { break }
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
77
119
|
# Creates a site object, which will have accessor methods for all pages that
|
78
120
|
# you have defined for the site. This object takes a hash argument. There is
|
79
121
|
# only one required value (the base_url for the site.) Example:
|
@@ -94,12 +136,25 @@ EOF
|
|
94
136
|
# site.bar
|
95
137
|
# => 1
|
96
138
|
# TODO: Sort args.
|
97
|
-
def initialize(base_url,
|
139
|
+
def initialize(base_url = nil, hsh={})
|
98
140
|
@arguments = hsh.with_indifferent_access
|
99
141
|
@base_url = base_url
|
100
142
|
@browser_type = (@arguments[:browser] ? @arguments[:browser].to_sym : nil)
|
101
143
|
@pages = self.class::DefinedPage.descendants.reject { |p| p.page_template? }
|
102
144
|
|
145
|
+
# Build generic components for custom tags, which are defined
|
146
|
+
# using Site.custom_tags.
|
147
|
+
if self.class.custom_tags
|
148
|
+
self.class.custom_tags.each do |tag|
|
149
|
+
# TODO: Ditch string interpolation.
|
150
|
+
self.class.class_eval %Q{
|
151
|
+
class #{tag.underscore.camelize} < Component
|
152
|
+
select_by tag_name: "#{tag}"
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
103
158
|
# Set up accessor methods for each page and page checking methods..
|
104
159
|
@pages.each do |current_page|
|
105
160
|
unless current_page.page_template?
|
@@ -144,6 +199,23 @@ EOF
|
|
144
199
|
"@most_recent_page=#{@most_recent_page}>"
|
145
200
|
end
|
146
201
|
|
202
|
+
def find_non_standard_tags
|
203
|
+
@browser.elements(xpath: non_standard_tag_xpath).map do |e|
|
204
|
+
e.html.match(/<(\S+?(?=[\s,>]))/)[1]
|
205
|
+
end.uniq.sort
|
206
|
+
end
|
207
|
+
|
208
|
+
def html_tags
|
209
|
+
%i(html title head body) + Insite::METHOD_MAP.values.flatten.each do |mth|
|
210
|
+
elem = @browser.send(mth)
|
211
|
+
elem.respond_to?(:selector) ? elem.selector.values.first.to_s : nil
|
212
|
+
end.sort
|
213
|
+
end
|
214
|
+
|
215
|
+
def non_standard_tag_xpath
|
216
|
+
"//*[#{html_tags.map { |sym| "not(local-name(.) = '#{sym}')" }.join(" and ") }]"
|
217
|
+
end
|
218
|
+
|
147
219
|
# In cases where Insite doesn't recognize a method call it will try to do the following:
|
148
220
|
# - Delegate the method call to the most recently accessed page, which is stored in
|
149
221
|
# Insite#most_recent_page.
|
@@ -153,19 +225,23 @@ EOF
|
|
153
225
|
#
|
154
226
|
# If delegation doesn't work then a NoMethodError will be raised with some details about
|
155
227
|
# what was attempted.
|
156
|
-
def method_missing(
|
228
|
+
def method_missing(mth, *args, &block)
|
157
229
|
original_page = @most_recent_page
|
158
|
-
if original_page.respond_to?(
|
159
|
-
original_page.public_send(
|
230
|
+
if original_page.respond_to?(mth)
|
231
|
+
original_page.public_send(mth, *args, &block)
|
160
232
|
else
|
161
233
|
new_page = page
|
162
|
-
if new_page.respond_to?(
|
163
|
-
page.public_send(
|
234
|
+
if new_page.respond_to?(mth)
|
235
|
+
page.public_send(mth, *args, &block)
|
236
|
+
elsif !new_page.defined?
|
237
|
+
raise NoMethodError, "Could not apply #{mth}. The current page could not be " \
|
238
|
+
"recognized. Current URL #{@browser.url}"
|
164
239
|
else
|
240
|
+
# TODO: Make it clearer where the method got called.
|
165
241
|
raise(
|
166
242
|
NoMethodError,
|
167
|
-
"Unable to apply
|
168
|
-
"currently displayed page doesn't support it either.\n" \
|
243
|
+
"Unable to apply method call :#{mth}. The site object doesn't support it. " \
|
244
|
+
"The currently displayed page (#{new_page}) doesn't support it either.\n" \
|
169
245
|
"Page:\t\t#{new_page.class}\n" \
|
170
246
|
"Current URL:\t#{@browser.url}\n\n",
|
171
247
|
caller
|
@@ -276,6 +352,15 @@ EOF
|
|
276
352
|
end
|
277
353
|
end
|
278
354
|
|
355
|
+
def respond_to_missing?(mth, include_priv = false)
|
356
|
+
# TODO: Page context changes.
|
357
|
+
@most_recent_page.respond_to?(mth, include_priv) || super
|
358
|
+
end
|
359
|
+
|
360
|
+
def target
|
361
|
+
@browser
|
362
|
+
end
|
363
|
+
|
279
364
|
def text
|
280
365
|
@browser.text
|
281
366
|
end
|