rudra 1.0.16 → 1.1.3

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 +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