Skip to content

Commit

Permalink
[rb] add support for all specified chrome options in constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
titusfortner committed Jun 17, 2019
1 parent d5a93cc commit 00a708f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 88 deletions.
2 changes: 2 additions & 0 deletions rb/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ Metrics/PerceivedComplexity:
Exclude:
- 'lib/selenium/webdriver/chrome/driver.rb'
- 'lib/selenium/webdriver/remote/capabilities.rb'
- 'lib/selenium/webdriver/chrome/options.rb'

Metrics/CyclomaticComplexity:
Max: 9
Exclude:
- 'lib/selenium/webdriver/chrome/driver.rb'
- 'lib/selenium/webdriver/remote/capabilities.rb'
- 'lib/selenium/webdriver/chrome/options.rb'
- 'spec/integration/selenium/webdriver/spec_support/test_environment.rb'

Metrics/ClassLength:
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/chrome/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def create_capabilities(opts)
if profile
profile = profile.as_json

options.args ||= []
if options.args.none?(&/user-data-dir/.method(:match?))
options.add_argument("--user-data-dir=#{profile['directory']}")
end
Expand Down
114 changes: 80 additions & 34 deletions rb/lib/selenium/webdriver/chrome/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,23 @@ module Selenium
module WebDriver
module Chrome
class Options < WebDriver::Common::Options
attr_reader :args, :prefs, :options, :emulation, :extensions, :encoded_extensions
attr_accessor :binary

KEY = 'goog:chromeOptions'

#
# see: http://chromedriver.chromium.org/capabilities
CAPABILITIES = %i[args binary extensions local_state prefs detach debugger_address exclude_switches
minidump_path mobile_emulation perf_logging_prefs window_types].freeze

(CAPABILITIES + %i[options emulation encoded_extensions]).each do |key|
define_method key do
@options[key]
end

define_method "#{key}=" do |value|
@options[key] = value
end
end

# Create a new Options instance.
#
# @example
Expand All @@ -40,18 +51,31 @@ class Options < WebDriver::Common::Options
# @option opts [Array<String>] :extensions A list of paths to (.crx) Chrome extensions to install on startup
# @option opts [Hash] :options A hash for raw options
# @option opts [Hash] :emulation A hash for raw emulation options
#

def initialize(**opts)
@args = Set.new(opts.delete(:args) || [])
@binary = opts.delete(:binary) || Chrome.path
@prefs = opts.delete(:prefs) || {}
@extensions = opts.delete(:extensions) || []
@options = opts.delete(:options) || {}
@emulation = opts.delete(:emulation) || {}
@encoded_extensions = []
# @option opts [Hash] :local_state A hash for the Local State file in the user data folder
# @option opts [Boolean] :detach whether browser is closed when the driver is sent the quit command
# @option opts [String] :debugger_address address of a Chrome debugger server to connect to
# @option opts [Array<String>] :exclude_switches command line switches to exclude
# @option opts [String] :minidump_path Directory to store Chrome minidumps (linux only)
# @option opts [Hash] :perf_logging_prefs A hash for performance logging preferences
# @option opts [Array<String>] :window_types A list of window types to appear in the list of window handles
#

def initialize(emulation: nil, encoded_extensions: nil, options: nil, **opts)
@options = if options
WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
"values directly in #new constructor")
opts.merge(options)
else
opts
end
@options[:mobile_emulation] ||= emulation if emulation
@options[:encoded_extensions] = encoded_extensions if encoded_extensions
@options[:extensions]&.each(&method(:validate_extension))
end

alias_method :emulation, :mobile_emulation
alias_method :emulation=, :mobile_emulation=

#
# Add an extension by local path.
#
Expand All @@ -63,10 +87,9 @@ def initialize(**opts)
#

def add_extension(path)
raise Error::WebDriverError, "could not find extension at #{path.inspect}" unless File.file?(path)
raise Error::WebDriverError, "file was not an extension #{path.inspect}" unless File.extname(path) == '.crx'

@extensions << path
validate_extension(path)
@options[:extensions] ||= []
@options[:extensions] << path
end

#
Expand All @@ -80,7 +103,8 @@ def add_extension(path)
#

def add_encoded_extension(encoded)
@encoded_extensions << encoded
@options[:encoded_extensions] ||= []
@options[:encoded_extensions] << encoded
end

#
Expand All @@ -94,7 +118,8 @@ def add_encoded_extension(encoded)
#

def add_argument(arg)
@args << arg
@options[:args] ||= []
@options[:args] << arg
end

#
Expand Down Expand Up @@ -124,7 +149,8 @@ def add_option(name, value)
#

def add_preference(name, value)
prefs[name] = value
@options[:prefs] ||= {}
@options[:prefs][name] = value
end

#
Expand Down Expand Up @@ -155,30 +181,50 @@ def headless!
# @param [String] user_agent Full user agent
#

def add_emulation(device_name: nil, device_metrics: nil, user_agent: nil)
@emulation[:deviceName] = device_name if device_name
@emulation[:deviceMetrics] = device_metrics if device_metrics
@emulation[:userAgent] = user_agent if user_agent
def add_emulation(**opt)
@options[:mobile_emulation] = opt
end

