dvla-browser-drivers 3.1.2 → 3.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be6fbb0dea13bdb8c0cffdf3c2e865a707792e92ad0779d27ee9e9ca69c31d37
4
- data.tar.gz: f17d454ef4b54fcf99adffa8bb9d62872a03740bf54671923183245b33bad027
3
+ metadata.gz: 7aa4731f086211ebf900305c5b23685993cfc5044fa860e486da5ea8804fac73
4
+ data.tar.gz: fa59836175631246cd2a77c730e5c705286e8443660cf59de967cf636284b0f0
5
5
  SHA512:
6
- metadata.gz: 41730ce1a0217eb34d5ae46722d6387bac90dbbfe869bce8a9ba5f60f32a54290cab759327fdb870da109d8e5c8daa1eb4c4e730b98190c261d3b40904775ba2
7
- data.tar.gz: 16988c0eb23835442a45f68f07d9d2c0132980888b4cc983600d33e34ea654a0a4c57df7bde4b41a1ebb0f241e5f1422d066584da93a5da0f09ecd9956219f4f
6
+ metadata.gz: 437c48c98e2f8ad9abd93dba34f7ada10e26a21be5e5e62a2b52cf24c029cd3c22f7dd7456ece23e74da6b2f27b32b9be296cba7c7d72bd2efa848b7acb9434b
7
+ data.tar.gz: fea370ae0e14a53e11854f8fb67d35a903b385d9abf50bcb39b616b19020c83db4e1e355d035dcffa0bf42df0a2cd970c05bcfdc15c49cd27d6541cd9e7de9d3
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.0
1
+ 4.0.1
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in dvla-browser-drivers.gemspec
6
6
  gemspec
7
7
 
8
+ gem "ostruct", "~> 0.6.3"
8
9
  gem 'bundler-audit', '~> 0.9.3'
9
10
  gem 'dvla-lint', '~> 1.7'
10
11
  gem 'pry', '~> 0.14'
@@ -12,3 +13,5 @@ gem 'rake', '~> 13.3'
12
13
  gem 'rspec', '~> 3.13'
13
14
  gem 'simplecov', '~> 0.22'
14
15
  gem 'simplecov-console', '~> 0.9'
16
+
17
+ gem "irb", "~> 1.18"
data/README.md CHANGED
@@ -25,25 +25,33 @@ Once installed, you are able to use any pre-configured browser driver from the l
25
25
 
26
26
  ### Selenium drivers
27
27
 
28
- | Driver | Usage |
29
- |---------------------------|----------------------------------------------------|
30
- | selenium_chrome | `DVLA::Browser::Drivers.selenium_chrome` |
31
- | headless_selenium_chrome | `DVLA::Browser::Drivers.headless_selenium_chrome` |
32
- | selenium_firefox | `DVLA::Browser::Drivers.selenium_firefox` |
33
- | headless_selenium_firefox | `DVLA::Browser::Drivers.headless_selenium_firefox` |
34
- | selenium_edge | `DVLA::Browser::Drivers.selenium_edge` |
35
- | selenium_safari | `DVLA::Browser::Drivers.selenium_safari` |
28
+ | Driver | Usage |
29
+ |------------------|-----------------------------------------------|
30
+ | selenium_chrome | `DVLA::Browser::Drivers.selenium_chrome` |
31
+ | selenium_firefox | `DVLA::Browser::Drivers.selenium_firefox` |
32
+ | selenium_edge | `DVLA::Browser::Drivers.selenium_edge` |
33
+ | selenium_safari | `DVLA::Browser::Drivers.selenium_safari` |
36
34
 
37
35
  ### Non-selenium drivers
38
36
 
39
- | Driver | Usage |
40
- |---------------------|----------------------------------------------|
41
- | cuprite | `DVLA::Browser::Drivers.cuprite` |
42
- | headless_cuprite | `DVLA::Browser::Drivers.headless_cuprite` |
43
- | apparition | `DVLA::Browser::Drivers.apparition` |
44
- | headless_apparition | `DVLA::Browser::Drivers.headless_apparition` |
37
+ | Driver | Usage |
38
+ |------------|-------------------------------------|
39
+ | cuprite | `DVLA::Browser::Drivers.cuprite` |
40
+ | apparition | `DVLA::Browser::Drivers.apparition` |
45
41
 
46
- **Note:** Safari and Edge do not support headless mode.
42
+ ### Driver modifiers
43
+
44
+ The following modifiers can be applied to any driver above (except selenium_safari):
45
+
46
+ | Modifier | Example | Description |
47
+ |-------------|-----------------------------------------------------|-------------------------------------------------------------------------------|
48
+ | headless_ | `headless_selenium_chrome` | Runs the browser in headless mode |
49
+ | _no_js | `selenium_chrome_no_js` | Disables JavaScript in the browser |
50
+ | _proxied | `selenium_firefox_proxied(proxy: 'http://foo.bar')` | Routes traffic through a proxy, requires a url passed as the `proxy` argument |
51
+
52
+ Modifiers can be combined, e.g. `headless_selenium_firefox_no_js_proxied`
53
+
54
+ **Note:** selenium_safari does not support any modifiers.
47
55
 
48
56
  ---
49
57
 
