rudra 1.1.0 → 1.1.5

This diff has not been reviewed by any users.
Log in in order to be able to vote.
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