Bidi2pdf is a powerful Ruby gem that transforms modern web pages into high-fidelity PDFs using Chromeβs BiDirectional (BiDi) protocol. Whether you're automating reports, archiving websites, or shipping documentation, Bidi2pdf gives you precision, flexibility, and full control.
β
One-liner CLI β From URL to PDF in a single command
β
Full customization β Inject cookies, headers, auth credentials
β
Smart waiting β Wait for complete page load or network idle
β
Headless support β Run quietly in the background
β
Docker-ready β Plug and play with containers
β
Modern architecture β Built on Chrome's next-gen BiDi protocol
β
Network logging β Know which requests fail during rendering
β
Console log capture β See what goes wrong inside the browser
gem 'bidi2pdf'
gem install bidi2pdf
- Ruby β₯ 3.3
- Chrome/Chromium
- Automatic ChromeDriver management via chromedriver-binary
bidi2pdf render --url https://example.com --output example.pdf
bidi2pdf render \
--url https://example.com \
--output example.pdf \
--cookie session=abc123 \
--header X-API-KEY=token \
--auth admin:password \
--wait_network_idle \
--wait_window_loaded \
--log-level debug
require 'bidi2pdf'
launcher = Bidi2pdf::Launcher.new(
url: 'https://example.com',
output: 'example.pdf',
cookies: { 'session' => 'abc123' },
headers: { 'X-API-KEY' => 'token' },
auth: { username: 'admin', password: 'password' },
wait_window_loaded: true,
wait_network_idle: true
)
launcher.launch
require "bidi2pdf"
Bidi2pdf::DSL.with_tab(headless: true) do |tab|
tab.navigate_to("https://example.com")
tab.wait_until_network_idle
tab.print("example.pdf")
end
Get fine-grained control using Chrome sessions, tabs, and BiDi commands:
π Show full example
require "bidi2pdf"
# 1. Remote or local session?
session = Bidi2pdf::Bidi::Session.new(
session_url: "http://localhost:9092/session",
headless: true,
)
# Alternative: local session via ChromeDriver
# manager = Bidi2pdf::ChromedriverManager.new(headless: false)
# manager.start
# session = manager.session
session.start
session.client.on_close { puts "WebSocket session closed" }
# 2. Create browser/tab
browser = session.browser
context = browser.create_user_context
window = context.create_browser_window
tab = window.create_browser_tab
# 3. Inject configuration
tab.set_cookie(name: "auth", value: "secret", domain: "example.com", secure: true)
tab.add_headers(url_patterns: [{ type: "pattern", protocol: "https", hostname: "example.com", port: "443" }],
headers: [{ name: "X-API-KEY", value: "12345678" }])
tab.basic_auth(url_patterns: [{ type: "pattern", protocol: "https", hostname: "example.com", port: "443" }],
username: "username", password: "secret")
# 4. Render PDF
tab.navigate_to "https://example.com"
# Alternative: send html code to the browser
# tab.render_html_content("<html>...</html>")
# Inject JavaScript if, needed
# as an url
# tab.inject_script "https://example.com/script.js"
# or inline
# tab.inject_script "console.log('Hello from injected script!')"
# Inject CSS if needed
# as an url
# tab.inject_style url: "https://example.com/simple.css"
# or inline
# tab.inject_style content: "body { background-color: red; }"
tab.wait_until_network_idle
tab.print("my.pdf")
# 5. Cleanup
tab.close
window.close
context.close
session.close
# Prepare the environment
rake build
# Build the Docker image
docker build -t bidi2pdf -f docker/Dockerfile .
# Run the container and generate a PDF
docker run -it --rm \
-v ./output:/reports \
bidi2pdf \
bidi2pdf render --url=https://example.com --output /reports/example.pdf
Grab it directly from Docker Hub
docker run -it --rm \
-v ./output:/reports \
dieters877565/bidi2pdf:main-slim \
bidi2pdf render --url=https://example.com --output /reports/example.pdf
β Tip: Mount your local directory (e.g. ./output) to /reports in the container to easily access the generated PDFs.
rake build
docker compose -f docker/docker-compose.yml up -d
# simple example
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/sample.html --wait_window_loaded --wait_network_idle --output /reports/simple.pdf
# with a local file
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=file:///reports/sample.html--wait_network_idle --output /reports/simple.pdf
# basic auth example
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/basic/sample.html --auth admin:secret --wait_window_loaded --wait_network_idle --output /reports/basic.pdf
# header example
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/header/sample.html --header "X-API-KEY=secret" --wait_window_loaded --wait_network_idle --output /reports/header.pdf
# cookie example
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/cookie/sample.html --cookie "auth=secret" --wait_window_loaded --wait_network_idle --output /reports/cookie.pdf
# remote chrome example
docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/cookie/sample.html --remote_browser_url http://remote-chrome:3000/session --cookie "auth=secret" --wait_window_loaded --wait_network_idle --output /reports/remote.pdf
docker compose -f docker/docker-compose.yml down
Flag | Description |
---|---|
--url |
Target URL (required) |
--output |
Output PDF file (default: output.pdf) |
--cookie |
Set cookie in name=value format |
--header |
Inject custom header name=value |
--auth |
Basic auth as user:pass |
--headless |
Run Chrome headless (default: true) |
--port |
ChromeDriver port (0 = auto) |
--wait_window_loaded |
Wait until window.loaded is set to true |
--wait_network_idle |
Wait until network is idle |
--log_level |
Log level: debug, info, warn, error, fatal |
--remote_browser_url |
Connect to remote Chrome session |
--default_timeout |
Operation timeout (default: 60s) |
Rails integration is available as an additional gem:
# In your Gemfile
gem 'bidi2pdf-rails'
For full documentation and usage examples, visit: https://github.com/dieter-medium/bidi2pdf-rails
Bidi2pdf provides a suite of RSpec helpers (activated with pdf: true
) to
simplify PDF-related testing:
β spec_dir
β returns your spec directory
β tmp_dir
β returns your tmp directory
β tmp_file(*parts)
β builds a tmp file path
β random_tmp_dir(*dirs, prefix:)
β builds a random tmp directory
fixture_file(*parts)
β returns the path to a fixture file
β with_pdf_debug(pdf_data) { |data| β¦ }
β on failure, writes PDF to disk
β store_pdf_file(pdf_data, filename_prefix = "test")
β saves PDF and returns path
have_pdf_page_count
β checks if the PDF has a specific number of pagesmatch_pdf_text
β checks if the PDF equals a specific text, after stripping whitespace and normalizing characterscontains_pdf_text
β checks if the PDF contains a specific text, after stripping whitespace and normalizing characters, supporting regexcontains_pdf_image
β checks if the PDF contains a specific image
require "bidi2pdf/test_helpers/testcontainers"
you can use the chromedriver_container
helper to
start a ChromeDriver container for your tests. This is useful if you don't want to run ChromeDriver locally
or if you want to ensure a clean environment for your tests.
This also provides the helper methods:
session_url
β returns the session URL for the ChromeDriver containerchromedriver_container
β returns the Testcontainers container objectcreate_session
-> creates aBidi2pdf::Bidi::Session
object for the ChromeDriver container
With the environment variable DISABLE_CHROME_SANDBOX
set to true
, the container will run Chrome without
the sandbox. This is useful for CI environments where the sandbox may cause issues.
require "bidi2pdf/test_helpers"
require "bidi2pdf/test_helpers/images" # <= for image matching, requires lib-vips
require "bidi2pdf/test_helpers/testcontainers" # <= requires testcontainers gem
RSpec.describe "PDF generation", :pdf, :chromedriver do
it "generates a PDF with the correct content" do
pdf_data = generate_pdf("https://example.com")
expect(pdf_data).to have_pdf_page_count(1)
expect(pdf_data).to match_pdf_text("Hello, world!")
expect(pdf_data).to contain_pdf_image(fixture_file("logo.png"))
end
end
# Setup
bin/setup
# Run tests
rake spec
# Open interactive console
bin/console
This project is licensed under the MIT License.