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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rudra.rb +208 -21
  3. metadata +42 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6b9b8353fbb965300bb0e6c9a96ef162717fc1b7e3afaa46192d414a0828584
4
- data.tar.gz: 5ca513a1614e4c9fbae46dc5caf4636d5b579fd8e1fc74c6f745cc61d7d9ddcf
3
+ metadata.gz: fea1605ee31709cb1dcb4c868ec5df73c650b459dcafea5c78504b3e90abe4f6
4
+ data.tar.gz: 0e3ea81a32db0e5f85e3b90af70abac74c54842dc8556f57256fc1f39b1e3f0e
5
5
  SHA512:
6
- metadata.gz: e775d57a29612f265578c1ecfd665e093f837460b32012763ab88983d47a1b7ae674903e1e5169b30609e65cce59d621800ab20500013d506b4dd8120fa76f55
7
- data.tar.gz: 98b306bd3f397ae54f53224fa88d57aacb5d6b6699a92706440626ddbe8dee4d75e3d7ff751fd71e2d64d0bd88a3e95852d1680720b7731fed6eccb22a7213bc
6
+ metadata.gz: 3634c208291be7f59897a94b85c1cec4b2666db41551b5aad64b3761590819524c7a43ea501dbf031285284bb5df6ab2f12d0354d5428707bd17f76e3da6d4c7
7
+ data.tar.gz: 1359e36111a9f186fbc53d8d7b7e183f0c6f40465a895e28d28a6ab54a0e680b2235b2a8f2dfe2b7fa358b788a85e74bb68c5259d2c7fbeca978b174dfe85d6c
@@ -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 executed methods
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 executed methods
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 (true) verbose mode
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, true)
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
- abort("Failed to find element: #{locator}") unless element
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
- 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
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
- mkdir(@screen_dir) unless Dir.exist?(@screen_dir)
284
- driver.save_screenshot(
285
- File.join(
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).property(attribute)
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
- 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
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
- abort("Cannot parse locator: #{locator}") unless HOWS.include?(how)
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.0.17
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-04 00:00:00.000000000 Z
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