rudra 1.0.17 → 1.1.4
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/rudra.rb +208 -21
- metadata +42 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fea1605ee31709cb1dcb4c868ec5df73c650b459dcafea5c78504b3e90abe4f6
|
4
|
+
data.tar.gz: 0e3ea81a32db0e5f85e3b90af70abac74c54842dc8556f57256fc1f39b1e3f0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3634c208291be7f59897a94b85c1cec4b2666db41551b5aad64b3761590819524c7a43ea501dbf031285284bb5df6ab2f12d0354d5428707bd17f76e3da6d4c7
|
7
|
+
data.tar.gz: 1359e36111a9f186fbc53d8d7b7e183f0c6f40465a895e28d28a6ab54a0e680b2235b2a8f2dfe2b7fa358b788a85e74bb68c5259d2c7fbeca978b174dfe85d6c
|
data/lib/rudra.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require 'selenium-webdriver'
|
2
2
|
require 'webdrivers'
|
3
|
+
require 'zip'
|
4
|
+
require 'base64'
|
5
|
+
require 'json'
|
6
|
+
require 'stringio'
|
3
7
|
|
4
8
|
# Selenium IDE-like WebDriver based upon Ruby binding
|
5
9
|
# @author Aaron Chen
|
@@ -11,9 +15,12 @@ require 'webdrivers'
|
|
11
15
|
# @attr_reader [Boolean] headless Headless mode for Google Chrome
|
12
16
|
# @attr_reader [String] window_size Chrome window size when headless
|
13
17
|
# @attr_reader [String] screen_dir The screenshot directory of save_screenshot
|
14
|
-
# @attr_reader [String] log_prefix Prefix for logging
|
18
|
+
# @attr_reader [String] log_prefix Prefix for logging descriptions and methods
|
15
19
|
# @attr_reader [Integer] timeout The driver timeout
|
16
|
-
# @attr_reader [Boolean] verbose Verbose mode
|
20
|
+
# @attr_reader [Boolean] verbose Turn on/off Verbose mode
|
21
|
+
# @attr_reader [Boolean] silent Turn off Turn on/off descriptions
|
22
|
+
# @attr_reader [String] auth_username username for Basic Access Authentication Extension (Chrome only)
|
23
|
+
# @attr_reader [String] auth_password password for Basic Access Authentication Extension (Chrome only)
|
17
24
|
class Rudra
|
18
25
|
# Supported Browsers
|
19
26
|
BROWSERS = %i[chrome firefox ie safari].freeze
|
@@ -28,12 +35,14 @@ class Rudra
|
|
28
35
|
ATTRIBUTES = %i[
|
29
36
|
browser driver install_dir locale
|
30
37
|
headless window_size screen_dir
|
31
|
-
log_prefix timeout verbose
|
38
|
+
log_prefix timeout verbose silent
|
39
|
+
auth_username auth_password
|
32
40
|
].freeze
|
33
41
|
|
34
42
|
attr_reader :browser, :driver, :install_dir, :locale,
|
35
43
|
:headless, :window_size, :screen_dir,
|
36
|
-
:log_prefix, :timeout, :verbose
|
44
|
+
:log_prefix, :timeout, :verbose, :silent,
|
45
|
+
:auth_username, :auth_password
|
37
46
|
|
38
47
|
# Initialize an instance of Rudra
|
39
48
|
# @param [Hash] options the options to initialize Rudra
|
@@ -45,9 +54,12 @@ class Rudra
|
|
45
54
|
# @option options [Boolean] :headless (false) headless mode
|
46
55
|
# @option options [String] :window_size ('1280,720') window size when headless
|
47
56
|
# @option options [String] :screen_dir ('./screens/') the location of screenshots
|
48
|
-
# @option options [String] :log_prefix (' - ') prefix for logging
|
57
|
+
# @option options [String] :log_prefix (' - ') prefix for logging descriptions and methods
|
49
58
|
# @option options [Integer] :timeout (30) implicit_wait timeout
|
50
|
-
# @option options [Boolean] :verbose (
|
59
|
+
# @option options [Boolean] :verbose (false) Turn on/off verbose mode
|
60
|
+
# @option options [Boolean] :silent (false) Turn on/off descriptions
|
61
|
+
# @option options [String] :auth_username ('') username for Basic Access Authentication extension
|
62
|
+
# @option options [String] :auth_password ('') password for Basic Access Authentication extension
|
51
63
|
def initialize(options = {})
|
52
64
|
self.browser = options.fetch(:browser, :chrome)
|
53
65
|
self.install_dir = options.fetch(:install_dir, './webdrivers/')
|
@@ -56,7 +68,10 @@ class Rudra
|
|
56
68
|
self.window_size = options.fetch(:window_size, '1280,720')
|
57
69
|
self.screen_dir = options.fetch(:screen_dir, './screens/')
|
58
70
|
self.log_prefix = options.fetch(:log_prefix, ' - ')
|
59
|
-
self.verbose = options.fetch(:verbose,
|
71
|
+
self.verbose = options.fetch(:verbose, false)
|
72
|
+
self.silent = options.fetch(:silent, false)
|
73
|
+
self.auth_username = options.fetch(:auth_username, '')
|
74
|
+
self.auth_password = options.fetch(:auth_password, '')
|
60
75
|
self.main_label = caller_locations(2, 1).first.label
|
61
76
|
|
62
77
|
initialize_driver
|
@@ -146,6 +161,26 @@ class Rudra
|
|
146
161
|
driver.manage.delete_cookie(name)
|
147
162
|
end
|
148
163
|
|
164
|
+
# Check if an element is found
|
165
|
+
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
166
|
+
# identify the element or Selenium::WebDriver::Element
|
167
|
+
# @param [Integer] seconds seconds before timed out
|
168
|
+
def element_found?(locator, seconds = 1)
|
169
|
+
how, what = parse_locator(locator)
|
170
|
+
|
171
|
+
implicit_wait(seconds)
|
172
|
+
|
173
|
+
begin
|
174
|
+
wait_for(seconds) { driver.find_element(how, what).displayed? }
|
175
|
+
rescue Selenium::WebDriver::Error::TimeoutError
|
176
|
+
false
|
177
|
+
rescue Net::ReadTimeout
|
178
|
+
false
|
179
|
+
ensure
|
180
|
+
implicit_wait(timeout)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
149
184
|
# Execute the given JavaScript
|
150
185
|
# @param [String] script JavaScript source to execute
|
151
186
|
# @param [Selenium::WebDriver::Element, Integer, Float, Boolean, NilClass,
|
@@ -174,7 +209,7 @@ class Rudra
|
|
174
209
|
|
175
210
|
element ||= driver.find_element(how, what)
|
176
211
|
|
177
|
-
|
212
|
+
raise Selenium::WebDriver::Error::NoSuchElementError, "Failed to find element: #{locator}" unless element
|
178
213
|
|
179
214
|
wait_for { element.displayed? }
|
180
215
|
|
@@ -186,8 +221,11 @@ class Rudra
|
|
186
221
|
# @return [Array<Selenium::WebDriver::Element>] the elements found
|
187
222
|
def find_elements(locator)
|
188
223
|
how, what = parse_locator(locator)
|
189
|
-
driver.find_elements(how, what)
|
190
|
-
|
224
|
+
elements = driver.find_elements(how, what)
|
225
|
+
|
226
|
+
raise Selenium::WebDriver::Error::NoSuchElementError, "Failed to find elements: #{locator}" if elements.empty?
|
227
|
+
|
228
|
+
elements
|
191
229
|
end
|
192
230
|
|
193
231
|
# Move forward a single entry in the browser's history
|
@@ -265,6 +303,12 @@ class Rudra
|
|
265
303
|
driver.page_source
|
266
304
|
end
|
267
305
|
|
306
|
+
# Print description in the console
|
307
|
+
# @param [String] description description to show
|
308
|
+
def puts(description)
|
309
|
+
$stdout.puts "#{log_prefix}#{description.chomp}" unless silent
|
310
|
+
end
|
311
|
+
|
268
312
|
# Refresh the current pagef
|
269
313
|
def refresh
|
270
314
|
driver.navigate.refresh
|
@@ -280,13 +324,16 @@ class Rudra
|
|
280
324
|
# Save a PNG screenshot to file
|
281
325
|
# @param [String] filename the filename of PNG screenshot
|
282
326
|
def save_screenshot(filename)
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
@screen_dir,
|
287
|
-
filename.end_with?('.png') ? filename : "#{filename}.png"
|
288
|
-
)
|
327
|
+
file = File.join(
|
328
|
+
@screen_dir,
|
329
|
+
sanitize(filename.end_with?('.png') ? filename : "#{filename}.png")
|
289
330
|
)
|
331
|
+
|
332
|
+
dir = File.dirname(file)
|
333
|
+
|
334
|
+
mkdir(dir) unless Dir.exist?(dir)
|
335
|
+
|
336
|
+
driver.save_screenshot(file)
|
290
337
|
end
|
291
338
|
|
292
339
|
# Switch to the currently active modal dialog
|
@@ -301,6 +348,7 @@ class Rudra
|
|
301
348
|
end
|
302
349
|
|
303
350
|
# Switch to the frame with the given id
|
351
|
+
# @param [String] id the frame id
|
304
352
|
def switch_to_frame(id)
|
305
353
|
driver.switch_to.frame(id)
|
306
354
|
end
|
@@ -336,6 +384,50 @@ class Rudra
|
|
336
384
|
wait_for { find_element(locator).enabled? }
|
337
385
|
end
|
338
386
|
|
387
|
+
# Switch to a frame and wait until the element, identified by locator, is found
|
388
|
+
# @param [String] frame_id the frame id
|
389
|
+
# @param [String] locator the locator to identify the element
|
390
|
+
def switch_to_frame_and_wait_for_element_found(frame_id, locator)
|
391
|
+
switch_to_frame frame_id
|
392
|
+
|
393
|
+
how, what = parse_locator(locator)
|
394
|
+
|
395
|
+
wait_for do
|
396
|
+
begin
|
397
|
+
driver.find_element(how, what)
|
398
|
+
rescue Selenium::WebDriver::Error::NoSuchWindowError
|
399
|
+
false
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Wait (in seconds) until the element is not displayed
|
405
|
+
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
406
|
+
# identify the element or Selenium::WebDriver::Element
|
407
|
+
# @param [Integer] seconds seconds before timed out
|
408
|
+
def wait_for_not_visible(locator, seconds = 3)
|
409
|
+
how, what = parse_locator(locator)
|
410
|
+
|
411
|
+
implicit_wait(seconds)
|
412
|
+
|
413
|
+
begin
|
414
|
+
wait_for(seconds) do
|
415
|
+
begin
|
416
|
+
elements = driver.find_elements(how, what)
|
417
|
+
elements.empty? || elements.map(&:displayed?).none?
|
418
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
419
|
+
false
|
420
|
+
end
|
421
|
+
end
|
422
|
+
rescue Selenium::WebDriver::Error::TimeoutError
|
423
|
+
true
|
424
|
+
rescue Net::ReadTimeout
|
425
|
+
true
|
426
|
+
ensure
|
427
|
+
implicit_wait(timeout)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
339
431
|
# Wait until the title of the page including the given string
|
340
432
|
# @param [String] string the string to compare
|
341
433
|
def wait_for_title(string)
|
@@ -384,7 +476,7 @@ class Rudra
|
|
384
476
|
# @param [String] attribute the name of the attribute
|
385
477
|
# @return [String, nil] attribute value
|
386
478
|
def attribute(locator, attribute)
|
387
|
-
find_element(locator).
|
479
|
+
find_element(locator).attribute(attribute)
|
388
480
|
end
|
389
481
|
|
390
482
|
# If the element, identified by locator, has the given attribute
|
@@ -419,7 +511,14 @@ class Rudra
|
|
419
511
|
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
420
512
|
# identify the element or Selenium::WebDriver::Element
|
421
513
|
def click(locator)
|
422
|
-
|
514
|
+
wait_for do
|
515
|
+
begin
|
516
|
+
element = find_element(locator)
|
517
|
+
element.enabled? && element.click.nil?
|
518
|
+
rescue Selenium::WebDriver::Error::ElementClickInterceptedError
|
519
|
+
false
|
520
|
+
end
|
521
|
+
end
|
423
522
|
end
|
424
523
|
|
425
524
|
# Click the given element, identified by locator, with an offset
|
@@ -732,6 +831,38 @@ class Rudra
|
|
732
831
|
), find_element(locator), event)
|
733
832
|
end
|
734
833
|
|
834
|
+
# Wait until the element, identified by locator, attribute has value
|
835
|
+
# @param [String, Selenium::WebDriver::Element] locator the locator to identify the element
|
836
|
+
# @param [String] attribute the name of the attribute
|
837
|
+
# @param [String] value the value of the attribute
|
838
|
+
def wait_for_attribute_to_include(locator, attribute, value)
|
839
|
+
how, what = parse_locator(locator)
|
840
|
+
|
841
|
+
wait_for do
|
842
|
+
begin
|
843
|
+
driver.find_element(how, what)&.attribute(attribute)&.downcase&.include?(value.downcase)
|
844
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
845
|
+
false
|
846
|
+
end
|
847
|
+
end
|
848
|
+
end
|
849
|
+
|
850
|
+
# Wait until the element, identified by locator, excluding string in text
|
851
|
+
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
852
|
+
# identify the element or Selenium::WebDriver::Element
|
853
|
+
# @param [String] string the string to exclude
|
854
|
+
def wait_for_text_to_exclude(locator, string)
|
855
|
+
wait_for { text(locator).exclude?(string) }
|
856
|
+
end
|
857
|
+
|
858
|
+
# Wait until the element, identified by locator, including string in text
|
859
|
+
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
860
|
+
# identify the element or Selenium::WebDriver::Element
|
861
|
+
# @param [String] string the string to compare
|
862
|
+
def wait_for_text_to_include(locator, string)
|
863
|
+
wait_for { text(locator).include?(string) }
|
864
|
+
end
|
865
|
+
|
735
866
|
#
|
736
867
|
# Tool Functions
|
737
868
|
#
|
@@ -1069,7 +1200,7 @@ class Rudra
|
|
1069
1200
|
end
|
1070
1201
|
|
1071
1202
|
(instance_methods - superclass.instance_methods).map do |method_name|
|
1072
|
-
next if private_method_defined?(method_name) || ATTRIBUTES.include?(method_name)
|
1203
|
+
next if private_method_defined?(method_name) || ATTRIBUTES.include?(method_name) || method_name == :puts
|
1073
1204
|
|
1074
1205
|
original_method = instance_method(method_name)
|
1075
1206
|
|
@@ -1082,7 +1213,7 @@ class Rudra
|
|
1082
1213
|
private
|
1083
1214
|
|
1084
1215
|
attr_accessor :main_label
|
1085
|
-
attr_writer :window_size
|
1216
|
+
attr_writer :silent, :window_size, :auth_username, :auth_password
|
1086
1217
|
|
1087
1218
|
def browser=(brw)
|
1088
1219
|
unless BROWSERS.include?(brw)
|
@@ -1153,6 +1284,10 @@ class Rudra
|
|
1153
1284
|
options.add_argument('--headless')
|
1154
1285
|
options.add_argument("--window-size=#{window_size}")
|
1155
1286
|
end
|
1287
|
+
if auth_username && auth_password
|
1288
|
+
encoded = chrome_basic_auth_extension(auth_username, auth_password)
|
1289
|
+
options.add_encoded_extension(encoded)
|
1290
|
+
end
|
1156
1291
|
options.add_option(
|
1157
1292
|
'excludeSwitches',
|
1158
1293
|
%w[enable-automation enable-logging]
|
@@ -1197,7 +1332,7 @@ class Rudra
|
|
1197
1332
|
how.to_sym
|
1198
1333
|
end
|
1199
1334
|
|
1200
|
-
|
1335
|
+
raise Selenium::WebDriver::Error::InvalidSelectorError, "Cannot parse locator: #{locator}" unless HOWS.include?(how)
|
1201
1336
|
|
1202
1337
|
[how, what]
|
1203
1338
|
end
|
@@ -1212,9 +1347,61 @@ class Rudra
|
|
1212
1347
|
)
|
1213
1348
|
end
|
1214
1349
|
|
1350
|
+
def sanitize(filename)
|
1351
|
+
invalid_characters = ['/', '\\', '?', '%', '*', ':', '|', '"', '<', '>']
|
1352
|
+
invalid_characters.each do |character|
|
1353
|
+
filename.gsub!(character, '')
|
1354
|
+
end
|
1355
|
+
filename
|
1356
|
+
end
|
1357
|
+
|
1215
1358
|
def random_id(length = 8)
|
1216
1359
|
charset = [(0..9), ('a'..'z')].flat_map(&:to_a)
|
1217
1360
|
id = Array.new(length) { charset.sample }.join
|
1218
1361
|
"rudra_#{id}"
|
1219
1362
|
end
|
1363
|
+
|
1364
|
+
def chrome_basic_auth_extension(username, password)
|
1365
|
+
manifest = {
|
1366
|
+
"manifest_version": 2,
|
1367
|
+
"name": 'Rudra Basic Access Authentication Extension',
|
1368
|
+
"version": '1.0.0',
|
1369
|
+
"permissions": ['*://*/*', 'webRequest', 'webRequestBlocking'],
|
1370
|
+
"background": {
|
1371
|
+
"scripts": ['background.js']
|
1372
|
+
}
|
1373
|
+
}
|
1374
|
+
|
1375
|
+
background = <<~JAVASCRIPT
|
1376
|
+
var username = '#{username}';
|
1377
|
+
var password = '#{password}';
|
1378
|
+
|
1379
|
+
chrome.webRequest.onAuthRequired.addListener(
|
1380
|
+
function handler(details) {
|
1381
|
+
if (username == null) {
|
1382
|
+
return { cancel: true };
|
1383
|
+
}
|
1384
|
+
|
1385
|
+
var authCredentials = { username: username, password: username };
|
1386
|
+
username = password = null;
|
1387
|
+
|
1388
|
+
return { authCredentials: authCredentials };
|
1389
|
+
},
|
1390
|
+
{ urls: ['<all_urls>'] },
|
1391
|
+
['blocking']
|
1392
|
+
);
|
1393
|
+
JAVASCRIPT
|
1394
|
+
|
1395
|
+
stringio = Zip::OutputStream.write_buffer do |zos|
|
1396
|
+
zos.put_next_entry('manifest.json')
|
1397
|
+
zos.write manifest.to_json
|
1398
|
+
zos.put_next_entry('background.js')
|
1399
|
+
zos.write background
|
1400
|
+
end
|
1401
|
+
# File.open('basic_auth.crx', 'wb') do |f|
|
1402
|
+
# f << stringio.string
|
1403
|
+
# end
|
1404
|
+
|
1405
|
+
Base64.strict_encode64(stringio.string)
|
1406
|
+
end
|
1220
1407
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rudra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Chen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: yard
|
@@ -24,6 +24,46 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.9.25
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.9.0
|
34
|
+
- - "~>"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3.9'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.9.0
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.9'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rubyzip
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.3.0
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '2.3'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 2.3.0
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.3'
|
27
67
|
- !ruby/object:Gem::Dependency
|
28
68
|
name: selenium-webdriver
|
29
69
|
requirement: !ruby/object:Gem::Requirement
|