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 +4 -4
- data/API_COVERAGE.md +2 -2
- data/benchmark/README.md +47 -0
- data/benchmark/benchmark_puppeteer_bidi.rb +68 -0
- data/benchmark/benchmark_puppeteer_ruby.rb +67 -0
- data/benchmark/benchmark_selenium.rb +69 -0
- data/lib/puppeteer/bidi/frame.rb +10 -8
- data/lib/puppeteer/bidi/page.rb +266 -0
- data/lib/puppeteer/bidi/version.rb +1 -1
- data/sig/puppeteer/bidi/page.rbs +34 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b84a92afdbca64531daa953e1685d6efcb5dff4acd9ad21138a1fb38837b0e6
|
|
4
|
+
data.tar.gz: 983bdfbdd8bce6b286db4078760be029ef15f32b502beb8c2aaa2a9bb8335c19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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: `
|
|
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` | ✅ |
|
data/benchmark/README.md
ADDED
|
@@ -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"
|
data/lib/puppeteer/bidi/frame.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
|
data/lib/puppeteer/bidi/page.rb
CHANGED
|
@@ -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.
|
data/sig/puppeteer/bidi/page.rbs
CHANGED
|
@@ -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.
|
|
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
|