bidi2pdf 0.1.13 → 0.1.14
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 +4 -4
- data/.devcontainer/Dockerfile +53 -0
- data/.devcontainer/devcontainer.json +51 -0
- data/.devcontainer/docker-compose.yml +33 -0
- data/.ruby-gemset +1 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +46 -6
- data/README.md +12 -0
- data/docker/Dockerfile +1 -1
- data/docker/Dockerfile.slim +2 -2
- data/docker/chrome-driver/chrome.json +874 -0
- data/lib/bidi2pdf/bidi/browser_tab.rb +52 -0
- data/lib/bidi2pdf/bidi/commands/capture_screenshot.rb +33 -0
- data/lib/bidi2pdf/bidi/commands/set_viewport.rb +32 -0
- data/lib/bidi2pdf/bidi/commands.rb +2 -0
- data/lib/bidi2pdf/cli.rb +11 -1
- data/lib/bidi2pdf/launcher.rb +5 -3
- data/lib/bidi2pdf/test_helpers/images.rb +8 -0
- data/lib/bidi2pdf/test_helpers/pdf_reader_utils.rb +1 -2
- data/lib/bidi2pdf/test_helpers/pdf_text_sanitizer.rb +1 -2
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb +1 -1
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +18 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/container_endpoint.rb +30 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb +33 -0
- data/lib/bidi2pdf/test_helpers/testcontainers.rb +1 -0
- data/lib/bidi2pdf/version.rb +1 -1
- data/lib/bidi2pdf.rb +2 -0
- data/sig/bidi2pdf/bidi/add_headers_interceptor.rbs +16 -9
- data/sig/bidi2pdf/bidi/auth_interceptor.rbs +32 -7
- data/sig/bidi2pdf/bidi/browser.rbs +4 -31
- data/sig/bidi2pdf/bidi/browser_console_logger.rbs +59 -0
- data/sig/bidi2pdf/bidi/browser_tab.rbs +252 -25
- data/sig/bidi2pdf/bidi/client.rbs +95 -43
- data/sig/bidi2pdf/bidi/command_manager.rbs +5 -17
- data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +16 -8
- data/sig/bidi2pdf/bidi/commands/base.rbs +49 -9
- data/sig/bidi2pdf/bidi/commands/browser_close.rbs +3 -1
- data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +2 -0
- data/sig/bidi2pdf/bidi/commands/browser_remove_user_context.rbs +19 -0
- data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +8 -2
- data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +12 -6
- data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +10 -5
- data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +8 -2
- data/sig/bidi2pdf/bidi/commands/capture_screenshot.rbs +31 -0
- data/sig/bidi2pdf/bidi/commands/cdp_get_session.rbs +17 -0
- data/sig/bidi2pdf/bidi/commands/create_window.rbs +4 -0
- data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +2 -0
- data/sig/bidi2pdf/bidi/commands/network_continue.rbs +4 -0
- data/sig/bidi2pdf/bidi/commands/page_print.rbs +28 -0
- data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +3 -0
- data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +12 -6
- data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +4 -0
- data/sig/bidi2pdf/bidi/commands/session_end.rbs +2 -0
- data/sig/bidi2pdf/bidi/commands/session_status.rbs +3 -1
- data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +4 -0
- data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +51 -23
- data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +17 -19
- data/sig/bidi2pdf/bidi/commands/set_viewport.rbs +31 -0
- data/sig/bidi2pdf/bidi/connection_manager.rbs +2 -1
- data/sig/bidi2pdf/bidi/event_manager.rbs +14 -19
- data/sig/bidi2pdf/bidi/interceptor.rbs +5 -0
- data/sig/bidi2pdf/bidi/js_logger_helper.rbs +9 -0
- data/sig/bidi2pdf/bidi/logger_events.rbs +21 -0
- data/sig/bidi2pdf/bidi/navigation_failed_events.rbs +17 -0
- data/sig/bidi2pdf/bidi/network_event.rbs +54 -46
- data/sig/bidi2pdf/bidi/network_event_formatters/network_event_console_formatter.rbs +63 -0
- data/sig/bidi2pdf/bidi/network_event_formatters/network_event_formatter_utils.rbs +15 -0
- data/sig/bidi2pdf/bidi/network_event_formatters/network_event_html_formatter.rbs +18 -0
- data/sig/bidi2pdf/bidi/network_event_formatters.rbs +6 -0
- data/sig/bidi2pdf/bidi/network_events.rbs +14 -39
- data/sig/bidi2pdf/bidi/session.rbs +144 -31
- data/sig/bidi2pdf/bidi/user_context.rbs +64 -45
- data/sig/bidi2pdf/bidi/web_socket_dispatcher.rbs +22 -30
- data/sig/bidi2pdf/chromedriver_manager.rbs +57 -22
- data/sig/bidi2pdf/cli.rbs +22 -2
- data/sig/bidi2pdf/dsl.rbs +34 -0
- data/sig/bidi2pdf/launcher.rbs +39 -1
- data/sig/bidi2pdf/notifications/event.rbs +40 -0
- data/sig/bidi2pdf/notifications/instrumenter.rbs +21 -0
- data/sig/bidi2pdf/notifications/logging_subscriber.rbs +41 -0
- data/sig/bidi2pdf/notifications.rbs +24 -0
- data/sig/bidi2pdf/process_tree.rbs +2 -2
- data/sig/bidi2pdf/session_runner.rbs +41 -1
- data/sig/bidi2pdf/test_helpers/configuration.rbs +51 -0
- data/sig/bidi2pdf/test_helpers/images/extractor.rbs +43 -0
- data/sig/bidi2pdf/test_helpers/images/image_similarity_checker.rbs +35 -0
- data/sig/bidi2pdf/test_helpers/images/tiff_helper.rbs +70 -0
- data/sig/bidi2pdf/test_helpers/images.rbs +0 -0
- data/sig/bidi2pdf/test_helpers/matchers/contains_pdf_image.rbs +0 -0
- data/sig/bidi2pdf/test_helpers/matchers/contains_pdf_text.rbs +0 -0
- data/sig/bidi2pdf/test_helpers/matchers/have_pdf_page_count.rbs +0 -0
- data/sig/bidi2pdf/test_helpers/matchers/match_pdf_text.rbs +0 -0
- data/sig/bidi2pdf/test_helpers/pdf_file_helper.rbs +23 -0
- data/sig/bidi2pdf/test_helpers/pdf_reader_utils.rbs +49 -0
- data/sig/bidi2pdf/test_helpers/pdf_text_sanitizer.rbs +38 -0
- data/sig/bidi2pdf/test_helpers/spec_paths_helper.rbs +35 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rbs +43 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rbs +45 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/container_endpoint.rbs +25 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/shared_docker_network.rbs +3 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rbs +47 -0
- data/sig/bidi2pdf/test_helpers/testcontainers.rbs +6 -0
- data/sig/bidi2pdf/test_helpers.rbs +0 -0
- data/sig/bidi2pdf/verbose_logger.rbs +37 -0
- data/sig/bidi2pdf/version.rbs +3 -0
- data/sig/bidi2pdf.rbs +112 -2
- data/tasks/generate_rbs.rake +11 -11
- metadata +59 -7
- data/sig/bidi2pdf/utils.rbs +0 -5
|
@@ -444,6 +444,58 @@ module Bidi2pdf
|
|
|
444
444
|
|
|
445
445
|
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
446
446
|
|
|
447
|
+
# Captures a screenshot of the browser tab, mirroring #print's own
|
|
448
|
+
# file/block/base64-return trichotomy.
|
|
449
|
+
#
|
|
450
|
+
# @param [String, nil] outputfile The output file for the PNG. Defaults to nil.
|
|
451
|
+
# @param [String] origin "document" (default) or "viewport" - see browsingContext.captureScreenshot.
|
|
452
|
+
# @param [String, nil] format Image format override, e.g. "image/png". Defaults to the browser's own default.
|
|
453
|
+
# @param [Hash, nil] clip A clip rectangle/element, passed through verbatim. Defaults to nil (full capture).
|
|
454
|
+
# @yield [png_base64] A block to handle the PNG content.
|
|
455
|
+
# @return [String, nil] The base64-encoded PNG content, or nil if outputfile or block is provided.
|
|
456
|
+
# rubocop:disable Metrics/AbcSize
|
|
457
|
+
def screenshot(outputfile = nil, origin: "document", format: nil, clip: nil, &block)
|
|
458
|
+
Bidi2pdf.notification_service.instrument("screenshot.bidi2pdf") do |instrumentation_payload|
|
|
459
|
+
cmd = Commands::CaptureScreenshot.new(context: browsing_context_id, origin: origin, format: format, clip: clip)
|
|
460
|
+
|
|
461
|
+
instrumentation_payload[:cmd] = cmd
|
|
462
|
+
|
|
463
|
+
client.send_cmd_and_wait(cmd) do |response|
|
|
464
|
+
if response["result"]
|
|
465
|
+
png_base64 = response.dig("result", "data")
|
|
466
|
+
|
|
467
|
+
instrumentation_payload[:png_base64] = png_base64
|
|
468
|
+
|
|
469
|
+
if outputfile
|
|
470
|
+
raise ScreenshotError, "Folder does not exist: #{File.dirname(outputfile)}" unless File.directory?(File.dirname(outputfile))
|
|
471
|
+
|
|
472
|
+
File.binwrite(outputfile, Base64.decode64(png_base64))
|
|
473
|
+
Bidi2pdf.logger.info "Screenshot saved as '#{outputfile}'."
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
block.call(png_base64) if block_given?
|
|
477
|
+
|
|
478
|
+
return png_base64 unless outputfile || block_given?
|
|
479
|
+
else
|
|
480
|
+
Bidi2pdf.logger.error "Error capturing screenshot: #{response}"
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# rubocop:enable Metrics/AbcSize
|
|
487
|
+
|
|
488
|
+
# Sets the browsing context's viewport - makes a headless render deterministic across
|
|
489
|
+
# machines, unlike leaving it at the browser's own default.
|
|
490
|
+
#
|
|
491
|
+
# @param [Integer] width
|
|
492
|
+
# @param [Integer] height
|
|
493
|
+
# @param [Float, nil] device_pixel_ratio Defaults to nil (browser default).
|
|
494
|
+
def set_viewport(width:, height:, device_pixel_ratio: nil)
|
|
495
|
+
cmd = Commands::SetViewport.new(context: browsing_context_id, width: width, height: height, device_pixel_ratio: device_pixel_ratio)
|
|
496
|
+
client.send_cmd_and_wait(cmd) { |response| response }
|
|
497
|
+
end
|
|
498
|
+
|
|
447
499
|
private
|
|
448
500
|
|
|
449
501
|
def build_command_and_extractor(print_options)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bidi2pdf
|
|
4
|
+
module Bidi
|
|
5
|
+
module Commands
|
|
6
|
+
class CaptureScreenshot
|
|
7
|
+
include Base
|
|
8
|
+
|
|
9
|
+
attr_reader :context, :origin, :format, :clip
|
|
10
|
+
|
|
11
|
+
def initialize(context:, origin: "document", format: nil, clip: nil)
|
|
12
|
+
@context = context
|
|
13
|
+
@origin = origin
|
|
14
|
+
@format = format
|
|
15
|
+
@clip = clip
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def method_name
|
|
19
|
+
"browsingContext.captureScreenshot"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def params
|
|
23
|
+
{
|
|
24
|
+
context: context,
|
|
25
|
+
origin: origin,
|
|
26
|
+
format: format,
|
|
27
|
+
clip: clip
|
|
28
|
+
}.compact
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bidi2pdf
|
|
4
|
+
module Bidi
|
|
5
|
+
module Commands
|
|
6
|
+
class SetViewport
|
|
7
|
+
include Base
|
|
8
|
+
|
|
9
|
+
attr_reader :context, :width, :height, :device_pixel_ratio
|
|
10
|
+
|
|
11
|
+
def initialize(context:, width:, height:, device_pixel_ratio: nil)
|
|
12
|
+
@context = context
|
|
13
|
+
@width = width
|
|
14
|
+
@height = height
|
|
15
|
+
@device_pixel_ratio = device_pixel_ratio
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def method_name
|
|
19
|
+
"browsingContext.setViewport"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def params
|
|
23
|
+
{
|
|
24
|
+
context: context,
|
|
25
|
+
viewport: { width: width, height: height },
|
|
26
|
+
devicePixelRatio: device_pixel_ratio
|
|
27
|
+
}.compact
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -18,6 +18,8 @@ module Bidi2pdf
|
|
|
18
18
|
require_relative "commands/browsing_context_close"
|
|
19
19
|
require_relative "commands/browsing_context_navigate"
|
|
20
20
|
require_relative "commands/browsing_context_print"
|
|
21
|
+
require_relative "commands/capture_screenshot"
|
|
22
|
+
require_relative "commands/set_viewport"
|
|
21
23
|
require_relative "commands/cdp_get_session"
|
|
22
24
|
require_relative "commands/page_print"
|
|
23
25
|
require_relative "commands/session_subscribe"
|
data/lib/bidi2pdf/cli.rb
CHANGED
|
@@ -40,6 +40,8 @@ module Bidi2pdf
|
|
|
40
40
|
option :header, type: :array, default: [], banner: "name=value", desc: "One or more custom headers", aliases: "-H"
|
|
41
41
|
option :auth, type: :string, banner: "user:pass", desc: "Basic auth credentials", aliases: "-a"
|
|
42
42
|
option :headless, type: :boolean, default: true, desc: "Run Chrome in headless mode"
|
|
43
|
+
option :no_sandbox, type: :boolean, default: false, desc: "Disable Chrome sandbox (needed inside containers)"
|
|
44
|
+
option :chrome_flag, type: :array, default: [], banner: "--flag[=value]", desc: "Extra Chrome flags appended to defaults", aliases: "--cf"
|
|
43
45
|
option :port, type: :numeric, default: 0, desc: "Port to run ChromeDriver on (0 = auto)"
|
|
44
46
|
option :wait_window_loaded,
|
|
45
47
|
type: :boolean,
|
|
@@ -114,6 +116,8 @@ module Bidi2pdf
|
|
|
114
116
|
"url" => "https://example.com",
|
|
115
117
|
"output" => "output.pdf",
|
|
116
118
|
"headless" => true,
|
|
119
|
+
"no_sandbox" => false,
|
|
120
|
+
"chrome_flag" => [],
|
|
117
121
|
"print_options" => {
|
|
118
122
|
"background" => true,
|
|
119
123
|
"orientation" => "portrait",
|
|
@@ -210,6 +214,11 @@ module Bidi2pdf
|
|
|
210
214
|
@launcher ||= begin
|
|
211
215
|
username, password = parse_auth(merged_options[:auth]) if merged_options[:auth]
|
|
212
216
|
|
|
217
|
+
chrome_args = Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup
|
|
218
|
+
no_sandbox = merged_options[:no_sandbox] || ENV["DISABLE_CHROME_SANDBOX"] == "true"
|
|
219
|
+
chrome_args << "--no-sandbox" if no_sandbox
|
|
220
|
+
chrome_args.concat(Array(merged_options[:chrome_flag]))
|
|
221
|
+
|
|
213
222
|
Bidi2pdf::Launcher.new(
|
|
214
223
|
url: merged_options[:url],
|
|
215
224
|
inputfile: merged_options[:hmtl_file],
|
|
@@ -223,7 +232,8 @@ module Bidi2pdf
|
|
|
223
232
|
wait_window_loaded: merged_options[:wait_window_loaded],
|
|
224
233
|
wait_network_idle: merged_options[:wait_network_idle],
|
|
225
234
|
print_options: print_options,
|
|
226
|
-
network_log_format: merged_options[:network_log_format]
|
|
235
|
+
network_log_format: merged_options[:network_log_format],
|
|
236
|
+
chrome_args: chrome_args
|
|
227
237
|
)
|
|
228
238
|
end
|
|
229
239
|
end
|
data/lib/bidi2pdf/launcher.rb
CHANGED
|
@@ -38,7 +38,8 @@ module Bidi2pdf
|
|
|
38
38
|
class Launcher
|
|
39
39
|
# rubocop:disable Metrics/ParameterLists
|
|
40
40
|
def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
|
|
41
|
-
wait_network_idle: false, print_options: {}, remote_browser_url: nil, network_log_format: :console
|
|
41
|
+
wait_network_idle: false, print_options: {}, remote_browser_url: nil, network_log_format: :console,
|
|
42
|
+
chrome_args: Bidi::Session::DEFAULT_CHROME_ARGS)
|
|
42
43
|
@url = url
|
|
43
44
|
@inputfile = inputfile
|
|
44
45
|
@port = port
|
|
@@ -54,6 +55,7 @@ module Bidi2pdf
|
|
|
54
55
|
@remote_browser_url = remote_browser_url
|
|
55
56
|
@custom_session = nil
|
|
56
57
|
@network_log_format = network_log_format
|
|
58
|
+
@chrome_args = chrome_args
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
# rubocop:enable Metrics/ParameterLists
|
|
@@ -84,9 +86,9 @@ module Bidi2pdf
|
|
|
84
86
|
|
|
85
87
|
def session
|
|
86
88
|
if @remote_browser_url
|
|
87
|
-
@custom_session = Bidi::Session.new(session_url: @remote_browser_url, headless: @headless)
|
|
89
|
+
@custom_session = Bidi::Session.new(session_url: @remote_browser_url, headless: @headless, chrome_args: @chrome_args)
|
|
88
90
|
else
|
|
89
|
-
@manager = ChromedriverManager.new(port: @port, headless: @headless)
|
|
91
|
+
@manager = ChromedriverManager.new(port: @port, headless: @headless, chrome_args: @chrome_args)
|
|
90
92
|
@manager.start
|
|
91
93
|
@manager.session
|
|
92
94
|
end
|
|
@@ -6,6 +6,14 @@ rescue LoadError
|
|
|
6
6
|
warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf image test helpers."
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
# CVE-2026-66066 (Rails Active Storage, 2026-07-29): libvips ships loaders/savers that were never
|
|
10
|
+
# fuzz-tested (SVG, JPEG XL, JPEG 2000, BMP, ICO, PSD, anything delegated to ImageMagick, ...) -
|
|
11
|
+
# left reachable, a crafted file can trigger arbitrary file read or RCE. Applied once, here, for
|
|
12
|
+
# every consumer of this opt-in module (Extractor, ImageSimilarityChecker, contains_pdf_image),
|
|
13
|
+
# rather than left to each call site to remember - PNG/JPEG (what this gem's own screenshot/PDF
|
|
14
|
+
# rendering actually produces) load through already-fuzzed loaders and are unaffected.
|
|
15
|
+
Vips.block_untrusted(true) if defined?(Vips)
|
|
16
|
+
|
|
9
17
|
require_relative "images/tiff_helper"
|
|
10
18
|
require_relative "images/extractor"
|
|
11
19
|
require_relative "images/image_similarity_checker"
|
|
@@ -49,7 +49,7 @@ module Bidi2pdf
|
|
|
49
49
|
# @param pdf_data [String, StringIO, File] The PDF data to be converted.
|
|
50
50
|
# @return [IO] An IO object containing the PDF data.
|
|
51
51
|
def convert_data_to_io(pdf_data)
|
|
52
|
-
# rubocop:disable Lint/DuplicateBranch
|
|
52
|
+
# rubocop:disable-next Lint/DuplicateBranch
|
|
53
53
|
if pdf_data.is_a?(String) && (pdf_data.start_with?("JVBERi") || pdf_data.start_with?("JVBER"))
|
|
54
54
|
StringIO.new(Base64.decode64(pdf_data))
|
|
55
55
|
elsif pdf_data.start_with?("%PDF-")
|
|
@@ -61,7 +61,6 @@ module Bidi2pdf
|
|
|
61
61
|
else
|
|
62
62
|
StringIO.new(pdf_data)
|
|
63
63
|
end
|
|
64
|
-
# rubocop:enable Lint/DuplicateBranch
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
66
|
|
|
@@ -6,7 +6,7 @@ require "diff/lcs/hunk"
|
|
|
6
6
|
|
|
7
7
|
module Bidi2pdf
|
|
8
8
|
module TestHelpers
|
|
9
|
-
# rubocop: disable Metrics/ModuleLength
|
|
9
|
+
# rubocop: disable-next Metrics/ModuleLength
|
|
10
10
|
module PDFTextSanitizer
|
|
11
11
|
class << self
|
|
12
12
|
def clean(text)
|
|
@@ -148,6 +148,5 @@ module Bidi2pdf
|
|
|
148
148
|
end
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
|
-
# rubocop:enable Metrics/ModuleLength
|
|
152
151
|
end
|
|
153
152
|
end
|
|
@@ -36,6 +36,24 @@ module Bidi2pdf
|
|
|
36
36
|
def create_session(session_url)
|
|
37
37
|
Bidi2pdf::Bidi::Session.new(session_url: session_url, headless: true, chrome_args: chrome_args)
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
# Full session/tab lifecycle in one call - create_session above only builds the session
|
|
41
|
+
# object, it never starts or closes it, and there is no after(:each) teardown for a spec
|
|
42
|
+
# that uses it directly. Mirrors Bidi2pdf::DSL.with_tab's own ensure-based teardown, but
|
|
43
|
+
# against a session_url this module already knows about (a long-lived remote-chrome
|
|
44
|
+
# reached over `session:` metadata, not a freshly spawned local chromedriver per call).
|
|
45
|
+
def with_tab(session_url)
|
|
46
|
+
session = create_session(session_url)
|
|
47
|
+
session.start
|
|
48
|
+
user_context = session.browser.create_user_context
|
|
49
|
+
tab = user_context.create_browser_window
|
|
50
|
+
|
|
51
|
+
yield tab
|
|
52
|
+
ensure
|
|
53
|
+
tab&.close
|
|
54
|
+
user_context&.close
|
|
55
|
+
session&.close
|
|
56
|
+
end
|
|
39
57
|
end
|
|
40
58
|
end
|
|
41
59
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bidi2pdf
|
|
4
|
+
module TestHelpers
|
|
5
|
+
module Testcontainers
|
|
6
|
+
# Builds a URL for reaching a container's published port from the test
|
|
7
|
+
# process, using the container's own host/port resolution.
|
|
8
|
+
class ContainerEndpoint
|
|
9
|
+
def initialize(container, port)
|
|
10
|
+
@container = container
|
|
11
|
+
@port = port
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def url(scheme: "http", path: "")
|
|
15
|
+
"#{scheme}://#{@container.accessible_host}:#{@container.mapped_port(@port)}/#{path}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Address reachable from another container on the same Docker network (e.g. remote-chrome
|
|
19
|
+
# reaching nginx), by alias and internal port - as opposed to #url's host-accessible,
|
|
20
|
+
# externally-mapped port. Uses the same @port this instance was built with, not a
|
|
21
|
+
# container_ports.first guess: the two methods must agree on which port they're both
|
|
22
|
+
# describing for the same container, or a caller comparing #url and #alias_url for what's
|
|
23
|
+
# meant to be the same endpoint could silently get two different services.
|
|
24
|
+
def alias_url(scheme: "http", path: "")
|
|
25
|
+
"#{scheme}://#{@container.aliases.first}:#{@port}/#{path}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -29,6 +29,39 @@ module Bidi2pdf
|
|
|
29
29
|
self
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def container_json
|
|
33
|
+
@_container&.json
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Address of the Docker host at which this container's *mapped* ports
|
|
37
|
+
# are reachable from the test process.
|
|
38
|
+
#
|
|
39
|
+
# Resolution belongs to testcontainers: it honours TC_HOST, a tcp:/ssh:
|
|
40
|
+
# DOCKER_HOST, a native local daemon, and a sibling container reaching
|
|
41
|
+
# the daemon over the bridge gateway — and it derives #mapped_port from
|
|
42
|
+
# the same decision, so the two must stay on one path. We only add a
|
|
43
|
+
# fallback for the cases 0.2.0 leaves unresolved.
|
|
44
|
+
#
|
|
45
|
+
# This is not the address containers use to reach each other; for that
|
|
46
|
+
# the helpers join a shared network and address containers by alias.
|
|
47
|
+
def accessible_host
|
|
48
|
+
resolved_host || "localhost"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# testcontainers-core 0.2.0 cannot resolve a host when the test process
|
|
52
|
+
# runs inside a container and the container under test is on a custom
|
|
53
|
+
# network: #container_gateway_ip hardcodes the "bridge" network key
|
|
54
|
+
# (docker_container.rb:1074), so #host returns nil. On the default
|
|
55
|
+
# bridge it can instead reach docker_container.rb:703, which calls an
|
|
56
|
+
# undefined `bridge_ip`. Both are fixed on testcontainers-ruby main;
|
|
57
|
+
# treat either as "unresolved" and fall back.
|
|
58
|
+
def resolved_host
|
|
59
|
+
value = host
|
|
60
|
+
value unless value.nil? || value.empty?
|
|
61
|
+
rescue NoMethodError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
32
65
|
def _container_create_options
|
|
33
66
|
opts = super
|
|
34
67
|
network_name = network&.info&.[]("Name")
|
|
@@ -10,6 +10,7 @@ module Bidi2pdf
|
|
|
10
10
|
module TestHelpers
|
|
11
11
|
module Testcontainers
|
|
12
12
|
require_relative "testcontainers/testcontainers_refinement"
|
|
13
|
+
require_relative "testcontainers/container_endpoint"
|
|
13
14
|
require_relative "testcontainers/chromedriver_container"
|
|
14
15
|
require_relative "testcontainers/chromedriver_test_helper"
|
|
15
16
|
end
|
data/lib/bidi2pdf/version.rb
CHANGED
data/lib/bidi2pdf.rb
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
module Bidi2pdf
|
|
2
2
|
module Bidi
|
|
3
3
|
class AddHeadersInterceptor
|
|
4
|
-
@
|
|
5
|
-
@client: untyped
|
|
6
|
-
@headers: Hash[String, String]
|
|
4
|
+
@headers: untyped
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
attr_reader headers: Hash[String, String]
|
|
6
|
+
@url_patterns: untyped
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
@context: untyped
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
include Interceptor
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
def self.phases: () -> ::Array[untyped]
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
def self.events: () -> ::Array["network.beforeRequestSent"]
|
|
15
|
+
|
|
16
|
+
attr_reader headers: untyped
|
|
17
|
+
|
|
18
|
+
attr_reader url_patterns: untyped
|
|
19
|
+
|
|
20
|
+
attr_reader context: untyped
|
|
21
|
+
|
|
22
|
+
def initialize: (headers: untyped, url_patterns: untyped, context: untyped) -> void
|
|
23
|
+
|
|
24
|
+
def process_interception: (untyped _event_response, untyped _navigation_id, untyped network_id, untyped _url) -> untyped
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
end
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
module Bidi2pdf
|
|
2
2
|
module Bidi
|
|
3
3
|
class AuthInterceptor
|
|
4
|
-
@
|
|
4
|
+
@url_patterns: untyped
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
@context: untyped
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
@username: untyped
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
@password: untyped
|
|
11
|
+
|
|
12
|
+
@network_ids: untyped
|
|
13
|
+
|
|
14
|
+
include Interceptor
|
|
15
|
+
|
|
16
|
+
def self.phases: () -> ::Array[untyped]
|
|
17
|
+
|
|
18
|
+
def self.events: () -> ::Array["network.authRequired"]
|
|
19
|
+
|
|
20
|
+
attr_reader headers: untyped
|
|
21
|
+
|
|
22
|
+
attr_reader url_patterns: untyped
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
attr_reader context: untyped
|
|
25
|
+
|
|
26
|
+
attr_reader username: untyped
|
|
27
|
+
|
|
28
|
+
attr_reader password: untyped
|
|
29
|
+
|
|
30
|
+
attr_reader network_ids: untyped
|
|
31
|
+
|
|
32
|
+
def initialize: (username: untyped, password: untyped, url_patterns: untyped, context: untyped) -> void
|
|
33
|
+
|
|
34
|
+
def process_interception: (untyped _event_response, untyped navigation_id, untyped network_id, untyped url) -> untyped
|
|
35
|
+
|
|
36
|
+
private
|
|
13
37
|
|
|
14
|
-
|
|
38
|
+
# rubocop:disable Naming/PredicateMethod
|
|
39
|
+
def handled_bad_credentials: (untyped navigation_id, untyped network_id, untyped url) -> (false | true)
|
|
15
40
|
end
|
|
16
41
|
end
|
|
17
|
-
end
|
|
42
|
+
end
|
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
module Bidi2pdf
|
|
2
2
|
module Bidi
|
|
3
3
|
class Browser
|
|
4
|
-
@
|
|
5
|
-
@chromedriver_manager: Bidi2pdf::ChromedriverManager
|
|
6
|
-
@browser: untyped
|
|
7
|
-
@timeout: Integer
|
|
8
|
-
@debug: bool
|
|
4
|
+
@client: untyped
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
attr_reader timeout: Integer
|
|
6
|
+
def initialize: (untyped client) -> void
|
|
12
7
|
|
|
13
|
-
def
|
|
14
|
-
url: String,
|
|
15
|
-
chromedriver_manager: Bidi2pdf::ChromedriverManager,
|
|
16
|
-
?timeout: Integer,
|
|
17
|
-
?debug: bool
|
|
18
|
-
) -> void
|
|
19
|
-
|
|
20
|
-
def navigate: (?reload: bool) -> void
|
|
21
|
-
|
|
22
|
-
def current_url: -> String
|
|
23
|
-
|
|
24
|
-
def wait_until: [T] (
|
|
25
|
-
?timeout: Integer,
|
|
26
|
-
?message: String
|
|
27
|
-
) { () -> T? } -> T
|
|
28
|
-
|
|
29
|
-
def close: () -> void
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def connect: () -> untyped
|
|
34
|
-
|
|
35
|
-
def setup_browser: () -> untyped
|
|
8
|
+
def create_user_context: () -> untyped
|
|
36
9
|
end
|
|
37
10
|
end
|
|
38
|
-
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
module Bidi
|
|
3
|
+
class BrowserConsoleLoggerSuggar
|
|
4
|
+
@browser_console_logger: untyped
|
|
5
|
+
|
|
6
|
+
@level: untyped
|
|
7
|
+
|
|
8
|
+
@prefix: untyped
|
|
9
|
+
|
|
10
|
+
@timestamp: untyped
|
|
11
|
+
|
|
12
|
+
@text: untyped
|
|
13
|
+
|
|
14
|
+
@args: untyped
|
|
15
|
+
|
|
16
|
+
@stack_trace: untyped
|
|
17
|
+
|
|
18
|
+
attr_reader browser_console_logger: untyped
|
|
19
|
+
|
|
20
|
+
def initialize: (untyped browser_console_logger) -> void
|
|
21
|
+
|
|
22
|
+
def with_level: (untyped level) -> self
|
|
23
|
+
|
|
24
|
+
def with_prefix: (untyped prefix) -> self
|
|
25
|
+
|
|
26
|
+
def with_timestamp: (untyped timestamp) -> self
|
|
27
|
+
|
|
28
|
+
def with_text: (untyped text) -> self
|
|
29
|
+
|
|
30
|
+
def with_args: (untyped args) -> self
|
|
31
|
+
|
|
32
|
+
def with_stack_trace: (untyped stack_trace) -> self
|
|
33
|
+
|
|
34
|
+
def log_event: () -> untyped
|
|
35
|
+
|
|
36
|
+
def prefix: () -> untyped
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class BrowserConsoleLogger
|
|
40
|
+
@logger: untyped
|
|
41
|
+
|
|
42
|
+
include JsLoggerHelper
|
|
43
|
+
|
|
44
|
+
attr_accessor logger: untyped
|
|
45
|
+
|
|
46
|
+
def initialize: (untyped logger) -> void
|
|
47
|
+
|
|
48
|
+
def builder: () -> untyped
|
|
49
|
+
|
|
50
|
+
def log_message: (untyped level, untyped prefix, untyped text) -> (nil | untyped)
|
|
51
|
+
|
|
52
|
+
def log_args: (untyped prefix, untyped args) -> (nil | untyped)
|
|
53
|
+
|
|
54
|
+
def log_stack_trace: (untyped prefix, untyped trace) -> untyped
|
|
55
|
+
|
|
56
|
+
def self.format_timestamp: (untyped timestamp) -> ("N/A" | untyped)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|