rudra 1.0.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/lib/rudra.rb +938 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a05b8e6ccca218d827893ba34129b1db566ec3430efd6c8fe230186ae9802db8
|
4
|
+
data.tar.gz: 3fa3b5480a8860a8df4d4ccc9fda2f3ade920ac7cf2e235d598e71ba1c8833f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7b1edf0cd915db94cf05bc2f0a4830a1ce09dd9eb3d0d2c73729ef31798347f19eca5f7d3ec3a3e1cd649671bf8895b66619962d5be3fa91b67e8216e00297e
|
7
|
+
data.tar.gz: 48641f836416ef38c08d78b338051841ab143f76bfae6bfff9c277a3c66d2af4a7475a504f1851ed7abdb7103e9d7a7c115da0b1be85080e759e54c0d7153df3
|
data/lib/rudra.rb
ADDED
@@ -0,0 +1,938 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'webdrivers/chromedriver'
|
3
|
+
require 'webdrivers/geckodriver'
|
4
|
+
|
5
|
+
Webdrivers.install_dir = './webdrivers/'
|
6
|
+
# Selenium::WebDriver::Chrome::Service.driver_path = './webdrivers/chromedriver'
|
7
|
+
# Selenium::WebDriver::Firefox::Service.driver_path = './webdrivers/geckodriver'
|
8
|
+
|
9
|
+
# Selenium IDE-like WebDriver based upon Ruby binding
|
10
|
+
# @author Aaron Chen
|
11
|
+
# @attr_reader [Symbol] browser The chosen browser
|
12
|
+
# @attr_reader [WebDriver::Driver] driver The driver instance
|
13
|
+
# of the chosen browser
|
14
|
+
# @attr_reader [String] locale The browser locale
|
15
|
+
# @attr_reader [Integer] timeout The driver timeout
|
16
|
+
class Rudra
|
17
|
+
# Supported Browsers
|
18
|
+
BROWSERS = %i[chrome firefox safari].freeze
|
19
|
+
|
20
|
+
# Element Finder Methods
|
21
|
+
HOWS = %i[
|
22
|
+
class class_name css id link link_text
|
23
|
+
name partial_link_text tag_name xpath
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
attr_reader :browser, :driver, :locale, :timeout
|
27
|
+
|
28
|
+
# Initialize an instance of Rudra
|
29
|
+
# @param [Hash] options the options to initialize Rudra
|
30
|
+
# @option options [Symbol] :browser (:chrome) the supported
|
31
|
+
# browsers: :chrome, :firefox, :safari
|
32
|
+
# @option options [String] :locale ('en') the browser locale
|
33
|
+
# @option options [Integer] :timeout (30) implicit_wait timeout
|
34
|
+
def initialize(options = {})
|
35
|
+
self.browser = options.fetch(:browser, :chrome)
|
36
|
+
self.locale = options.fetch(:locale, 'en')
|
37
|
+
|
38
|
+
initialize_driver
|
39
|
+
|
40
|
+
self.timeout = options.fetch(:timeout, 30)
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Driver Functions
|
45
|
+
#
|
46
|
+
|
47
|
+
# Get the active element
|
48
|
+
# @return [WebDriver::Element] the active element
|
49
|
+
def active_element
|
50
|
+
driver.switch_to.active_element
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add a cookie to the browser
|
54
|
+
# @param [Hash] opts the options to create a cookie with
|
55
|
+
# @option opts [String] :name a name
|
56
|
+
# @option opts [String] :value a value
|
57
|
+
# @option opts [String] :path ('/') a path
|
58
|
+
# @option opts [Boolean] :secure (false) a boolean
|
59
|
+
# @option opts [Time, DateTime, Numeric, nil] :expires (nil) expiry date
|
60
|
+
def add_cookie(opts = {})
|
61
|
+
driver.manage.add_cookie(opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Accept an alert
|
65
|
+
def alert_accept
|
66
|
+
switch_to_alert.accept
|
67
|
+
end
|
68
|
+
|
69
|
+
# Dismiss an alert
|
70
|
+
def alert_dismiss
|
71
|
+
switch_to_alert.dismiss
|
72
|
+
end
|
73
|
+
|
74
|
+
# Send keys to an alert
|
75
|
+
def alert_send_keys(keys)
|
76
|
+
switch_to_alert.send_keys(keys)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Move back a single entry in the browser's history
|
80
|
+
def back
|
81
|
+
driver.navigate.back
|
82
|
+
end
|
83
|
+
|
84
|
+
# Open a blank page
|
85
|
+
def blank
|
86
|
+
open('about:blank')
|
87
|
+
end
|
88
|
+
|
89
|
+
# Close the current window, or the browser if no windows are left
|
90
|
+
def close
|
91
|
+
driver.close
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get the cookie with the given name
|
95
|
+
# @param [String] name the name of the cookie
|
96
|
+
# @return [Hash, nil] the cookie, or nil if it wasn't found
|
97
|
+
def cookie_named(name)
|
98
|
+
driver.manage.cookie_named(name)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get the URL of the current page
|
102
|
+
# @return (String) the URL of the current page
|
103
|
+
def current_url
|
104
|
+
driver.current_url
|
105
|
+
end
|
106
|
+
|
107
|
+
# Delete all cookies
|
108
|
+
def delete_all_cookies
|
109
|
+
driver.manage.delete_all_cookies
|
110
|
+
end
|
111
|
+
|
112
|
+
# Delete the cookie with the given name
|
113
|
+
# @param [String] name the name of the cookie
|
114
|
+
def delete_cookie(name)
|
115
|
+
driver.manage.delete_cookie(name)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Execute the given JavaScript
|
119
|
+
# @param [String] script JavaScript source to execute
|
120
|
+
# @param [WebDriver::Element, Integer, Float, Boolean, NilClass, String,
|
121
|
+
# Array] args arguments will be available in the given script in the
|
122
|
+
# 'arguments' pseudo-array
|
123
|
+
# @return [WebDriver::Element, Integer, Float, Boolean, NilClass, String,
|
124
|
+
# Array] the value returned from the script
|
125
|
+
def execute_script(script, *args)
|
126
|
+
driver.execute_script(script, *args)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Find the first element matching the given locator
|
130
|
+
# @param [String] locator the locator to identify the element
|
131
|
+
# @return [WebDriver::Element] the element found
|
132
|
+
def find_element(locator)
|
133
|
+
element = nil
|
134
|
+
how, what = parse_locator(locator)
|
135
|
+
|
136
|
+
if how == :css
|
137
|
+
new_what, nth = what.scan(/(.*):eq\((\d+)\)/).flatten
|
138
|
+
element = find_elements("#{how}=#{new_what}")[nth.to_i] if nth
|
139
|
+
end
|
140
|
+
|
141
|
+
element ||= driver.find_element(how, what)
|
142
|
+
|
143
|
+
abort("Failed to find element: #{locator}") unless element
|
144
|
+
|
145
|
+
wait_for { element.displayed? }
|
146
|
+
|
147
|
+
element
|
148
|
+
end
|
149
|
+
|
150
|
+
# Find all elements matching the given locator
|
151
|
+
# @param [String] locator the locator to identify the elements
|
152
|
+
# @return [Array<WebDriver::Element>] the elements found
|
153
|
+
def find_elements(locator)
|
154
|
+
how, what = parse_locator(locator)
|
155
|
+
driver.find_elements(how, what) ||
|
156
|
+
abort("Failed to find elements: #{locator}")
|
157
|
+
end
|
158
|
+
|
159
|
+
# Move forward a single entry in the browser's history
|
160
|
+
def forward
|
161
|
+
driver.navigate.forward
|
162
|
+
end
|
163
|
+
|
164
|
+
# Make the current window full screen
|
165
|
+
def full_screen
|
166
|
+
driver.manage.window.full_screen
|
167
|
+
end
|
168
|
+
|
169
|
+
# Quit the browser
|
170
|
+
def quit
|
171
|
+
driver.quit
|
172
|
+
end
|
173
|
+
|
174
|
+
# Maximize the current window
|
175
|
+
def maximize
|
176
|
+
driver.manage.window.maximize
|
177
|
+
end
|
178
|
+
|
179
|
+
# Maximize the current window to the size of the screen
|
180
|
+
def maximize_to_screen
|
181
|
+
size = execute_script(%(
|
182
|
+
return { width: window.screen.width, height: window.screen.height };
|
183
|
+
))
|
184
|
+
|
185
|
+
move_window_to(0, 0)
|
186
|
+
resize_window_to(size['width'], size['height'])
|
187
|
+
end
|
188
|
+
|
189
|
+
# Minimize the current window
|
190
|
+
def minimize
|
191
|
+
driver.manage.window.minimize
|
192
|
+
end
|
193
|
+
|
194
|
+
# Move the current window to the given position
|
195
|
+
# @param [Integer] point_x the x coordinate
|
196
|
+
# @param [Integer] point_y the y coordinate
|
197
|
+
def move_window_to(point_x, point_y)
|
198
|
+
driver.manage.window.move_to(point_x, point_y)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Open a new tab
|
202
|
+
# @return [String] the id of the new tab obtained from #window_handles
|
203
|
+
def new_tab
|
204
|
+
execute_script('window.open();')
|
205
|
+
window_handles.last
|
206
|
+
end
|
207
|
+
|
208
|
+
# Open a new window
|
209
|
+
# @param [String] name the name of the window
|
210
|
+
def new_window(name)
|
211
|
+
execute_script(%(
|
212
|
+
var w = Math.max(
|
213
|
+
document.documentElement.clientWidth, window.innerWidth || 0
|
214
|
+
);
|
215
|
+
var h = Math.max(
|
216
|
+
document.documentElement.clientHeight, window.innerHeight || 0
|
217
|
+
);
|
218
|
+
window.open("about:blank", arguments[0], `width=${w},height=${h}`);
|
219
|
+
), name)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Open the specified URL in the browser
|
223
|
+
# @param [String] url the URL of the page to open
|
224
|
+
def open(url)
|
225
|
+
driver.get(url)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Refresh the current page
|
229
|
+
def refresh
|
230
|
+
driver.navigate.refresh
|
231
|
+
end
|
232
|
+
|
233
|
+
# Resize the current window to the given dimension
|
234
|
+
# @param [Integer] width the width of the window
|
235
|
+
# @param [Integer] height the height of the window
|
236
|
+
def resize_window_to(width, height)
|
237
|
+
driver.manage.window.resize_to(width, height)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Save a PNG screenshot to the given path
|
241
|
+
# @param [String] png_path the path of PNG screenshot
|
242
|
+
def save_screenshot(png_path)
|
243
|
+
driver.save_screenshot(
|
244
|
+
png_path.end_with?('.png') ? png_path : "#{png_path}.png"
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Switch to the currently active modal dialog
|
249
|
+
def switch_to_alert
|
250
|
+
driver.switch_to.alert
|
251
|
+
end
|
252
|
+
|
253
|
+
# Select either the first frame on the page,
|
254
|
+
# or the main document when a page contains iframes
|
255
|
+
def switch_to_default_content
|
256
|
+
driver.switch_to.default_content
|
257
|
+
end
|
258
|
+
|
259
|
+
# Switch to the frame with the given id
|
260
|
+
def switch_to_frame(id)
|
261
|
+
driver.switch_to.frame(id)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Switch to the parent frame
|
265
|
+
def switch_to_parent_frame
|
266
|
+
driver.switch_to.parent_frame
|
267
|
+
end
|
268
|
+
|
269
|
+
# Switch to the given window handle
|
270
|
+
# @param [String] id the window handle obtained through #window_handles
|
271
|
+
def switch_to_window(id)
|
272
|
+
driver.switch_to.window(id)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Get the title of the current page
|
276
|
+
# @return [String] the title of the current page
|
277
|
+
def title
|
278
|
+
driver.title
|
279
|
+
end
|
280
|
+
|
281
|
+
# Wait until the given block returns a true value
|
282
|
+
# @param [Integer] seconds seconds before timed out
|
283
|
+
# @return [Object] the result of the block
|
284
|
+
def wait_for(seconds = timeout)
|
285
|
+
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
|
286
|
+
end
|
287
|
+
|
288
|
+
# Wait until the title of the page including the given string
|
289
|
+
# @param [String] string the string to compare
|
290
|
+
def wait_for_title(string)
|
291
|
+
wait_for { title.downcase.include?(string.downcase) }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Wait until the URL of the page including the given url
|
295
|
+
# @param [String] url the URL to compare
|
296
|
+
def wait_for_url(url)
|
297
|
+
wait_for { current_url.include?(url) }
|
298
|
+
end
|
299
|
+
|
300
|
+
# Wait until the element, identified by locator, is visible
|
301
|
+
# @param [String] locator the locator to identify the element
|
302
|
+
def wait_for_visible(locator)
|
303
|
+
wait_for { find_element(locator).displayed? }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Get the current window handle
|
307
|
+
# @return [String] the id of the current window handle
|
308
|
+
def window_handle
|
309
|
+
driver.window_handle
|
310
|
+
end
|
311
|
+
|
312
|
+
# Get the window handles of open browser windows
|
313
|
+
# @return [Array<String>] the ids of window handles
|
314
|
+
def window_handles
|
315
|
+
driver.window_handles
|
316
|
+
end
|
317
|
+
|
318
|
+
# Zoom the current page
|
319
|
+
# @param [Float] scale the scale of zoom
|
320
|
+
def zoom(scale)
|
321
|
+
execute_script(%(document.body.style.zoom = arguments[0];), scale)
|
322
|
+
end
|
323
|
+
|
324
|
+
#
|
325
|
+
# Element Functions
|
326
|
+
#
|
327
|
+
|
328
|
+
# Get the value of the given attribute of the element,
|
329
|
+
# identified by locator
|
330
|
+
# @param [String] locator the locator to identify the element
|
331
|
+
# @param [String] attribute the name of the attribute
|
332
|
+
# @return [String, nil] attribute value
|
333
|
+
def attribute(locator, attribute)
|
334
|
+
find_element(locator).property(attribute)
|
335
|
+
end
|
336
|
+
|
337
|
+
# If the element, identified by locator, has the given attribute
|
338
|
+
# @param [String] locator the locator to identify the element
|
339
|
+
# @param [String] attribute the name of the attribute
|
340
|
+
# @return [Boolean] the result of the existence of the given attribute
|
341
|
+
def attribute?(locator, attribute)
|
342
|
+
execute_script(%(
|
343
|
+
return arguments[0].hasAttribute(arguments[1]);
|
344
|
+
), find_element(locator), attribute)
|
345
|
+
end
|
346
|
+
|
347
|
+
# Blur the given element, identfied by locator
|
348
|
+
# @param [String] locator the locator to identify the element
|
349
|
+
def blur(locator)
|
350
|
+
execute_script(
|
351
|
+
'var element = arguments[0]; element.blur();',
|
352
|
+
find_element(locator)
|
353
|
+
)
|
354
|
+
end
|
355
|
+
|
356
|
+
# Clear the input of the given element, identified by locator
|
357
|
+
# @param [String] locator the locator to identify the element
|
358
|
+
def clear(locator)
|
359
|
+
find_element(locator).clear
|
360
|
+
end
|
361
|
+
|
362
|
+
# Click the given element, identified by locator
|
363
|
+
# @param [String] locator the locator to identify the element
|
364
|
+
def click(locator)
|
365
|
+
find_element(locator).click
|
366
|
+
end
|
367
|
+
|
368
|
+
# If the given element, identified by locator, is displayed
|
369
|
+
# @param [String] locator the locator to identify the element
|
370
|
+
# @return [Boolean]
|
371
|
+
def displayed?(locator)
|
372
|
+
find_element(locator).displayed?
|
373
|
+
end
|
374
|
+
|
375
|
+
# If the given element, identified by locator, is enabled
|
376
|
+
# @param [String] locator the locator to identify the element
|
377
|
+
# @return [Boolean]
|
378
|
+
def enabled?(locator)
|
379
|
+
find_element(locator).enabled?
|
380
|
+
end
|
381
|
+
|
382
|
+
# Focus the given element, identfied by locator
|
383
|
+
# @param [String] locator the locator to identify the element
|
384
|
+
def focus(locator)
|
385
|
+
execute_script(
|
386
|
+
'var element = arguments[0]; element.focus();',
|
387
|
+
find_element(locator)
|
388
|
+
)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Hide the given element, identfied by locator
|
392
|
+
# @param [String] locator the locator to identify the element
|
393
|
+
def hide(locator)
|
394
|
+
execute_script(%(
|
395
|
+
arguments[0].style.display = 'none';
|
396
|
+
), find_element(locator))
|
397
|
+
end
|
398
|
+
|
399
|
+
# Hightlight the given element, identfied by locator
|
400
|
+
# @param [String] locator the locator to identify the element
|
401
|
+
def highlight(locator)
|
402
|
+
execute_script(%(
|
403
|
+
arguments[0].style.backgroundColor = '#FFFF33';
|
404
|
+
), find_element(locator))
|
405
|
+
end
|
406
|
+
|
407
|
+
# Click the given element, identified by locator, via Javascript
|
408
|
+
# @param [String] locator the locator to identify the element
|
409
|
+
def js_click(locator)
|
410
|
+
execute_script('arguments[0].click();', find_element(locator))
|
411
|
+
end
|
412
|
+
|
413
|
+
# Get the location of the given element, identfied by locator
|
414
|
+
# @param [String] locator the locator to identify the element
|
415
|
+
# @return [WebDriver::Point] the point of the given element
|
416
|
+
def location(locator)
|
417
|
+
find_element(locator).location
|
418
|
+
end
|
419
|
+
|
420
|
+
# Get the dimensions and coordinates of the given element,
|
421
|
+
# identfied by locator
|
422
|
+
# @param [String] locator the locator to identify the element
|
423
|
+
# @return [WebDriver::Rectangle] the retangle of the given element
|
424
|
+
def rect(locator)
|
425
|
+
find_element(locator).rect
|
426
|
+
end
|
427
|
+
|
428
|
+
# Remove the given attribute from the given element,
|
429
|
+
# identfied by locator
|
430
|
+
# @param [String] locator the locator to identify the element
|
431
|
+
# @param [String] attribute the name of the attribute
|
432
|
+
def remove_attribute(locator, attribute)
|
433
|
+
execute_script(%(
|
434
|
+
var element = arguments[0];
|
435
|
+
var attributeName = arguments[1];
|
436
|
+
if (element.hasAttribute(attributeName)) {
|
437
|
+
element.removeAttribute(attributeName);
|
438
|
+
}
|
439
|
+
), find_element(locator), attribute)
|
440
|
+
end
|
441
|
+
|
442
|
+
# Scroll the given element, identfied by locator, into view
|
443
|
+
# @param [String] locator the locator to identify the element
|
444
|
+
# @param [Boolean] align_to true if aligned on top or
|
445
|
+
# false if aligned at the bottom
|
446
|
+
def scroll_into_view(locator, align_to = true)
|
447
|
+
execute_script(
|
448
|
+
'arguments[0].scrollIntoView(arguments[1]);',
|
449
|
+
find_element(locator),
|
450
|
+
align_to
|
451
|
+
)
|
452
|
+
end
|
453
|
+
|
454
|
+
# Select the given option, identified by locator
|
455
|
+
# @param [String] option_locator the option locator to identify the element
|
456
|
+
def select(option_locator)
|
457
|
+
find_element(option_locator).click
|
458
|
+
end
|
459
|
+
|
460
|
+
# If the given element, identified by locator, is selected
|
461
|
+
# @param [String] locator the locator to identify the element
|
462
|
+
# @return [Boolean]
|
463
|
+
def selected?(locator)
|
464
|
+
find_element(locator).selected?
|
465
|
+
end
|
466
|
+
|
467
|
+
# Send keystrokes to the given element, identfied by locator
|
468
|
+
# @param [String] locator the locator to identify the element
|
469
|
+
# @param [String, Symbol, Array] args keystrokes to send
|
470
|
+
def send_keys(locator, *args)
|
471
|
+
find_element(locator).send_keys(*args)
|
472
|
+
end
|
473
|
+
|
474
|
+
# Set the attribute's value of the given element, identfied by locator
|
475
|
+
# @param [String] locator the locator to identify the element
|
476
|
+
# @param [String] attribute the name of the attribute
|
477
|
+
# @param [String] value the value of the attribute
|
478
|
+
def set_attribute(locator, attribute, value)
|
479
|
+
executeScript(%(
|
480
|
+
var element = arguments[0];
|
481
|
+
var attribute = arguments[1];
|
482
|
+
var value = arguments[2];
|
483
|
+
element.setAttribute(attribute, value);
|
484
|
+
), find_element(locator), attribute, value)
|
485
|
+
end
|
486
|
+
|
487
|
+
# Show the given element, identfied by locator
|
488
|
+
# @param [String] locator the locator to identify the element
|
489
|
+
def show(locator)
|
490
|
+
execute_script(%(
|
491
|
+
arguments[0].style.display = '';
|
492
|
+
), find_element(locator))
|
493
|
+
end
|
494
|
+
|
495
|
+
# Get the size of the given element, identfied by locator
|
496
|
+
# @param [String] locator the locator to identify the element
|
497
|
+
# @return [WebDriver::Dimension]
|
498
|
+
def size(locator)
|
499
|
+
find_element(locator).size
|
500
|
+
end
|
501
|
+
|
502
|
+
# Submit the given element, identfied by locator
|
503
|
+
# @param [String] locator the locator to identify the element
|
504
|
+
def submit(locator)
|
505
|
+
find_element(locator).submit
|
506
|
+
end
|
507
|
+
|
508
|
+
# Get the tag name of the given element, identfied by locator
|
509
|
+
# @param [String] locator the locator to identify the element
|
510
|
+
# @return [String] the tag name of the given element
|
511
|
+
def tag_name(locator)
|
512
|
+
find_element(locator).tag_name
|
513
|
+
end
|
514
|
+
|
515
|
+
# Get the text content of the given element, identfied by locator
|
516
|
+
# @param [String] locator the locator to identify the element
|
517
|
+
# @return [String] the text content of the given element
|
518
|
+
def text(locator)
|
519
|
+
find_element(locator).text
|
520
|
+
end
|
521
|
+
|
522
|
+
# Trigger the given event on the given element, identfied by locator
|
523
|
+
# @param [String] locator the locator to identify the element
|
524
|
+
# @param [String] event the event name
|
525
|
+
def trigger(locator, event)
|
526
|
+
execute_script(%(
|
527
|
+
var element = arguments[0];
|
528
|
+
var eventName = arguments[1];
|
529
|
+
var event = new Event(eventName, {"bubbles": false, "cancelable": false});
|
530
|
+
element.dispatchEvent(event);
|
531
|
+
), find_element(locator), event)
|
532
|
+
end
|
533
|
+
|
534
|
+
#
|
535
|
+
# Tool Functions
|
536
|
+
#
|
537
|
+
|
538
|
+
# Clear all drawing
|
539
|
+
def clear_drawings
|
540
|
+
execute_script(%(
|
541
|
+
var elements = window.document.body.querySelectorAll('[id*="rudra_"]');
|
542
|
+
for (var i = 0; i < elements.length; i++) {
|
543
|
+
elements[i].remove();
|
544
|
+
}
|
545
|
+
window.rudraTooltipSymbol = 9311;
|
546
|
+
window.rudraTooltipLastPos = { x: 0, y: 0 };
|
547
|
+
))
|
548
|
+
end
|
549
|
+
|
550
|
+
# Draw an arrow from an element to an element2
|
551
|
+
# @param [String] from_locator the locator where the arrow starts
|
552
|
+
# @param [String] to_locator the locator where the arrow ends
|
553
|
+
# @return [WebDriver::Element] the arrow element
|
554
|
+
def draw_arrow(from_locator, to_locator)
|
555
|
+
id = random_id
|
556
|
+
|
557
|
+
execute_script(%(
|
558
|
+
var element1 = arguments[0];
|
559
|
+
var element2 = arguments[1];
|
560
|
+
var rect1 = element1.getBoundingClientRect();
|
561
|
+
var rect2 = element2.getBoundingClientRect();
|
562
|
+
var from = {y: rect1.top};
|
563
|
+
var to = {y: rect2.top};
|
564
|
+
if (rect1.left > rect2.left) {
|
565
|
+
from.x = rect1.left; to.x = rect2.right;
|
566
|
+
} else if (rect1.left < rect2.left) {
|
567
|
+
from.x = rect1.right; to.x = rect2.left;
|
568
|
+
} else {
|
569
|
+
from.x = rect1.left; to.x = rect2.left;
|
570
|
+
}
|
571
|
+
// create canvas
|
572
|
+
var canvas = document.createElement('canvas');
|
573
|
+
canvas.id = "#{id}";
|
574
|
+
canvas.style.left = "0px";
|
575
|
+
canvas.style.top = "0px";
|
576
|
+
canvas.width = window.innerWidth;
|
577
|
+
canvas.height = window.innerHeight;
|
578
|
+
canvas.style.zIndex = '100000';
|
579
|
+
canvas.style.position = "absolute";
|
580
|
+
document.body.appendChild(canvas);
|
581
|
+
var headlen = 10;
|
582
|
+
var angle = Math.atan2(to.y - from.y, to.x - from.x);
|
583
|
+
var ctx = canvas.getContext("2d");
|
584
|
+
// line
|
585
|
+
ctx.beginPath();
|
586
|
+
ctx.moveTo(from.x, from.y);
|
587
|
+
ctx.lineTo(to.x, to.y);
|
588
|
+
ctx.lineWidth = 3;
|
589
|
+
ctx.strokeStyle = '#ff0000';
|
590
|
+
ctx.stroke();
|
591
|
+
// arrow
|
592
|
+
ctx.beginPath();
|
593
|
+
ctx.moveTo(to.x, to.y);
|
594
|
+
ctx.lineTo(
|
595
|
+
to.x - headlen * Math.cos(angle - Math.PI/7),
|
596
|
+
to.y - headlen * Math.sin(angle - Math.PI/7)
|
597
|
+
);
|
598
|
+
ctx.lineTo(
|
599
|
+
to.x - headlen * Math.cos(angle + Math.PI/7),
|
600
|
+
to.y - headlen * Math.sin(angle + Math.PI/7)
|
601
|
+
);
|
602
|
+
ctx.lineTo(to.x, to.y);
|
603
|
+
ctx.lineTo(
|
604
|
+
to.x - headlen * Math.cos(angle - Math.PI/7),
|
605
|
+
to.y - headlen * Math.sin(angle - Math.PI/7)
|
606
|
+
);
|
607
|
+
ctx.lineWidth = 3;
|
608
|
+
ctx.strokeStyle = '#ff0000';
|
609
|
+
ctx.stroke();
|
610
|
+
return;
|
611
|
+
), find_element(from_locator), find_element(to_locator))
|
612
|
+
|
613
|
+
find_element("id=#{id}")
|
614
|
+
end
|
615
|
+
|
616
|
+
# Draw color fill on the given element, identfied by locator
|
617
|
+
# @param [String] locator the locator to identify the element
|
618
|
+
# @param [String] color CSS style of backgroundColor
|
619
|
+
# @return [WebDriver::Element] the color fill element
|
620
|
+
def draw_color_fill(locator, color = 'rgba(255,0,0,0.8)')
|
621
|
+
rectangle = rect(locator)
|
622
|
+
id = random_id
|
623
|
+
|
624
|
+
execute_script(%(
|
625
|
+
var colorfill = window.document.createElement('div');
|
626
|
+
colorfill.id = '#{id}';
|
627
|
+
colorfill.style.backgroundColor = '#{color}';
|
628
|
+
colorfill.style.border = 'none';
|
629
|
+
colorfill.style.display = 'block';
|
630
|
+
colorfill.style.height = #{rectangle.height} + 'px';
|
631
|
+
colorfill.style.left = #{rectangle.x} + 'px';
|
632
|
+
colorfill.style.margin = '0px';
|
633
|
+
colorfill.style.padding = '0px';
|
634
|
+
colorfill.style.position = 'absolute';
|
635
|
+
colorfill.style.top = #{rectangle.y} + 'px';
|
636
|
+
colorfill.style.width = #{rectangle.width} + 'px';
|
637
|
+
colorfill.style.zIndex = '99999';
|
638
|
+
window.document.body.appendChild(colorfill);
|
639
|
+
return;
|
640
|
+
))
|
641
|
+
|
642
|
+
find_element("id=#{id}")
|
643
|
+
end
|
644
|
+
|
645
|
+
# Draw tooltip of the given element, identfied by locator
|
646
|
+
# @param [String] locator the locator to identify the element
|
647
|
+
# @param [Hash] options the options to create a tooltip
|
648
|
+
# @option options [String] :attribute (title) attribute to draw
|
649
|
+
# the flyover with
|
650
|
+
# @option options [Integer] :offset_x (5) offset on x coordinate
|
651
|
+
# @option options [Integer] :offset_y (15) offset on y coordinate
|
652
|
+
# @option options [Boolean] :from_last_pos (false) if to draw
|
653
|
+
# from last position
|
654
|
+
# @option options [Boolean] :draw_symbol (false) if to draw symbol
|
655
|
+
# @return [WebDriver::Element] the tooltip element
|
656
|
+
def draw_flyover(locator, options = {})
|
657
|
+
attribute_name = options.fetch(:attribute, 'title')
|
658
|
+
offset_x = options.fetch(:offset_x, 5)
|
659
|
+
offset_y = options.fetch(:offset_y, 15)
|
660
|
+
from_last_pos = options.fetch(:from_last_pos, false)
|
661
|
+
draw_symbol = options.fetch(:draw_symbol, false)
|
662
|
+
|
663
|
+
symbol_id = random_id
|
664
|
+
tooltip_id = random_id
|
665
|
+
|
666
|
+
execute_script(%(
|
667
|
+
var element = arguments[0];
|
668
|
+
if (! window.rudraTooltipSymbol) {
|
669
|
+
window.rudraTooltipSymbol = 9311;
|
670
|
+
}
|
671
|
+
if (! window.rudraTooltipLastPos) {
|
672
|
+
window.rudraTooltipLastPos = { x: 0, y: 0 };
|
673
|
+
}
|
674
|
+
var rect = element.getBoundingClientRect();
|
675
|
+
var title = element.getAttribute("#{attribute_name}") || 'N/A';
|
676
|
+
var left = window.scrollX + rect.left;
|
677
|
+
var top = window.scrollY + rect.top;
|
678
|
+
if (#{draw_symbol}) {
|
679
|
+
window.rudraTooltipSymbol++;
|
680
|
+
var symbol = document.createElement('div');
|
681
|
+
symbol.id = "#{symbol_id}";
|
682
|
+
symbol.textContent = String.fromCharCode(rudraTooltipSymbol);
|
683
|
+
symbol.style.color = '#f00';
|
684
|
+
symbol.style.display = 'block';
|
685
|
+
symbol.style.fontSize = '12px';
|
686
|
+
symbol.style.left = (left - 12) + 'px';
|
687
|
+
symbol.style.position = 'absolute';
|
688
|
+
symbol.style.top = top + 'px';
|
689
|
+
symbol.style.zIndex = '99999';
|
690
|
+
document.body.appendChild(symbol);
|
691
|
+
}
|
692
|
+
var tooltip = document.createElement('div');
|
693
|
+
tooltip.id = "#{tooltip_id}";
|
694
|
+
tooltip.textContent = (#{draw_symbol}) ?
|
695
|
+
String.fromCharCode(rudraTooltipSymbol) + " " + title : title;
|
696
|
+
tooltip.style.position = 'absolute';
|
697
|
+
tooltip.style.color = '#000';
|
698
|
+
tooltip.style.backgroundColor = '#F5FCDE';
|
699
|
+
tooltip.style.border = '3px solid #f00';
|
700
|
+
tooltip.style.fontSize = '12px';
|
701
|
+
tooltip.style.zIndex = '99999';
|
702
|
+
tooltip.style.display = 'block';
|
703
|
+
tooltip.style.height = '16px';
|
704
|
+
tooltip.style.padding = '2px';
|
705
|
+
tooltip.style.verticalAlign = 'middle';
|
706
|
+
tooltip.style.top = ((#{from_last_pos}) ?
|
707
|
+
window.rudraTooltipLastPos.y : (top + #{offset_y})) + 'px';
|
708
|
+
tooltip.style.left = ((#{from_last_pos}) ?
|
709
|
+
window.rudraTooltipLastPos.x : (left + #{offset_x})) + 'px';
|
710
|
+
document.body.appendChild(tooltip);
|
711
|
+
if (tooltip.scrollHeight > tooltip.offsetHeight) {
|
712
|
+
tooltip.style.height = (tooltip.scrollHeight + 3) + 'px';
|
713
|
+
}
|
714
|
+
var lastPos = tooltip.getBoundingClientRect();
|
715
|
+
window.rudraTooltipLastPos = {
|
716
|
+
x: window.scrollX + lastPos.left, y: window.scrollY + lastPos.bottom
|
717
|
+
};
|
718
|
+
return;
|
719
|
+
), find_element(locator))
|
720
|
+
|
721
|
+
find_element("id=#{tooltip_id}")
|
722
|
+
end
|
723
|
+
|
724
|
+
# Draw redmark around the given element, identfied by locator
|
725
|
+
# @param [String] locator the locator to identify the element
|
726
|
+
# @param [Hash] padding the padding of the given redmark
|
727
|
+
# @option padding [Integer] :top (5) top padding
|
728
|
+
# @option padding [Integer] :right (5) right padding
|
729
|
+
# @option padding [Integer] :bottom (5) bottom padding
|
730
|
+
# @option padding [Integer] :left (5) left padding
|
731
|
+
# @return [WebDriver::Element] the redmark element
|
732
|
+
def draw_redmark(locator, padding = {})
|
733
|
+
top = padding.fetch(:top, 5)
|
734
|
+
right = padding.fetch(:right, 5)
|
735
|
+
bottom = padding.fetch(:bottom, 5)
|
736
|
+
left = padding.fetch(:left, 5)
|
737
|
+
|
738
|
+
rectangle = rect(locator)
|
739
|
+
id = random_id
|
740
|
+
|
741
|
+
execute_script(%(
|
742
|
+
var redmark = window.document.createElement('div');
|
743
|
+
redmark.id = '#{id}';
|
744
|
+
redmark.style.border = '3px solid red';
|
745
|
+
redmark.style.display = 'block';
|
746
|
+
redmark.style.height = (#{rectangle.height} + 8 + #{bottom}) + 'px';
|
747
|
+
redmark.style.left = (#{rectangle.x} - 4 - #{left}) + 'px';
|
748
|
+
redmark.style.margin = '0px';
|
749
|
+
redmark.style.padding = '0px';
|
750
|
+
redmark.style.position = 'absolute';
|
751
|
+
redmark.style.top = (#{rectangle.y} - 4 - #{top}) + 'px';
|
752
|
+
redmark.style.width = (#{rectangle.width} + 8 + #{right}) + 'px';
|
753
|
+
redmark.style.zIndex = '99999';
|
754
|
+
window.document.body.appendChild(redmark);
|
755
|
+
return;
|
756
|
+
))
|
757
|
+
|
758
|
+
find_element("id=#{id}")
|
759
|
+
end
|
760
|
+
|
761
|
+
# Draw dropdown menu on the given SELECT element, identfied by locator
|
762
|
+
# @param [String] locator the locator to identify the element
|
763
|
+
# @param [Hash] options the options to create the dropdown menu
|
764
|
+
# @option options [Integer] :offset_x (0) offset on x coordinate
|
765
|
+
# @option options [Integer] :offset_y (0) offset on y coordinate
|
766
|
+
# @return [WebDriver::Element] the dropdown menu element
|
767
|
+
def draw_select(locator, options = {})
|
768
|
+
offset_x = options.fetch(:offset_x, 0)
|
769
|
+
offset_y = options.fetch(:offset_y, 0)
|
770
|
+
|
771
|
+
id = random_id
|
772
|
+
|
773
|
+
executeScript(%(
|
774
|
+
var element = arguments[0];
|
775
|
+
var rect = element.getBoundingClientRect();
|
776
|
+
var x = rect.left;
|
777
|
+
var y = rect.bottom;
|
778
|
+
var width = element.offsetWidth;
|
779
|
+
function escape(str) {
|
780
|
+
return str.replace(
|
781
|
+
/[\\x26\\x0A<>'"]/g,
|
782
|
+
function(r) { return "&#" + r.charCodeAt(0) + ";"; }
|
783
|
+
);
|
784
|
+
}
|
785
|
+
var content = "";
|
786
|
+
for (var i = 0; i < element.length; i++) {
|
787
|
+
if (!element.options[i].disabled) {
|
788
|
+
content += escape(element.options[i].text) + "<br />";
|
789
|
+
}
|
790
|
+
}
|
791
|
+
var dropdown = document.createElement('div');
|
792
|
+
dropdown.id = "#{id}";
|
793
|
+
dropdown.innerHTML = content;
|
794
|
+
dropdown.style.backgroundColor = '#fff';
|
795
|
+
dropdown.style.border = '1px solid #000';
|
796
|
+
dropdown.style.color = '#000';
|
797
|
+
dropdown.style.display = 'block';
|
798
|
+
dropdown.style.fontSize = '12px';
|
799
|
+
dropdown.style.height = '1px';
|
800
|
+
dropdown.style.padding = '2px';
|
801
|
+
dropdown.style.position = 'absolute';
|
802
|
+
dropdown.style.width = width + 'px';
|
803
|
+
dropdown.style.zIndex = '99999';
|
804
|
+
document.body.appendChild(dropdown);
|
805
|
+
dropdown.style.height = (dropdown.scrollHeight + 8) + 'px';
|
806
|
+
if (dropdown.scrollWidth > width) {
|
807
|
+
dropdown.style.width = (dropdown.scrollWidth + 8) + 'px';
|
808
|
+
}
|
809
|
+
dropdown.style.left = (x + #{offset_x}) + "px";
|
810
|
+
dropdown.style.top = (y + #{offset_y}) + "px";
|
811
|
+
return;
|
812
|
+
), find_element(locator))
|
813
|
+
|
814
|
+
find_element("id=#{id}")
|
815
|
+
end
|
816
|
+
|
817
|
+
# Draw text on top of the given element, identfied by locator
|
818
|
+
# @param [String] locator the locator to identify the element
|
819
|
+
# @param [String] text the text to draw
|
820
|
+
# @param [Hash] options the options to create the text
|
821
|
+
# @option options [String] :color ('#f00') the color of the text
|
822
|
+
# @option options [Integer] :font_size (13) the font size of the text
|
823
|
+
# @option options [Integer] :top (2) CSS style of top
|
824
|
+
# @option options [Integer] :right (20) CSS style of right
|
825
|
+
# @return [WebDriver::Element] the text element
|
826
|
+
def draw_text(locator, text, options = {})
|
827
|
+
color = options.fetch(:color, '#f00')
|
828
|
+
font_size = options.fetch(:font_size, 13)
|
829
|
+
top = options.fetch(:top, 2)
|
830
|
+
right = options.fetch(:right, 20)
|
831
|
+
|
832
|
+
rect = rect(locator)
|
833
|
+
id = random_id
|
834
|
+
|
835
|
+
execute_script(%(
|
836
|
+
var textbox = window.document.createElement('div');
|
837
|
+
textbox.id = '#{id}';
|
838
|
+
textbox.innerText = '#{text}';
|
839
|
+
textbox.style.border = 'none';
|
840
|
+
textbox.style.color = '#{color}';
|
841
|
+
textbox.style.display = 'block';
|
842
|
+
textbox.style.font = '#{font_size}px Verdana, sans-serif';
|
843
|
+
textbox.style.left = #{rect.x} + 'px';
|
844
|
+
textbox.style.margin = '0';
|
845
|
+
textbox.style.padding = '0';
|
846
|
+
textbox.style.position = 'absolute';
|
847
|
+
textbox.style.right = #{right} + 'px';
|
848
|
+
textbox.style.top = (#{rect.y} + #{rect.height} + #{top}) + 'px';
|
849
|
+
textbox.style.zIndex = '99999';
|
850
|
+
window.document.body.appendChild(textbox);
|
851
|
+
return;
|
852
|
+
))
|
853
|
+
|
854
|
+
find_element("id=#{id}")
|
855
|
+
end
|
856
|
+
|
857
|
+
# Create directories, recursively, for the given dir
|
858
|
+
# @param [String] dir the directories to create
|
859
|
+
def mkdir(dir)
|
860
|
+
FileUtils.mkdir_p(dir)
|
861
|
+
end
|
862
|
+
|
863
|
+
private
|
864
|
+
|
865
|
+
def browser=(brw)
|
866
|
+
unless BROWSERS.include?(brw)
|
867
|
+
browsers = BROWSERS.map { |b| ":#{b}" }.join(', ')
|
868
|
+
abort("Supported browsers are: #{browsers}")
|
869
|
+
end
|
870
|
+
|
871
|
+
@browser = brw
|
872
|
+
end
|
873
|
+
|
874
|
+
def locale=(loc)
|
875
|
+
@locale = if browser == :firefox
|
876
|
+
loc.sub('_', '-').gsub(/(-[a-zA-Z]{2})$/, &:downcase)
|
877
|
+
else
|
878
|
+
loc.sub('_', '-').gsub(/(-[a-zA-Z]{2})$/, &:upcase)
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
def timeout=(seconds)
|
883
|
+
driver.manage.timeouts.implicit_wait = seconds
|
884
|
+
driver.manage.timeouts.page_load = seconds
|
885
|
+
driver.manage.timeouts.script_timeout = seconds
|
886
|
+
@timeout = seconds
|
887
|
+
end
|
888
|
+
|
889
|
+
def initialize_driver
|
890
|
+
@driver = if browser == :chrome
|
891
|
+
Selenium::WebDriver.for(:chrome, options: chrome_options)
|
892
|
+
elsif browser == :firefox
|
893
|
+
Selenium::WebDriver.for(:firefox, options: firefox_options)
|
894
|
+
else
|
895
|
+
Selenium::WebDriver.for(:safari)
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
def chrome_options
|
900
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
901
|
+
options.add_argument('disable-infobars')
|
902
|
+
options.add_argument('disable-notifications')
|
903
|
+
options.add_preference('intl.accept_languages', locale)
|
904
|
+
options
|
905
|
+
end
|
906
|
+
|
907
|
+
def firefox_options
|
908
|
+
options = Selenium::WebDriver::Firefox::Options.new
|
909
|
+
options.add_preference('intl.accept_languages', locale)
|
910
|
+
options
|
911
|
+
end
|
912
|
+
|
913
|
+
def parse_locator(locator)
|
914
|
+
unmatched, how, what = locator.split(/^([A-Za-z]+)=(.+)/)
|
915
|
+
|
916
|
+
how = if !unmatched.strip.empty?
|
917
|
+
what = unmatched
|
918
|
+
case unmatched
|
919
|
+
when /^[\.#\[]/
|
920
|
+
:css
|
921
|
+
when %r{^(\/\/|\()}
|
922
|
+
:xpath
|
923
|
+
end
|
924
|
+
else
|
925
|
+
how.to_sym
|
926
|
+
end
|
927
|
+
|
928
|
+
abort("Cannot parse locator: #{locator}") unless HOWS.include?(how)
|
929
|
+
|
930
|
+
[how, what]
|
931
|
+
end
|
932
|
+
|
933
|
+
def random_id(length = 8)
|
934
|
+
charset = [(0..9), ('a'..'z')].flat_map(&:to_a)
|
935
|
+
id = Array.new(length) { charset.sample }.join
|
936
|
+
"rudra_#{id}"
|
937
|
+
end
|
938
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rudra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Chen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.20
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.20
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: selenium-webdriver
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.142.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.142.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: webdrivers
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.0.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.0.1
|
55
|
+
description: Ruby binding of selenium-webdriver
|
56
|
+
email: aaron@611b.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/rudra.rb
|
62
|
+
homepage: https://www.github.com/aaronchen/rudra
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubygems_version: 3.0.3
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Selenium IDE-like Webdriver
|
85
|
+
test_files: []
|