rudra 1.0.16 → 1.1.3

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 +229 -33
  3. metadata +42 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 425842a9405ec0a244bca0e722f6eb25cdb881b355346f3d61318a2fde111a25
4
- data.tar.gz: 4ed27b935bd5a3192d421fad516fa024fc9783f1f10c2b75da85679717511a70
3
+ metadata.gz: d1094036482a36f5196cd490c2594b66aabf2eebb1aa6c5adfb2b49c426d03e3
4
+ data.tar.gz: 4fd39aae44a3cf44d263014147cba3eb50a3c0622445fb0452dfda3f438120a1
5
5
  SHA512:
6
- metadata.gz: 0aa62d7dcfa60e50e74fcc61a11a264a5e1b20f77355e4776e6821d75d06a4b3ecf9ff89c1a4197d72ace5175fe25163b7b68309f05d5ff64c60aa86e33f1aaa
7
- data.tar.gz: 8f4353692ce71ef0276786a0bad7a062c1d88095184947a1d2f682f5ddae0d515764bb958726fe9dfafcc704b9ac40b129659b1a0a277e673e38ffd3e8cf6edc
6
+ metadata.gz: 8c1fca4fe84b87a53c2681df286f0862fa88fec448af16a4a2752c34dca6c9c0e3686b4a4f4db05fd222d539a08586e6750a524801033d127fda974345f79025
7
+ data.tar.gz: 813860ba0fa303826bcc1ea916592b73b0b42130c2ecb92082562b576be71a26a445cc3128964cc6b2342d1837d37b0b3855867bf66259b5c772cbe8d1702099
@@ -1,10 +1,9 @@
1
1
  require 'selenium-webdriver'
2
- require 'webdrivers/chromedriver'
3
- require 'webdrivers/geckodriver'
4
- require 'webdrivers/iedriver'
5
-
6
- # Selenium::WebDriver::Chrome::Service.driver_path = './webdrivers/chromedriver'
7
- # Selenium::WebDriver::Firefox::Service.driver_path = './webdrivers/geckodriver'
2
+ require 'webdrivers'
3
+ require 'zip'
4
+ require 'base64'
5
+ require 'json'
6
+ require 'stringio'
8
7
 
9
8
  # Selenium IDE-like WebDriver based upon Ruby binding
10
9
  # @author Aaron Chen
@@ -14,10 +13,14 @@ require 'webdrivers/iedriver'
14
13
  # @attr_reader [String] install_dir The install directory of WebDrivers
15
14
  # @attr_reader [String] locale The browser locale
16
15
  # @attr_reader [Boolean] headless Headless mode for Google Chrome
16
+ # @attr_reader [String] window_size Chrome window size when headless
17
17
  # @attr_reader [String] screen_dir The screenshot directory of save_screenshot
18
- # @attr_reader [String] log_prefix Prefix for logging executed methods
18
+ # @attr_reader [String] log_prefix Prefix for logging descriptions and methods
19
19
  # @attr_reader [Integer] timeout The driver timeout
20
- # @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] chrome_auth_username Chrome Basic Auth Extension - username
23
+ # @attr_reader [String] chrome_auth_password Chrome Basic Auth Extension - password
21
24
  class Rudra
22
25
  # Supported Browsers
23
26
  BROWSERS = %i[chrome firefox ie safari].freeze
@@ -31,13 +34,15 @@ class Rudra
31
34
  # Attributes
32
35
  ATTRIBUTES = %i[
33
36
  browser driver install_dir locale
34
- headless screen_dir log_prefix
35
- timeout verbose
37
+ headless window_size screen_dir
38
+ log_prefix timeout verbose silent
39
+ chrome_auth_username chrome_auth_password
36
40
  ].freeze
37
41
 
38
42
  attr_reader :browser, :driver, :install_dir, :locale,
39
- :headless, :screen_dir, :log_prefix,
40
- :timeout, :verbose
43
+ :headless, :window_size, :screen_dir,
44
+ :log_prefix, :timeout, :verbose, :silent,
45
+ :chrome_auth_username, :chrome_auth_password
41
46
 
42
47
  # Initialize an instance of Rudra
43
48
  # @param [Hash] options the options to initialize Rudra
@@ -47,18 +52,26 @@ class Rudra
47
52
  # directory of WebDrivers
48
53
  # @option options [Symbol] :locale (:en) the browser locale
49
54
  # @option options [Boolean] :headless (false) headless mode
55
+ # @option options [String] :window_size ('1280,720') window size when headless
50
56
  # @option options [String] :screen_dir ('./screens/') the location of screenshots
51
- # @option options [String] :log_prefix (' - ') prefix for logging executed methods
57
+ # @option options [String] :log_prefix (' - ') prefix for logging descriptions and methods
52
58
  # @option options [Integer] :timeout (30) implicit_wait timeout
53
- # @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] :chrome_auth_username ('') username for Chrome Basic Auth extension
62
+ # @option options [String] :chrome_auth_password ('') password for Chrome Basic Auth extension
54
63
  def initialize(options = {})
