puppeteer-bidi 0.0.3 → 0.0.4

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: 6258f257557d7e7ad54126303faab369f6cf1fc356346f9606de321516f60ef6
4
- data.tar.gz: a857af87cb9ed377eb5c266e64eab95dd7183f3c7174d9948ceb1eea08718c71
3
+ metadata.gz: 2b84a92afdbca64531daa953e1685d6efcb5dff4acd9ad21138a1fb38837b0e6
4
+ data.tar.gz: 983bdfbdd8bce6b286db4078760be029ef15f32b502beb8c2aaa2a9bb8335c19
5
5
  SHA512:
6
- metadata.gz: dd7ed38b4927219f89f37ba2f4c6c76af4696c6892900281d337bcd166e1e616257304ffdf6b575960eaa271ae23b31b6db3ccecad3087cbc69ed5297fbbf617
7
- data.tar.gz: df98ee653d7543e3c26fa31420e8983185f1a5f5f8e50bcb4d7595435b1014ca6ea8dbe29a7f7983de2874c882431a77c45c29cce0106088a46e1203116bd360
6
+ metadata.gz: 207cd876e877691aba3b0c498b120b81144dd9562948ba99821f7555868967fbaa746541594c3fd798839b0753b8c188a3cc3632c22d220a842c9cdc04d518f4
7
+ data.tar.gz: ee9853253bcc36c74e901cd7864495ec724ed0a0c36c96ba15d9b5f4090dc238b2968e34bc0135d1978fa0a2d82405d99c28c6ffbf558e98e865269e4d419d68
data/API_COVERAGE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  - Puppeteer commit: `7d750c25cb29764f2fb31cb90b750a8eec350199`
4
4
  - Generated by: `development/generate_api_coverage.rb`
5
- - Coverage: `182/265` (`68.68%`)
5
+ - Coverage: `183/265` (`69.06%`)
6
6
 
7
7
  ## Browser (Puppeteer::Bidi::Browser)
8
8
 
@@ -272,7 +272,7 @@
272
272
  | `Page.mainFrame` | `Puppeteer::Bidi::Page#main_frame` | ✅ |
273
273
  | `Page.metrics` | `Puppeteer::Bidi::Page#metrics` | ❌ |
274
274
  | `Page.openDevTools` | `Puppeteer::Bidi::Page#open_dev_tools` | ❌ |
275
- | `Page.pdf` | `Puppeteer::Bidi::Page#pdf` | |
275
+ | `Page.pdf` | `Puppeteer::Bidi::Page#pdf` | |
276
276
  | `Page.queryObjects` | `Puppeteer::Bidi::Page#query_objects` | ❌ |
277
277
  | `Page.reload` | `Puppeteer::Bidi::Page#reload` | ✅ |
278
278
  | `Page.removeExposedFunction` | `Puppeteer::Bidi::Page#remove_exposed_function` | ✅ |
