Skip to content

Commit

Permalink
[rb] implement remote downloads (#12037)
Browse files Browse the repository at this point in the history
  • Loading branch information
titusfortner committed Nov 1, 2023
1 parent 605fccd commit af1af0b
Show file tree
Hide file tree
Showing 19 changed files with 269 additions and 39 deletions.
3 changes: 3 additions & 0 deletions rb/.rubocop.yml
Expand Up @@ -21,6 +21,7 @@ Layout/SpaceInsideHashLiteralBraces:
Metrics/AbcSize:
Max: 30
Exclude:
- 'lib/selenium/webdriver/common/options.rb'
- 'lib/selenium/webdriver/remote/capabilities.rb'
- 'lib/selenium/webdriver/remote/http/curb.rb'
- 'lib/selenium/webdriver/support/color.rb'
Expand Down Expand Up @@ -52,6 +53,7 @@ Metrics/MethodLength:
Max: 22
Exclude:
- 'lib/selenium/server.rb'
- 'lib/selenium/webdriver/common/options.rb'
- 'lib/selenium/webdriver/common/driver.rb'
- 'lib/selenium/webdriver/remote/http/default.rb'

Expand All @@ -65,6 +67,7 @@ Metrics/ModuleLength:
Metrics/PerceivedComplexity:
Max: 9
Exclude:
- 'lib/selenium/webdriver/common/options.rb'
- 'lib/selenium/webdriver/common/local_driver.rb'
- 'lib/selenium/webdriver/common/logger.rb'

Expand Down
6 changes: 5 additions & 1 deletion rb/lib/selenium/webdriver/chrome/features.rb
Expand Up @@ -35,8 +35,12 @@ module Features
send_command: [:post, 'session/:session_id/goog/cdp/execute']
}.freeze

def command_list
CHROME_COMMANDS.merge(CHROMIUM_COMMANDS).merge(self.class::COMMANDS)
end

def commands(command)
CHROME_COMMANDS[command] || CHROMIUM_COMMANDS[command] || self.class::COMMANDS[command]
command_list[command]
end
end # Bridge
end # Chrome
Expand Down
4 changes: 0 additions & 4 deletions rb/lib/selenium/webdriver/chromium/features.rb
Expand Up @@ -31,10 +31,6 @@ module Features
get_log: [:post, 'session/:session_id/se/log']
}.freeze

def commands(command)
CHROME_COMMANDS[command] || self.class::COMMANDS[command]
end

def launch_app(id)
execute :launch_app, {}, {id: id}
end
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/common.rb
Expand Up @@ -81,6 +81,7 @@
require 'selenium/webdriver/common/driver_extensions/has_addons'
require 'selenium/webdriver/common/driver_extensions/has_bidi'
require 'selenium/webdriver/common/driver_extensions/has_devtools'
require 'selenium/webdriver/common/driver_extensions/has_file_downloads'
require 'selenium/webdriver/common/driver_extensions/has_authentication'
require 'selenium/webdriver/common/driver_extensions/has_logs'
require 'selenium/webdriver/common/driver_extensions/has_log_events'
Expand Down
@@ -0,0 +1,65 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module DriverExtensions
module HasFileDownloads
def downloadable_files
verify_enabled

@bridge.downloadable_files['names']
end

def download_file(file_name, target_directory)
verify_enabled

response = @bridge.download_file(file_name)
contents = response['contents']

File.open("#{file_name}.zip", 'wb') { |f| f << Base64.decode64(contents) }
target_directory += '/' unless target_directory.end_with?('/')
FileUtils.mkdir_p(target_directory)

begin
Zip::File.open("#{file_name}.zip") do |zip|
zip.each { |entry| zip.extract(entry, "#{target_directory}#{file_name}") }
end
ensure
FileUtils.rm_f("#{file_name}.zip")
end
end

def delete_downloadable_files
verify_enabled

@bridge.delete_downloadable_files
end

private

def verify_enabled
return if capabilities['se:downloadsEnabled']

raise Error::WebDriverError, 'You must enable downloads in order to work with downloadable files.'
end
end # HasFileDownloads
end # DriverExtensions
end # WebDriver
end # Selenium
4 changes: 4 additions & 0 deletions rb/lib/selenium/webdriver/common/options.rb
Expand Up @@ -24,6 +24,8 @@ class Options
set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability
web_socket_url].freeze

GRID_OPTIONS = %i[enable_downloads].freeze

class << self
attr_reader :driver_path

Expand Down Expand Up @@ -104,6 +106,8 @@ def ==(other)
def as_json(*)
options = @options.dup

downloads = options.delete(:enable_downloads)
options['se:downloadsEnabled'] = downloads unless downloads.nil?
w3c_options = process_w3c_options(options)

browser_options = self.class::CAPABILITIES.each_with_object({}) do |(capability_alias, capability_name), hash|
Expand Down
6 changes: 5 additions & 1 deletion rb/lib/selenium/webdriver/edge/features.rb
Expand Up @@ -35,8 +35,12 @@ module Features
send_command: [:post, 'session/:session_id/ms/cdp/execute']
}.freeze

def command_list
EDGE_COMMANDS.merge(CHROMIUM_COMMANDS).merge(self.class::COMMANDS)
end

def commands(command)
EDGE_COMMANDS[command] || CHROMIUM_COMMANDS[command] || self.class::COMMANDS[command]
command_list[command]
end
end # Bridge
end # Edge
Expand Down
6 changes: 5 additions & 1 deletion rb/lib/selenium/webdriver/firefox/features.rb
Expand Up @@ -29,8 +29,12 @@ module Features
full_page_screenshot: [:get, 'session/:session_id/moz/screenshot/full']
}.freeze