@@ -64,14 +72,54 @@ Once installed, you are able to use any pre-configured browser driver from the l
64
72
  [Cuprite Documentation](https://www.rubydoc.info/gems/cuprite/)
65
73
  [Selenium Additional Preferences Documentation](https://www.selenium.dev/selenium/docs/api/rb/Selenium/WebDriver/Chromium/Options.html#add_preference-instance_method)
66
74
 
67
- | Option | Driver | Usage | Description |
68
- |------------------------|-------------------------------|-------------------------------------------------------------------------------------------------|--------------------------------------------|
69
- | remote | Selenium, Cuprite, Apparition | `selenium_chrome(remote: 'http://localhost:4444/wd/hub')` | Allows you to talk to a remote browser |
70
- | additional_arguments | Selenium | `selenium_chrome(additional_arguments: ['window-size=1400,1920'] ` | Pass additional arguments to the driver |
71
- | additional_preferences | Selenium | `selenium_chrome(additional_preferences: [{'download.default_directory': '<download_path>'}] )` | Pass additional preferences to the driver |
72
- | timeout | Cuprite, Apparition | `cuprite(timeout: 60 )` | Sets the default timeout for the driver |
73
- | save_path | Cuprite, Apparition | `cuprite(save_path: 'File.expand_path('./somewhere')' )` | Tells the browser where to store downloads |
74
- | browser_options | Cuprite, Apparition | `cuprite(browser_options: { option: value, option: value })` | Pass additional options to the browser |
75
+ | Option | Driver | Usage | Description |
76
+ |------------------------|--------------------------------------|-------------------------------------------------------------------------------------------------|--------------------------------------------|
77
+ | remote | Selenium, Cuprite, Apparition | `selenium_chrome(remote: 'http://localhost:4444/wd/hub')` | Allows you to talk to a remote browser |
78
+ | additional_arguments | Selenium | `selenium_chrome(additional_arguments: ['window-size=1400,1920'] ` | Pass additional arguments to the driver |
79
+ | additional_preferences | Selenium | `selenium_chrome(additional_preferences: [{'download.default_directory': '<download_path>'}] )` | Pass additional preferences to the driver |
80
+ | proxy | Selenium, Cuprite | `selenium_firefox_proxied(proxy: 'http://proxy:8080')` | Sets the proxy URL for proxied drivers |
81
+ | window_size | Chrome, Edge, Cuprite, Apparition | `selenium_chrome(window_size: [1400, 900])` | Sets the browser window size. Accepts an array `[w, h]` or a string `'1400x900'`. Not supported on Firefox or Safari. Overridden by `emulate_device` |
82
+ | emulate_device | Chrome, Edge | `selenium_chrome(emulate_device: :iphone_15)` | Emulates a mobile device. Accepts a symbol matching a built-in profile (see [Mobile emulation](#mobile-emulation)) or a custom hash |
83
+ | timeout | Cuprite, Apparition | `cuprite(timeout: 60 )` | Sets the default timeout for the driver |
84
+ | save_path | Cuprite, Apparition | `cuprite(save_path: 'File.expand_path('./somewhere')' )` | Tells the browser where to store downloads |
85
+ | browser_options | Cuprite, Apparition | `cuprite(browser_options: { option: value, option: value })` | Pass additional options to the browser |
86
+
87
+ ---
88
+
89
+ ### Mobile emulation
90
+
91
+ The `emulate_device` option sets device metrics (width, height, pixel ratio, touch) and the user agent string via Chrome's emulation API.
92
+
93
+ Pass a symbol to match one of the built-in profiles from `DVLA::Browser::Drivers::MOBILE_PROFILES`:
94
+
95
+ ```ruby
96
+ DVLA::Browser::Drivers.selenium_chrome(emulate_device: :iphone_15)
97
+ DVLA::Browser::Drivers.headless_selenium_chrome(emulate_device: :ipad_pro)
98
+ DVLA::Browser::Drivers.selenium_chrome(emulate_device: :galaxy_s9_plus_landscape)
99
+ ```
100
+
101
+ Or pass a custom hash using [Chrome emulation keys](https://www.selenium.dev/selenium/docs/api/rb/Selenium/WebDriver/Chromium/Options.html#add_emulation-instance_method):
102
+
103
+ ```ruby
104
+ DVLA::Browser::Drivers.selenium_chrome(
105
+ emulate_device: {
106
+ device_metrics: { width: 390, height: 844, pixelRatio: 3.0, touch: true },
107
+ user_agent: 'Mozilla/5.0 (custom)'
108
+ }
109
+ )
110
+ ```
111
+
112
+ Available built-in profile symbols follow the naming pattern `<device_name>` or `<device_name>_landscape`, e.g.:
113
+
114
+ | Symbol | Dimensions |
115
+ |--------|------------|
116
+ | `:iphone_15` | 393×659 |
117
+ | `:iphone_15_landscape` | 734×343 |
118
+ | `:ipad_pro` | 1024×1366 |
119
+ | `:pixel_5` | 393×851 |
120
+ | `:galaxy_s9_plus` | 320×658 |
121
+
122
+ See `lib/dvla/browser/drivers/mobile_profiles.rb` for the full list.
75
123
 
76
124
  ---
77
125
 
@@ -84,6 +132,37 @@ It is still in active development so breaking changes are expected. Check the do
84
132
  * [W3C specification](https://w3c.github.io/webdriver-bidi/)
85
133
 
86
134
 
135
+ ## Rake Tasks
136
+
137
+ The gem ships with a set of rake tasks to quickly launch any driver against a URL for manual inspection.
138
+ Add the following to your `Rakefile` to make them available:
139
+
140
+ ```ruby
141
+ require 'dvla/browser/drivers/tasks'
142
+ ```
143
+
144
+ Tasks are namespaced by browser. Run any `browser:<browser>:<variant>` task, e.g.:
145
+
146
+ ```bash
147
+ bundle exec rake browser:chrome
148
+ bundle exec rake browser:chrome:headless
149
+ bundle exec rake browser:chrome:headless_no_js
150
+ bundle exec rake browser:chrome:window_size
151
+ bundle exec rake browser:chrome:emulated
152
+ bundle exec rake browser:cuprite:headless
153
+ ```
154
+
155
+ The following environment variables can be used to configure the tasks:
156
+
157
+ | Variable | Default | Description |
158
+ |-----------------------|--------------------------|------------------------------------------------|
159
+ | `BROWSER_URL` | `http://localhost:3000` | URL the browser will open |
160
+ | `PROXY_URL` | `http://localhost:8080` | Proxy URL for `proxied` tasks |
161
+ | `BROWSER_OPEN_TIME` | `10` | Seconds to hold the browser open |
162
+ | `BROWSER_WINDOW_SIZE` | `1337x800` | Window size for `window_size` tasks |
163
+
164
+ ---
165
+
87
166
  ## Development
88
167
 
89
168
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
37
37
  `git ls-files -z`.split("\x0").select do |f|
38
38
  f.match(/\A(?:lib|bin|exe)/) ||
39
- f.match(/.(?:gemspec|md|ruby-version)\Z/) ||
39
+ f.match(/.(?:gemspec|md|rake|ruby-version)\Z/) ||
40
40
  f.match(/\AGemfile\Z/)
41
41
  end
42
42
  end
@@ -1,11 +1,11 @@
1
1
  module DVLA
2
2
  module Browser
3
3
  module Drivers
4
- DRIVER_REGEX = /^(?:(?<headless>headless_)(?<driver>selenium_(?<browser>chrome|firefox)|cuprite|apparition)|(?<driver_no_headless>selenium_(?<browser_no_headless>chrome|firefox|edge|safari)|cuprite|apparition))$/
4
+ DRIVER_REGEX = /^(?:(?<headless>headless_)(?<driver>selenium_(?<browser>chrome|firefox|edge)|cuprite|apparition)(?<no_js>_no_js)?(?<proxied>_proxied)?|(?<driver_no_headless>selenium_(?<browser_no_headless>chrome|firefox|edge|safari)|cuprite|apparition)(?<no_js_no_headless>_no_js)?(?<proxied_no_headless>_proxied)?)$/
5
5
 
6
- OTHER_ACCEPTED_PARAMS = %i[timeout browser_options save_path remote].freeze
6
+ OTHER_ACCEPTED_PARAMS = %i[timeout browser_options save_path remote proxy window_size].freeze
7
7
  OTHER_DRIVERS = %i[cuprite apparition].freeze
8
- SELENIUM_ACCEPTED_PARAMS = %i[remote additional_arguments additional_preferences binary].freeze
8
+ SELENIUM_ACCEPTED_PARAMS = %i[remote additional_arguments additional_preferences binary proxy window_size emulate_device].freeze
9
9
  SELENIUM_DRIVERS = %i[selenium_chrome selenium_firefox selenium_edge selenium_safari].freeze
10
10
 
11
11
  # Creates methods in the Drivers module that matches the DRIVER_REGEX
@@ -17,84 +17,164 @@ module DVLA
17
17
  # @example Driver with additional arguments
18
18
  # DVLA::Browser::Drivers.chrome(remote: 'http://localhost:4444/wd/hub')
19
19
  def self.method_missing(method, *args, **kwargs, &)
20
- if (matches = method.match(DRIVER_REGEX))
21
- headless = matches[:headless].is_a? String
22
- driver = matches[:driver]&.to_sym || matches[:driver_no_headless]&.to_sym
23
- browser_match = matches[:browser] || matches[:browser_no_headless]
20
+ matches = method.match(DRIVER_REGEX)
21
+ return super unless matches
24
22
 
25
- case driver
26
- when *SELENIUM_DRIVERS
27
- browser = browser_match.to_sym
23
+ headless = matches[:headless].is_a?(String)
24
+ no_js = matches[:no_js].is_a?(String) || matches[:no_js_no_headless].is_a?(String)
25
+ proxied = matches[:proxied].is_a?(String) || matches[:proxied_no_headless].is_a?(String)
26
+ driver = matches[:driver]&.to_sym || matches[:driver_no_headless]&.to_sym
27
+ browser = matches[:browser] || matches[:browser_no_headless]
28
28
 
29
- kwargs.each do |key, _value|
30
- puts "Key: '#{key}' will be ignored | Use one from: '#{SELENIUM_ACCEPTED_PARAMS}'" unless SELENIUM_ACCEPTED_PARAMS.include?(key)
31
- end
29
+ raise ArgumentError, "Method '#{method}' requires proxy parameter" if proxied && !kwargs[:proxy]
32
30
 
33
- ::Capybara.register_driver method do |app|
34
- if browser == :safari
35
- options = Selenium::WebDriver::Safari::Options.new
36
- else
37
- options = Object.const_get("Selenium::WebDriver::#{browser.to_s.capitalize}::Options").new(web_socket_url: true)
38
- options.binary = kwargs[:binary] if kwargs[:binary]
39
- options.add_argument('--disable-dev-shm-usage')
40
-
41
- if headless
42
- options.add_argument('--headless')
43
- options.add_argument('--no-sandbox')
44
- end
45
-
46
- kwargs[:additional_arguments] && kwargs[:additional_arguments].each do |argument|
47
- argument.prepend('--') unless argument.start_with?('--')
48
- options.add_argument(argument)
49
- end
50
-
51
- kwargs[:additional_preferences] && kwargs[:additional_preferences].each do |preference|
52
- key, value = preference.first
53
- options.add_preference(key, value)
54
- end
55
- end
56
-
57
- driver_browser = kwargs[:remote] ? :remote : browser
58
- driver_options = { browser: driver_browser, options: }
59
- driver_options[:url] = kwargs[:remote] if kwargs[:remote]
60
-
61
- ::Capybara::Selenium::Driver.new(app, **driver_options)
62
- end
63
- else
64
- kwargs.each do |key, _value|
65
- puts "Key: '#{key}' will be ignored | Use one from: '#{OTHER_ACCEPTED_PARAMS}'" unless OTHER_ACCEPTED_PARAMS.include?(key)
66
- end
31
+ case driver
32
+ when *SELENIUM_DRIVERS then register_selenium_driver(method, browser.to_sym, headless:, no_js:, **kwargs)
33
+ else register_other_driver(method, driver, headless:, no_js:, **kwargs)
34
+ end
67
35
 
68
- browser_options = { 'no-sandbox': nil, 'disable-smooth-scrolling': true }
69
- kwargs[:browser_options] && kwargs[:browser_options].each do |key, value|
70
- browser_options[key] = value
71
- end
36
+ puts "Driver set to: '#{method}'"
37
+ ::Capybara.javascript_driver = method
38
+ ::Capybara.default_driver = method
39
+ ::Capybara.current_driver = method
40
+ end
41
+
42
+ def self.respond_to_missing?(method, *args)
43
+ method.match(DRIVER_REGEX) || super
44
+ end
45
+
46
+ def self.register_selenium_driver(method, browser, headless:, no_js:, **kwargs)
47
+ warn_ignored_kwargs(kwargs, SELENIUM_ACCEPTED_PARAMS)
48
+ puts "Warning: window_size is not supported for #{browser}" if kwargs[:window_size] && browser == :safari
49
+ puts 'Warning: window_size will be overridden by emulate_device' if kwargs[:window_size] && kwargs[:emulate_device]
50
+
51
+ ::Capybara.register_driver method do |app|
52
+ options = build_selenium_options(browser, headless:, no_js:, **kwargs)
53
+ driver_browser = kwargs[:remote] ? :remote : browser
54
+ driver_options = { browser: driver_browser, options: }
55
+ driver_options[:url] = kwargs[:remote] if kwargs[:remote]
72
56
 
73
- ::Capybara.register_driver method do |app|
74
- Object.const_get("Capybara::#{driver.to_s.capitalize}::Driver").new(
75
- app,
76
- headless:,
77
- timeout: kwargs[:timeout] || 60,
78
- browser_options:,
79
- save_path: kwargs[:save_path],
80
- url: kwargs[:remote],
81
- )
57
+ ::Capybara::Selenium::Driver.new(app, **driver_options).tap do |d|
58
+ d.browser.execute_cdp('Emulation.setScriptExecutionDisabled', value: true) if no_js && browser == :edge
59
+ if kwargs[:window_size] && browser == :firefox
60
+ size = parse_window_size(kwargs[:window_size])
61
+ d.browser.manage.window.resize_to(size[0], size[1])
82
62
  end
83
63
  end
64
+ end
65
+ end
66
+ private_class_method :register_selenium_driver
67
+
68
+ def self.register_other_driver(method, driver, headless:, no_js:, **kwargs)
69
+ warn_ignored_kwargs(kwargs, OTHER_ACCEPTED_PARAMS)
70
+
71
+ browser_options = { 'no-sandbox': nil, 'disable-smooth-scrolling': true }
72
+ browser_options.merge!(kwargs[:browser_options]) if kwargs[:browser_options]
73
+ browser_options[:'blink-settings'] = 'scriptEnabled=false' if no_js
74
+ if kwargs[:proxy]
75
+ browser_options[:'proxy-server'] = kwargs[:proxy]
76
+ browser_options[:'ignore-certificate-errors'] = nil
77
+ end
78
+
79
+ ::Capybara.register_driver method do |app|
80
+ opts = { headless:, timeout: kwargs[:timeout] || 60, browser_options:, save_path: kwargs[:save_path], url: kwargs[:remote] }
81
+ opts[:screen_size] = parse_window_size(kwargs[:window_size]) if kwargs[:window_size]
82
+ Object.const_get("Capybara::#{driver.to_s.capitalize}::Driver").new(app, **opts)
83
+ end
84
+ end
85
+ private_class_method :register_other_driver
86
+
87
+ def self.build_selenium_options(browser, headless:, no_js:, **kwargs)
88
+ return Selenium::WebDriver::Safari::Options.new if browser == :safari
89
+
90
+ options = Object.const_get("Selenium::WebDriver::#{browser.to_s.capitalize}::Options").new(web_socket_url: true)
91
+ options.binary = kwargs[:binary] if kwargs[:binary]
92
+ options.add_argument('--disable-dev-shm-usage')
93
+
94
+ if headless
95
+ options.add_argument('--headless')
96
+ options.add_argument('--no-sandbox')
97
+ end
84
98
 
85
- puts "Driver set to: '#{method}'"
99
+ apply_selenium_proxy(options, browser, kwargs[:proxy]) if kwargs[:proxy]
86
100
 
87
- ::Capybara.javascript_driver = method
88
- ::Capybara.default_driver = method
89
- ::Capybara.current_driver = method
101
+ if kwargs[:window_size] && %i[chrome edge].include?(browser)
102
+ size = parse_window_size(kwargs[:window_size])
103
+ options.add_argument("--window-size=#{size[0]},#{size[1]}")
104
+ end
105
+
106
+ if kwargs[:emulate_device] && %i[chrome edge].include?(browser)
107
+ emulation = resolve_emulate_device(kwargs[:emulate_device])
108
+ puts "Mobile emulation: #{kwargs[:emulate_device]} | #{emulation[:device_metrics]&.map { |k, v| "#{k}: #{v}" }&.join(', ')}" if emulation[:device_metrics]
109
+ options.add_emulation(**emulation)
110
+ end
111
+
112
+ kwargs[:additional_arguments]&.each do |argument|
113
+ argument.prepend('--') unless argument.start_with?('--')
114
+ options.add_argument(argument)
115
+ end
116
+
117
+ kwargs[:additional_preferences]&.each do |preference|
118
+ options.add_preference(*preference.first)
119
+ end
120
+
121
+ if no_js
122
+ options.add_preference('profile.managed_default_content_settings.javascript', 2) if browser == :chrome
123
+ options.add_preference('javascript.enabled', false) if browser == :firefox
124
+ end
125
+
126
+ options
127
+ end
128
+ private_class_method :build_selenium_options
129
+
130
+ def self.apply_selenium_proxy(options, browser, proxy)
131
+ if browser == :firefox
132
+ proxy_uri = URI.parse(proxy)
133
+ proxy_host = proxy_uri.host == '0.0.0.0' ? '127.0.0.1' : proxy_uri.host
134
+ options.add_preference('network.proxy.type', 1)
135
+ options.add_preference('network.proxy.http', proxy_host)
136
+ options.add_preference('network.proxy.http_port', proxy_uri.port)
137
+ options.add_preference('network.proxy.ssl', proxy_host)
138
+ options.add_preference('network.proxy.ssl_port', proxy_uri.port)
139
+ options.add_preference('network.proxy.no_proxies_on', '')
140
+ options.add_preference('security.cert_pinning.enforcement_level', 0)
141
+ options.add_preference('security.enterprise_roots.enabled', true)
90
142
  else
91
- super.method_missing(method, *args, &)
143
+ options.add_argument("--proxy-server=#{proxy}")
144
+ options.add_argument('--ignore-certificate-errors')
92
145
  end
93
146
  end
147
+ private_class_method :apply_selenium_proxy
94
148
 
95
- def self.respond_to_missing?(method, *args)
96
- method.match(DRIVER_REGEX) || super
149
+ def self.warn_ignored_kwargs(kwargs, accepted)
150
+ kwargs.each_key do |key|
151
+ puts "Key: '#{key}' will be ignored | Use one from: '#{accepted}'" unless accepted.include?(key)
152
+ end
153
+ end
154
+ private_class_method :warn_ignored_kwargs
155
+
156
+ def self.parse_window_size(window_size)
157
+ size = window_size.is_a?(Array) ? window_size : window_size.to_s.split(/[x,]/).map(&:to_i)
158
+ raise ArgumentError, "window_size must have exactly 2 elements [width, height], got: #{window_size.inspect}" unless size.length == 2
159
+
160
+ size
161
+ end
162
+ private_class_method :parse_window_size
163
+
164
+ def self.resolve_emulate_device(emulate_device)
165
+ if emulate_device.is_a?(Symbol)
166
+ profile = MOBILE_PROFILES[emulate_device]
167
+ raise ArgumentError, "Unknown mobile profile: ':#{emulate_device}'. Available: #{MOBILE_PROFILES.keys.map { |k| ":#{k}" }.join(', ')}" unless profile
168
+
169
+ {
170
+ device_metrics: { width: profile[:width], height: profile[:height], pixelRatio: profile[:device_scale_factor], touch: profile[:has_touch] },
171
+ user_agent: profile[:user_agent]
172
+ }
173
+ else
174
+ emulate_device.transform_keys(&:to_sym)
175
+ end
97
176
  end
177
+ private_class_method :resolve_emulate_device
98
178
  end
99
179
  end
100
180
  end
@@ -0,0 +1,533 @@
1
+ module DVLA
2
+ module Browser
3
+ module Drivers
4
+ # Borrowed from https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/common/Device.ts
5
+ MOBILE_PROFILES = {
6
+ blackberry_playbook: {
7
+ user_agent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
8
+ width: 600, height: 1024, device_scale_factor: 1, mobile: true, has_touch: true
9
+ },
10
+ blackberry_playbook_landscape: {
11
+ user_agent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
12
+ width: 1024, height: 600, device_scale_factor: 1, mobile: true, has_touch: true
13
+ },
14
+ blackberry_z30: {
15
+ user_agent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
16
+ width: 360, height: 640, device_scale_factor: 2, mobile: true, has_touch: true
17
+ },
18
+ blackberry_z30_landscape: {
19
+ user_agent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
20
+ width: 640, height: 360, device_scale_factor: 2, mobile: true, has_touch: true
21
+ },
22
+ galaxy_note_3: {
23
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
24
+ width: 360, height: 640, device_scale_factor: 3, mobile: true, has_touch: true
25
+ },
26
+ galaxy_note_3_landscape: {
27
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
28
+ width: 640, height: 360, device_scale_factor: 3, mobile: true, has_touch: true
29
+ },
30
+ galaxy_note_ii: {
31
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
32
+ width: 360, height: 640, device_scale_factor: 2, mobile: true, has_touch: true
33
+ },
34
+ galaxy_note_ii_landscape: {
35
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
36
+ width: 640, height: 360, device_scale_factor: 2, mobile: true, has_touch: true
37
+ },
38
+ galaxy_s_iii: {
39
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
40
+ width: 360, height: 640, device_scale_factor: 2, mobile: true, has_touch: true
41
+ },
42
+ galaxy_s_iii_landscape: {
43
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
44
+ width: 640, height: 360, device_scale_factor: 2, mobile: true, has_touch: true
45
+ },
46
+ galaxy_s5: {
47
+ user_agent: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
48
+ width: 360, height: 640, device_scale_factor: 3, mobile: true, has_touch: true
49
+ },
50
+ galaxy_s5_landscape: {
51
+ user_agent: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
52
+ width: 640, height: 360, device_scale_factor: 3, mobile: true, has_touch: true
53
+ },
54
+ galaxy_s8: {
55
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',
56
+ width: 360, height: 740, device_scale_factor: 3, mobile: true, has_touch: true
57
+ },
58
+ galaxy_s8_landscape: {
59
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',
60
+ width: 740, height: 360, device_scale_factor: 3, mobile: true, has_touch: true
61
+ },
62
+ galaxy_s9_plus: {
63
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36',
64
+ width: 320, height: 658, device_scale_factor: 4.5, mobile: true, has_touch: true
65
+ },
66
+ galaxy_s9_plus_landscape: {
67
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36',
68
+ width: 658, height: 320, device_scale_factor: 4.5, mobile: true, has_touch: true
69
+ },
70
+ galaxy_tab_s4: {
71
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.0 Safari/537.36',
72
+ width: 712, height: 1138, device_scale_factor: 2.25, mobile: true, has_touch: true
73
+ },
74
+ galaxy_tab_s4_landscape: {
75
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.0 Safari/537.36',
76
+ width: 1138, height: 712, device_scale_factor: 2.25, mobile: true, has_touch: true
77
+ },
78
+ ipad: {
79
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
80
+ width: 768, height: 1024, device_scale_factor: 2, mobile: true, has_touch: true
81
+ },
82
+ ipad_landscape: {
83
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
84
+ width: 1024, height: 768, device_scale_factor: 2, mobile: true, has_touch: true
85
+ },
86
+ ipad_gen_6: {
87
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
88
+ width: 768, height: 1024, device_scale_factor: 2, mobile: true, has_touch: true
89
+ },
90
+ ipad_gen_6_landscape: {
91
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
92
+ width: 1024, height: 768, device_scale_factor: 2, mobile: true, has_touch: true
93
+ },
94
+ ipad_gen_7: {
95
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 13_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
96
+ width: 810, height: 1080, device_scale_factor: 2, mobile: true, has_touch: true
97
+ },
98
+ ipad_gen_7_landscape: {
99
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 13_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
100
+ width: 1080, height: 810, device_scale_factor: 2, mobile: true, has_touch: true
101
+ },
102
+ ipad_mini: {
103
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
104
+ width: 768, height: 1024, device_scale_factor: 2, mobile: true, has_touch: true
105
+ },
106
+ ipad_mini_landscape: {
107
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
108
+ width: 1024, height: 768, device_scale_factor: 2, mobile: true, has_touch: true
109
+ },
110
+ ipad_pro: {
111
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
112
+ width: 1024, height: 1366, device_scale_factor: 2, mobile: true, has_touch: true
113
+ },
114
+ ipad_pro_landscape: {
115
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
116
+ width: 1366, height: 1024, device_scale_factor: 2, mobile: true, has_touch: true
117
+ },
118
+ ipad_pro_11: {
119
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
120
+ width: 834, height: 1194, device_scale_factor: 2, mobile: true, has_touch: true
121
+ },
122
+ ipad_pro_11_landscape: {
123
+ user_agent: 'Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
124
+ width: 1194, height: 834, device_scale_factor: 2, mobile: true, has_touch: true
125
+ },
126
+ iphone_4: {
127
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
128
+ width: 320, height: 480, device_scale_factor: 2, mobile: true, has_touch: true
129
+ },
130
+ iphone_4_landscape: {
131
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
132
+ width: 480, height: 320, device_scale_factor: 2, mobile: true, has_touch: true
133
+ },
134
+ iphone_5: {
135
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
136
+ width: 320, height: 568, device_scale_factor: 2, mobile: true, has_touch: true
137
+ },
138
+ iphone_5_landscape: {
139
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
140
+ width: 568, height: 320, device_scale_factor: 2, mobile: true, has_touch: true
141
+ },
142
+ iphone_6: {
143
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
144
+ width: 375, height: 667, device_scale_factor: 2, mobile: true, has_touch: true
145
+ },
146
+ iphone_6_landscape: {
147
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
148
+ width: 667, height: 375, device_scale_factor: 2, mobile: true, has_touch: true
149
+ },
150
+ iphone_6_plus: {
151
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
152
+ width: 414, height: 736, device_scale_factor: 3, mobile: true, has_touch: true
153
+ },
154
+ iphone_6_plus_landscape: {
155
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
156
+ width: 736, height: 414, device_scale_factor: 3, mobile: true, has_touch: true
157
+ },
158
+ iphone_7: {
159
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
160
+ width: 375, height: 667, device_scale_factor: 2, mobile: true, has_touch: true
161
+ },
162
+ iphone_7_landscape: {
163
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
164
+ width: 667, height: 375, device_scale_factor: 2, mobile: true, has_touch: true
165
+ },
166
+ iphone_7_plus: {
167
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
168
+ width: 414, height: 736, device_scale_factor: 3, mobile: true, has_touch: true
169
+ },
170
+ iphone_7_plus_landscape: {
171
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
172
+ width: 736, height: 414, device_scale_factor: 3, mobile: true, has_touch: true
173
+ },
174
+ iphone_8: {
175
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
176
+ width: 375, height: 667, device_scale_factor: 2, mobile: true, has_touch: true
177
+ },
178
+ iphone_8_landscape: {
179
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
180
+ width: 667, height: 375, device_scale_factor: 2, mobile: true, has_touch: true
181
+ },
182
+ iphone_8_plus: {
183
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
184
+ width: 414, height: 736, device_scale_factor: 3, mobile: true, has_touch: true
185
+ },
186
+ iphone_8_plus_landscape: {
187
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
188
+ width: 736, height: 414, device_scale_factor: 3, mobile: true, has_touch: true
189
+ },
190
+ iphone_se: {
191
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
192
+ width: 320, height: 568, device_scale_factor: 2, mobile: true, has_touch: true
193
+ },
194
+ iphone_se_landscape: {
195
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
196
+ width: 568, height: 320, device_scale_factor: 2, mobile: true, has_touch: true
197
+ },
198
+ iphone_x: {
199
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
200
+ width: 375, height: 812, device_scale_factor: 3, mobile: true, has_touch: true
201
+ },
202
+ iphone_x_landscape: {
203
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
204
+ width: 812, height: 375, device_scale_factor: 3, mobile: true, has_touch: true
205
+ },
206
+ iphone_xr: {
207
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
208
+ width: 414, height: 896, device_scale_factor: 2, mobile: true, has_touch: true
209
+ },
210
+ iphone_xr_landscape: {
211
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
212
+ width: 896, height: 414, device_scale_factor: 2, mobile: true, has_touch: true
213
+ },
214
+ iphone_11: {
215
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
216
+ width: 414, height: 828, device_scale_factor: 2, mobile: true, has_touch: true
217
+ },
218
+ iphone_11_landscape: {
219
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
220
+ width: 828, height: 414, device_scale_factor: 2, mobile: true, has_touch: true
221
+ },
222
+ iphone_11_pro: {
223
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
224
+ width: 375, height: 812, device_scale_factor: 3, mobile: true, has_touch: true
225
+ },
226
+ iphone_11_pro_landscape: {
227
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
228
+ width: 812, height: 375, device_scale_factor: 3, mobile: true, has_touch: true
229
+ },
230
+ iphone_11_pro_max: {
231
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
232
+ width: 414, height: 896, device_scale_factor: 3, mobile: true, has_touch: true
233
+ },
234
+ iphone_11_pro_max_landscape: {
235
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1',
236
+ width: 896, height: 414, device_scale_factor: 3, mobile: true, has_touch: true
237
+ },
238
+ iphone_12: {
239
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
240
+ width: 390, height: 844, device_scale_factor: 3, mobile: true, has_touch: true
241
+ },
242
+ iphone_12_landscape: {
243
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
244
+ width: 844, height: 390, device_scale_factor: 3, mobile: true, has_touch: true
245
+ },
246
+ iphone_12_pro: {
247
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
248
+ width: 390, height: 844, device_scale_factor: 3, mobile: true, has_touch: true
249
+ },
250
+ iphone_12_pro_landscape: {
251
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
252
+ width: 844, height: 390, device_scale_factor: 3, mobile: true, has_touch: true
253
+ },
254
+ iphone_12_pro_max: {
255
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
256
+ width: 428, height: 926, device_scale_factor: 3, mobile: true, has_touch: true
257
+ },
258
+ iphone_12_pro_max_landscape: {
259
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
260
+ width: 926, height: 428, device_scale_factor: 3, mobile: true, has_touch: true
261
+ },
262
+ iphone_12_mini: {
263
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
264
+ width: 375, height: 812, device_scale_factor: 3, mobile: true, has_touch: true
265
+ },
266
+ iphone_12_mini_landscape: {
267
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
268
+ width: 812, height: 375, device_scale_factor: 3, mobile: true, has_touch: true
269
+ },
270
+ iphone_13: {
271
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
272
+ width: 390, height: 844, device_scale_factor: 3, mobile: true, has_touch: true
273
+ },
274
+ iphone_13_landscape: {
275
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
276
+ width: 844, height: 390, device_scale_factor: 3, mobile: true, has_touch: true
277
+ },
278
+ iphone_13_pro: {
279
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
280
+ width: 390, height: 844, device_scale_factor: 3, mobile: true, has_touch: true
281
+ },
282
+ iphone_13_pro_landscape: {
283
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
284
+ width: 844, height: 390, device_scale_factor: 3, mobile: true, has_touch: true
285
+ },
286
+ iphone_13_pro_max: {
287
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
288
+ width: 428, height: 926, device_scale_factor: 3, mobile: true, has_touch: true
289
+ },
290
+ iphone_13_pro_max_landscape: {
291
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
292
+ width: 926, height: 428, device_scale_factor: 3, mobile: true, has_touch: true
293
+ },
294
+ iphone_13_mini: {
295
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
296
+ width: 375, height: 812, device_scale_factor: 3, mobile: true, has_touch: true
297
+ },
298
+ iphone_13_mini_landscape: {
299
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
300
+ width: 812, height: 375, device_scale_factor: 3, mobile: true, has_touch: true
301
+ },
302
+ iphone_14: {
303
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
304
+ width: 390, height: 663, device_scale_factor: 3, mobile: true, has_touch: true
305
+ },
306
+ iphone_14_landscape: {
307
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
308
+ width: 750, height: 340, device_scale_factor: 3, mobile: true, has_touch: true
309
+ },
310
+ iphone_14_plus: {
311
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
312
+ width: 428, height: 745, device_scale_factor: 3, mobile: true, has_touch: true
313
+ },
314
+ iphone_14_plus_landscape: {
315
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
316
+ width: 832, height: 378, device_scale_factor: 3, mobile: true, has_touch: true
317
+ },
318
+ iphone_14_pro: {
319
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
320
+ width: 393, height: 659, device_scale_factor: 3, mobile: true, has_touch: true
321
+ },
322
+ iphone_14_pro_landscape: {
323
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
324
+ width: 734, height: 343, device_scale_factor: 3, mobile: true, has_touch: true
325
+ },
326
+ iphone_14_pro_max: {
327
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
328
+ width: 430, height: 739, device_scale_factor: 3, mobile: true, has_touch: true
329
+ },
330
+ iphone_14_pro_max_landscape: {
331
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
332
+ width: 814, height: 380, device_scale_factor: 3, mobile: true, has_touch: true
333
+ },
334
+ iphone_15: {
335
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
336
+ width: 393, height: 659, device_scale_factor: 3, mobile: true, has_touch: true
337
+ },
338
+ iphone_15_landscape: {
339
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
340
+ width: 734, height: 343, device_scale_factor: 3, mobile: true, has_touch: true
341
+ },
342
+ iphone_15_plus: {
343
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
344
+ width: 430, height: 739, device_scale_factor: 3, mobile: true, has_touch: true
345
+ },
346
+ iphone_15_plus_landscape: {
347
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
348
+ width: 814, height: 380, device_scale_factor: 3, mobile: true, has_touch: true
349
+ },
350
+ iphone_15_pro: {
351
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
352
+ width: 393, height: 659, device_scale_factor: 3, mobile: true, has_touch: true
353
+ },
354
+ iphone_15_pro_landscape: {
355
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
356
+ width: 734, height: 343, device_scale_factor: 3, mobile: true, has_touch: true
357
+ },
358
+ iphone_15_pro_max: {
359
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
360
+ width: 430, height: 739, device_scale_factor: 3, mobile: true, has_touch: true
361
+ },
362
+ iphone_15_pro_max_landscape: {
363
+ user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
364
+ width: 814, height: 380, device_scale_factor: 3, mobile: true, has_touch: true
365
+ },
366
+ jiophone_2: {
367
+ user_agent: 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
368
+ width: 240, height: 320, device_scale_factor: 1, mobile: true, has_touch: true
369
+ },
370
+ jiophone_2_landscape: {
371
+ user_agent: 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
372
+ width: 320, height: 240, device_scale_factor: 1, mobile: true, has_touch: true
373
+ },
374
+ kindle_fire_hdx: {
375
+ user_agent: 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
376
+ width: 800, height: 1280, device_scale_factor: 2, mobile: true, has_touch: true
377
+ },
378
+ kindle_fire_hdx_landscape: {
379
+ user_agent: 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
380
+ width: 1280, height: 800, device_scale_factor: 2, mobile: true, has_touch: true
381
+ },
382
+ lg_optimus_l70: {
383
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36',
384
+ width: 384, height: 640, device_scale_factor: 1.25, mobile: true, has_touch: true
385
+ },
386
+ lg_optimus_l70_landscape: {
387
+ user_agent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36',
388
+ width: 640, height: 384, device_scale_factor: 1.25, mobile: true, has_touch: true
389
+ },
390
+ microsoft_lumia_550: {
391
+ user_agent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
392
+ width: 640, height: 360, device_scale_factor: 2, mobile: true, has_touch: true
393
+ },
394
+ microsoft_lumia_950: {
395
+ user_agent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
396
+ width: 360, height: 640, device_scale_factor: 4, mobile: true, has_touch: true
397
+ },
398
+ microsoft_lumia_950_landscape: {
399
+ user_agent: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
400
+ width: 640, height: 360, device_scale_factor: 4, mobile: true, has_touch: true
401
+ },
402
+ moto_g4: {
403
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
404
+ width: 360, height: 640, device_scale_factor: 3, mobile: true, has_touch: true
405
+ },
406
+ moto_g4_landscape: {
407
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
408
+ width: 640, height: 360, device_scale_factor: 3, mobile: true, has_touch: true
409
+ },
410
+ nexus_10: {
411
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
412
+ width: 800, height: 1280, device_scale_factor: 2, mobile: true, has_touch: true
413
+ },
414
+ nexus_10_landscape: {
415
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
416
+ width: 1280, height: 800, device_scale_factor: 2, mobile: true, has_touch: true
417
+ },
418
+ nexus_4: {
419
+ user_agent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
420
+ width: 384, height: 640, device_scale_factor: 2, mobile: true, has_touch: true
421
+ },
422
+ nexus_4_landscape: {
423
+ user_agent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
424
+ width: 640, height: 384, device_scale_factor: 2, mobile: true, has_touch: true
425
+ },
426
+ nexus_5: {
427
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
428
+ width: 360, height: 640, device_scale_factor: 3, mobile: true, has_touch: true
429
+ },
430
+ nexus_5_landscape: {
431
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
432
+ width: 640, height: 360, device_scale_factor: 3, mobile: true, has_touch: true
433
+ },
434
+ nexus_5x: {
435
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
436
+ width: 412, height: 732, device_scale_factor: 2.625, mobile: true, has_touch: true
437
+ },
438
+ nexus_5x_landscape: {
439
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
440
+ width: 732, height: 412, device_scale_factor: 2.625, mobile: true, has_touch: true
441
+ },
442
+ nexus_6: {
443
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
444
+ width: 412, height: 732, device_scale_factor: 3.5, mobile: true, has_touch: true
445
+ },
446
+ nexus_6_landscape: {
447
+ user_agent: 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
448
+ width: 732, height: 412, device_scale_factor: 3.5, mobile: true, has_touch: true
449
+ },
450
+ nexus_6p: {
451
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
452
+ width: 412, height: 732, device_scale_factor: 3.5, mobile: true, has_touch: true
453
+ },
454
+ nexus_6p_landscape: {
455
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
456
+ width: 732, height: 412, device_scale_factor: 3.5, mobile: true, has_touch: true
457
+ },
458
+ nexus_7: {
459
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
460
+ width: 600, height: 960, device_scale_factor: 2, mobile: true, has_touch: true
461
+ },
462
+ nexus_7_landscape: {
463
+ user_agent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36',
464
+ width: 960, height: 600, device_scale_factor: 2, mobile: true, has_touch: true
465
+ },
466
+ nokia_lumia_520: {
467
+ user_agent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
468
+ width: 320, height: 533, device_scale_factor: 1.5, mobile: true, has_touch: true
469
+ },
470
+ nokia_lumia_520_landscape: {
471
+ user_agent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
472
+ width: 533, height: 320, device_scale_factor: 1.5, mobile: true, has_touch: true
473
+ },
474
+ nokia_n9: {
475
+ user_agent: 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
476
+ width: 480, height: 854, device_scale_factor: 1, mobile: true, has_touch: true
477
+ },
478
+ nokia_n9_landscape: {
479
+ user_agent: 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
480
+ width: 854, height: 480, device_scale_factor: 1, mobile: true, has_touch: true
481
+ },
482
+ pixel_2: {
483
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
484
+ width: 411, height: 731, device_scale_factor: 2.625, mobile: true, has_touch: true
485
+ },
486
+ pixel_2_landscape: {
487
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
488
+ width: 731, height: 411, device_scale_factor: 2.625, mobile: true, has_touch: true
489
+ },
490
+ pixel_2_xl: {
491
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
492
+ width: 411, height: 823, device_scale_factor: 3.5, mobile: true, has_touch: true
493
+ },
494
+ pixel_2_xl_landscape: {
495
+ user_agent: 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
496
+ width: 823, height: 411, device_scale_factor: 3.5, mobile: true, has_touch: true
497
+ },
498
+ pixel_3: {
499
+ user_agent: 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36',
500
+ width: 393, height: 786, device_scale_factor: 2.75, mobile: true, has_touch: true
501
+ },
502
+ pixel_3_landscape: {
503
+ user_agent: 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36',
504
+ width: 786, height: 393, device_scale_factor: 2.75, mobile: true, has_touch: true
505
+ },
506
+ pixel_4: {
507
+ user_agent: 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',
508
+ width: 353, height: 745, device_scale_factor: 3, mobile: true, has_touch: true
509
+ },
510
+ pixel_4_landscape: {
511
+ user_agent: 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',
512
+ width: 745, height: 353, device_scale_factor: 3, mobile: true, has_touch: true
513
+ },
514
+ pixel_4a_5g: {
515
+ user_agent: 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
516
+ width: 353, height: 745, device_scale_factor: 3, mobile: true, has_touch: true
517
+ },
518
+ pixel_4a_5g_landscape: {
519
+ user_agent: 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
520
+ width: 745, height: 353, device_scale_factor: 3, mobile: true, has_touch: true
521
+ },
522
+ pixel_5: {
523
+ user_agent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
524
+ width: 393, height: 851, device_scale_factor: 3, mobile: true, has_touch: true
525
+ },
526
+ pixel_5_landscape: {
527
+ user_agent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4812.0 Mobile Safari/537.36',
528
+ width: 851, height: 393, device_scale_factor: 3, mobile: true, has_touch: true
529
+ }
530
+ }.freeze
531
+ end
532
+ end
533
+ end
@@ -0,0 +1,168 @@
1
+ require 'capybara'
2
+ require 'dvla/browser/drivers'
3
+
4
+ DVLA_BROWSER_URL = ENV.fetch('BROWSER_URL', 'http://localhost:3000').freeze
5
+ DVLA_BROWSER_PROXY_URL = ENV.fetch('PROXY_URL', 'http://localhost:8080').freeze
6
+ DVLA_BROWSER_OPEN_TIME = ENV.fetch('BROWSER_OPEN_TIME', 10).to_i
7
+ DVLA_BROWSER_WINDOW_SIZE = ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800').then { |s| s.split('x').map(&:to_i) }.freeze
8
+ DVLA_BROWSER_MOBILE_PROFILES = DVLA::Browser::Drivers::MOBILE_PROFILES.keys
9
+
10
+ def dvla_browser_launch(driver_name, **opts)
11
+ DVLA::Browser::Drivers.send(driver_name, **opts)
12
+ session = Capybara::Session.new(driver_name)
13
+ session.visit(DVLA_BROWSER_URL)
14
+ sleep DVLA_BROWSER_OPEN_TIME
15
+ ensure
16
+ session.quit
17
+ end
18
+
19
+ namespace :browser do
20
+ # ── Selenium Chrome ───────────────────────────────────────────────────────────
21
+
22
+ desc 'Launch Chrome'
23
+ task(chrome: 'browser:chrome:default')
24
+
25
+ namespace :chrome do
26
+ desc 'Launch Chrome'
27
+ task(:default) { dvla_browser_launch(:selenium_chrome) }
28
+ desc 'Launch Chrome (headless)'
29
+ task(:headless) { dvla_browser_launch(:headless_selenium_chrome) }
30
+ desc 'Launch Chrome (no JS)'
31
+ task(:no_js) { dvla_browser_launch(:selenium_chrome_no_js) }
32
+ desc 'Launch Chrome (headless, no JS)'
33
+ task(:headless_no_js) { dvla_browser_launch(:headless_selenium_chrome_no_js) }
34
+ desc 'Launch Chrome (proxied)'
35
+ task(:proxied) { dvla_browser_launch(:selenium_chrome_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
36
+ desc 'Launch Chrome (headless, proxied)'
37
+ task(:headless_proxied) { dvla_browser_launch(:headless_selenium_chrome_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
38
+ desc 'Launch Chrome (headless, no JS, proxied)'
39
+ task(:headless_no_js_proxied) { dvla_browser_launch(:headless_selenium_chrome_no_js_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
40
+ desc 'Launch Chrome with mobile emulation'
41
+ task(:emulated) { dvla_browser_launch(:selenium_chrome, emulate_device: DVLA_BROWSER_MOBILE_PROFILES.sample) }
42
+ desc 'Launch Chrome (headless) with mobile emulation'
43
+ task(:headless_emulated) { dvla_browser_launch(:headless_selenium_chrome, emulate_device: DVLA_BROWSER_MOBILE_PROFILES.sample) }
44
+ desc "Launch Chrome at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
45
+ task(:window_size) { dvla_browser_launch(:selenium_chrome, window_size: DVLA_BROWSER_WINDOW_SIZE) }
46
+ desc "Launch Chrome (headless) at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
47
+ task(:headless_window_size) { dvla_browser_launch(:headless_selenium_chrome, window_size: DVLA_BROWSER_WINDOW_SIZE) }
48
+ end
49
+
50
+ # ── Selenium Firefox ──────────────────────────────────────────────────────────
51
+
52
+ desc 'Launch Firefox'
53
+ task(firefox: 'browser:firefox:default')
54
+
55
+ namespace :firefox do
56
+ desc 'Launch Firefox'
57
+ task(:default) { dvla_browser_launch(:selenium_firefox) }
58
+ desc 'Launch Firefox (headless)'
59
+ task(:headless) { dvla_browser_launch(:headless_selenium_firefox) }
60
+ desc 'Launch Firefox (no JS)'
61
+ task(:no_js) { dvla_browser_launch(:selenium_firefox_no_js) }
62
+ desc 'Launch Firefox (headless, no JS)'
63
+ task(:headless_no_js) { dvla_browser_launch(:headless_selenium_firefox_no_js) }
64
+ desc 'Launch Firefox (proxied)'
65
+ task(:proxied) { dvla_browser_launch(:selenium_firefox_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
66
+ desc 'Launch Firefox (headless, proxied)'
67
+ task(:headless_proxied) { dvla_browser_launch(:headless_selenium_firefox_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
68
+ desc 'Launch Firefox (headless, no JS, proxied)'
69
+ task(:headless_no_js_proxied) { dvla_browser_launch(:headless_selenium_firefox_no_js_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
70
+ desc "Launch Firefox at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
71
+ task(:window_size) { dvla_browser_launch(:selenium_firefox, window_size: DVLA_BROWSER_WINDOW_SIZE) }
72
+ desc "Launch Firefox (headless) at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
73
+ task(:headless_window_size) { dvla_browser_launch(:headless_selenium_firefox, window_size: DVLA_BROWSER_WINDOW_SIZE) }
74
+ end
75
+
76
+ # ── Selenium Edge ─────────────────────────────────────────────────────────────
77
+
78
+ desc 'Launch Edge'
79
+ task(edge: 'browser:edge:default')
80
+
81
+ namespace :edge do
82
+ desc 'Launch Edge'
83
+ task(:default) { dvla_browser_launch(:selenium_edge) }
84
+ desc 'Launch Edge (headless)'
85
+ task(:headless) { dvla_browser_launch(:headless_selenium_edge) }
86
+ desc 'Launch Edge (no JS)'
87
+ task(:no_js) { dvla_browser_launch(:selenium_edge_no_js) }
88
+ desc 'Launch Edge (headless, no JS)'
89
+ task(:headless_no_js) { dvla_browser_launch(:headless_selenium_edge_no_js) }
90
+ desc 'Launch Edge (proxied)'
91
+ task(:proxied) { dvla_browser_launch(:selenium_edge_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
92
+ desc 'Launch Edge (headless, proxied)'
93
+ task(:headless_proxied) { dvla_browser_launch(:headless_selenium_edge_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
94
+ desc 'Launch Edge (headless, no JS, proxied)'
95
+ task(:headless_no_js_proxied) { dvla_browser_launch(:headless_selenium_edge_no_js_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
96
+ desc 'Launch Edge with mobile emulation'
97
+ task(:emulated) { dvla_browser_launch(:selenium_edge, emulate_device: DVLA_BROWSER_MOBILE_PROFILES.sample) }
98
+ desc 'Launch Edge (headless) with mobile emulation'
99
+ task(:headless_emulated) { dvla_browser_launch(:headless_selenium_edge, emulate_device: DVLA_BROWSER_MOBILE_PROFILES.sample) }
100
+ desc "Launch Edge at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
101
+ task(:window_size) { dvla_browser_launch(:selenium_edge, window_size: DVLA_BROWSER_WINDOW_SIZE) }
102
+ desc "Launch Edge (headless) at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
103
+ task(:headless_window_size) { dvla_browser_launch(:headless_selenium_edge, window_size: DVLA_BROWSER_WINDOW_SIZE) }
104
+ end
105
+
106
+ # ── Selenium Safari ───────────────────────────────────────────────────────────
107
+
108
+ desc 'Launch Safari'
109
+ task(safari: 'browser:safari:default')
110
+
111
+ namespace :safari do
112
+ desc 'Launch Safari'
113
+ task(:default) { dvla_browser_launch(:selenium_safari) }
114
+ end
115
+
116
+ # ── Cuprite ───────────────────────────────────────────────────────────────────
117
+
118
+ desc 'Launch Cuprite'
119
+ task(cuprite: 'browser:cuprite:default')
120
+
121
+ namespace :cuprite do
122
+ desc 'Launch Cuprite'
123
+ task(:default) { dvla_browser_launch(:cuprite) }
124
+ desc 'Launch Cuprite (headless)'
125
+ task(:headless) { dvla_browser_launch(:headless_cuprite) }
126
+ desc 'Launch Cuprite (no JS)'
127
+ task(:no_js) { dvla_browser_launch(:cuprite_no_js) }
128
+ desc 'Launch Cuprite (headless, no JS)'
129
+ task(:headless_no_js) { dvla_browser_launch(:headless_cuprite_no_js) }
130
+ desc 'Launch Cuprite (proxied)'
131
+ task(:proxied) { dvla_browser_launch(:cuprite_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
132
+ desc 'Launch Cuprite (headless, proxied)'
133
+ task(:headless_proxied) { dvla_browser_launch(:headless_cuprite_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
134
+ desc 'Launch Cuprite (headless, no JS, proxied)'
135
+ task(:headless_no_js_proxied) { dvla_browser_launch(:headless_cuprite_no_js_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
136
+ desc "Launch Cuprite at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
137
+ task(:window_size) { dvla_browser_launch(:cuprite, window_size: DVLA_BROWSER_WINDOW_SIZE) }
138
+ desc "Launch Cuprite (headless) at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
139
+ task(:headless_window_size) { dvla_browser_launch(:headless_cuprite, window_size: DVLA_BROWSER_WINDOW_SIZE) }
140
+ end
141
+
142
+ # ── Apparition ────────────────────────────────────────────────────────────────
143
+
144
+ desc 'Launch Apparition'
145
+ task(apparition: 'browser:apparition:default')
146
+
147
+ namespace :apparition do
148
+ desc 'Launch Apparition'
149
+ task(:default) { dvla_browser_launch(:apparition) }
150
+ desc 'Launch Apparition (headless)'
151
+ task(:headless) { dvla_browser_launch(:headless_apparition) }
152
+ desc 'Launch Apparition (no JS)'
153
+ task(:no_js) { dvla_browser_launch(:apparition_no_js) }
154
+ desc 'Launch Apparition (headless, no JS)'
155
+ task(:headless_no_js) { dvla_browser_launch(:headless_apparition_no_js) }
156
+ desc 'Launch Apparition (proxied)'
157
+ task(:proxied) { dvla_browser_launch(:apparition_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
158
+ desc 'Launch Apparition (headless, proxied)'
159
+ task(:headless_proxied) { dvla_browser_launch(:headless_apparition_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
160
+ desc 'Launch Apparition (headless, no JS, proxied)'
161
+ task(:headless_no_js_proxied) { dvla_browser_launch(:headless_apparition_no_js_proxied, proxy: DVLA_BROWSER_PROXY_URL) }
162
+ desc "Launch Apparition at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
163
+ task(:window_size) { dvla_browser_launch(:apparition, window_size: DVLA_BROWSER_WINDOW_SIZE) }
164
+ desc "Launch Apparition (headless) at window size #{ENV.fetch('BROWSER_WINDOW_SIZE', '1337x800')}"
165
+ task(:headless_window_size) { dvla_browser_launch(:headless_apparition, window_size: DVLA_BROWSER_WINDOW_SIZE) }
166
+ end
167
+
168
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('tasks/browser.rake', __dir__)
@@ -3,7 +3,7 @@
3
3
  module DVLA
4
4
  module Browser
5
5
  module Drivers
6
- VERSION = '3.1.2'
6
+ VERSION = '3.3.0'
7
7
  end
8
8
  end
9
9
  end
@@ -1,3 +1,4 @@
1
+ require_relative 'drivers/mobile_profiles'
1
2
  require_relative 'drivers/meta_drivers'
2
3
  require_relative 'drivers/version'
3
4
 
@@ -0,0 +1,17 @@
1
+ For testing browsers locally
2
+
3
+ Run a simple file server and proxy from the top level using: `ruby playground/servers.rb`
4
+
5
+ Available rake tasks can be found by running:
6
+ `bundle exec rake --tasks browser`
7
+
8
+ Each task will just open the browser and close after a few seconds
9
+
10
+ ## Environment variables
11
+
12
+ | Variable | Default | Description |
13
+ |----------|---------|-------------|
14
+ | `BROWSER_URL` | `http://localhost:3000` | URL the browser will open |
15
+ | `PROXY_URL` | `http://localhost:8080` | Proxy URL for `proxied` tasks |
16
+ | `BROWSER_OPEN_TIME` | `10` | Seconds to hold the browser open |
17
+ | `BROWSER_WINDOW_SIZE` | `1337x800` | Window size for `window_size` tasks |
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dvla-browser-drivers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Driver and Vehicle Licensing Agency (DVLA)
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-01-22 00:00:00.000000000 Z
12
+ date: 2026-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: apparition
@@ -86,7 +86,11 @@ files:
86
86
  - dvla-browser-drivers.gemspec
87
87
  - lib/dvla/browser/drivers.rb
88
88
  - lib/dvla/browser/drivers/meta_drivers.rb
89
+ - lib/dvla/browser/drivers/mobile_profiles.rb
90
+ - lib/dvla/browser/drivers/tasks.rb
91
+ - lib/dvla/browser/drivers/tasks/browser.rake
89
92
  - lib/dvla/browser/drivers/version.rb
93
+ - playground/README.md
90
94
  homepage: https://github.com/dvla/dvla-browser-drivers
91
95
  licenses:
92
96
  - MIT