#
# @api private
#

def as_json(*)
extensions = @extensions.map do |crx_path|
File.open(crx_path, 'rb') { |crx_file| Base64.strict_encode64 crx_file.read }
options = @options.dup

opts = CAPABILITIES.each_with_object({}) do |capability_name, hash|
capability_value = options.delete(capability_name)
hash[capability_name] = capability_value unless capability_value.nil?
end
extensions.concat(@encoded_extensions)

opts = @options
opts[:binary] = @binary if @binary
opts[:args] = @args.to_a if @args.any?
opts[:extensions] = extensions if extensions.any?
opts[:mobileEmulation] = @emulation unless @emulation.empty?
opts[:prefs] = @prefs unless @prefs.empty?
opts[:binary] ||= Chrome.path if Chrome.path
extensions = opts[:extensions] || []
opts[:extensions] = extensions.map(&method(:encode_extension)) +
(options.delete(:encoded_extensions) || [])
opts.delete(:extensions) if opts[:extensions].empty?
opts[:mobile_emulation] = process_emulation(opts[:mobile_emulation] || options.delete(:emulation) || {})
opts.delete(:mobile_emulation) if opts[:mobile_emulation].empty?

{KEY => generate_as_json(opts.merge(options))}
end

private

{KEY => generate_as_json(opts)}
def process_emulation(device_name: nil, device_metrics: nil, user_agent: nil)
emulation = {}
emulation[:device_name] = device_name if device_name
emulation[:device_metrics] = device_metrics if device_metrics
emulation[:user_agent] = user_agent if user_agent
emulation
end

def encode_extension(path)
File.open(path, 'rb') { |crx_file| Base64.strict_encode64 crx_file.read }
end

def validate_extension(path)
raise Error::WebDriverError, "could not find extension at #{path.inspect}" unless File.file?(path)
raise Error::WebDriverError, "file was not an extension #{path.inspect}" unless File.extname(path) == '.crx'
end
end # Options
end # Chrome
Expand Down
6 changes: 3 additions & 3 deletions rb/lib/selenium/webdriver/ie/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ def add_option(name, value)

def as_json(*)
opts = {}
options = @options.dup

CAPABILITIES.each do |capability_alias, capability_name|
capability_value = @options.delete(capability_alias)
capability_value = options.delete(capability_alias)
opts[capability_name] = capability_value unless capability_value.nil?
end
opts['ie.browserCommandLineSwitches'] = @args.to_a.join(' ') if @args.any?
opts.merge!(@options)

{KEY => generate_as_json(opts)}
{KEY => generate_as_json(opts.merge(options))}
end
end # Options
end # IE
Expand Down
2 changes: 0 additions & 2 deletions rb/spec/unit/selenium/webdriver/chrome/driver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,11 @@ module Chrome
profile = Profile.new

profile['some_pref'] = true
profile.add_extension(__FILE__)

Driver.new(http_client: http, profile: profile)

profile_data = profile.as_json
expect(caps['goog:chromeOptions']['args'].first).to include(profile_data['directory'])
expect(caps['goog:chromeOptions']['extensions']).to eq(profile_data['extensions'])
end

context 'with custom desired capabilities' do
Expand Down
116 changes: 67 additions & 49 deletions rb/spec/unit/selenium/webdriver/chrome/options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,51 @@ module Chrome
subject(:options) { described_class.new }

describe '#initialize' do
it 'sets passed args' do
opt = Options.new(args: %w[foo bar])
expect(opt.args.to_a).to eq(%w[foo bar])
end
it 'accepts defined parameters' do
allow(File).to receive(:file?).and_return(true)
# allow_any_instance_of(Options).to receive(:encode_file).with('foo.crx').and_return("encoded_foo")
# allow_any_instance_of(Options).to receive(:encode_file).with('bar.crx').and_return("encoded_bar")

opt = Options.new(args: %w[foo bar],
prefs: {foo: 'bar'},
binary: '/foo/bar',
extensions: ['foo.crx', 'bar.crx'],
encoded_extensions: ['encoded_foobar'],
foo: 'bar',
emulation: {device_name: :bar},
local_state: {foo: 'bar'},
detach: true,
debugger_address: '127.0.0.1:8181',
exclude_switches: %w[foobar barfoo],
minidump_path: 'linux/only',
perf_logging_prefs: {enable_network: true},
window_types: %w[normal devtools])

it 'sets passed prefs' do
opt = Options.new(prefs: {foo: 'bar'})
expect(opt.args.to_a).to eq(%w[foo bar])
expect(opt.prefs[:foo]).to eq('bar')
end

it 'sets passed binary value' do
opt = Options.new(binary: '/foo/bar')
expect(opt.binary).to eq('/foo/bar')
end

it 'sets passed extensions' do
opt = Options.new(extensions: ['foo.crx', 'bar.crx'])
expect(opt.extensions).to eq(['foo.crx', 'bar.crx'])
end

