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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rudra.rb +196 -14
  3. metadata +42 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b59cfa6497e4560d80369dc076df45638ec043abe069b5f6665e5749aefdef87
4
- data.tar.gz: 51e97a957330ffce97411d9f3ad40e01a4be439cc677306d5507ae0136577beb
3
+ metadata.gz: 27e45015364b99148e3e92162d6069446d7b711745d660089661d14ce1da5113
4
+ data.tar.gz: c9fb052da4d604b1247440f1ce660b31c0cbeb67d20cad5c0c90bc481a5805cd
5
5
  SHA512:
6
- metadata.gz: f7984dccb05ea056c716dd533c5d4662e83ea8711c62a2fc3f02f95d0d7a795914d16c2ceacd2ef2cf1eff26374ae9c8ed795a14797186f696356f3c02c687bf
7
- data.tar.gz: 4bfb07ee8abe3ce212c85ca19ab052fb134b47c42e225e7160ea6463795e425e22a594998d2e89365709a3d2b1495a2c5cbbb0894b64bd1bfc073a190519ef8f
6
+ metadata.gz: a8213217762733881e6249e7e5f23c380ff88ea6b4378a2b495792f8838327dee6f6de1ed4c940f0742b09da9c7357a69352652047da038deb2c81baaaed335c
7
+ data.tar.gz: 56d3540043140a8c1c2ed1b00e1fd16e4ec7521956670a9a158455b5881b49628de0c30fc48f5f0b0bb182850462efb742954b3e0409ad47758ebaaac43b4716
@@ -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
- abort("Failed to find element: #{locator}") unless element
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
- abort("Failed to find elements: #{locator}")
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
- mkdir(@screen_dir) unless Dir.exist?(@screen_dir)
293
- driver.save_screenshot(
294
- File.join(
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).property(attribute)
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
- find_element(locator).click
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
- abort("Cannot parse locator: #{locator}") unless HOWS.include?(how)
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.0
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-08 00:00:00.000000000 Z
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