isomorfeus-puppetmaster 0.1.0
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 +7 -0
- data/README.md +191 -0
- data/lib/isomorfeus-puppetmaster.rb +43 -0
- data/lib/isomorfeus/puppetmaster.rb +62 -0
- data/lib/isomorfeus/puppetmaster/console_message.rb +19 -0
- data/lib/isomorfeus/puppetmaster/cookie.rb +46 -0
- data/lib/isomorfeus/puppetmaster/document.rb +160 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom.rb +370 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_document.rb +908 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_node.rb +836 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer.rb +401 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_document.rb +944 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_node.rb +866 -0
- data/lib/isomorfeus/puppetmaster/driver_registration.rb +19 -0
- data/lib/isomorfeus/puppetmaster/dsl.rb +40 -0
- data/lib/isomorfeus/puppetmaster/errors.rb +90 -0
- data/lib/isomorfeus/puppetmaster/iframe.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node.rb +241 -0
- data/lib/isomorfeus/puppetmaster/node/checkbox.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node/content_editable.rb +18 -0
- data/lib/isomorfeus/puppetmaster/node/filechooser.rb +9 -0
- data/lib/isomorfeus/puppetmaster/node/input.rb +21 -0
- data/lib/isomorfeus/puppetmaster/node/radiobutton.rb +13 -0
- data/lib/isomorfeus/puppetmaster/node/select.rb +36 -0
- data/lib/isomorfeus/puppetmaster/node/textarea.rb +7 -0
- data/lib/isomorfeus/puppetmaster/request.rb +17 -0
- data/lib/isomorfeus/puppetmaster/response.rb +26 -0
- data/lib/isomorfeus/puppetmaster/rspec/features.rb +23 -0
- data/lib/isomorfeus/puppetmaster/rspec/matcher_proxies.rb +80 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers.rb +164 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/base.rb +98 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/become_closed.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/compound.rb +88 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_current_path.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_selector.rb +69 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_text.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_title.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_selector.rb +27 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_style.rb +38 -0
- data/lib/isomorfeus/puppetmaster/self_forwardable.rb +31 -0
- data/lib/isomorfeus/puppetmaster/server.rb +128 -0
- data/lib/isomorfeus/puppetmaster/server/checker.rb +40 -0
- data/lib/isomorfeus/puppetmaster/server/middleware.rb +60 -0
- data/lib/isomorfeus/puppetmaster/server_registration.rb +37 -0
- data/lib/isomorfeus/puppetmaster/version.rb +3 -0
- metadata +282 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
Isomorfeus::Puppetmaster.register_driver(:chromium) do |app|
|
2
|
+
Isomorfeus::Puppetmaster::Driver::Puppeteer.new(browser_type: :chromium, headless: true, app: app)
|
3
|
+
end
|
4
|
+
|
5
|
+
Isomorfeus::Puppetmaster.register_driver(:chromium_debug) do |app|
|
6
|
+
Isomorfeus::Puppetmaster::Driver::Puppeteer.new(browser_type: :chromium, headless: false, devtools: true, app: app)
|
7
|
+
end
|
8
|
+
|
9
|
+
Isomorfeus::Puppetmaster.register_driver(:firefox) do |app|
|
10
|
+
Isomorfeus::Puppetmaster::Driver::Puppeteer.new(browser_type: :firefox, headless: true, app: app)
|
11
|
+
end
|
12
|
+
|
13
|
+
Isomorfeus::Puppetmaster.register_driver(:firefox_debug) do |app|
|
14
|
+
Isomorfeus::Puppetmaster::Driver::Puppeteer.new(browser_type: :firefox, headless: false, devtools: true, app: app)
|
15
|
+
end
|
16
|
+
|
17
|
+
Isomorfeus::Puppetmaster.register_driver(:jsdom) do |app|
|
18
|
+
Isomorfeus::Puppetmaster::Driver::Jsdom.new(app: app)
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
module DSL
|
4
|
+
def goto(uri)
|
5
|
+
puppetmaster_session.document_goto(puppetmaster_default_document, uri)
|
6
|
+
@puppetmaster_default_document
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method :visit, :goto
|
10
|
+
|
11
|
+
def open_new_document(uri = nil)
|
12
|
+
puppetmaster_session.document_open_new_document(uri)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset_session!
|
16
|
+
@@puppetmaster_session = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get_default_document
|
22
|
+
doc = puppetmaster_session.default_document
|
23
|
+
return doc if doc
|
24
|
+
puppetmaster_session.document_open_new_document('about:blank')
|
25
|
+
end
|
26
|
+
|
27
|
+
def puppetmaster_default_document
|
28
|
+
@puppetmaster_default_document ||= get_default_document
|
29
|
+
end
|
30
|
+
|
31
|
+
def puppetmaster_session
|
32
|
+
@@puppetmaster_session ||= new_session(Isomorfeus::Puppetmaster.served_app)
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_session(app = nil, driver = nil)
|
36
|
+
Isomorfeus::Puppetmaster.drivers[driver ? driver : Isomorfeus::Puppetmaster.driver].call(app ? app : Isomorfeus::Puppetmaster.served_app)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Isomorfeus
|
4
|
+
module Puppetmaster
|
5
|
+
class Error < StandardError; end
|
6
|
+
class KeyError < Error; end
|
7
|
+
class JavaScriptError < Error; end
|
8
|
+
class ExecutionContextError < Error; end
|
9
|
+
class CertificateError < Error; end
|
10
|
+
class DNSError < Error; end
|
11
|
+
class NoModalError < Error; end
|
12
|
+
class FrozenInTime < Error; end
|
13
|
+
class NotSupported < Error; end
|
14
|
+
class DOMException < Error; end
|
15
|
+
|
16
|
+
class ElementNotFound < Error
|
17
|
+
def initialize(selector_query)
|
18
|
+
@selector_query = selector_query
|
19
|
+
super "Unable to find \"#{@selector_query}\""
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ClientError < Error
|
24
|
+
attr_reader :response
|
25
|
+
|
26
|
+
def initialize(response)
|
27
|
+
@response = response
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FrameNotFound < ClientError
|
32
|
+
def name
|
33
|
+
response["args"].first
|
34
|
+
end
|
35
|
+
|
36
|
+
def message
|
37
|
+
"The frame "#{name}" was not found."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class InvalidSelector < ClientError
|
42
|
+
def initialize(response, method, selector)
|
43
|
+
super(response)
|
44
|
+
@method, @selector = method, selector
|
45
|
+
end
|
46
|
+
|
47
|
+
def message
|
48
|
+
"Browser raised error trying to find #{@method}: #{@selector.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class MouseEventFailed < ClientError
|
53
|
+
attr_reader :name, :selector, :position
|
54
|
+
|
55
|
+
def initialize(*)
|
56
|
+
super
|
57
|
+
data = /\A\w+: (\w+), (.+?), ([\d\.-]+), ([\d\.-]+)/.match(@response)
|
58
|
+
@name, @selector = data.values_at(1, 2)
|
59
|
+
@position = data.values_at(3, 4).map(&:to_f)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def message
|
64
|
+
"Firing a #{name} at coordinates [#{position.join(", ")}] failed. Cuprite detected " \
|
65
|
+
"another element with CSS selector \"#{selector}\" at this position. " \
|
66
|
+
"It may be overlapping the element you are trying to interact with. " \
|
67
|
+
"If you don't care about overlapping elements, try using node.trigger(\"#{name}\")."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class NodeError < ClientError
|
72
|
+
attr_reader :node
|
73
|
+
|
74
|
+
def initialize(node, response)
|
75
|
+
@node = node
|
76
|
+
super(response)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class ObsoleteNode < NodeError
|
81
|
+
def message
|
82
|
+
"The element you are trying to interact with is either not part of the DOM, or is " \
|
83
|
+
"not currently visible on the page (perhaps display: none is set). " \
|
84
|
+
"It is possible the element has been replaced by another element and you meant to interact with " \
|
85
|
+
"the new element. If so you need to do a new find in order to get a reference to the " \
|
86
|
+
"new element."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
class Node
|
4
|
+
SUPPORTED_HTML_ELEMENTS = %w[
|
5
|
+
a abbr address area article aside audio
|
6
|
+
b base bdi bdo blockquote body br button
|
7
|
+
canvas caption cite code col colgroup
|
8
|
+
data datalist dd del details dfn dialog div dl dt
|
9
|
+
em embed
|
10
|
+
fieldset figcaption figure footer form
|
11
|
+
h1 h2 h3 h4 h5 h6 head header hr html
|
12
|
+
i iframe img input ins
|
13
|
+
kbd
|
14
|
+
label legend li link
|
15
|
+
main map mark meta meter
|
16
|
+
nav noscript
|
17
|
+
object ol optgroup option output
|
18
|
+
p param picture pre progress
|
19
|
+
q
|
20
|
+
rp rt rtc ruby
|
21
|
+
s samp script section select small source span strong style sub summary sup
|
22
|
+
table tbody td template textarea tfoot th thead time title tr track
|
23
|
+
u ul
|
24
|
+
var video
|
25
|
+
wbr
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
# https://www.w3.org/TR/SVG11/eltindex.html
|
29
|
+
# elements listed above not mentioned a second time
|
30
|
+
SUPPORTED_SVG_ELEMENTS = %w[
|
31
|
+
altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion animateTransform
|
32
|
+
circle clipPath color-profile cursor
|
33
|
+
defs desc
|
34
|
+
ellipse
|
35
|
+
feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting
|
36
|
+
feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
|
37
|
+
feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting
|
38
|
+
feSpotLight feTile feTurbulence
|
39
|
+
filter font font-face font-face-format font-face-name font-face-src font-face-uri foreignObject
|
40
|
+
g glyph glyphRef
|
41
|
+
hkern
|
42
|
+
image
|
43
|
+
line linearGradient
|
44
|
+
marker mask metadata missing-glyph mpath
|
45
|
+
path pattern polygon polyline
|
46
|
+
radialGradient rect
|
47
|
+
script set stop style svg switch symbol
|
48
|
+
text textPath tref tspan
|
49
|
+
use
|
50
|
+
view vkern
|
51
|
+
].freeze
|
52
|
+
SUPPORTED_HTML_AND_SVG_ELEMENTS = (SUPPORTED_HTML_ELEMENTS + SUPPORTED_SVG_ELEMENTS).freeze
|
53
|
+
|
54
|
+
extend Isomorfeus::Puppetmaster::SelfForwardable
|
55
|
+
|
56
|
+
node_forward %i[
|
57
|
+
all_text
|
58
|
+
click
|
59
|
+
disabled?
|
60
|
+
dispatch_event
|
61
|
+
double_click
|
62
|
+
drag_to
|
63
|
+
evaluate_script
|
64
|
+
execute_script
|
65
|
+
find
|
66
|
+
find_all
|
67
|
+
find_all_xpath
|
68
|
+
find_xpath
|
69
|
+
focus
|
70
|
+
hover
|
71
|
+
in_viewport?
|
72
|
+
render_base64
|
73
|
+
right_click
|
74
|
+
save_screenshot
|
75
|
+
scroll_by
|
76
|
+
scroll_into_view
|
77
|
+
scroll_to
|
78
|
+
style
|
79
|
+
source
|
80
|
+
type_keys
|
81
|
+
visible_text
|
82
|
+
visible?
|
83
|
+
wait_for
|
84
|
+
wait_for_xpath
|
85
|
+
]
|
86
|
+
|
87
|
+
attr_reader :document, :handle, :css_selector, :tag, :xpath_query
|
88
|
+
|
89
|
+
SUPPORTED_HTML_ELEMENTS.each do |element|
|
90
|
+
is_name = element == 'a' ? :is_link? : "is_#{element}?".to_sym
|
91
|
+
define_method(is_name) do
|
92
|
+
@tag == element
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.new_by_tag(driver, document, node_data)
|
97
|
+
tag = node_data[:tag] || node_data['tag']
|
98
|
+
case tag
|
99
|
+
when 'iframe' then Isomorfeus::Puppetmaster::Iframe.new(driver, document, node_data)
|
100
|
+
when 'input'
|
101
|
+
type = node_data[:type] || node_data['type']
|
102
|
+
case type
|
103
|
+
when 'checkbox' then Isomorfeus::Puppetmaster::Checkbox.new(driver, document, node_data)
|
104
|
+
when 'filechooser' then Isomorfeus::Puppetmaster::Filechooser.new(driver, document, node_data)
|
105
|
+
when 'radiobutton' then Isomorfeus::Puppetmaster::Radiobutton.new(driver, document, node_data)
|
106
|
+
when 'select' then Isomorfeus::Puppetmaster::Select.new(driver, document, node_data)
|
107
|
+
else
|
108
|
+
Isomorfeus::Puppetmaster::Input.new(driver, document, node_data)
|
109
|
+
end
|
110
|
+
when 'textarea' then Isomorfeus::Puppetmaster::Textarea.new(driver, document, node_data)
|
111
|
+
else
|
112
|
+
content_editable = node_data[:content_editable] || node_data['content_editable']
|
113
|
+
if content_editable
|
114
|
+
Isomorfeus::Puppetmaster::ContentEditable.new(driver, document, node_data)
|
115
|
+
else
|
116
|
+
Isomorfeus::Puppetmaster::Node.new(driver, document, node_data)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize(driver, document, node_data)
|
122
|
+
@css_selector = node_data[:css_selector] || node_data['css_selector']
|
123
|
+
@document = document
|
124
|
+
@driver = driver
|
125
|
+
@handle = node_data[:handle] || node_data['handle']
|
126
|
+
@tag = node_data[:tag] || node_data['tag']
|
127
|
+
@type = node_data[:type] || node_data['type']
|
128
|
+
@xpath_query = node_data[:xpath_query] || node_data['xpath_query']
|
129
|
+
ObjectSpace.define_finalizer(self, @driver.class.node_handle_disposer(@driver, @element_handle))
|
130
|
+
end
|
131
|
+
|
132
|
+
def [](attribute)
|
133
|
+
get_attribute(attribute)
|
134
|
+
end
|
135
|
+
|
136
|
+
def ==(other)
|
137
|
+
@driver.node_equal(self, other)
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_attribute(attribute)
|
141
|
+
attribute = attribute.to_s
|
142
|
+
if !(attribute.start_with?('aria-') || attribute.start_with?('data-'))
|
143
|
+
attribute = attribute.camelize(:lower)
|
144
|
+
end
|
145
|
+
@driver.node_get_attribute(self, attribute)
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_property(property)
|
149
|
+
property = property.to_s.camelize(:lower)
|
150
|
+
@driver.node_get_property(self, property)
|
151
|
+
end
|
152
|
+
|
153
|
+
def has_content?(content, **options)
|
154
|
+
visible_text.include?(content)
|
155
|
+
end
|
156
|
+
|
157
|
+
def has_css?(selector, **options)
|
158
|
+
res = find_all(selector)
|
159
|
+
return false unless res
|
160
|
+
return false if options.has_key?(:count) && options[:count] != res.size
|
161
|
+
return true
|
162
|
+
end
|
163
|
+
|
164
|
+
def has_text?(text, **options)
|
165
|
+
count = visible_text.scan(/#{text}/).size
|
166
|
+
return false if options.has_key?(:count) && options[:count] != count
|
167
|
+
count > 0
|
168
|
+
end
|
169
|
+
|
170
|
+
def has_xpath?(query, **options)
|
171
|
+
res = find_all_xpath(query)
|
172
|
+
return false unless res
|
173
|
+
return false if options.has_key?(:count) && options[:count] != res.size
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
|
177
|
+
def html
|
178
|
+
get_property(:outerHTML)
|
179
|
+
end
|
180
|
+
|
181
|
+
def inner_html
|
182
|
+
get_property(:innerHTML)
|
183
|
+
end
|
184
|
+
|
185
|
+
def method_missing(name, *args)
|
186
|
+
method_name = name.to_s
|
187
|
+
if method_name.start_with?('find_by_')
|
188
|
+
what = method_name[8..-1]
|
189
|
+
return find("[#{what}=\"#{args.first}\"]") if %w[name type value].include?(what)
|
190
|
+
return find_xpath("//*[text()=\"#{args.first}\"]") if what == 'content'
|
191
|
+
# elsif method_name.start_with?('has_')
|
192
|
+
# :has_checked_field?, #
|
193
|
+
# :has_content?,
|
194
|
+
# :has_css?,
|
195
|
+
# :has_field?,
|
196
|
+
# :has_link?,
|
197
|
+
# :has_select?,
|
198
|
+
# :has_selector?,
|
199
|
+
# :has_table?,
|
200
|
+
# :has_text?,
|
201
|
+
# :has_unchecked_field?,
|
202
|
+
# :has_xpath?,
|
203
|
+
# :has_button?, # method_missing
|
204
|
+
end
|
205
|
+
super(name, *args)
|
206
|
+
end
|
207
|
+
|
208
|
+
def open_document_by(&block)
|
209
|
+
open_documents = @driver.document_handles
|
210
|
+
block.call
|
211
|
+
new_documents = @driver.document_handles - open_documents
|
212
|
+
raise 'Multiple documents opened' if new_documents.size > 1
|
213
|
+
Isomorfeus::Puppetmaster::Document.new(@driver, new_documents.first, Isomorfeus::Puppetmaster::Response.new)
|
214
|
+
end
|
215
|
+
|
216
|
+
def parents
|
217
|
+
find_all_xpath('./ancestor::*').reverse
|
218
|
+
end
|
219
|
+
|
220
|
+
def respond_to?(name, include_private = false)
|
221
|
+
return true if %i[find_by_content find_by_name find_by_type find_by_value].include?(name)
|
222
|
+
super(name, include_private)
|
223
|
+
end
|
224
|
+
|
225
|
+
def within(&block)
|
226
|
+
instance_exec(&block)
|
227
|
+
end
|
228
|
+
|
229
|
+
# assertions
|
230
|
+
# # probably can keep
|
231
|
+
# :assert_all_of_selectors,
|
232
|
+
# :assert_any_of_selectors,
|
233
|
+
# :assert_selector,
|
234
|
+
# :assert_text,
|
235
|
+
# :assert_no_selector,
|
236
|
+
# :assert_none_of_selectors,
|
237
|
+
# :assert_no_text,
|
238
|
+
# :refute_selector
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
class ContentEditable < Isomorfeus::Puppetmaster::Node
|
4
|
+
node_forward %i[
|
5
|
+
disabled?
|
6
|
+
value
|
7
|
+
value=
|
8
|
+
]
|
9
|
+
|
10
|
+
def readonly?
|
11
|
+
!!self[:readOnly]
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :text, :value
|
15
|
+
alias_method :text=, :value=
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|