it 'sets passed options' do
opt = Options.new(options: {foo: 'bar'})
expect(opt.options[:foo]).to eq('bar')
end

it 'sets passed emulation options' do
opt = Options.new(emulation: {foo: 'bar'})
expect(opt.emulation[:foo]).to eq('bar')
expect(opt.encoded_extensions).to eq(%w[encoded_foobar])
expect(opt.instance_variable_get('@options')[:foo]).to eq('bar')
expect(opt.mobile_emulation[:device_name]).to eq(:bar)
expect(opt.local_state[:foo]).to eq('bar')
expect(opt.detach).to eq(true)
expect(opt.debugger_address).to eq('127.0.0.1:8181')
expect(opt.exclude_switches).to eq(%w[foobar barfoo])
expect(opt.minidump_path).to eq('linux/only')
expect(opt.perf_logging_prefs[:enable_network]).to eq(true)
expect(opt.window_types).to eq(%w[normal devtools])
end
end

describe '#add_extension' do
it 'adds an extension' do
allow(File).to receive(:file?).with('/foo/bar.crx').and_return(true)
allow(File).to receive(:file?).and_return(true)
ext = 'foo.crx'
allow_any_instance_of(Options).to receive(:encode_file).with(ext).and_return("encoded_#{ext[/([^\.]*)/]}")

options.add_extension('/foo/bar.crx')
expect(options.extensions).to include('/foo/bar.crx')
options.add_extension(ext)
expect(options.extensions).to eq([ext])
end

it 'raises error when the extension file is missing' do
Expand Down Expand Up @@ -109,7 +117,7 @@ module Chrome
describe '#add_option' do
it 'adds an option' do
options.add_option(:foo, 'bar')
expect(options.options[:foo]).to eq('bar')
expect(options.instance_variable_get('@options')[:foo]).to eq('bar')
end
end

Expand All @@ -123,45 +131,55 @@ module Chrome
describe '#add_emulation' do
it 'add an emulated device by name' do
options.add_emulation(device_name: 'iPhone 6')
expect(options.emulation).to eq(deviceName: 'iPhone 6')
expect(options.emulation).to eq(device_name: 'iPhone 6')
end

it 'adds emulated device metrics' do
options.add_emulation(device_metrics: {width: 400})
expect(options.emulation).to eq(deviceMetrics: {width: 400})
expect(options.emulation).to eq(device_metrics: {width: 400})
end

it 'adds emulated user agent' do
options.add_emulation(user_agent: 'foo')
expect(options.emulation).to eq(userAgent: 'foo')
expect(options.emulation).to eq(user_agent: 'foo')
end
end

describe '#as_json' do
it 'encodes extensions to base64' do
it 'returns a JSON hash' do
allow(File).to receive(:file?).and_return(true)
options.add_extension('/foo.crx')

allow(File).to receive(:open).and_yield(instance_double(File, read: :foo))
expect(Base64).to receive(:strict_encode64).with(:foo)
options.as_json
end
allow_any_instance_of(Options).to receive(:encode_extension).with('foo.crx').and_return("encoded_foo")
allow_any_instance_of(Options).to receive(:encode_extension).with('bar.crx').and_return("encoded_bar")

it 'returns a JSON hash' do
allow(File).to receive(:open).and_return('bar')
opts = Options.new(args: ['foo'],
opts = Options.new(args: %w[foo bar],
prefs: {foo: 'bar'},
binary: '/foo/bar',
prefs: {a: 1},
extensions: ['/foo.crx'],
options: {foo: :bar},
emulation: {device_name: 'mine'})
extensions: ['foo.crx', 'bar.crx'],
encoded_extensions: ['encoded_foobar'],
foo: 'bar',
emulation: {device_name: :mine},
local_state: {foo: 'bar'},
detach: true,
debugger_address: '127.0.0.1:8181',
exclude_switches: %w[foobar barfoo],
minidump_path: 'linux/only',
perf_logging_prefs: {'enable_network': true},
window_types: %w[normal devtools])

json = opts.as_json['goog:chromeOptions']
expect(json).to eq('args' => ['foo'], 'binary' => '/foo/bar',
'prefs' => {'a' => 1},
'extensions' => ['bar'],
expect(json).to eq('args' => %w[foo bar],
'prefs' => {'foo' => 'bar'},
'binary' => '/foo/bar',
'extensions' => %w[encoded_foo encoded_bar encoded_foobar],
'foo' => 'bar',
'mobileEmulation' => {'deviceName' => 'mine'})
'mobileEmulation' => {'deviceName' => 'mine'},
'localState' => {'foo' => 'bar'},
'detach' => true,
'debuggerAddress' => '127.0.0.1:8181',
'excludeSwitches' => %w[foobar barfoo],
'minidumpPath' => 'linux/only',
'perfLoggingPrefs' => {'enableNetwork' => true},
'windowTypes' => %w[normal devtools])
end
end
end # Options
Expand Down

0 comments on commit 00a708f

Please sign in to comment.