55
64
  self.browser = options.fetch(:browser, :chrome)
56
65
  self.install_dir = options.fetch(:install_dir, './webdrivers/')
57
66
  self.locale = options.fetch(:locale, :en)
58
67
  self.headless = options.fetch(:headless, false)
68
+ self.window_size = options.fetch(:window_size, '1280,720')
59
69
  self.screen_dir = options.fetch(:screen_dir, './screens/')
60
70
  self.log_prefix = options.fetch(:log_prefix, ' - ')
61
- self.verbose = options.fetch(:verbose, true)
71
+ self.verbose = options.fetch(:verbose, false)
72
+ self.silent = options.fetch(:silent, false)
73
+ self.chrome_auth_username = options.fetch(:chrome_auth_username, '')
74
+ self.chrome_auth_password = options.fetch(:chrome_auth_password, '')
62
75
  self.main_label = caller_locations(2, 1).first.label
63
76
 
64
77
  initialize_driver
@@ -104,6 +117,7 @@ class Rudra
104
117
  end
105
118
 
106
119
  # Send keys to an alert
120
+ # @param [String] keys keystrokes to send
107
121
  def alert_send_keys(keys)
108
122
  switch_to_alert.send_keys(keys)
109
123
  end
@@ -147,6 +161,26 @@ class Rudra
147
161
  driver.manage.delete_cookie(name)
148
162
  end
149
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
+
150
184
  # Execute the given JavaScript
151
185
  # @param [String] script JavaScript source to execute
152
186
  # @param [Selenium::WebDriver::Element, Integer, Float, Boolean, NilClass,
@@ -175,7 +209,7 @@ class Rudra
175
209
 
176
210
  element ||= driver.find_element(how, what)
177
211
 
178
- abort("Failed to find element: #{locator}") unless element
212
+ raise Selenium::WebDriver::Error::NoSuchElementError, "Failed to find element: #{locator}" unless element
179
213
 
180
214
  wait_for { element.displayed? }
181
215
 
@@ -187,8 +221,11 @@ class Rudra
187
221
  # @return [Array<Selenium::WebDriver::Element>] the elements found
188
222
  def find_elements(locator)
189
223
  how, what = parse_locator(locator)
190
- driver.find_elements(how, what) ||
191
- 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
192
229
  end
193
230
 
194
231
  # Move forward a single entry in the browser's history
@@ -208,7 +245,7 @@ class Rudra
208
245
 
209
246
  # Maximize the current window
210
247
  def maximize
211
- driver.manage.window.maximize
248
+ driver.manage.window.maximize unless headless
212
249
  end
213
250
 
214
251
  # Maximize the current window to the size of the screen
@@ -266,6 +303,12 @@ class Rudra
266
303
  driver.page_source
267
304
  end
268
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
+
269
312
  # Refresh the current pagef
270
313
  def refresh
271
314
  driver.navigate.refresh
@@ -281,13 +324,16 @@ class Rudra
281
324
  # Save a PNG screenshot to file
282
325
  # @param [String] filename the filename of PNG screenshot
283
326
  def save_screenshot(filename)
284
- mkdir(@screen_dir) unless Dir.exist?(@screen_dir)
285
- driver.save_screenshot(
286
- File.join(
287
- @screen_dir,
288
- filename.end_with?('.png') ? filename : "#{filename}.png"
289
- )
327
+ file = File.join(
328
+ @screen_dir,
329
+ sanitize(filename.end_with?('.png') ? filename : "#{filename}.png")
290
330
  )
331
+
332
+ dir = File.dirname(file)
333
+
334
+ mkdir(dir) unless Dir.exist?(dir)
335
+
336
+ driver.save_screenshot(file)
291
337
  end
292
338
 
293
339
  # Switch to the currently active modal dialog
@@ -302,6 +348,7 @@ class Rudra
302
348
  end
303
349
 
304
350
  # Switch to the frame with the given id
351
+ # @param [String] id the frame id
305
352
  def switch_to_frame(id)
306
353
  driver.switch_to.frame(id)
307
354
  end
@@ -337,6 +384,50 @@ class Rudra
337
384
  wait_for { find_element(locator).enabled? }
338
385
  end
339
386
 
387
+ # Wait until the element, identified by locator, is found in frame
388
+ # @param [String] frame_id the frame id
389
+ # @param [String] locator the locator to identify the element
390
+ def wait_for_element_found_in_frame(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
+
340
431
  # Wait until the title of the page including the given string
341
432
  # @param [String] string the string to compare
342
433
  def wait_for_title(string)
@@ -385,7 +476,7 @@ class Rudra
385
476
  # @param [String] attribute the name of the attribute
386
477
  # @return [String, nil] attribute value
387
478
  def attribute(locator, attribute)
388
- find_element(locator).property(attribute)
479
+ find_element(locator).attribute(attribute)
389
480
  end
390
481
 
391
482
  # If the element, identified by locator, has the given attribute
@@ -420,7 +511,14 @@ class Rudra
420
511
  # @param [String, Selenium::WebDriver::Element] locator the locator to
421
512
  # identify the element or Selenium::WebDriver::Element
422
513
  def click(locator)
423
- 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
424
522
  end
425
523
 
426
524
  # Click the given element, identified by locator, with an offset
@@ -733,6 +831,38 @@ class Rudra
733
831
  ), find_element(locator), event)