@@ -0,0 +1,47 @@
1
+ # Performance Benchmark
2
+
3
+ Comparison of Firefox automation libraries performance.
4
+
5
+ ## Results
6
+
7
+ Tested with Firefox 139.0, 150 iterations of DOM operations.
8
+ Time measured from after browser launch to before browser close.
9
+
10
+ | Library | Protocol | Total Time | Per Iteration |
11
+ |---------|----------|------------|---------------|
12
+ | Selenium WebDriver | WebDriver | 2.12s | 14.11 ms |
13
+ | **puppeteer-bidi** | WebDriver BiDi | 2.85s | 18.97 ms |
14
+ | puppeteer-ruby | CDP (Juggler) | 31.02s | 206.81 ms |
15
+
16
+ ## Key Findings
17
+
18
+ - **puppeteer-bidi is ~11x faster than puppeteer-ruby** for Firefox automation
19
+ - Selenium WebDriver is fastest for simple operations, but lacks Puppeteer's high-level API (e.g., `set_content()`, `wait_for_selector()`, request interception)
20
+ - puppeteer-bidi provides Puppeteer-compatible API while being significantly faster than puppeteer-ruby
21
+ - puppeteer-ruby's CDP-over-Juggler bridge adds significant overhead on Firefox
22
+
23
+ ## Test Operations
24
+
25
+ Each iteration performs:
26
+ - Set HTML content (150 elements)
27
+ - Query selectors (`h1`, `li` x20, `.description`)
28
+ - Multiple JavaScript evaluations
29
+ - DOM property access
30
+
31
+ ## Running Benchmarks
32
+
33
+ ```bash
34
+ # Set Firefox path (optional)
35
+ export FIREFOX_PATH="/path/to/firefox"
36
+
37
+ # Run individual benchmarks
38
+ ruby benchmark/benchmark_selenium.rb
39
+ ruby benchmark/benchmark_puppeteer_bidi.rb
40
+ ruby benchmark/benchmark_puppeteer_ruby.rb
41
+ ```
42
+
43
+ ## Notes
44
+
45
+ - Selenium uses data URL navigation; Puppeteer variants use `set_content()`
46
+ - All tests run in headless mode
47
+ - Results may vary based on system configuration
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'puppeteer/bidi'
6
+
7
+ FIREFOX_PATH = ENV.fetch('FIREFOX_PATH', '/tmp/Firefox139.app/Contents/MacOS/firefox')
8
+ ITERATIONS = 150
9
+
10
+ html_template = ->(i) { <<~HTML }
11
+ <!DOCTYPE html>
12
+ <html>
13
+ <head><title>Page #{i}</title></head>
14
+ <body>
15
+ <div id="content">
16
+ <h1>Benchmark Page #{i}</h1>
17
+ <ul>
18
+ #{(1..20).map { |j| "<li>Item #{j}</li>" }.join("\n ")}
19
+ </ul>
20
+ <p class="description">This is test page number #{i}</p>
21
+ </div>
22
+ </body>
23
+ </html>
24
+ HTML
25
+
26
+ puts "=== puppeteer-bidi Benchmark (Firefox #{`#{FIREFOX_PATH} --version`.strip.split.last}) ==="
27
+ puts "Iterations: #{ITERATIONS}"
28
+ puts
29
+
30
+ total_time = nil
31
+ begin
32
+ Puppeteer::Bidi.launch(
33
+ executable_path: FIREFOX_PATH,
34
+ headless: true
35
+ ) do |browser|
36
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
37
+
38
+ page = browser.new_page
39
+
40
+ ITERATIONS.times do |i|
41
+ # Set HTML content
42
+ page.set_content(html_template.call(i))
43
+
44
+ # Query selectors
45
+ title = page.query_selector('h1')
46
+ items = page.query_selector_all('li')
47
+ desc = page.query_selector('.description')
48
+
49
+ # Evaluate JavaScript
50
+ page.evaluate('() => document.title')
51
+ page.evaluate('(el) => el.textContent', title)
52
+ page.evaluate('(els) => els.length', items)
53
+ page.evaluate('(el) => el.textContent', desc)
54
+
55
+ # More evaluations
56
+ page.evaluate('() => window.innerWidth')
57
+ page.evaluate('() => document.querySelectorAll("li").length')
58
+
59
+ print '.' if (i + 1) % 10 == 0
60
+ end
61
+ total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
62
+ end
63
+ end
64
+
65
+ puts
66
+ puts
67
+ puts "Total time: #{total_time.round(2)} seconds"
68
+ puts "Average per iteration: #{(total_time / ITERATIONS * 1000).round(2)} ms"
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'puppeteer-ruby'
5
+
6
+ FIREFOX_PATH = ENV.fetch('FIREFOX_PATH', '/tmp/Firefox139.app/Contents/MacOS/firefox')
7
+ ITERATIONS = 150
8
+
9
+ html_template = ->(i) { <<~HTML }
10
+ <!DOCTYPE html>
11
+ <html>
12
+ <head><title>Page #{i}</title></head>
13
+ <body>
14
+ <div id="content">
15
+ <h1>Benchmark Page #{i}</h1>
16
+ <ul>
17
+ #{(1..20).map { |j| "<li>Item #{j}</li>" }.join("\n ")}
18
+ </ul>
19
+ <p class="description">This is test page number #{i}</p>
20
+ </div>
21
+ </body>
22
+ </html>
23
+ HTML
24
+
25
+ puts "=== puppeteer-ruby Benchmark (Firefox #{`#{FIREFOX_PATH} --version`.strip.split.last}) ==="
26
+ puts "Iterations: #{ITERATIONS}"
27
+ puts
28
+
29
+ total_time = nil
30
+ begin
31
+ Puppeteer.launch(
32
+ product: 'firefox',
33
+ executable_path: FIREFOX_PATH,
34
+ headless: true
35
+ ) do |browser|
36
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
37
+ page = browser.new_page
38
+
39
+ ITERATIONS.times do |i|
40
+ # Set HTML content
41
+ page.set_content(html_template.call(i))
42
+
43
+ # Query selectors
44
+ title = page.query_selector('h1')
45
+ items = page.query_selector_all('li')
46
+ desc = page.query_selector('.description')
47
+
48
+ # Evaluate JavaScript
49
+ page.evaluate('() => document.title')
50
+ page.evaluate('(el) => el.textContent', title)
51
+ page.evaluate('(els) => els.length', items)
52
+ page.evaluate('(el) => el.textContent', desc)
53
+
54
+ # More evaluations
55
+ page.evaluate('() => window.innerWidth')
56
+ page.evaluate('() => document.querySelectorAll("li").length')
57
+
58
+ print '.' if (i + 1) % 10 == 0
59
+ end
60
+ total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
61
+ end
62
+ end
63
+
64
+ puts
65
+ puts
66
+ puts "Total time: #{total_time.round(2)} seconds"
67
+ puts "Average per iteration: #{(total_time / ITERATIONS * 1000).round(2)} ms"
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'selenium-webdriver'
5
+ require 'erb'
6
+
7
+ FIREFOX_PATH = ENV.fetch('FIREFOX_PATH', '/tmp/Firefox139.app/Contents/MacOS/firefox')
8
+ ITERATIONS = 150
9
+
10
+ html_template = ->(i) { <<~HTML }
11
+ <!DOCTYPE html>
12
+ <html>
13
+ <head><title>Page #{i}</title></head>
14
+ <body>
15
+ <div id="content">
16
+ <h1>Benchmark Page #{i}</h1>
17
+ <ul>
18
+ #{(1..20).map { |j| "<li>Item #{j}</li>" }.join("\n ")}
19
+ </ul>
20
+ <p class="description">This is test page number #{i}</p>
21
+ </div>
22
+ </body>
23
+ </html>
24
+ HTML
25
+
26
+ puts "=== Selenium WebDriver Benchmark (Firefox #{`#{FIREFOX_PATH} --version`.strip.split.last}) ==="
27
+ puts "Iterations: #{ITERATIONS}"
28
+ puts
29
+
30
+ options = Selenium::WebDriver::Firefox::Options.new
31
+ options.binary = FIREFOX_PATH
32
+ options.add_argument('-headless')
33
+
34
+ total_time = nil
35
+ begin
36
+ driver = Selenium::WebDriver.for(:firefox, options: options)
37
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
38
+
39
+ ITERATIONS.times do |i|
40
+ # Set HTML content via data URL
41
+ html = html_template.call(i)
42
+ driver.navigate.to("data:text/html;charset=utf-8,#{ERB::Util.url_encode(html)}")
43
+
44
+ # Query selectors
45
+ title = driver.find_element(:css, 'h1')
46
+ items = driver.find_elements(:css, 'li')
47
+ desc = driver.find_element(:css, '.description')
48
+
49
+ # Get text content (equivalent to evaluate)
50
+ driver.title
51
+ title.text
52
+ items.size
53
+ desc.text
54
+
55
+ # Execute JavaScript
56
+ driver.execute_script('return window.innerWidth')
57
+ driver.execute_script("return document.querySelectorAll('li').length")
58
+
59
+ print '.' if (i + 1) % 10 == 0
60
+ end
61
+ total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
62
+ ensure
63
+ driver&.quit
64
+ end
65
+
66
+ puts
67
+ puts
68
+ puts "Total time: #{total_time.round(2)} seconds"
69
+ puts "Average per iteration: #{(total_time / ITERATIONS * 1000).round(2)} ms"
@@ -406,17 +406,14 @@ module Puppeteer
406
406
  lifecycle_events = wait_until_array.select { |e| ['load', 'domcontentloaded'].include?(e) }
