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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rudra.rb +938 -0
  3. 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: []