734
832
  end
735
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
+
736
866
  #
737
867
  # Tool Functions
738
868
  #
@@ -1070,7 +1200,7 @@ class Rudra
1070
1200
  end
1071
1201
 
1072
1202
  (instance_methods - superclass.instance_methods).map do |method_name|
1073
- 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
1074
1204
 
1075
1205
  original_method = instance_method(method_name)
1076
1206
 
@@ -1082,7 +1212,8 @@ class Rudra
1082
1212
 
1083
1213
  private
1084
1214
 
1085
- attr_writer :main_label
1215
+ attr_accessor :main_label
1216
+ attr_writer :silent, :window_size, :chrome_auth_username, :chrome_auth_password
1086
1217
 
1087
1218
  def browser=(brw)
1088
1219
  unless BROWSERS.include?(brw)
@@ -1135,6 +1266,8 @@ class Rudra
1135
1266
  def initialize_driver
1136
1267
  @driver = if browser == :chrome
1137
1268
  Selenium::WebDriver.for(:chrome, options: chrome_options)
1269
+ # elsif browser == :edge
1270
+ # Selenium::WebDriver.for(:edge, options: edge_options)
1138
1271
  elsif browser == :firefox
1139
1272
  Selenium::WebDriver.for(:firefox, options: firefox_options)
1140
1273
  elsif browser == :ie
@@ -1147,7 +1280,14 @@ class Rudra
1147
1280
  def chrome_options
1148
1281
  options = Selenium::WebDriver::Chrome::Options.new
1149
1282
  options.add_argument('--disable-notifications')
1150
- options.add_argument('--headless') if headless
1283
+ if headless
1284
+ options.add_argument('--headless')
1285
+ options.add_argument("--window-size=#{window_size}")
1286
+ end
1287
+ if chrome_auth_username && chrome_auth_password
1288
+ encoded = chrome_basic_auth_extension(chrome_auth_username, chrome_auth_password)
1289
+ options.add_encoded_extension(encoded)
1290
+ end
1151
1291
  options.add_option(
1152
1292
  'excludeSwitches',
1153
1293
  %w[enable-automation enable-logging]
@@ -1156,6 +1296,10 @@ class Rudra
1156
1296
  options
1157
1297
  end
1158
1298
 
1299
+ # def edge_options
1300
+ # Selenium::WebDriver::Edge::Options.new
1301
+ # end
1302
+
1159
1303
  def firefox_options
1160
1304
  options = Selenium::WebDriver::Firefox::Options.new
1161
1305
  options.add_preference('intl.accept_languages', locale)
@@ -1188,24 +1332,76 @@ class Rudra
1188
1332
  how.to_sym
1189
1333
  end
1190
1334
 
1191
- abort("Cannot parse locator: #{locator}") unless HOWS.include?(how)
1335
+ raise Selenium::WebDriver::Error::InvalidSelectorError, "Cannot parse locator: #{locator}" unless HOWS.include?(how)
1192
1336
 
1193
1337
  [how, what]
1194
1338
  end
1195
1339
 
1196
1340
  def log(method_name, *args)
1197
- return unless @verbose && caller_locations(2, 1).first.label == @main_label
1341
+ return unless verbose && caller_locations(2, 1).first.label == main_label
1198
1342
 
1199
1343
  arguments = args.map(&:to_s).join(', ')
1200
1344
 
1201
- puts @log_prefix + (
1345
+ puts log_prefix + (
1202
1346
  arguments.empty? ? method_name.to_s : "#{method_name}(#{arguments})"
1203
1347
  )
1204
1348
  end
1205
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
+
1206
1358
  def random_id(length = 8)
1207
1359
  charset = [(0..9), ('a'..'z')].flat_map(&:to_a)
1208
1360
  id = Array.new(length) { charset.sample }.join
1209
1361
  "rudra_#{id}"
1210
1362
  end
1363
+
1364
+ def chrome_basic_auth_extension(username, password)
1365
+ manifest = {
1366
+ "manifest_version": 2,
1367
+ "name": 'Rudra Basic Auth Extension',
1368
+ "version": '1.0.0',
1369
+ "permissions": ['*://*/*', 'webRequest', 'webRequestBlocking'],
1370
+ "background": {
1371
+ "scripts": ['background.js']
1372
+ }
1373
+ }
1374
+
1375
+ background = <<~JSCRIPT
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
+ JSCRIPT
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
1211
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.16
4
+ version: 1.1.3
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-02 00:00:00.000000000 Z
11
+ date: 2020-06-18 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