407
407
  network_idle_events = wait_until_array.select { |e| ['networkidle0', 'networkidle2'].include?(e) }
408
408
 
409
- # Default to 'load' if no lifecycle event specified
410
- lifecycle_events = ['load'] if lifecycle_events.empty? && network_idle_events.any?
411
-
412
- # Determine which load event to wait for (use the first one)
409
+ # Only wait for lifecycle events if explicitly requested (matches Puppeteer)
413
410
  load_event = case lifecycle_events.first
414
411
  when 'load'
415
412
  :load
416
413
  when 'domcontentloaded'
417
414
  :dom_content_loaded
418
415
  else
419
- :load # Default
416
+ nil
420
417
  end
421
418
 
422
419
  # Use Async::Promise for signaling (Fiber-based, not Thread-based)
@@ -454,9 +451,14 @@ module Puppeteer
454
451
  end
455
452
 
456
453
  # Also listen for load/domcontentloaded events to complete navigation
457
- unless load_listener_registered
458
- @browsing_context.once(load_event, &load_listener)
459
- load_listener_registered = true
454
+ if load_event
455
+ unless load_listener_registered
456
+ @browsing_context.once(load_event, &load_listener)
457
+ load_listener_registered = true
458
+ end
459
+ else
460
+ # No lifecycle events requested; resolve once navigation is observed.
461
+ promise.resolve(:full_page) unless promise.resolved?
460
462
  end
