rudra 1.0.17 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
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