def command_list
FIREFOX_COMMANDS.merge(self.class::COMMANDS)
end

def commands(command)
FIREFOX_COMMANDS[command] || self.class::COMMANDS[command]
command_list[command]
end

def install_addon(path, temporary)
Expand Down
7 changes: 4 additions & 3 deletions rb/lib/selenium/webdriver/ie.rb
Expand Up @@ -20,9 +20,10 @@
module Selenium
module WebDriver
module IE
autoload :Driver, 'selenium/webdriver/ie/driver'
autoload :Options, 'selenium/webdriver/ie/options'
autoload :Service, 'selenium/webdriver/ie/service'
autoload :Features, 'selenium/webdriver/ie/features'
autoload :Driver, 'selenium/webdriver/ie/driver'
autoload :Options, 'selenium/webdriver/ie/options'
autoload :Service, 'selenium/webdriver/ie/service'
end # IE
end # WebDriver
end # Selenium
34 changes: 34 additions & 0 deletions rb/lib/selenium/webdriver/ie/features.rb
@@ -0,0 +1,34 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module IE
module Features
def command_list
self.class::COMMANDS
end

def commands(command)
command_list[command]
end
end # Bridge
end # Ie
end # WebDriver
end # Selenium
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/remote.rb
Expand Up @@ -23,6 +23,7 @@
module Selenium
module WebDriver
module Remote
autoload :Features, 'selenium/webdriver/remote/features'
autoload :Bridge, 'selenium/webdriver/remote/bridge'
autoload :Driver, 'selenium/webdriver/remote/driver'
autoload :Response, 'selenium/webdriver/remote/response'
Expand Down
21 changes: 1 addition & 20 deletions rb/lib/selenium/webdriver/remote/bridge.rb
Expand Up @@ -391,30 +391,11 @@ def click_element(element)
end

def send_keys_to_element(element, keys)
# TODO: rework file detectors before Selenium 4.0
if @file_detector
local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
if local_files&.any?
keys = local_files.map { |local_file| upload(local_file) }
keys = Array(keys.join("\n"))
end
end

# Keep .split(//) for backward compatibility for now
keys = upload_if_necessary(keys) if @file_detector
text = keys.join
execute :element_send_keys, {id: element}, {value: text.chars, text: text}
end

def upload(local_file)
unless File.file?(local_file)
WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!",
id: :file_detector)
raise Error::WebDriverError, "You are trying to work with something that isn't a file."
end

execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
end

def clear_element(element)
execute :element_clear, id: element
end
Expand Down
6 changes: 0 additions & 6 deletions rb/lib/selenium/webdriver/remote/bridge/commands.rb
Expand Up @@ -144,12 +144,6 @@ class Bridge
take_screenshot: [:get, 'session/:session_id/screenshot'],
take_element_screenshot: [:get, 'session/:session_id/element/:id/screenshot'],

#
# server extensions
#

upload_file: [:post, 'session/:session_id/se/file'],

#
# virtual-authenticator
#
Expand Down
4 changes: 4 additions & 0 deletions rb/lib/selenium/webdriver/remote/driver.rb
Expand Up @@ -28,6 +28,7 @@ module Remote
class Driver < WebDriver::Driver
include DriverExtensions::UploadsFiles
include DriverExtensions::HasSessionId
include DriverExtensions::HasFileDownloads

def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
raise ArgumentError, "Can not set :service object on #{self.class}" if service
Expand All @@ -36,6 +37,9 @@ def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
caps = process_options(options, capabilities)
super(caps: caps, url: url, **opts)
@bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
command_list = @bridge.command_list
@bridge.extend(WebDriver::Remote::Features)
@bridge.add_commands(command_list)
end

private
Expand Down
75 changes: 75 additions & 0 deletions rb/lib/selenium/webdriver/remote/features.rb
@@ -0,0 +1,75 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module Remote
module Features
REMOTE_COMMANDS = {
upload_file: [:post, 'session/:session_id/se/file'],
get_downloadable_files: [:get, 'session/:session_id/se/files'],
download_file: [:post, 'session/:session_id/se/files'],
delete_downloadable_files: [:delete, 'session/:session_id/se/files']
}.freeze

def add_commands(commands)
@command_list = command_list.merge(commands)
end

def command_list
@command_list ||= REMOTE_COMMANDS
end

def commands(command)
command_list[command]
end

def upload(local_file)
unless File.file?(local_file)
WebDriver.logger.error("File detector only works with files. #{local_file.inspect} isn`t a file!",
id: :file_detector)
raise Error::WebDriverError, "You are trying to upload something that isn't a file."
end

execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
end

def upload_if_necessary(keys)
local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
return keys unless local_files&.any?

keys = local_files.map { |local_file| upload(local_file) }
Array(keys.join("\n"))
end

def downloadable_files
execute :get_downloadable_files
end

def download_file(name)
execute :download_file, {}, {name: name}
end

def delete_downloadable_files
execute :delete_downloadable_files
end
end
end # Remote
end # WebDriver
end # Selenium
6 changes: 5 additions & 1 deletion rb/lib/selenium/webdriver/safari/features.rb
Expand Up @@ -28,8 +28,12 @@ module Features
attach_debugger: [:post, 'session/:session_id/apple/attach_debugger']
}.freeze

def command_list
SAFARI_COMMANDS.merge(self.class::COMMANDS)
end

def commands(command)
SAFARI_COMMANDS[command] || self.class::COMMANDS[command]
command_list[command]
end

def permissions
Expand Down

0 comments on commit af1af0b

Please sign in to comment.