461
463
  end
462
464
 
@@ -11,6 +11,59 @@ module Puppeteer
11
11
  # Page represents a single page/tab in the browser
12
12
  # This is a high-level wrapper around Core::BrowsingContext
13
13
  class Page
14
+ UNIT_TO_PIXELS = {
15
+ "px" => 1,
16
+ "in" => 96,
17
+ "cm" => 37.8,
18
+ "mm" => 3.78
19
+ }.freeze
20
+ PAPER_FORMATS = {
21
+ "letter" => {
22
+ "cm" => { width: 21.59, height: 27.94 },
23
+ "in" => { width: 8.5, height: 11 }
24
+ },
25
+ "legal" => {
26
+ "cm" => { width: 21.59, height: 35.56 },
27
+ "in" => { width: 8.5, height: 14 }
28
+ },
29
+ "tabloid" => {
30
+ "cm" => { width: 27.94, height: 43.18 },
31
+ "in" => { width: 11, height: 17 }
32
+ },
33
+ "ledger" => {
34
+ "cm" => { width: 43.18, height: 27.94 },
35
+ "in" => { width: 17, height: 11 }
36
+ },
37
+ "a0" => {
38
+ "cm" => { width: 84.1, height: 118.9 },
39
+ "in" => { width: 33.1102, height: 46.811 }
40
+ },
41
+ "a1" => {
42
+ "cm" => { width: 59.4, height: 84.1 },
43
+ "in" => { width: 23.3858, height: 33.1102 }
44
+ },
45
+ "a2" => {
46
+ "cm" => { width: 42, height: 59.4 },
47
+ "in" => { width: 16.5354, height: 23.3858 }
48
+ },
49
+ "a3" => {
50
+ "cm" => { width: 29.7, height: 42 },
51
+ "in" => { width: 11.6929, height: 16.5354 }
52
+ },
53
+ "a4" => {
54
+ "cm" => { width: 21, height: 29.7 },
55
+ "in" => { width: 8.2677, height: 11.6929 }
56
+ },
57
+ "a5" => {
58
+ "cm" => { width: 14.8, height: 21 },
59
+ "in" => { width: 5.8268, height: 8.2677 }
60
+ },
61
+ "a6" => {
62
+ "cm" => { width: 10.5, height: 14.8 },
63
+ "in" => { width: 4.1339, height: 5.8268 }
64
+ }
65
+ }.freeze
66
+
14
67
  attr_reader :browsing_context #: Core::BrowsingContext
15
68
  attr_reader :browser_context #: BrowserContext
16
69
  attr_reader :timeout_settings #: TimeoutSettings
@@ -257,6 +310,116 @@ module Puppeteer
257
310
  data
258
311
  end
259
312
 
