Last active
May 5, 2025 06:59
-
-
Save dieter-medium/01a5bbe9e8e24dee6627820d8002b734 to your computer and use it in GitHub Desktop.
This proof of concept demonstrates how to generate a PDF from a local HTML file using plain Ruby and WebDriver BiDi. The script starts Chromedriver in headless mode, creates a new browsing context via BiDi, navigates to the specified local file (using a placeholder for the file path), and prints the page as a PDF over a WebSocket connection. No …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# see: | |
# https://w3c.github.io/webdriver-bidi/#command-browsingContext-print | |
# https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF | |
# https://github.com/GoogleChromeLabs/chromium-bidi/blob/main/examples/print_example.py | |
# https://github.com/GoogleChromeLabs/chromium-bidi/blob/main/examples/_helpers.py | |
require 'net/http' | |
require 'uri' | |
require 'json' | |
require 'websocket-client-simple' | |
require 'base64' | |
require 'webdrivers/chromedriver' | |
Webdrivers.logger.level = :DEBUG | |
Webdrivers::Chromedriver.update | |
chromedriver_bin = Webdrivers::Chromedriver.driver_path | |
# Start Chromedriver on port 9515 in headless mode | |
chromedriver_cmd = "#{chromedriver_bin} --headless --port=9515" | |
chromedriver_pid = Process.spawn(chromedriver_cmd) | |
puts "Started Chromedriver with PID #{chromedriver_pid}" | |
# Ensure Chromedriver is terminated when the program exits | |
at_exit do | |
Process.kill("TERM", chromedriver_pid) rescue nil | |
end | |
# Wait a short moment to ensure Chromedriver is ready | |
sleep 2 | |
# Create a new WebDriver session | |
uri = URI("http://localhost:9515/session") | |
session_request = { | |
"capabilities" => { | |
"alwaysMatch" => { | |
"browserName" => "chrome", | |
"goog:chromeOptions" => { | |
"args" => ["--headless", "--disable-gpu"] | |
}, | |
"acceptInsecureCerts" => true, | |
"webSocketUrl" => true | |
} | |
} | |
} | |
response = Net::HTTP.post(uri, session_request.to_json, "Content-Type" => "application/json") | |
session_data = JSON.parse(response.body) | |
session_id = session_data["value"]["sessionId"] | |
websocket_url = session_data["value"]["capabilities"]["webSocketUrl"] | |
puts "Session data: #{session_data}" | |
puts "Created session with ID: #{session_id}" | |
puts "WebSocket URL: #{websocket_url}" | |
# Define the path to your local HTML file. | |
# Replace "YOUR_LOCAL_HTML_FILE.html" with your actual file path. | |
local_html_path = File.expand_path("YOUR_LOCAL_HTML_FILE.html") | |
local_file_url = "file://#{local_html_path}" | |
# Connect to the WebDriver BiDi endpoint via WebSocket | |
ws = WebSocket::Client::Simple.connect(websocket_url) | |
browsing_context_id = nil | |
ws.on :open do | |
puts "Connected to WebDriver BiDi WebSocket." | |
# Create a new browsing context (i.e. a new tab) | |
create_context_msg = { | |
id: 1, | |
method: "browsingContext.create", | |
params: { type: "tab" } | |
} | |
ws.send(create_context_msg.to_json) | |
end | |
ws.on :message do |msg| | |
data = JSON.parse(msg.data) | |
puts data | |
if data["id"] == 1 && data["result"] | |
browsing_context_id = data["result"]["context"] | |
puts "Created browsing context: #{browsing_context_id}" | |
puts "Navigating to #{local_file_url}" | |
# Navigate to the local HTML file and wait for the page to load completely | |
navigate_msg = { | |
id: 2, | |
method: "browsingContext.navigate", | |
params: { | |
url: local_file_url, | |
context: browsing_context_id, | |
wait: "complete" | |
} | |
} | |
ws.send(navigate_msg.to_json) | |
elsif data["id"] == 2 && data["result"] | |
puts "Navigation complete in browsing context: #{browsing_context_id}" | |
# Print the page as a PDF using the WebDriver BiDi print command | |
print_pdf_msg = { | |
id: 3, | |
method: "browsingContext.print", | |
params: { | |
context: browsing_context_id | |
} | |
} | |
ws.send(print_pdf_msg.to_json) | |
elsif data["id"] == 3 && data["result"] | |
pdf_base64 = data["result"]["data"] | |
File.open("output.pdf", "wb") do |file| | |
file.write(Base64.decode64(pdf_base64)) | |
end | |
puts "PDF saved as 'output.pdf'." | |
# Close the browsing context | |
close_context_msg = { | |
id: 4, | |
method: "browsingContext.close", | |
params: { context: browsing_context_id } | |
} | |
ws.send(close_context_msg.to_json) | |
ws.close | |
end | |
end | |
ws.on :error do |e| | |
puts "Error: #{e.message}" | |
end | |
ws.on :close do |_e| | |
puts "WebSocket connection closed." | |
exit 0 | |
end | |
# Keep the program running until the WebSocket connection is closed | |
loop do | |
sleep 1 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment