rudra 1.1.0 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rudra.rb +196 -14
- 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: 27e45015364b99148e3e92162d6069446d7b711745d660089661d14ce1da5113
|
4
|
+
data.tar.gz: c9fb052da4d604b1247440f1ce660b31c0cbeb67d20cad5c0c90bc481a5805cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8213217762733881e6249e7e5f23c380ff88ea6b4378a2b495792f8838327dee6f6de1ed4c940f0742b09da9c7357a69352652047da038deb2c81baaaed335c
|
7
|
+
data.tar.gz: 56d3540043140a8c1c2ed1b00e1fd16e4ec7521956670a9a158455b5881b49628de0c30fc48f5f0b0bb182850462efb742954b3e0409ad47758ebaaac43b4716
|
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
|
@@ -15,6 +19,8 @@ require 'webdrivers'
|
|
15
19
|
# @attr_reader [Integer] timeout The driver timeout
|
16
20
|
# @attr_reader [Boolean] verbose Turn on/off Verbose mode
|
17
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)
|
18
24
|
class Rudra
|
19
25
|
# Supported Browsers
|
20
26
|
BROWSERS = %i[chrome firefox ie safari].freeze
|
@@ -30,11 +36,13 @@ class Rudra
|
|
30
36
|
browser driver install_dir locale
|
31
37
|
headless window_size screen_dir
|
32
38
|
log_prefix timeout verbose silent
|
39
|
+
auth_username auth_password
|
33
40
|
].freeze
|
34
41
|
|
35
42
|
attr_reader :browser, :driver, :install_dir, :locale,
|
36
43
|
:headless, :window_size, :screen_dir,
|
37
|
-
:log_prefix, :timeout, :verbose, :silent
|
44
|
+
:log_prefix, :timeout, :verbose, :silent,
|
45
|
+
:auth_username, :auth_password
|
38
46
|
|
39
47
|
# Initialize an instance of Rudra
|
40
48
|
# @param [Hash] options the options to initialize Rudra
|
@@ -50,6 +58,8 @@ class Rudra
|
|
50
58
|
# @option options [Integer] :timeout (30) implicit_wait timeout
|
51
59
|
# @option options [Boolean] :verbose (false) Turn on/off verbose mode
|
52
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
|
53
63
|
def initialize(options = {})
|
54
64
|
self.browser = options.fetch(:browser, :chrome)
|
55
65
|
self.install_dir = options.fetch(:install_dir, './webdrivers/')
|
@@ -60,6 +70,8 @@ class Rudra
|
|
60
70
|
self.log_prefix = options.fetch(:log_prefix, ' - ')
|
61
71
|
self.verbose = options.fetch(:verbose, false)
|
62
72
|
self.silent = options.fetch(:silent, false)
|
73
|
+
self.auth_username = options.fetch(:auth_username, '')
|
74
|
+
self.auth_password = options.fetch(:auth_password, '')
|
63
75
|
self.main_label = caller_locations(2, 1).first.label
|
64
76
|
|
65
77
|
initialize_driver
|
@@ -149,6 +161,26 @@ class Rudra
|
|
149
161
|
driver.manage.delete_cookie(name)
|
150
162
|
end
|
151
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
|
+
|
152
184
|
# Execute the given JavaScript
|
153
185
|
# @param [String] script JavaScript source to execute
|
154
186
|
# @param [Selenium::WebDriver::Element, Integer, Float, Boolean, NilClass,
|
@@ -177,7 +209,7 @@ class Rudra
|
|
177
209
|
|
178
210
|
element ||= driver.find_element(how, what)
|
179
211
|
|
180
|
-
|
212
|
+
raise Selenium::WebDriver::Error::NoSuchElementError, "Failed to find element: #{locator}" unless element
|
181
213
|
|
182
214
|
wait_for { element.displayed? }
|
183
215
|
|
@@ -189,8 +221,11 @@ class Rudra
|
|
189
221
|
# @return [Array<Selenium::WebDriver::Element>] the elements found
|
190
222
|
def find_elements(locator)
|
191
223
|
how, what = parse_locator(locator)
|
192
|
-
driver.find_elements(how, what)
|
193
|
-
|
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
|
194
229
|
end
|
195
230
|
|
196
231
|
# Move forward a single entry in the browser's history
|
@@ -289,13 +324,16 @@ class Rudra
|
|
289
324
|
# Save a PNG screenshot to file
|
290
325
|
# @param [String] filename the filename of PNG screenshot
|
291
326
|
def save_screenshot(filename)
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
@screen_dir,
|
296
|
-
filename.end_with?('.png') ? filename : "#{filename}.png"
|
297
|
-
)
|
327
|
+
file = File.join(
|
328
|
+
@screen_dir,
|
329
|
+
sanitize(filename.end_with?('.png') ? filename : "#{filename}.png")
|
298
330
|
)
|
331
|
+
|
332
|
+
dir = File.dirname(file)
|
333
|
+
|
334
|
+
mkdir(dir) unless Dir.exist?(dir)
|
335
|
+
|
336
|
+
driver.save_screenshot(file)
|
299
337
|
end
|
300
338
|
|
301
339
|
# Switch to the currently active modal dialog
|
@@ -310,6 +348,7 @@ class Rudra
|
|
310
348
|
end
|
311
349
|
|
312
350
|
# Switch to the frame with the given id
|
351
|
+
# @param [String] id the frame id
|
313
352
|
def switch_to_frame(id)
|
314
353
|
driver.switch_to.frame(id)
|
315
354
|
end
|
@@ -345,6 +384,50 @@ class Rudra
|
|
345
384
|
wait_for { find_element(locator).enabled? }
|
346
385
|
end
|
347
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
|
+
|
348
431
|
# Wait until the title of the page including the given string
|
349
432
|
# @param [String] string the string to compare
|
350
433
|
def wait_for_title(string)
|
@@ -393,7 +476,7 @@ class Rudra
|
|
393
476
|
# @param [String] attribute the name of the attribute
|
394
477
|
# @return [String, nil] attribute value
|
395
478
|
def attribute(locator, attribute)
|
396
|
-
find_element(locator).
|
479
|
+
find_element(locator).attribute(attribute)
|
397
480
|
end
|
398
481
|
|
399
482
|
# If the element, identified by locator, has the given attribute
|
@@ -428,7 +511,14 @@ class Rudra
|
|
428
511
|
# @param [String, Selenium::WebDriver::Element] locator the locator to
|
429
512
|
# identify the element or Selenium::WebDriver::Element
|
430
513
|
def click(locator)
|
431
|
-
|
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
|
432
522
|
end
|
433
523
|
|
434
524
|
# Click the given element, identified by locator, with an offset
|
@@ -741,6 +831,38 @@ class Rudra
|
|
741
831
|
), find_element(locator), event)
|
742
832
|
end
|
743
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
|
+
|
744
866
|
#
|
745
867
|
# Tool Functions
|
746
868
|
#
|
@@ -1091,7 +1213,7 @@ class Rudra
|
|
1091
1213
|
private
|
1092
1214
|
|
1093
1215
|
attr_accessor :main_label
|
1094
|
-
attr_writer :silent, :window_size
|
1216
|
+
attr_writer :silent, :window_size, :auth_username, :auth_password
|
1095
1217
|
|
1096
1218
|
def browser=(brw)
|
1097
1219
|
unless BROWSERS.include?(brw)
|
@@ -1162,6 +1284,14 @@ class Rudra
|
|
1162
1284
|
options.add_argument('--headless')
|
1163
1285
|
options.add_argument("--window-size=#{window_size}")
|
1164
1286
|
end
|
1287
|
+
if auth_username
|
1288
|
+
if headless
|
1289
|
+
$stdout.puts('Basic Access Authentication Extension cannot be installed while headless')
|
1290
|
+
else
|
1291
|
+
encoded = chrome_basic_auth_extension(auth_username, auth_password)
|
1292
|
+
options.add_encoded_extension(encoded)
|
1293
|
+
end
|
1294
|
+
end
|
1165
1295
|
options.add_option(
|
1166
1296
|
'excludeSwitches',
|
1167
1297
|
%w[enable-automation enable-logging]
|
@@ -1206,7 +1336,7 @@ class Rudra
|
|
1206
1336
|
how.to_sym
|
1207
1337
|
end
|
1208
1338
|
|
1209
|
-
|
1339
|
+
raise Selenium::WebDriver::Error::InvalidSelectorError, "Cannot parse locator: #{locator}" unless HOWS.include?(how)
|
1210
1340
|
|
1211
1341
|
[how, what]
|
1212
1342
|
end
|
@@ -1221,9 +1351,61 @@ class Rudra
|
|
1221
1351
|
)
|
1222
1352
|
end
|
1223
1353
|
|
1354
|
+
def sanitize(filename)
|
1355
|
+
invalid_characters = ['/', '\\', '?', '%', '*', ':', '|', '"', '<', '>']
|
1356
|
+
invalid_characters.each do |character|
|
1357
|
+
filename.gsub!(character, '')
|
1358
|
+
end
|
1359
|
+
filename
|
1360
|
+
end
|
1361
|
+
|
1224
1362
|
def random_id(length = 8)
|
1225
1363
|
charset = [(0..9), ('a'..'z')].flat_map(&:to_a)
|
1226
1364
|
id = Array.new(length) { charset.sample }.join
|
1227
1365
|
"rudra_#{id}"
|
1228
1366
|
end
|
1367
|
+
|
1368
|
+
def chrome_basic_auth_extension(username, password)
|
1369
|
+
manifest = {
|
1370
|
+
"manifest_version": 2,
|
1371
|
+
"name": 'Rudra Basic Access Authentication Extension',
|
1372
|
+
"version": '1.0.0',
|
1373
|
+
"permissions": ['*://*/*', 'webRequest', 'webRequestBlocking'],
|
1374
|
+
"background": {
|
1375
|
+
"scripts": ['background.js']
|
1376
|
+
}
|
1377
|
+
}
|
1378
|
+
|
1379
|
+
background = <<~JAVASCRIPT
|
1380
|
+
var username = '#{username}';
|
1381
|
+
var password = '#{password}';
|
1382
|
+
|
1383
|
+
chrome.webRequest.onAuthRequired.addListener(
|
1384
|
+
function handler(details) {
|
1385
|
+
if (username == null) {
|
1386
|
+
return { cancel: true };
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
var authCredentials = { username: username, password: username };
|
1390
|
+
username = password = null;
|
1391
|
+
|
1392
|
+
return { authCredentials: authCredentials };
|
1393
|
+
},
|
1394
|
+
{ urls: ['<all_urls>'] },
|
1395
|
+
['blocking']
|
1396
|
+
);
|
1397
|
+
JAVASCRIPT
|
1398
|
+
|
1399
|
+
stringio = Zip::OutputStream.write_buffer do |zos|
|
1400
|
+
zos.put_next_entry('manifest.json')
|
1401
|
+
zos.write manifest.to_json
|
1402
|
+
zos.put_next_entry('background.js')
|
1403
|
+
zos.write background
|
1404
|
+
end
|
1405
|
+
# File.open('basic_auth.crx', 'wb') do |f|
|
1406
|
+
# f << stringio.string
|
1407
|
+
# end
|
1408
|
+
|
1409
|
+
Base64.strict_encode64(stringio.string)
|
1410
|
+
end
|
1229
1411
|
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.1.
|
4
|
+
version: 1.1.5
|
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-30 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
|