313
+ # Generate a PDF of the page.
314
+ # @rbs path: String? -- File path to save PDF
315
+ # @rbs scale: Numeric? -- Scale of the webpage rendering
316
+ # @rbs display_header_footer: bool? -- Display header and footer
317
+ # @rbs header_template: String? -- HTML template for header
318
+ # @rbs footer_template: String? -- HTML template for footer
319
+ # @rbs print_background: bool? -- Print background graphics
320
+ # @rbs landscape: bool? -- Print in landscape orientation
321
+ # @rbs page_ranges: String? -- Paper ranges to print (e.g. "1-5, 8")
322
+ # @rbs format: String? -- Paper format (e.g. "A4")
323
+ # @rbs width: String | Numeric? -- Paper width
324
+ # @rbs height: String | Numeric? -- Paper height
325
+ # @rbs prefer_css_page_size: bool? -- Prefer CSS @page size
326
+ # @rbs margin: Hash[Symbol, String | Numeric]? -- Paper margins
327
+ # @rbs omit_background: bool? -- Omit background
328
+ # @rbs tagged: bool? -- Generate tagged PDF
329
+ # @rbs outline: bool? -- Generate document outline
330
+ # @rbs timeout: Numeric? -- Timeout in ms (0 disables)
331
+ # @rbs wait_for_fonts: bool? -- Wait for document fonts to load
332
+ # @rbs return: String -- PDF data as binary string
333
+ def pdf(
334
+ path: nil,
335
+ scale: nil,
336
+ display_header_footer: nil,
337
+ header_template: nil,
338
+ footer_template: nil,
339
+ print_background: nil,
340
+ landscape: nil,
341
+ page_ranges: nil,
342
+ format: nil,
343
+ width: nil,
344
+ height: nil,
345
+ prefer_css_page_size: nil,
346
+ margin: nil,
347
+ omit_background: nil,
348
+ tagged: nil,
349
+ outline: nil,
350
+ timeout: nil,
351
+ wait_for_fonts: nil
352
+ )
353
+ assert_not_closed
354
+
355
+ timeout_ms = timeout.nil? ? @timeout_settings.timeout : timeout
356
+
357
+ options = {
358
+ scale: scale,
359
+ display_header_footer: display_header_footer,
360
+ header_template: header_template,
361
+ footer_template: footer_template,
362
+ print_background: print_background,
363
+ landscape: landscape,
364
+ page_ranges: page_ranges,
365
+ format: format,
366
+ width: width,
367
+ height: height,
368
+ prefer_css_page_size: prefer_css_page_size,
369
+ margin: margin,
370
+ omit_background: omit_background,
371
+ tagged: tagged,
372
+ outline: outline,
373
+ wait_for_fonts: wait_for_fonts
374
+ }.compact
375
+
376
+ parsed = parse_pdf_options(options, "cm")
377
+ ranges = parsed[:page_ranges]
378
+ page_ranges = ranges && !ranges.empty? ? ranges.split(", ") : []
379
+
380
+ # Wait for fonts to load
381
+ begin
382
+ if timeout_ms == 0
383
+ main_frame.isolated_realm.evaluate("() => document.fonts.ready")
384
+ else
385
+ AsyncUtils.async_timeout(timeout_ms, -> do
386
+ main_frame.isolated_realm.evaluate("() => document.fonts.ready")
387
+ end).wait
388
+ end
389
+ rescue Async::TimeoutError
390
+ raise TimeoutError, "Timed out after waiting #{timeout_ms}ms"
391
+ end
392
+
393
+ print_options = {
394
+ background: parsed[:print_background],
395
+ margin: parsed[:margin],
396
+ orientation: parsed[:landscape] ? "landscape" : "portrait",
397
+ page: {
398
+ width: parsed[:width],
399
+ height: parsed[:height]
400
+ },
401
+ pageRanges: page_ranges,
402
+ scale: parsed[:scale],
403
+ shrinkToFit: !parsed[:prefer_css_page_size]
404
+ }
405
+
406
+ begin
407
+ data = if timeout_ms == 0
408
+ @browsing_context.print(**print_options).wait
409
+ else
410
+ AsyncUtils.async_timeout(timeout_ms, -> do
411
+ @browsing_context.print(**print_options).wait
412
+ end).wait
413
+ end
414
+ rescue Async::TimeoutError
415
+ raise TimeoutError, "Timed out after waiting #{timeout_ms}ms"
416
+ end
417
+
418
+ pdf_data = Base64.decode64(data)
419
+ File.binwrite(path, pdf_data) if path
420
+ pdf_data
421
+ end
422
+
260
423
  # Evaluate JavaScript in the page context
261
424
  # @rbs script: String -- JavaScript code to evaluate
