chromedriver-binary 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e37c7672fc5a5a74432545fb3b11927e0e1a7157e4f1210a43b7116b6d78a67f
4
+ data.tar.gz: 3ba62d5d9a4c307623c33c53c04e9cc95e461eee852f59d3a2baf7e8da28d893
5
+ SHA512:
6
+ metadata.gz: 1b9c2c0d4fec758a0f12d5e70d052e9c9dc1f20cfd5374f3feb132b884a6f56bf44a5c2db22f90b3536aeb83dfbdbc088bc2eae57994dd5820de149c1ead754f
7
+ data.tar.gz: 041ecdf8e7a3b39dbc870630241968f87173cd348003c2e4089fcb44dd908d4b72aade8fb964d260b44f6b5fffa27f53e9b0bd562b7602ad9ab443e3860aa4b5
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,5 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="PROJECT_PROFILE" />
4
+ </settings>
5
+ </component>
data/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,47 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.0
4
+
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ EnforcedStyle: double_quotes
10
+
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ RSpec/SubjectStub:
15
+ Enabled: false
16
+
17
+ RSpec/ExampleLength:
18
+ Enabled: false
19
+
20
+ Layout/MultilineMethodCallIndentation:
21
+ Enabled: false
22
+
23
+ Layout/FirstHashElementIndentation:
24
+ EnforcedStyle: consistent
25
+
26
+ Layout/FirstArrayElementIndentation:
27
+ EnforcedStyle: consistent
28
+
29
+ RSpec/MultipleMemoizedHelpers:
30
+ Max: 10
31
+
32
+ Metrics/MethodLength:
33
+ Enabled: false
34
+
35
+ Metrics/ClassLength:
36
+ Enabled: false
37
+
38
+ Metrics/ParameterLists:
39
+ Max: 10
40
+
41
+
42
+ Gemspec/DevelopmentDependencies:
43
+ EnforcedStyle: gemspec
44
+
45
+ plugins:
46
+ - rubocop-rake
47
+ - rubocop-rspec
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ chromedriver-binary
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.3.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-03-28
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Dieter S.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # chromedriver-binary
2
+
3
+ A Ruby gem that automatically downloads and installs ChromeDriver binaries that match your installed Chrome browser
4
+ version.
5
+
6
+ > Inspired by [webdrivers](https://github.com/titusfortner/webdrivers/tree/main)
7
+
8
+ ## Features
9
+
10
+ - Automatically detects Chrome browser version
11
+ - Downloads the matching ChromeDriver version
12
+ - Works across Windows, macOS, and Linux platforms
13
+ - Supports environment variable configuration
14
+ - Minimal dependencies
15
+
16
+ ## Installation
17
+
18
+ Add this to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'chromedriver-binary'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ ```shell
27
+ $ bundle install
28
+ ```
29
+
30
+ Or install it yourself:
31
+
32
+ ```shell
33
+ $ gem install chromedriver-binary
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ Simply require the gem in your code:
39
+
40
+ ```ruby
41
+ require 'chromedriver/binary'
42
+
43
+ Chromedriver::Binary.configure do |config|
44
+ # default STDOUT logger
45
+ config.logger = logger
46
+
47
+ # Proxy default nil
48
+ config.proxy_addr = 'myproxy_address.com'
49
+ config.proxy_port = '8080'
50
+ config.proxy_user = 'username'
51
+ config.proxy_pass = 'password'
52
+
53
+ # install dir
54
+ config.install_dir = "/tmp" # default "~/.webdrivers"
55
+ end
56
+
57
+ Chromedriver::Binary::ChromedriverDownloader.update force: true
58
+
59
+ Chromedriver::Binary::ChromedriverDownloader.driver_path
60
+
61
+ ```
62
+
63
+ ### Rake Tasks
64
+
65
+ ```ruby
66
+
67
+ require "chromedriver/binary"
68
+
69
+ load 'chromedriver/Rakefile'
70
+ ```
71
+
72
+ The gem will:
73
+
74
+ 1. Detect your installed Chrome version
75
+ 2. Download the matching ChromeDriver binary if needed
76
+ 3. Make it available in your PATH
77
+
78
+ ### Configuration
79
+
80
+ Use environment variables to customize behavior:
81
+
82
+ - `CHROMEDRIVER_CHROME_PATH`: Specify the Chrome binary location
83
+ - `CHROMEDRIVER_BINARY_PATH`: Override where ChromeDriver is installed
84
+
85
+ ## Development
86
+
87
+ After checking out the repo:
88
+
89
+ ```shell
90
+ $ bin/setup # Install dependencies
91
+ $ rake spec # Run the tests
92
+ $ bundle exec rake install # Install the gem locally
93
+ ```
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
13
+
14
+ require "chromedriver/binary"
15
+
16
+ load "chromedriver/Rakefile"
17
+
18
+ desc "Run tests with coverage"
19
+ task :coverage do
20
+ ENV["COVERAGE"] = "true"
21
+ Rake::Task["spec"].execute
22
+ puts "Coverage report generated in coverage/ directory"
23
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "chromedriver/binary"
4
+
5
+ path = File.expand_path(__dir__)
6
+ Dir.glob("#{path}/binary/tasks/*.rake").each { |f| import f }
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require_relative "version_resolver"
5
+ require_relative "platform"
6
+ require_relative "downloader_helper"
7
+ require_relative "system_helper"
8
+
9
+ module Chromedriver
10
+ module Binary
11
+ class ChromedriverDownloader
12
+ class << self
13
+ include VersionResolver
14
+ include Platform
15
+ include DownloaderHelper
16
+ include SystemHelper
17
+
18
+ # Define where to install ChromeDriver.
19
+ def install_dir
20
+ Chromedriver::Binary.install_dir
21
+ end
22
+
23
+ # Returns the absolute path to the driver_path ChromeDriver binary.
24
+ def driver_path
25
+ File.absolute_path File.join(install_dir, file_name)
26
+ end
27
+
28
+ # Downloads and extracts the latest ChromeDriver.
29
+ #
30
+ # @return [String] the full path to the downloaded executable.
31
+ # rubocop:disable Metrics/AbcSize
32
+ def update(force: false)
33
+ return driver_path if up_to_date_binary?(force)
34
+
35
+ version = latest_patch_version_for_build
36
+ zip_filename = "chromedriver_#{platform_id}.zip"
37
+ zip_path = File.join(install_dir, zip_filename)
38
+ download_url = direct_url_from_api(version, driver_filename)
39
+
40
+ prepare_install_dir
41
+ log_download_start(version, download_url)
42
+
43
+ download_file(download_url, zip_path)
44
+ extract_zip(zip_path, install_dir)
45
+ make_executable(driver_path)
46
+ cleanup_zip(zip_path)
47
+
48
+ Chromedriver::Binary.logger.debug "ChromeDriver downloaded and extracted to #{install_dir}"
49
+
50
+ driver_path
51
+ end
52
+
53
+ # rubocop:enable Metrics/AbcSize
54
+
55
+ def correct_binary?
56
+ current_installed_version == browser_version || current_installed_version == latest_patch_version_for_build
57
+ rescue ConnectionError, VersionError
58
+ false
59
+ end
60
+
61
+ private
62
+
63
+ def up_to_date_binary?(force)
64
+ if correct_binary? && !force
65
+ Chromedriver::Binary.logger.debug "A working webdriver version is already on the system"
66
+ true
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ def prepare_install_dir
73
+ FileUtils.mkdir_p(install_dir)
74
+ end
75
+
76
+ def log_download_start(version, url)
77
+ Chromedriver::Binary.logger.debug(
78
+ "Downloading ChromeDriver version #{version} for #{platform_id} from #{url}..."
79
+ )
80
+ end
81
+
82
+ def make_executable(path)
83
+ FileUtils.chmod "ugo+rx", path
84
+ Chromedriver::Binary.logger.debug "Completed download and processing of #{path}"
85
+ end
86
+
87
+ def cleanup_zip(zip_path)
88
+ File.delete(zip_path)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chromedriver
4
+ module Binary
5
+ class DownloadProgress
6
+ def initialize(logger:, step_bytes: 1 * 1024 * 1024)
7
+ # Default: 1 MB
8
+ @logger = logger
9
+ @step_bytes = step_bytes
10
+ @total_bytes = 0
11
+ @next_log_at = step_bytes
12
+ end
13
+
14
+ def track(chunk_size)
15
+ @total_bytes += chunk_size
16
+
17
+ return unless @total_bytes >= @next_log_at
18
+
19
+ mb = (@total_bytes.to_f / (1024 * 1024)).round(2)
20
+ @logger.debug("Downloaded ~#{mb} MB 📦")
21
+ @next_log_at += @step_bytes
22
+ end
23
+
24
+ def finish
25
+ mb = (@total_bytes.to_f / (1024 * 1024)).round(2)
26
+ @logger.info("Download complete! ✅ Total: #{mb} MB")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "zip"
6
+ require "fileutils"
7
+
8
+ require_relative "download_progress"
9
+
10
+ module Chromedriver
11
+ module Binary
12
+ module DownloaderHelper
13
+ def download_file(url, destination)
14
+ uri = URI.parse(url)
15
+ logger = Chromedriver::Binary.logger
16
+ http = create_http(uri)
17
+
18
+ logger.debug("Saving to: #{destination}")
19
+
20
+ exec_get_request(http, uri) do |response|
21
+ handle_unsuccessful_response(response)
22
+ write_response_to_file(response, destination, logger)
23
+ end
24
+ rescue StandardError => e
25
+ logger.debug("Error: #{e.message}")
26
+ raise "Error downloading file: #{e.message}"
27
+ end
28
+
29
+ def exec_get_request(http, uri, &block)
30
+ request = Net::HTTP::Get.new(uri)
31
+ Chromedriver::Binary.logger.debug("Sending HTTP GET request...")
32
+
33
+ http.request(request, &block)
34
+ end
35
+
36
+ def extract_zip(zip_file, destination)
37
+ Zip::File.open(zip_file) do |zip|
38
+ zip.each do |entry|
39
+ # Extract all files as top-level (ignoring any folder structure)
40
+ destination_file = File.join(destination, File.basename(entry.name))
41
+ entry.extract(destination_file) { true }
42
+ end
43
+ end
44
+ rescue StandardError => e
45
+ raise "Error extracting ZIP file: #{e.message}"
46
+ end
47
+
48
+ private
49
+
50
+ def handle_unsuccessful_response(response)
51
+ return if response.is_a?(Net::HTTPSuccess)
52
+
53
+ raise "Download failed: #{response.code} #{response.message}"
54
+ end
55
+
56
+ def write_response_to_file(response, destination, logger)
57
+ progress = DownloadProgress.new(logger: logger)
58
+
59
+ File.open(destination, "wb") do |file|
60
+ response.read_body do |chunk|
61
+ file.write(chunk)
62
+ progress.track(chunk.bytesize)
63
+ end
64
+ end
65
+
66
+ progress.finish
67
+ end
68
+
69
+ def create_http(uri)
70
+ logger = Chromedriver::Binary.logger
71
+
72
+ logger.debug("Starting download from: #{uri}")
73
+
74
+ Net::HTTP.new(
75
+ uri.host,
76
+ uri.port,
77
+ Chromedriver::Binary.proxy_addr,
78
+ Chromedriver::Binary.proxy_port,
79
+ Chromedriver::Binary.proxy_user,
80
+ Chromedriver::Binary.proxy_pass
81
+ ).tap do |http|
82
+ http.use_ssl = uri.scheme == "https"
83
+
84
+ log_proxy_info(http, logger)
85
+ end
86
+ end
87
+
88
+ def log_proxy_info(http, logger)
89
+ if http.proxy?
90
+ source = http.proxy_from_env? ? "environment" : "Chromedriver::Binary"
91
+ logger.debug("Using proxy from #{source}:")
92
+ logger.debug("Proxy address: #{http.proxy_address}")
93
+ logger.debug("Proxy port: #{http.proxy_port}")
94
+
95
+ return
96
+ end
97
+
98
+ logger.debug("No proxy is being used.")
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "platform"
4
+ require_relative "system_helper"
5
+
6
+ module Chromedriver
7
+ module Binary
8
+ class Finder
9
+ include Platform
10
+ include SystemHelper
11
+
12
+ def version
13
+ version = send("#{platform}_version", location)
14
+ raise VersionError, "Failed to determine Chrome version." if version.nil? || version.empty?
15
+
16
+ Chromedriver::Binary.logger.debug "Browser version: #{version}"
17
+ version[/\d+\.\d+\.\d+\.\d+/]
18
+ end
19
+
20
+ def location
21
+ if chrome_bin_from_env
22
+ Chromedriver::Binary.logger.debug "CHROMEDRIVER_CHROME_PATH: #{chrome_bin_from_env}"
23
+
24
+ return chrome_bin_from_env
25
+ end
26
+
27
+ send("#{platform}_location").tap do |chrome_bin|
28
+ raise Chromedriver::Binary::BrowserNotFound, "Failed to determine Chrome binary location." unless chrome_bin
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def chrome_bin_from_env
35
+ ENV.fetch("CHROMEDRIVER_CHROME_PATH", nil)
36
+ end
37
+
38
+ def find_in_paths(directories, files)
39
+ directories.each do |dir|
40
+ files.each do |file|
41
+ path = File.join(dir, file)
42
+ return path if file_exists?(path)
43
+ end
44
+ end
45
+ nil
46
+ end
47
+
48
+ def file_exists?(path)
49
+ File.exist?(path)
50
+ end
51
+
52
+ def win_location
53
+ find_in_paths(%w[LOCALAPPDATA PROGRAMFILES PROGRAMFILES(X86)],
54
+ ['\\Google\\Chrome\\Application\\chrome.exe', '\\Chromium\\Application\\chrome.exe'])
55
+ end
56
+
57
+ def mac_location
58
+ find_in_paths(["", File.expand_path("~")],
59
+ ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
60
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"])
61
+ end
62
+
63
+ def linux_location
64
+ find_in_paths(
65
+ %w[/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /snap/bin /opt/google/chrome
66
+ /opt/chromium.org/chromium], %w[google-chrome chrome chromium chromium-browser]
67
+ )
68
+ end
69
+
70
+ def win_version(location)
71
+ call("powershell.exe \"(Get-ItemProperty '#{location}').VersionInfo.ProductVersion\"")&.strip
72
+ end
73
+
74
+ def linux_version(location)
75
+ call(location, "--product-version")&.strip
76
+ end
77
+
78
+ def mac_version(location)
79
+ call(location, "--version")&.strip
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Chromedriver
6
+ module Binary
7
+ module Platform
8
+ def platform
9
+ case RbConfig::CONFIG["host_os"].downcase
10
+ when /linux/ then "linux"
11
+ when /darwin/ then "mac"
12
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ then "win"
13
+ else
14
+ raise NotImplementedError, "Your OS is not supported by this gem."
15
+ end
16
+ end
17
+
18
+ def platform_id
19
+ case platform
20
+ when /mac/
21
+ "mac64"
22
+ when /linux/
23
+ if File.exist?("/proc/version") && File.read("/proc/version").include?("Microsoft")
24
+ "win32"
25
+ else
26
+ "linux64"
27
+ end
28
+ when /win/
29
+ "win32"
30
+ end
31
+ end
32
+
33
+ def file_name
34
+ platform_id == "win32" ? "chromedriver.exe" : "chromedriver"
35
+ end
36
+
37
+ def driver_filename
38
+ platform = platform_id
39
+ case platform
40
+ when "win32"
41
+ "win32"
42
+ when "linux64"
43
+ "linux64"
44
+ when "mac64"
45
+ # If you're on an Apple Silicon Mac, check Ruby platform for arm details.
46
+ if RUBY_PLATFORM.include?("arm64-darwin")
47
+ "mac-arm64"
48
+ else
49
+ "mac-x64"
50
+ end
51
+ else
52
+ raise "Failed to determine driver filename to download for your OS."
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+
5
+ module Chromedriver
6
+ module Binary
7
+ class Railtie < Rails::Railtie
8
+ railtie_name :chromedriver_binary
9
+
10
+ rake_tasks do
11
+ path = File.expand_path(__dir__)
12
+ Dir.glob("#{path}/tasks/*.rake").each { |f| load f }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Chromedriver
6
+ module Binary
7
+ module SystemHelper
8
+ def call(process, arg = nil)
9
+ cmd = arg ? [process, arg] : process
10
+ Chromedriver::Binary.logger.debug "making System call: #{cmd}"
11
+
12
+ output, status = capture(*cmd)
13
+
14
+ raise "Failed to make system call: #{cmd}" unless status.success?
15
+
16
+ Chromedriver::Binary.logger.debug "System call returned: #{output}"
17
+ output
18
+ end
19
+
20
+ def exists?(file)
21
+ File.exist?(file).tap do |result|
22
+ Chromedriver::Binary.logger.debug "#{file} is#{" not" unless result} already downloaded"
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def capture(*cmd)
29
+ Open3.capture2(*cmd)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :chromedriver do
4
+ namespace :binary do
5
+ require "chromedriver/binary"
6
+
7
+ Chromedriver::Binary.logger.level = :info
8
+
9
+ desc "Print current chromedriver version"
10
+ task :version do
11
+ gem_ver = Chromedriver::Binary::ChromedriverDownloader.current_installed_version
12
+ if gem_ver
13
+ Chromedriver::Binary.logger.info "chromedriver #{gem_ver.version}"
14
+ else
15
+ Chromedriver::Binary.logger.warn "No existing chromedriver found."
16
+ end
17
+ end
18
+
19
+ desc "Remove and download updated chromedriver if necessary"
20
+ task :update do
21
+ Chromedriver::Binary::ChromedriverDownloader.update force: true
22
+ # rubocop:disable Layout/LineLength
23
+ Chromedriver::Binary.logger.info "Updated to chromedriver #{Chromedriver::Binary::ChromedriverDownloader.current_installed_version}"
24
+ # rubocop:enable Layout/LineLength
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chromedriver
4
+ module Binary
5
+ VERSION = "0.1.1"
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open-uri"
4
+ require "json"
5
+ require_relative "finder"
6
+ require_relative "system_helper"
7
+ require_relative "downloader_helper"
8
+
9
+ module Chromedriver
10
+ module Binary
11
+ module VersionResolver
12
+ include SystemHelper
13
+ include DownloaderHelper
14
+
15
+ BASE_URL = "https://chromedriver.storage.googleapis.com"
16
+ CHROME_FOR_TESTING_BASE_URL = "https://googlechromelabs.github.io/chrome-for-testing/"
17
+
18
+ def normalize_version(version)
19
+ Gem::Version.new(version.to_s)
20
+ end
21
+
22
+ def browser_build_version
23
+ normalize_version(browser_version.segments[0..2].join("."))
24
+ end
25
+
26
+ def latest_patch_version_for_build
27
+ data = fetch_data("latest-patch-versions-per-build.json")
28
+ patch_version = data.dig(:builds, browser_build_version.to_s.to_sym, :version)
29
+ raise "No patch version found for build #{browser_build_version}" unless patch_version
30
+
31
+ patch_version
32
+ rescue StandardError => e
33
+ raise "Error fetching latest patch version: #{e.message}"
34
+ end
35
+
36
+ # @param [_ToS] driver_version
37
+ def direct_url_from_api(driver_version, driver_filename)
38
+ data = fetch_data("known-good-versions-with-downloads.json")
39
+ version_data = data[:versions].find { |e| e[:version] == driver_version.to_s }
40
+ platform = version_data.dig(:downloads, :chromedriver).find { |e| e[:platform] == driver_filename }
41
+ platform[:url]
42
+ end
43
+
44
+ #
45
+ # Returns current chromedriver version.
46
+ #
47
+ # @return [Gem::Version]
48
+ def current_installed_version
49
+ Chromedriver::Binary.logger.debug "Checking current version"
50
+ return nil unless exists?(driver_path)
51
+
52
+ version = query_binary_version
53
+ return nil if version.nil?
54
+
55
+ # Matches 2.46, 2.46.628411 and 73.0.3683.75
56
+ normalize_version version[/\d+\.\d+(\.\d+)?(\.\d+)?/]
57
+ end
58
+
59
+ def driver_path
60
+ raise "driver_path not defined"
61
+ end
62
+
63
+ def query_binary_version
64
+ version = call(driver_path, "--version")
65
+ Chromedriver::Binary.logger.debug "Current version of #{driver_path} is #{version}"
66
+ version
67
+ rescue Errno::ENOENT
68
+ Chromedriver::Binary.logger.debug "No Such File or Directory: #{driver_path}"
69
+ nil
70
+ end
71
+
72
+ private
73
+
74
+ def fetch_data(json_file)
75
+ uri = URI.join(CHROME_FOR_TESTING_BASE_URL, json_file)
76
+ http = create_http(uri)
77
+ response = nil
78
+
79
+ exec_get_request(http, uri) do |resp|
80
+ response = resp.body
81
+ end
82
+
83
+ JSON.parse(response, symbolize_names: true)
84
+ end
85
+
86
+ def browser_version
87
+ normalize_version Chromedriver::Binary::Finder.new.version
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary/version"
4
+ require_relative "binary/chromedriver_downloader"
5
+ require_relative "binary/railtie" if defined?(Rails)
6
+ require "logger"
7
+
8
+ module Chromedriver
9
+ module Binary
10
+ class Error < StandardError; end
11
+
12
+ class VersionError < Error; end
13
+
14
+ class BrowserNotFound < Error; end
15
+
16
+ DEFAULT_INSTALL_DIR = File.expand_path("~/.webdrivers")
17
+
18
+ @logger = Logger.new($stdout)
19
+ @logger.level = Logger::DEBUG
20
+
21
+ class << self
22
+ attr_accessor :logger, :proxy_addr, :proxy_port, :proxy_user, :proxy_pass
23
+ attr_writer :install_dir
24
+
25
+ #
26
+ # Returns the install (download) directory path for the drivers.
27
+ #
28
+ # @return [String]
29
+ def install_dir
30
+ @install_dir ||= ENV["CHROMEDRIVER_INSTALL_DIR"] || DEFAULT_INSTALL_DIR
31
+ end
32
+
33
+ #
34
+ # Provides a convenient way to configure the gem.
35
+ #
36
+ # @example Configure proxy and cache_time
37
+ # Chromedriver::Binary.configure do |config|
38
+ # config.proxy_addr = 'myproxy_address.com'
39
+ # config.proxy_port = '8080'
40
+ # config.proxy_user = 'username'
41
+ # config.proxy_pass = 'password'
42
+ # end
43
+ #
44
+ def configure
45
+ yield self if block_given?
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ module Chromedriver
2
+ module Binary
3
+ @logger: Logger
4
+
5
+ @install_dir: String
6
+
7
+ DEFAULT_INSTALL_DIR: String
8
+ VERSION: String
9
+
10
+ attr_writer self.install_dir: String
11
+ attr_accessor self.logger: Logger
12
+
13
+ attr_accessor self.proxy_addr: String
14
+
15
+ attr_accessor self.proxy_port: Integer
16
+
17
+ attr_accessor self.proxy_user: String
18
+
19
+ attr_accessor self.proxy_pass: String
20
+
21
+ #
22
+ # Returns the install (download) directory path for the drivers.
23
+ #
24
+ # @return [String]
25
+ def self.install_dir: () -> String
26
+
27
+ #
28
+ # Provides a convenient way to configure the gem.
29
+ #
30
+ # @example Configure proxy and cache_time
31
+ # Chromedriver::Binary.configure do |config|
32
+ # config.proxy_addr = 'myproxy_address.com'
33
+ # config.proxy_port = '8080'
34
+ # config.proxy_user = 'username'
35
+ # config.proxy_pass = 'password'
36
+ # end
37
+ #
38
+ def self.configure: () { (untyped) -> untyped } -> untyped
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ module Chromedriver
2
+ module Binary
3
+ class ChromedriverDownloader
4
+ extend VersionResolver
5
+ extend Platform
6
+ extend DownloaderHelper
7
+ extend SystemHelper
8
+
9
+ def self.install_dir: () -> String
10
+
11
+ def self.driver_path: () -> String
12
+
13
+ def self.update: (?force: bool) -> String
14
+
15
+ def self.correct_binary?: () -> bool
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Chromedriver
2
+ module Binary
3
+ class DownloadProgress
4
+ def initialize: (
5
+ logger: untyped, # Typically Logger, but untyped for flexibility
6
+ step_bytes: Integer
7
+ ) -> void
8
+
9
+ def track: (Integer) -> void
10
+
11
+ def finish: () -> void
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Chromedriver
2
+ module Binary
3
+ module DownloaderHelper
4
+ def download_file: (String, String) -> void
5
+
6
+ def extract_zip: (String, String) -> void
7
+
8
+ private
9
+
10
+ def log_proxy_info: (Net::HTTP, untyped) -> void
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Chromedriver
2
+ module Binary
3
+ module Platform
4
+ def platform: -> untyped
5
+
6
+ def platform_id: -> untyped
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Chromedriver
2
+ module Binary
3
+ module SystemHelper
4
+ def call: -> untyped
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Chromedriver
2
+ module Binary
3
+ module VersionResolver
4
+ def normalize_version: -> untyped
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chromedriver-binary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Dieter S.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.21'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.22'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.22'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webrick
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.9'
125
+ description: |
126
+ A Ruby gem that automatically downloads and installs ChromeDriver binaries that match your installed
127
+ Chrome browser version.
128
+ email:
129
+ - 101627195+dieter-medium@users.noreply.github.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - ".idea/.gitignore"
135
+ - ".idea/inspectionProfiles/profiles_settings.xml"
136
+ - ".idea/vcs.xml"
137
+ - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".ruby-gemset"
140
+ - ".ruby-version"
141
+ - CHANGELOG.md
142
+ - LICENSE.txt
143
+ - README.md
144
+ - Rakefile
145
+ - lib/chromedriver/Rakefile
146
+ - lib/chromedriver/binary.rb
147
+ - lib/chromedriver/binary/chromedriver_downloader.rb
148
+ - lib/chromedriver/binary/download_progress.rb
149
+ - lib/chromedriver/binary/downloader_helper.rb
150
+ - lib/chromedriver/binary/finder.rb
151
+ - lib/chromedriver/binary/platform.rb
152
+ - lib/chromedriver/binary/railtie.rb
153
+ - lib/chromedriver/binary/system_helper.rb
154
+ - lib/chromedriver/binary/tasks/chromedriver.rake
155
+ - lib/chromedriver/binary/version.rb
156
+ - lib/chromedriver/binary/version_resolver.rb
157
+ - sig/chromedriver/binary/binary.rbs
158
+ - sig/chromedriver/binary/chromedriver_downloader.rbs
159
+ - sig/chromedriver/binary/download_progress.rbs
160
+ - sig/chromedriver/binary/downloader_helper.rbs
161
+ - sig/chromedriver/binary/platform.rbs
162
+ - sig/chromedriver/binary/system_helper.rbs
163
+ - sig/chromedriver/binary/version_resolver.rbs
164
+ homepage: https://github.com/dieter-medium/chromedriver-binary
165
+ licenses:
166
+ - MIT
167
+ metadata:
168
+ allowed_push_host: https://rubygems.org
169
+ homepage_uri: https://github.com/dieter-medium/chromedriver-binary
170
+ source_code_uri: https://github.com/dieter-medium/chromedriver-binary
171
+ changelog_uri: https://github.com/dieter-medium/chromedriver-binary/blob/master/CHANGELOG.md
172
+ rubygems_mfa_required: 'true'
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 3.0.0
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 3.5.11
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Automatically downloads and installs ChromeDriver binaries.
192
+ test_files: []