262
425
  # @rbs *args: untyped -- Arguments to pass to the script
@@ -1357,6 +1520,109 @@ module Puppeteer
1357
1520
  JSON.generate(arg.to_s)
1358
1521
  end
1359
1522
  end
1523
+
1524
+ def parse_pdf_options(options, length_unit)
1525
+ defaults = {
1526
+ scale: 1,
1527
+ display_header_footer: false,
1528
+ header_template: "",
1529
+ footer_template: "",
1530
+ print_background: false,
1531
+ landscape: false,
1532
+ page_ranges: "",
1533
+ prefer_css_page_size: false,
1534
+ omit_background: false,
1535
+ outline: false,
1536
+ tagged: true,
1537
+ wait_for_fonts: true
1538
+ }
1539
+
1540
+ width = 8.5
1541
+ height = 11
1542
+ format = options[:format]
1543
+ if format
1544
+ format_key = format.to_s.downcase
1545
+ format_dimensions = PAPER_FORMATS[format_key]
1546
+ raise "Unknown paper format: #{format}" unless format_dimensions
1547
+
1548
+ dimensions = format_dimensions[length_unit]
1549
+ width = dimensions[:width]
1550
+ height = dimensions[:height]
1551
+ else
1552
+ width = convert_print_parameter_to_length_unit(options[:width], length_unit) || width
1553
+ height = convert_print_parameter_to_length_unit(options[:height], length_unit) || height
1554
+ end
1555
+
1556
+ margin_options = options[:margin]
1557
+ if margin_options.is_a?(Hash)
1558
+ margin_options = margin_options.transform_keys(&:to_sym)
1559
+ else
1560
+ margin_options = {}
1561
+ end
1562
+ margin = {
1563
+ top: convert_print_parameter_to_length_unit(margin_options[:top], length_unit) || 0,
1564
+ left: convert_print_parameter_to_length_unit(margin_options[:left], length_unit) || 0,
1565
+ bottom: convert_print_parameter_to_length_unit(margin_options[:bottom], length_unit) || 0,
1566
+ right: convert_print_parameter_to_length_unit(margin_options[:right], length_unit) || 0
1567
+ }
1568
+
1569
+ options[:tagged] = true if options[:outline]
1570
+
1571
+ defaults.merge(options).merge(
1572
+ width: width,
1573
+ height: height,
1574
+ margin: margin
1575
+ )
1576
+ end
1577
+
1578
+ def convert_print_parameter_to_length_unit(parameter, length_unit)
1579
+ return nil if parameter.nil?
1580
+
1581
+ pixels = nil
1582
+
1583
+ if parameter.is_a?(Numeric)
1584
+ pixels = parameter
1585
+ elsif parameter.is_a?(String)
1586
+ text = parameter
1587
+ unit = text[-2, 2].to_s.downcase
1588
+ if UNIT_TO_PIXELS.key?(unit)
1589
+ value_text = text[0...-2]
1590
+ else
1591
+ unit = "px"
1592
+ value_text = text
1593
+ end
1594
+ value = parse_print_parameter_value(text, value_text)
1595
+ pixels = value * UNIT_TO_PIXELS[unit]
1596
+ else
1597
+ raise "page.pdf() Cannot handle parameter type: #{js_typeof(parameter)}"
1598
+ end
1599
+
1600
+ pixels / UNIT_TO_PIXELS.fetch(length_unit)
1601
+ end
1602
+
1603
+ def parse_print_parameter_value(text, value_text)
1604
+ value = Float(value_text)
1605
+ raise "Failed to parse parameter value: #{text}" if value.nan?
1606
+
1607
+ value
1608
+ rescue ArgumentError
1609
+ raise "Failed to parse parameter value: #{text}"
1610
+ end
1611
+
1612
+ def js_typeof(value)
1613
+ case value
1614
+ when String
1615
+ "string"
1616
+ when Numeric
1617
+ "number"
1618
+ when TrueClass, FalseClass
1619
+ "boolean"
1620
+ when Proc, Method
1621
+ "function"
1622
+ else
1623
+ "object"
1624
+ end
1625
+ end
1360
1626
  end
1361
1627
 
1362
1628
  # Result of evaluateOnNewDocument containing the script identifier.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Puppeteer
4
4
  module Bidi
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.4"
6
6
  end
7
7
  end
@@ -5,6 +5,10 @@ module Puppeteer
5
5
  # Page represents a single page/tab in the browser
6
6
  # This is a high-level wrapper around Core::BrowsingContext
7
7
  class Page
8
+ UNIT_TO_PIXELS: untyped
9
+
10
+ PAPER_FORMATS: untyped
11
+
8
12
  attr_reader browsing_context: Core::BrowsingContext
9
13
 
10
14
  attr_reader browser_context: BrowserContext
@@ -71,6 +75,28 @@ module Puppeteer
71
75
  # @rbs return: String -- Base64-encoded image data
72
76
  def screenshot: (?path: String?, ?type: String, ?full_page: bool, ?clip: Hash[Symbol, Numeric]?, ?capture_beyond_viewport: bool) -> String
73
77
 
78
+ # Generate a PDF of the page.
79
+ # @rbs path: String? -- File path to save PDF
80
+ # @rbs scale: Numeric? -- Scale of the webpage rendering
81
+ # @rbs display_header_footer: bool? -- Display header and footer
82
+ # @rbs header_template: String? -- HTML template for header
83
+ # @rbs footer_template: String? -- HTML template for footer
84
+ # @rbs print_background: bool? -- Print background graphics
85
+ # @rbs landscape: bool? -- Print in landscape orientation
86
+ # @rbs page_ranges: String? -- Paper ranges to print (e.g. "1-5, 8")
87
+ # @rbs format: String? -- Paper format (e.g. "A4")
88
+ # @rbs width: String | Numeric? -- Paper width
89
+ # @rbs height: String | Numeric? -- Paper height
90
+ # @rbs prefer_css_page_size: bool? -- Prefer CSS @page size
91
+ # @rbs margin: Hash[Symbol, String | Numeric]? -- Paper margins
92
+ # @rbs omit_background: bool? -- Omit background
93
+ # @rbs tagged: bool? -- Generate tagged PDF
94
+ # @rbs outline: bool? -- Generate document outline
95
+ # @rbs timeout: Numeric? -- Timeout in ms (0 disables)
96
+ # @rbs wait_for_fonts: bool? -- Wait for document fonts to load
97
+ # @rbs return: String -- PDF data as binary string
98
+ def pdf: (?path: String?, ?scale: Numeric?, ?display_header_footer: bool?, ?header_template: String?, ?footer_template: String?, ?print_background: bool?, ?landscape: bool?, ?page_ranges: String?, ?format: String?, ?width: String | Numeric?, ?height: String | Numeric?, ?prefer_css_page_size: bool?, ?margin: Hash[Symbol, String | Numeric]?, ?omit_background: bool?, ?tagged: bool?, ?outline: bool?, ?timeout: Numeric?, ?wait_for_fonts: bool?) -> String
99
+
74
100
  # Evaluate JavaScript in the page context
75
101
  # @rbs script: String -- JavaScript code to evaluate
76
102
  # @rbs *args: untyped -- Arguments to pass to the script
@@ -433,6 +459,14 @@ module Puppeteer
433
459
  # @rbs arg: untyped -- Argument to serialize
434
460
  # @rbs return: String -- JavaScript literal
435
461
  def serialize_arg_for_preload: (untyped arg) -> String
462
+
463
+ def parse_pdf_options: (untyped options, untyped length_unit) -> untyped
464
+
465
+ def convert_print_parameter_to_length_unit: (untyped parameter, untyped length_unit) -> untyped
466
+
467
+ def parse_print_parameter_value: (untyped text, untyped value_text) -> untyped
468
+
469
+ def js_typeof: (untyped value) -> untyped
436
470
  end
437
471
 
438
472
  # Result of evaluateOnNewDocument containing the script identifier.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppeteer-bidi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
@@ -84,6 +84,10 @@ files:
84
84
  - README.md
85
85
  - Rakefile
86
86
  - Steepfile
87
+ - benchmark/README.md
88
+ - benchmark/benchmark_puppeteer_bidi.rb
89
+ - benchmark/benchmark_puppeteer_ruby.rb
90
+ - benchmark/benchmark_selenium.rb
87
91
  - lib/puppeteer/bidi.rb
88
92
  - lib/puppeteer/bidi/async_utils.rb
89
93
  - lib/puppeteer/bidi/browser.rb