bidi2pdf 0.1.21 → 0.1.23
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/CHANGELOG.md +45 -2
- data/README.md +113 -1
- data/lib/bidi2pdf/bidi/browser_tab.rb +66 -4
- data/lib/bidi2pdf/bidi/network_events.rb +13 -4
- data/lib/bidi2pdf/cli.rb +45 -11
- data/lib/bidi2pdf/launcher.rb +11 -37
- data/lib/bidi2pdf/notifications/json_subscriber.rb +3 -2
- data/lib/bidi2pdf/notifications/logging_subscriber.rb +7 -3
- data/lib/bidi2pdf/recipe/runner.rb +1 -0
- data/lib/bidi2pdf/recipe.rb +1 -1
- data/lib/bidi2pdf/schema.rb +15 -2
- data/lib/bidi2pdf/session_runner.rb +5 -1
- data/lib/bidi2pdf/version.rb +1 -1
- data/sig/bidi2pdf/bidi/browser_tab.rbs +35 -1
- data/sig/bidi2pdf/bidi/network_events.rbs +8 -1
- data/sig/bidi2pdf/cli.rbs +15 -4
- data/sig/bidi2pdf/launcher.rbs +5 -20
- data/sig/bidi2pdf/notifications/json_subscriber.rbs +2 -1
- data/sig/bidi2pdf/recipe.rbs +1 -1
- data/sig/bidi2pdf/schema.rbs +2 -2
- data/sig/bidi2pdf/session_runner.rbs +5 -1
- data/sig/bidi2pdf/version.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9bff5f993e54cb0f70e6a2c83e022bf29fdef7b0fafc6ce8b641d5ec33aea7ea
|
|
4
|
+
data.tar.gz: e2466bb66703e3a7ac4ca16bfdea22f836c8fff07492e9f44904ecf5bc9c9c63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6958ef3cc24d5350fdb090598940410f5cd3ab9abef4003f118d52ab61142b45c6427e0225ed4d37a9fa3a23d0dfd033e67a07cc981f891cb2f1f95f7af67612
|
|
7
|
+
data.tar.gz: 74a366dc1c4953bb6e17e8c7578d3ec79cd7a0ea9f1f058f9d353ebdab12b2b046d7e25424b22745081895b09f60a5ca7a701eb29763472b9320e6a564142ad7
|
data/CHANGELOG.md
CHANGED
|
@@ -8,10 +8,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
-
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
11
|
+
[unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.23..HEAD
|
|
12
12
|
|
|
13
13
|
<!-- generated by git-cliff end -->
|
|
14
14
|
|
|
15
|
+
## [0.1.23] - 2026-10-18
|
|
16
|
+
|
|
17
|
+
### 🐛 Fixed
|
|
18
|
+
- Spell the plugin version as semver
|
|
19
|
+
- Keep screenshot names apart and stop browsers
|
|
20
|
+
- Stop every browser even when one stop fails
|
|
21
|
+
- Keep stdout to the JSON document or the PDF
|
|
22
|
+
- Lay a mobile print out at the intended width
|
|
23
|
+
|
|
24
|
+
### 📝 Docs
|
|
25
|
+
- Explain how to install, pin and update the Claude Code skills
|
|
26
|
+
- Fix the script path and weigh the Chrome sandbox
|
|
27
|
+
|
|
28
|
+
### 🔄 Changed
|
|
29
|
+
- Merge pull request #170 from dieter-medium/feat/claude-skills
|
|
30
|
+
- Merge pull request #169 from dieter-medium/fix/clean-stdout
|
|
31
|
+
- Merge pull request #160 from dieter-medium/dependabot/bundler/main/simplecov-1.3.2
|
|
32
|
+
- Merge pull request #167 from dieter-medium/dependabot/bundler/main/rubocop-1.92.0
|
|
33
|
+
- Merge pull request #168 from dieter-medium/feat/emulate-media
|
|
34
|
+
|
|
35
|
+
### 🔧 Build
|
|
36
|
+
- Bump simplecov from 1.3.1 to 1.3.2
|
|
37
|
+
- Bump rubocop from 1.91.0 to 1.92.0
|
|
38
|
+
|
|
39
|
+
### 🚀 Added
|
|
40
|
+
- Ship Claude Code skills for bidi2pdf
|
|
41
|
+
- Let a tab emulate a CSS media type
|
|
42
|
+
|
|
43
|
+
## [0.1.22] - 2026-10-11
|
|
44
|
+
|
|
45
|
+
### 🐛 Fixed
|
|
46
|
+
- Report a network-idle timeout as a timeout
|
|
47
|
+
- Let the network-idle wait time out cleanly
|
|
48
|
+
|
|
49
|
+
### 🔄 Changed
|
|
50
|
+
- Merge pull request #164 from dieter-medium/fix/network-idle-timeout
|
|
51
|
+
- Merge pull request #163 from dieter-medium/feat/disable-script-execution
|
|
52
|
+
|
|
53
|
+
### 🚀 Added
|
|
54
|
+
- Let a tab switch off script execution
|
|
55
|
+
|
|
15
56
|
## [0.1.21] - 2026-10-09
|
|
16
57
|
|
|
17
58
|
### 🐛 Fixed
|
|
@@ -555,7 +596,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
555
596
|
|
|
556
597
|
### 🔄 Released
|
|
557
598
|
|
|
558
|
-
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
|
599
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.23..HEAD)
|
|
600
|
+
- [0.1.23](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.22..v0.1.23)
|
|
601
|
+
- [0.1.22](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.21..v0.1.22)
|
|
559
602
|
- [0.1.21](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.20..v0.1.21)
|
|
560
603
|
- [0.1.20](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.19..v0.1.20)
|
|
561
604
|
- [0.1.19](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.18..v0.1.19)
|
data/README.md
CHANGED
|
@@ -110,6 +110,26 @@ bidi2pdf render \
|
|
|
110
110
|
--log-level debug
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
#### The page as it looks on screen, dark mode, or its mobile version
|
|
114
|
+
|
|
115
|
+
By default Chrome prints with the page's print stylesheet. `--media screen` prints its screen styles
|
|
116
|
+
instead, `--color_scheme dark` its dark mode, `--reduced_motion reduce` with animations off.
|
|
117
|
+
|
|
118
|
+
A narrow page adds the page's mobile layout. A printed page is laid out at the width between its side
|
|
119
|
+
margins, not at the paper's width: page width minus `--margin_left` and `--margin_right`, at about
|
|
120
|
+
37.8 CSS px per cm (96 / 2.54). Without margins of your own Chrome uses 1 cm a side, so a 9.5 cm page
|
|
121
|
+
lays out at about 283 px. For a 360 px phone layout, set the side margins explicitly:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bidi2pdf render --url https://example.com --output mobile.pdf \
|
|
125
|
+
--media screen --color_scheme dark \
|
|
126
|
+
--page_width 9.525 --page_height 20 --margin_left 0 --margin_right 0
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
8.467 cm without side margins gives 320 px. All page sizes and margins are in centimetres.
|
|
130
|
+
|
|
131
|
+
`bidi2pdf diagnose` takes the same three options, so its screenshot shows the same emulation.
|
|
132
|
+
|
|
113
133
|
---
|
|
114
134
|
|
|
115
135
|
## 🤖 Agent and Automation Usage
|
|
@@ -243,7 +263,7 @@ bidi2pdf run invoice.yml --json # actions, then the PDF, then assertions a
|
|
|
243
263
|
```
|
|
244
264
|
|
|
245
265
|
Actions (`wait_for`, `click`, `evaluate`, `inject_script`, `inject_style`, `set_viewport`,
|
|
246
|
-
`wait_network_idle`) and page assertions (`selector_exists`, `text_present`, `no_console_errors`,
|
|
266
|
+
`wait_network_idle`, `emulate_media`) and page assertions (`selector_exists`, `text_present`, `no_console_errors`,
|
|
247
267
|
`no_network_failures`, `fonts_loaded`) are a thin layer over the same `BrowserTab` methods the
|
|
248
268
|
[Programmatic API](#-programmatic-api) below uses directly. PDF assertions (`page_count`,
|
|
249
269
|
`pdf_text_present`, `pdf_not_blank`) need the `pdf-reader` gem - a recipe using one fails
|
|
@@ -256,6 +276,91 @@ rejected by both `bidi2pdf schema recipe` and `--validate` rather than being sil
|
|
|
256
276
|
`wait_for` needs exactly one of `selector`, `paged_js`, `script` - zero or more than one is
|
|
257
277
|
rejected the same way, before any browser launches.
|
|
258
278
|
|
|
279
|
+
### Claude Code skills
|
|
280
|
+
|
|
281
|
+
The repository ships a [Claude Code](https://claude.com/claude-code) plugin with six skills, so an
|
|
282
|
+
agent uses bidi2pdf the way it is meant to be used instead of guessing options:
|
|
283
|
+
|
|
284
|
+
| Skill | For |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `render-pdf` | a URL or HTML file to PDF with the CLI: order of commands, waits, screen/dark/mobile view, reading the JSON result and exit codes |
|
|
287
|
+
| `pdf-recipe` | a recipe for pages that need steps before printing, validated without a browser |
|
|
288
|
+
| `diagnose-pdf` | a blank, unstyled or wrongly paged PDF: a checklist around `bidi2pdf diagnose` |
|
|
289
|
+
| `remote-chrome` | a shared chromedriver container: settings, sizing, session cleanup, untrusted HTML |
|
|
290
|
+
| `page-screenshots` | review screenshots per page at phone and desktop width, light and dark (with a script) |
|
|
291
|
+
| `pdf-specs` | RSpec specs with the gem's PDF matchers and Chrome container helpers |
|
|
292
|
+
|
|
293
|
+
#### Installing the skills
|
|
294
|
+
|
|
295
|
+
The plugin is not in a central store: this repository is its own Claude Code marketplace
|
|
296
|
+
(`.claude-plugin/marketplace.json`, marketplace name `bidi2pdf`). You add the repository as a
|
|
297
|
+
marketplace once, then install the plugin from it. In a Claude Code session:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
/plugin marketplace add dieter-medium/bidi2pdf
|
|
301
|
+
/plugin install bidi2pdf@bidi2pdf
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`/plugin install` does not install right away: it opens the plugin's details, where you choose the scope
|
|
305
|
+
(just you, the project, or only this checkout) and confirm the installation.
|
|
306
|
+
|
|
307
|
+
Or from a shell, where the install happens at once:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
claude plugin marketplace add dieter-medium/bidi2pdf
|
|
311
|
+
claude plugin install bidi2pdf@bidi2pdf # --scope user (default), project or local
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The skills then load on their own when a task fits them; nothing else needs configuring.
|
|
315
|
+
|
|
316
|
+
**Which version you get.** The plugin's version is the gem's (`claude-plugin/.claude-plugin/plugin.json`),
|
|
317
|
+
and Claude Code installs an update only when that version changes - not with every commit. Adding the
|
|
318
|
+
marketplace as above follows the default branch, so after a release the skills there can already
|
|
319
|
+
describe the next, unreleased gem (the version then reads e.g. `0.1.24-pre`). To stay on the skills of
|
|
320
|
+
the gem you actually use, add the marketplace at its release tag:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
claude plugin marketplace add dieter-medium/bidi2pdf#v0.1.23
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Updating and removing.** Auto-update is off for this marketplace unless you turn it on (`/plugin` >
|
|
327
|
+
Marketplaces). By hand:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
claude plugin marketplace update bidi2pdf # fetch the marketplace again
|
|
331
|
+
claude plugin update bidi2pdf@bidi2pdf # take the new version
|
|
332
|
+
claude plugin uninstall bidi2pdf@bidi2pdf # remove the plugin only
|
|
333
|
+
claude plugin marketplace remove bidi2pdf # remove the marketplace and every plugin installed from it
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**For a whole team.** To give everyone working in a project the skills, commit this to the project's
|
|
337
|
+
`.claude/settings.json` - each member gets them after accepting Claude Code's trust prompt for the
|
|
338
|
+
folder:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"extraKnownMarketplaces": {
|
|
343
|
+
"bidi2pdf": { "source": { "source": "github", "repo": "dieter-medium/bidi2pdf" } }
|
|
344
|
+
},
|
|
345
|
+
"enabledPlugins": { "bidi2pdf@bidi2pdf": true }
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`claude plugin marketplace add dieter-medium/bidi2pdf --scope project` and
|
|
350
|
+
`claude plugin install bidi2pdf@bidi2pdf --scope project` write the same entries for you.
|
|
351
|
+
|
|
352
|
+
**From a checkout, without installing** (to try a change to a skill):
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
claude --plugin-dir ./claude-plugin # for this one session
|
|
356
|
+
claude plugin validate ./claude-plugin --strict # check the plugin's structure
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
The skills point at `bidi2pdf help <command>` and `bidi2pdf schema <kind>` for exact options and
|
|
360
|
+
shapes; a spec (`spec/unit/claude_plugin_spec.rb`) checks every command, option, error code, recipe and
|
|
361
|
+
method they name against the gem, so they cannot drift from it. The plugin is not part of the gem
|
|
362
|
+
itself.
|
|
363
|
+
|
|
259
364
|
---
|
|
260
365
|
|
|
261
366
|
## 🧠 Programmatic API
|
|
@@ -347,6 +452,13 @@ tab.navigate_to "https://example.com/invoice/14432423"
|
|
|
347
452
|
# or inline
|
|
348
453
|
# tab.inject_style content: "body { background-color: red; }"
|
|
349
454
|
|
|
455
|
+
# Printing HTML you do not trust? Switch its scripts off before navigating:
|
|
456
|
+
# tab.disable_script_execution
|
|
457
|
+
|
|
458
|
+
# Print the screen styles in dark mode instead of the print stylesheet (holds for later
|
|
459
|
+
# navigations in this tab; tab.emulate_media without arguments ends it):
|
|
460
|
+
# tab.emulate_media(media: "screen", color_scheme: "dark")
|
|
461
|
+
|
|
350
462
|
tab.wait_until_network_idle
|
|
351
463
|
tab.print("my.pdf")
|
|
352
464
|
|
|
@@ -41,6 +41,13 @@ module Bidi2pdf
|
|
|
41
41
|
class BrowserTab
|
|
42
42
|
include JsLoggerHelper
|
|
43
43
|
|
|
44
|
+
# The values #emulate_media accepts, by argument - what Chrome's Emulation.setEmulatedMedia knows.
|
|
45
|
+
EMULATED_MEDIA = {
|
|
46
|
+
media: %w[screen print],
|
|
47
|
+
color_scheme: %w[light dark],
|
|
48
|
+
reduced_motion: %w[reduce no-preference]
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
44
51
|
# @return [Object] The WebSocket client.
|
|
45
52
|
attr_reader :client
|
|
46
53
|
|
|
@@ -334,15 +341,20 @@ module Bidi2pdf
|
|
|
334
341
|
end
|
|
335
342
|
end
|
|
336
343
|
|
|
337
|
-
# Waits until the network is idle in the browser tab.
|
|
344
|
+
# Waits until the network is idle in the browser tab. A timeout does not raise: the
|
|
345
|
+
# network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
|
|
346
|
+
# page as idle.
|
|
338
347
|
#
|
|
339
348
|
# @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
|
|
340
349
|
# @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
|
|
350
|
+
# @return [Symbol] :idle or :timeout
|
|
341
351
|
def wait_until_network_idle(timeout: 10, poll_interval: 0.1)
|
|
342
352
|
Bidi2pdf.notification_service.instrument("network_idle.bidi2pdf") do |instrumentation_payload|
|
|
343
|
-
network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
|
|
353
|
+
status = network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
|
|
344
354
|
|
|
345
355
|
instrumentation_payload[:requests] = network_events.all_events.dup
|
|
356
|
+
instrumentation_payload[:timed_out] = status == :timeout
|
|
357
|
+
status
|
|
346
358
|
end
|
|
347
359
|
end
|
|
348
360
|
|
|
@@ -434,7 +446,9 @@ module Bidi2pdf
|
|
|
434
446
|
|
|
435
447
|
block.call(pdf_base64) if block_given?
|
|
436
448
|
|
|
437
|
-
return
|
|
449
|
+
# The block's value, not a `return`: returning out of the instrumented block would
|
|
450
|
+
# skip the print event, and with it every subscriber (ResultCollector, --output -).
|
|
451
|
+
outputfile || block_given? ? nil : pdf_base64
|
|
438
452
|
else
|
|
439
453
|
Bidi2pdf.logger.error "Error printing: #{response}"
|
|
440
454
|
end
|
|
@@ -475,7 +489,8 @@ module Bidi2pdf
|
|
|
475
489
|
|
|
476
490
|
block.call(png_base64) if block_given?
|
|
477
491
|
|
|
478
|
-
|
|
492
|
+
# The block's value, not a `return` - see #print.
|
|
493
|
+
outputfile || block_given? ? nil : png_base64
|
|
479
494
|
else
|
|
480
495
|
Bidi2pdf.logger.error "Error capturing screenshot: #{response}"
|
|
481
496
|
end
|
|
@@ -496,8 +511,55 @@ module Bidi2pdf
|
|
|
496
511
|
client.send_cmd_and_wait(cmd) { |response| response }
|
|
497
512
|
end
|
|
498
513
|
|
|
514
|
+
# Switches JavaScript off (or on again) for this tab, through Chrome's own
|
|
515
|
+
# Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
|
|
516
|
+
# navigating: it holds for every later navigation in the tab, so a page printed from untrusted
|
|
517
|
+
# HTML runs none of its scripts.
|
|
518
|
+
#
|
|
519
|
+
# @param [Boolean] disabled Defaults to true.
|
|
520
|
+
def disable_script_execution(disabled: true)
|
|
521
|
+
cmd = Commands::CdpSendCommand.new(method: "Emulation.setScriptExecutionDisabled", params: { value: disabled },
|
|
522
|
+
session: cdp_session)
|
|
523
|
+
client.send_cmd_and_wait(cmd) { |response| response }
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Emulates a CSS media type and media features for this tab, through Chrome's own
|
|
527
|
+
# Emulation.setEmulatedMedia (CDP, via goog:cdp.sendCommand). It holds for every later
|
|
528
|
+
# navigation in the tab and for printing: +media: "screen"+ prints a page with its screen styles
|
|
529
|
+
# instead of its print stylesheet, +color_scheme: "dark"+ in its dark mode. On a narrow page the
|
|
530
|
+
# screen media type prints a page's mobile layout: the layout width is the page width minus the
|
|
531
|
+
# side margins (Chrome's default is 1 cm each), at 96 / 2.54 CSS px per cm - e.g.
|
|
532
|
+
# +page: { width: 9.525 }, margin: { left: 0, right: 0 }+ lays out at 360 px. Each call replaces
|
|
533
|
+
# the previous emulation, so a media type or feature it omits is reset; a call without arguments
|
|
534
|
+
# ends it.
|
|
535
|
+
#
|
|
536
|
+
# @param [String, Symbol, nil] media "screen" or "print"; nil leaves it to the browser.
|
|
537
|
+
# @param [String, Symbol, nil] color_scheme "light" or "dark" (prefers-color-scheme).
|
|
538
|
+
# @param [String, Symbol, nil] reduced_motion "reduce" or "no-preference" (prefers-reduced-motion).
|
|
539
|
+
# @raise [ArgumentError] for a value Chrome does not know, before anything is sent.
|
|
540
|
+
def emulate_media(media: nil, color_scheme: nil, reduced_motion: nil)
|
|
541
|
+
params = emulated_media_params(media: media, color_scheme: color_scheme, reduced_motion: reduced_motion)
|
|
542
|
+
cmd = Commands::CdpSendCommand.new(method: "Emulation.setEmulatedMedia", params: params, session: cdp_session)
|
|
543
|
+
client.send_cmd_and_wait(cmd) { |response| response }
|
|
544
|
+
end
|
|
545
|
+
|
|
499
546
|
private
|
|
500
547
|
|
|
548
|
+
def emulated_media_params(**values)
|
|
549
|
+
values.each { |name, value| check_emulated_media(name, value) }
|
|
550
|
+
|
|
551
|
+
features = { "prefers-color-scheme" => values[:color_scheme], "prefers-reduced-motion" => values[:reduced_motion] }
|
|
552
|
+
.filter_map { |name, value| { name: name, value: value.to_s } unless value.nil? }
|
|
553
|
+
{ media: values[:media].to_s, features: features }
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def check_emulated_media(name, value)
|
|
557
|
+
allowed = EMULATED_MEDIA.fetch(name)
|
|
558
|
+
return if value.nil? || allowed.include?(value.to_s)
|
|
559
|
+
|
|
560
|
+
raise ArgumentError, "#{name} must be one of #{allowed.join(", ")}, got #{value.inspect}"
|
|
561
|
+
end
|
|
562
|
+
|
|
501
563
|
def build_command_and_extractor(print_options)
|
|
502
564
|
cmd_type = (print_options.delete(:cmd_type) || :bidi).to_sym
|
|
503
565
|
|
|
@@ -95,24 +95,33 @@ module Bidi2pdf
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# Waits until no request is in flight, or until +timeout+ seconds passed.
|
|
99
|
+
#
|
|
100
|
+
# @return [Symbol] :idle when every request completed, :timeout when some were still open -
|
|
101
|
+
# it never raises for a timeout, so a caller decides whether a busy page is good enough.
|
|
98
102
|
def wait_until_network_idle(timeout: 10, poll_interval: 0.01)
|
|
99
103
|
start_time = Time.now
|
|
100
104
|
|
|
101
105
|
loop do
|
|
102
106
|
unless events.values.any?(&:in_progress?)
|
|
103
107
|
Bidi2pdf.logger.debug "✅ All network events completed."
|
|
104
|
-
|
|
108
|
+
return :idle
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
if Time.now - start_time > timeout
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
break
|
|
112
|
+
log_idle_timeout
|
|
113
|
+
return :timeout
|
|
111
114
|
end
|
|
112
115
|
|
|
113
116
|
sleep(poll_interval)
|
|
114
117
|
end
|
|
115
118
|
end
|
|
119
|
+
|
|
120
|
+
# Names the requests still open when the wait gives up - it carries on, it does not raise.
|
|
121
|
+
def log_idle_timeout
|
|
122
|
+
in_progress = events.values.select(&:in_progress?)
|
|
123
|
+
Bidi2pdf.logger.warn "⏰ Timeout while waiting for network events to complete. Still in progress: #{in_progress.map(&:id)}"
|
|
124
|
+
end
|
|
116
125
|
end
|
|
117
126
|
end
|
|
118
127
|
end
|
data/lib/bidi2pdf/cli.rb
CHANGED
|
@@ -78,10 +78,10 @@ module Bidi2pdf
|
|
|
78
78
|
option :manifest, type: :string, desc: "Write a render manifest (see `bidi2pdf schema manifest`) to this file"
|
|
79
79
|
|
|
80
80
|
option :background, type: :boolean, default: true, desc: "Print background graphics"
|
|
81
|
-
option :margin_top, type: :numeric, default: 1.0, desc: "Top margin in
|
|
82
|
-
option :margin_bottom, type: :numeric, default: 1.0, desc: "Bottom margin in
|
|
83
|
-
option :margin_left, type: :numeric, default: 1.0, desc: "Left margin in
|
|
84
|
-
option :margin_right, type: :numeric, default: 1.0, desc: "Right margin in
|
|
81
|
+
option :margin_top, type: :numeric, default: 1.0, desc: "Top margin in cm"
|
|
82
|
+
option :margin_bottom, type: :numeric, default: 1.0, desc: "Bottom margin in cm"
|
|
83
|
+
option :margin_left, type: :numeric, default: 1.0, desc: "Left margin in cm"
|
|
84
|
+
option :margin_right, type: :numeric, default: 1.0, desc: "Right margin in cm"
|
|
85
85
|
option :orientation, type: :string, default: "portrait", enum: %w[portrait landscape], desc: "Page orientation"
|
|
86
86
|
option :page_width, type: :numeric, default: 21.59, desc: "Page width in cm (min 0.0352)"
|
|
87
87
|
option :page_height, type: :numeric, default: 27.94, desc: "Page height in cm (min 0.0352)"
|
|
@@ -90,6 +90,9 @@ module Bidi2pdf
|
|
|
90
90
|
option :shrink_to_fit, type: :boolean, default: true, desc: "Shrink content to fit page"
|
|
91
91
|
option :generate_tagged_pdf, type: :boolean, default: false, desc: "Generate tagged PDF"
|
|
92
92
|
option :generate_document_outline, type: :boolean, default: false, desc: "Generate document outline"
|
|
93
|
+
option :media, type: :string, enum: %w[screen print], desc: "Emulate a CSS media type; screen prints the page with its screen styles. For its mobile layout, narrow the page: the layout width is --page_width minus the side margins, 9.525 cm with --margin_left 0 --margin_right 0 = 360 CSS px"
|
|
94
|
+
option :color_scheme, type: :string, enum: %w[light dark], desc: "Emulate prefers-color-scheme"
|
|
95
|
+
option :reduced_motion, type: :string, enum: %w[reduce no-preference], desc: "Emulate prefers-reduced-motion"
|
|
93
96
|
|
|
94
97
|
class << self
|
|
95
98
|
def exit_on_failure?
|
|
@@ -152,6 +155,9 @@ module Bidi2pdf
|
|
|
152
155
|
option :json, type: :boolean, default: false, desc: "Emit a single structured JSON document (see `bidi2pdf schema diagnose`) instead of a human-readable summary"
|
|
153
156
|
option :json_stream, type: :boolean, default: false, desc: "Emit one JSON progress event per line to stderr as diagnostics run, with or without --json (see `bidi2pdf schema event`)"
|
|
154
157
|
option :screenshot, type: :string, desc: "Also capture a screenshot to this PNG file"
|
|
158
|
+
option :media, type: :string, enum: %w[screen print], desc: "Emulate a CSS media type; screen prints the page with its screen styles. For its mobile layout, narrow the page: the layout width is --page_width minus the side margins, 9.525 cm with --margin_left 0 --margin_right 0 = 360 CSS px"
|
|
159
|
+
option :color_scheme, type: :string, enum: %w[light dark], desc: "Emulate prefers-color-scheme"
|
|
160
|
+
option :reduced_motion, type: :string, enum: %w[reduce no-preference], desc: "Emulate prefers-reduced-motion"
|
|
155
161
|
option :pdf, type: :string, desc: "Also render a PDF to this file and report its page count / blank-check"
|
|
156
162
|
|
|
157
163
|
def diagnose
|
|
@@ -307,9 +313,11 @@ module Bidi2pdf
|
|
|
307
313
|
emit_structured_render_output(result, collector)
|
|
308
314
|
|
|
309
315
|
exit_for_result(result)
|
|
316
|
+
ensure
|
|
317
|
+
stop_browsers
|
|
310
318
|
end
|
|
311
319
|
ensure
|
|
312
|
-
|
|
320
|
+
stop_browsers
|
|
313
321
|
stream&.unsubscribe
|
|
314
322
|
cleanup_stdin_tempfile
|
|
315
323
|
end
|
|
@@ -369,9 +377,11 @@ module Bidi2pdf
|
|
|
369
377
|
$stdout.puts(diagnose_payload(result, diagnostics).to_json)
|
|
370
378
|
|
|
371
379
|
exit_for_result(result)
|
|
380
|
+
ensure
|
|
381
|
+
stop_browsers
|
|
372
382
|
end
|
|
373
383
|
ensure
|
|
374
|
-
|
|
384
|
+
stop_browsers
|
|
375
385
|
stream&.unsubscribe
|
|
376
386
|
cleanup_stdin_tempfile
|
|
377
387
|
end
|
|
@@ -413,7 +423,6 @@ module Bidi2pdf
|
|
|
413
423
|
# --- run - Loader/Validator run before any browser work, both in --validate and in a real
|
|
414
424
|
# run, so an invalid recipe never launches one.
|
|
415
425
|
|
|
416
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
417
426
|
def perform_human_run(recipe_path)
|
|
418
427
|
recipe = load_and_validate_recipe(recipe_path)
|
|
419
428
|
return puts "Recipe '#{recipe_path}' is valid." if merged_options[:validate]
|
|
@@ -448,12 +457,12 @@ module Bidi2pdf
|
|
|
448
457
|
rescue Bidi2pdf::Error => e
|
|
449
458
|
raise Thor::Error, e.message
|
|
450
459
|
ensure
|
|
451
|
-
|
|
460
|
+
stop_browsers
|
|
452
461
|
stream&.unsubscribe
|
|
453
462
|
cleanup_stdin_tempfile
|
|
454
463
|
end
|
|
455
464
|
|
|
456
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
465
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
457
466
|
def perform_structured_run(recipe_path)
|
|
458
467
|
recipe = load_and_validate_recipe(recipe_path)
|
|
459
468
|
|
|
@@ -490,12 +499,14 @@ module Bidi2pdf
|
|
|
490
499
|
$stdout.puts(payload.to_json)
|
|
491
500
|
|
|
492
501
|
exit_for_result(result)
|
|
502
|
+
ensure
|
|
503
|
+
stop_browsers
|
|
493
504
|
end
|
|
494
505
|
rescue Bidi2pdf::InvalidRecipeError, Bidi2pdf::PdfInspectionUnavailableError, Bidi2pdf::InvalidPrintOptionError => e
|
|
495
506
|
$stdout.puts Bidi2pdf::Result.failure(command: "run", error: ErrorCodes.describe(e)).to_json
|
|
496
507
|
exit(Bidi2pdf::ExitCodes.for(ErrorCodes.for(e)))
|
|
497
508
|
ensure
|
|
498
|
-
|
|
509
|
+
stop_browsers
|
|
499
510
|
stream&.unsubscribe
|
|
500
511
|
cleanup_stdin_tempfile
|
|
501
512
|
end
|
|
@@ -510,6 +521,22 @@ module Bidi2pdf
|
|
|
510
521
|
recipe.source["url"] || recipe.source["file"] || "(stdin)"
|
|
511
522
|
end
|
|
512
523
|
|
|
524
|
+
# Stops whatever browser this command started, once. The structured paths call it inside
|
|
525
|
+
# reserve_stdout_for_machine_output, while the loggers still write to stderr: stopping logs
|
|
526
|
+
# ("WebSocket closed"), and after the block those lines would land on stdout behind the JSON
|
|
527
|
+
# document or the PDF bytes. Each browser is stopped on its own, and a failing stop is logged,
|
|
528
|
+
# not raised: this runs in an ensure, where raising would replace the command's own result and
|
|
529
|
+
# exit status.
|
|
530
|
+
def stop_browsers
|
|
531
|
+
launchers = [(@launcher if defined?(@launcher)), (@run_launcher if defined?(@run_launcher))].compact
|
|
532
|
+
@launcher = @run_launcher = nil
|
|
533
|
+
launchers.each do |launcher|
|
|
534
|
+
launcher.stop
|
|
535
|
+
rescue StandardError => e
|
|
536
|
+
Bidi2pdf.logger.error "Stopping the browser failed: #{e.class}: #{e.message}"
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
513
540
|
def with_recipe_tab(recipe)
|
|
514
541
|
@run_launcher = launcher_for_recipe(recipe)
|
|
515
542
|
tab = @run_launcher.diagnose
|
|
@@ -818,11 +845,18 @@ module Bidi2pdf
|
|
|
818
845
|
wait_network_idle: merged_options[:wait_network_idle],
|
|
819
846
|
print_options: print_options,
|
|
820
847
|
network_log_format: merged_options[:network_log_format],
|
|
821
|
-
chrome_args: chrome_args
|
|
848
|
+
chrome_args: chrome_args,
|
|
849
|
+
emulate_media: emulate_media_options
|
|
822
850
|
)
|
|
823
851
|
end
|
|
824
852
|
end
|
|
825
853
|
|
|
854
|
+
# The --media/--color_scheme/--reduced_motion choices for BrowserTab#emulate_media; empty when none
|
|
855
|
+
# is given, so the browser keeps its own.
|
|
856
|
+
def emulate_media_options
|
|
857
|
+
{ media: merged_options[:media], color_scheme: merged_options[:color_scheme], reduced_motion: merged_options[:reduced_motion] }.compact
|
|
858
|
+
end
|
|
859
|
+
|
|
826
860
|
def configure
|
|
827
861
|
Bidi2pdf.configure do |config|
|
|
828
862
|
config.logger.level = log_level
|
data/lib/bidi2pdf/launcher.rb
CHANGED
|
@@ -35,45 +35,31 @@ module Bidi2pdf
|
|
|
35
35
|
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
|
36
36
|
# @param [String, nil] remote_browser_url The URL of a remote browser to connect to. Defaults to nil.
|
|
37
37
|
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
|
38
|
+
# @param [Hash] emulate_media Media type and features for BrowserTab#emulate_media (media:,
|
|
39
|
+
# color_scheme:, reduced_motion:). Defaults to none.
|
|
38
40
|
class Launcher
|
|
39
41
|
# rubocop:disable Metrics/ParameterLists
|
|
40
42
|
def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
|
|
41
43
|
wait_network_idle: false, print_options: {}, remote_browser_url: nil, network_log_format: :console,
|
|
42
|
-
chrome_args: Bidi::Session::DEFAULT_CHROME_ARGS)
|
|
43
|
-
|
|
44
|
-
@
|
|
44
|
+
chrome_args: Bidi::Session::DEFAULT_CHROME_ARGS, emulate_media: {})
|
|
45
|
+
# What both runners get, unchanged - SessionRunner's own keyword arguments.
|
|
46
|
+
@runner_options = {
|
|
47
|
+
url: url, inputfile: inputfile, output: output, cookies: cookies, headers: headers, auth: auth,
|
|
48
|
+
wait_window_loaded: wait_window_loaded, wait_network_idle: wait_network_idle, print_options: print_options || {},
|
|
49
|
+
network_log_format: network_log_format, emulate_media: emulate_media || {}
|
|
50
|
+
}
|
|
45
51
|
@port = port
|
|
46
52
|
@headless = headless
|
|
47
|
-
@output = output
|
|
48
|
-
@cookies = cookies
|
|
49
|
-
@headers = headers
|
|
50
|
-
@auth = auth
|
|
51
53
|
@manager = nil
|
|
52
|
-
@wait_window_loaded = wait_window_loaded
|
|
53
|
-
@wait_network_idle = wait_network_idle
|
|
54
|
-
@print_options = print_options || {}
|
|
55
54
|
@remote_browser_url = remote_browser_url
|
|
56
55
|
@custom_session = nil
|
|
57
|
-
@network_log_format = network_log_format
|
|
58
56
|
@chrome_args = chrome_args
|
|
59
57
|
end
|
|
60
58
|
|
|
61
59
|
# rubocop:enable Metrics/ParameterLists
|
|
62
60
|
|
|
63
61
|
def launch
|
|
64
|
-
runner = SessionRunner.new(
|
|
65
|
-
session: session,
|
|
66
|
-
url: @url,
|
|
67
|
-
inputfile: @inputfile,
|
|
68
|
-
output: @output,
|
|
69
|
-
cookies: @cookies,
|
|
70
|
-
headers: @headers,
|
|
71
|
-
auth: @auth,
|
|
72
|
-
wait_window_loaded: @wait_window_loaded,
|
|
73
|
-
wait_network_idle: @wait_network_idle,
|
|
74
|
-
print_options: @print_options,
|
|
75
|
-
network_log_format: @network_log_format
|
|
76
|
-
)
|
|
62
|
+
runner = SessionRunner.new(session: session, **@runner_options)
|
|
77
63
|
runner.run
|
|
78
64
|
end
|
|
79
65
|
|
|
@@ -83,19 +69,7 @@ module Bidi2pdf
|
|
|
83
69
|
#
|
|
84
70
|
# @return [Bidi2pdf::Bidi::BrowserTab] the navigated tab.
|
|
85
71
|
def diagnose
|
|
86
|
-
@diagnose_runner = SessionRunner.new(
|
|
87
|
-
session: session,
|
|
88
|
-
url: @url,
|
|
89
|
-
inputfile: @inputfile,
|
|
90
|
-
output: @output,
|
|
91
|
-
cookies: @cookies,
|
|
92
|
-
headers: @headers,
|
|
93
|
-
auth: @auth,
|
|
94
|
-
wait_window_loaded: @wait_window_loaded,
|
|
95
|
-
wait_network_idle: @wait_network_idle,
|
|
96
|
-
print_options: @print_options,
|
|
97
|
-
network_log_format: @network_log_format
|
|
98
|
-
)
|
|
72
|
+
@diagnose_runner = SessionRunner.new(session: session, **@runner_options)
|
|
99
73
|
@diagnose_runner.run_diagnose
|
|
100
74
|
end
|
|
101
75
|
|
|
@@ -53,8 +53,9 @@ module Bidi2pdf
|
|
|
53
53
|
write(event: "page_loaded")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
# A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
|
|
57
|
+
def on_network_idle(event)
|
|
58
|
+
write(event: event.payload[:timed_out] ? "network_idle_timeout" : "network_idle")
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def on_console(event)
|
|
@@ -61,7 +61,7 @@ module Bidi2pdf
|
|
|
61
61
|
def network_idle(event)
|
|
62
62
|
return unless logger.info?
|
|
63
63
|
|
|
64
|
-
requests = event.payload[:requests]
|
|
64
|
+
requests = event.payload[:requests] || []
|
|
65
65
|
transfered = requests.map { |request| request.bytes_received || 0 }.sum
|
|
66
66
|
status_counts = requests
|
|
67
67
|
.group_by { |evt| evt.http_status_code || 0 }
|
|
@@ -70,8 +70,12 @@ module Bidi2pdf
|
|
|
70
70
|
.map { |code, count| "#{code}: #{count}" }
|
|
71
71
|
.join(", ")
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
summary = "#{requests.size} requests, transferred #{transfered} bytes (status codes: #{status_counts})"
|
|
74
|
+
if event.payload[:timed_out]
|
|
75
|
+
logger.warn "Network was still busy after #{event.duration.round(1)}ms (timed out), #{summary}"
|
|
76
|
+
else
|
|
77
|
+
logger.info "Network was idle after #{event.duration.round(1)}ms, #{summary}"
|
|
78
|
+
end
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
@@ -90,6 +90,7 @@ module Bidi2pdf
|
|
|
90
90
|
when "inject_style" then @tab.inject_style(url: opts["url"], content: opts["content"], id: opts["id"])
|
|
91
91
|
when "set_viewport" then @tab.set_viewport(width: opts["width"], height: opts["height"], device_pixel_ratio: opts["device_pixel_ratio"])
|
|
92
92
|
when "wait_network_idle" then @tab.wait_until_network_idle(timeout: opts.fetch("timeout", 10))
|
|
93
|
+
when "emulate_media" then @tab.emulate_media(media: opts["media"], color_scheme: opts["color_scheme"], reduced_motion: opts["reduced_motion"])
|
|
93
94
|
end
|
|
94
95
|
end
|
|
95
96
|
|
data/lib/bidi2pdf/recipe.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Bidi2pdf
|
|
|
12
12
|
# launches) -> Validator -> Runner.
|
|
13
13
|
class Recipe
|
|
14
14
|
KNOWN_TOP_LEVEL_KEYS = %w[version source browser headers cookies auth actions assert print output].freeze
|
|
15
|
-
KNOWN_ACTIONS = %w[wait_for click evaluate inject_script inject_style set_viewport wait_network_idle].freeze
|
|
15
|
+
KNOWN_ACTIONS = %w[wait_for click evaluate inject_script inject_style set_viewport wait_network_idle emulate_media].freeze
|
|
16
16
|
PAGE_ASSERTIONS = %w[selector_exists text_present no_console_errors no_network_failures fonts_loaded].freeze
|
|
17
17
|
PDF_ASSERTIONS = %w[page_count pdf_text_present pdf_not_blank].freeze
|
|
18
18
|
KNOWN_ASSERTIONS = (PAGE_ASSERTIONS + PDF_ASSERTIONS).freeze
|
data/lib/bidi2pdf/schema.rb
CHANGED
|
@@ -196,7 +196,8 @@ module Bidi2pdf
|
|
|
196
196
|
"properties" => {
|
|
197
197
|
"schema_version" => { "const" => 1 },
|
|
198
198
|
"t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" },
|
|
199
|
-
"event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle console print screenshot result]
|
|
199
|
+
"event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle network_idle_timeout console print screenshot result],
|
|
200
|
+
"description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" },
|
|
200
201
|
"url" => { "type" => "string", "description" => "present on a navigate event" },
|
|
201
202
|
"level" => { "type" => "string", "description" => "present on a console event" },
|
|
202
203
|
"text" => { "type" => "string", "description" => "present on a console event" },
|
|
@@ -218,7 +219,7 @@ module Bidi2pdf
|
|
|
218
219
|
}.freeze
|
|
219
220
|
|
|
220
221
|
RECIPE_ACTIONS = {
|
|
221
|
-
"description" => "one of the
|
|
222
|
+
"description" => "one of the 8 known actions - a thin dispatch over BrowserTab's own methods, see README's " \
|
|
222
223
|
"Agent and Automation Usage section",
|
|
223
224
|
"oneOf" => [
|
|
224
225
|
{
|
|
@@ -270,6 +271,18 @@ module Bidi2pdf
|
|
|
270
271
|
"type" => "object", "required" => %w[wait_network_idle], "additionalProperties" => false,
|
|
271
272
|
"properties" => { "wait_network_idle" => { "type" => "object", "additionalProperties" => false,
|
|
272
273
|
"properties" => { "timeout" => { "type" => "number" } } } }
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"type" => "object", "required" => %w[emulate_media], "additionalProperties" => false,
|
|
277
|
+
"properties" => {
|
|
278
|
+
"emulate_media" => {
|
|
279
|
+
"type" => "object", "additionalProperties" => false,
|
|
280
|
+
"description" => "the CSS media type and media features the page is shown and printed with, from here on; " \
|
|
281
|
+
"media: screen prints the screen styles; on a narrow print page (layout width = page width minus the side margins, " \
|
|
282
|
+
"9.525 cm without side margins = 360 CSS px) the mobile layout",
|
|
283
|
+
"properties" => Bidi2pdf::Bidi::BrowserTab::EMULATED_MEDIA.to_h { |name, values| [name.to_s, { "type" => "string", "enum" => values }] }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
273
286
|
}
|
|
274
287
|
]
|
|
275
288
|
}.freeze
|
|
@@ -33,10 +33,12 @@ module Bidi2pdf
|
|
|
33
33
|
# @param [Boolean] wait_network_idle Whether to wait for the network to become idle. Defaults to false.
|
|
34
34
|
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
|
35
35
|
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
|
36
|
+
# @param [Hash] emulate_media Media type and features for BrowserTab#emulate_media (media:,
|
|
37
|
+
# color_scheme:, reduced_motion:), applied before navigating. Defaults to none.
|
|
36
38
|
class SessionRunner
|
|
37
39
|
# rubocop: disable Metrics/ParameterLists
|
|
38
40
|
def initialize(session:, url:, inputfile:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
|
|
39
|
-
wait_network_idle: false, print_options: {}, network_log_format: :console)
|
|
41
|
+
wait_network_idle: false, print_options: {}, network_log_format: :console, emulate_media: {})
|
|
40
42
|
@session = session
|
|
41
43
|
@url = url
|
|
42
44
|
@inputfile = inputfile
|
|
@@ -48,6 +50,7 @@ module Bidi2pdf
|
|
|
48
50
|
@wait_network_idle = wait_network_idle
|
|
49
51
|
@print_options = print_options || {}
|
|
50
52
|
@network_log_format = network_log_format
|
|
53
|
+
@emulate_media = emulate_media || {}
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
# rubocop: enable Metrics/ParameterLists
|
|
@@ -100,6 +103,7 @@ module Bidi2pdf
|
|
|
100
103
|
|
|
101
104
|
add_headers
|
|
102
105
|
add_basic_auth
|
|
106
|
+
@tab.emulate_media(**@emulate_media) if @emulate_media.any?
|
|
103
107
|
end
|
|
104
108
|
|
|
105
109
|
def add_cookies(tab)
|
data/lib/bidi2pdf/version.rb
CHANGED
|
@@ -56,6 +56,9 @@ module Bidi2pdf
|
|
|
56
56
|
|
|
57
57
|
include JsLoggerHelper
|
|
58
58
|
|
|
59
|
+
# The values #emulate_media accepts, by argument - what Chrome's Emulation.setEmulatedMedia knows.
|
|
60
|
+
EMULATED_MEDIA: { media: ::Array["screen" | "print"], color_scheme: ::Array["light" | "dark"], reduced_motion: ::Array["reduce" | "no-preference"] }
|
|
61
|
+
|
|
59
62
|
# @return [Object] The WebSocket client.
|
|
60
63
|
attr_reader client: untyped
|
|
61
64
|
|
|
@@ -169,10 +172,13 @@ module Bidi2pdf
|
|
|
169
172
|
# @return [Object] The result from the style creation promise.
|
|
170
173
|
def inject_style: (?url: untyped?, ?content: untyped?, ?id: untyped?) -> untyped
|
|
171
174
|
|
|
172
|
-
# Waits until the network is idle in the browser tab.
|
|
175
|
+
# Waits until the network is idle in the browser tab. A timeout does not raise: the
|
|
176
|
+
# network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
|
|
177
|
+
# page as idle.
|
|
173
178
|
#
|
|
174
179
|
# @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
|
|
175
180
|
# @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
|
|
181
|
+
# @return [Symbol] :idle or :timeout
|
|
176
182
|
def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> untyped
|
|
177
183
|
|
|
178
184
|
# Waits until the page is fully loaded in the browser tab.
|
|
@@ -227,8 +233,36 @@ module Bidi2pdf
|
|
|
227
233
|
# @param [Float, nil] device_pixel_ratio Defaults to nil (browser default).
|
|
228
234
|
def set_viewport: (width: untyped, height: untyped, ?device_pixel_ratio: untyped?) -> untyped
|
|
229
235
|
|
|
236
|
+
# Switches JavaScript off (or on again) for this tab, through Chrome's own
|
|
237
|
+
# Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
|
|
238
|
+
# navigating: it holds for every later navigation in the tab, so a page printed from untrusted
|
|
239
|
+
# HTML runs none of its scripts.
|
|
240
|
+
#
|
|
241
|
+
# @param [Boolean] disabled Defaults to true.
|
|
242
|
+
def disable_script_execution: (?disabled: bool) -> untyped
|
|
243
|
+
|
|
244
|
+
# Emulates a CSS media type and media features for this tab, through Chrome's own
|
|
245
|
+
# Emulation.setEmulatedMedia (CDP, via goog:cdp.sendCommand). It holds for every later
|
|
246
|
+
# navigation in the tab and for printing: +media: "screen"+ prints a page with its screen styles
|
|
247
|
+
# instead of its print stylesheet, +color_scheme: "dark"+ in its dark mode. On a narrow page the
|
|
248
|
+
# screen media type prints a page's mobile layout: the layout width is the page width minus the
|
|
249
|
+
# side margins (Chrome's default is 1 cm each), at 96 / 2.54 CSS px per cm - e.g.
|
|
250
|
+
# +page: { width: 9.525 }, margin: { left: 0, right: 0 }+ lays out at 360 px. Each call replaces
|
|
251
|
+
# the previous emulation, so a media type or feature it omits is reset; a call without arguments
|
|
252
|
+
# ends it.
|
|
253
|
+
#
|
|
254
|
+
# @param [String, Symbol, nil] media "screen" or "print"; nil leaves it to the browser.
|
|
255
|
+
# @param [String, Symbol, nil] color_scheme "light" or "dark" (prefers-color-scheme).
|
|
256
|
+
# @param [String, Symbol, nil] reduced_motion "reduce" or "no-preference" (prefers-reduced-motion).
|
|
257
|
+
# @raise [ArgumentError] for a value Chrome does not know, before anything is sent.
|
|
258
|
+
def emulate_media: (?media: untyped?, ?color_scheme: untyped?, ?reduced_motion: untyped?) -> untyped
|
|
259
|
+
|
|
230
260
|
private
|
|
231
261
|
|
|
262
|
+
def emulated_media_params: (**untyped values) -> { media: untyped, features: untyped }
|
|
263
|
+
|
|
264
|
+
def check_emulated_media: (untyped name, untyped value) -> (nil | untyped)
|
|
265
|
+
|
|
232
266
|
def build_command_and_extractor: (untyped print_options) -> ::Array[untyped]
|
|
233
267
|
|
|
234
268
|
def cdp_session: () -> untyped
|
|
@@ -24,7 +24,14 @@ module Bidi2pdf
|
|
|
24
24
|
|
|
25
25
|
def log_network_traffic: (?format: ::Symbol) -> untyped
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# Waits until no request is in flight, or until +timeout+ seconds passed.
|
|
28
|
+
#
|
|
29
|
+
# @return [Symbol] :idle when every request completed, :timeout when some were still open -
|
|
30
|
+
# it never raises for a timeout, so a caller decides whether a busy page is good enough.
|
|
31
|
+
def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> (:idle | :timeout | untyped)
|
|
32
|
+
|
|
33
|
+
# Names the requests still open when the wait gives up - it carries on, it does not raise.
|
|
34
|
+
def log_idle_timeout: () -> untyped
|
|
28
35
|
end
|
|
29
36
|
end
|
|
30
37
|
end
|
data/sig/bidi2pdf/cli.rbs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Bidi2pdf
|
|
2
2
|
# rubocop:disable Metrics/AbcSize
|
|
3
3
|
class CLI < Thor
|
|
4
|
+
@launcher: untyped
|
|
5
|
+
|
|
4
6
|
@run_launcher: untyped
|
|
5
7
|
|
|
6
8
|
@render_input_path: untyped
|
|
@@ -9,8 +11,6 @@ module Bidi2pdf
|
|
|
9
11
|
|
|
10
12
|
@stdin_tempfile: untyped
|
|
11
13
|
|
|
12
|
-
@launcher: untyped
|
|
13
|
-
|
|
14
14
|
include JsonOutput
|
|
15
15
|
|
|
16
16
|
include SessionCommands
|
|
@@ -55,16 +55,23 @@ module Bidi2pdf
|
|
|
55
55
|
|
|
56
56
|
def capture_diagnose_pdf: (untyped tab) -> (nil | { path: untyped, pages: untyped, not_blank: untyped })
|
|
57
57
|
|
|
58
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
59
58
|
def perform_human_run: (untyped recipe_path) -> untyped
|
|
60
59
|
|
|
61
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
60
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
62
61
|
def perform_structured_run: (untyped recipe_path) -> untyped
|
|
63
62
|
|
|
64
63
|
def load_and_validate_recipe: (untyped recipe_path) -> untyped
|
|
65
64
|
|
|
66
65
|
def recipe_requested_url: (untyped recipe) -> untyped
|
|
67
66
|
|
|
67
|
+
# Stops whatever browser this command started, once. The structured paths call it inside
|
|
68
|
+
# reserve_stdout_for_machine_output, while the loggers still write to stderr: stopping logs
|
|
69
|
+
# ("WebSocket closed"), and after the block those lines would land on stdout behind the JSON
|
|
70
|
+
# document or the PDF bytes. Each browser is stopped on its own, and a failing stop is logged,
|
|
71
|
+
# not raised: this runs in an ensure, where raising would replace the command's own result and
|
|
72
|
+
# exit status.
|
|
73
|
+
def stop_browsers: () -> untyped
|
|
74
|
+
|
|
68
75
|
def with_recipe_tab: (untyped recipe) { (untyped) -> untyped } -> untyped
|
|
69
76
|
|
|
70
77
|
# A failing action/assertion raises Recipe::Runner::StepFailure (its own entries plus the real
|
|
@@ -144,6 +151,10 @@ module Bidi2pdf
|
|
|
144
151
|
|
|
145
152
|
def launcher: () -> untyped
|
|
146
153
|
|
|
154
|
+
# The --media/--color_scheme/--reduced_motion choices for BrowserTab#emulate_media; empty when none
|
|
155
|
+
# is given, so the browser keeps its own.
|
|
156
|
+
def emulate_media_options: () -> untyped
|
|
157
|
+
|
|
147
158
|
def configure: () -> untyped
|
|
148
159
|
|
|
149
160
|
def log_level: () -> untyped
|
data/sig/bidi2pdf/launcher.rbs
CHANGED
|
@@ -29,43 +29,28 @@ module Bidi2pdf
|
|
|
29
29
|
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
|
30
30
|
# @param [String, nil] remote_browser_url The URL of a remote browser to connect to. Defaults to nil.
|
|
31
31
|
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
|
32
|
+
# @param [Hash] emulate_media Media type and features for BrowserTab#emulate_media (media:,
|
|
33
|
+
# color_scheme:, reduced_motion:). Defaults to none.
|
|
32
34
|
class Launcher
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@inputfile: untyped
|
|
35
|
+
# What both runners get, unchanged - SessionRunner's own keyword arguments.
|
|
36
|
+
@runner_options: untyped
|
|
36
37
|
|
|
37
38
|
@port: untyped
|
|
38
39
|
|
|
39
40
|
@headless: untyped
|
|
40
41
|
|
|
41
|
-
@output: untyped
|
|
42
|
-
|
|
43
|
-
@cookies: untyped
|
|
44
|
-
|
|
45
|
-
@headers: untyped
|
|
46
|
-
|
|
47
|
-
@auth: untyped
|
|
48
|
-
|
|
49
42
|
@manager: untyped
|
|
50
43
|
|
|
51
|
-
@wait_window_loaded: untyped
|
|
52
|
-
|
|
53
|
-
@wait_network_idle: untyped
|
|
54
|
-
|
|
55
|
-
@print_options: untyped
|
|
56
|
-
|
|
57
44
|
@remote_browser_url: untyped
|
|
58
45
|
|
|
59
46
|
@custom_session: untyped
|
|
60
47
|
|
|
61
|
-
@network_log_format: untyped
|
|
62
|
-
|
|
63
48
|
@chrome_args: untyped
|
|
64
49
|
|
|
65
50
|
@diagnose_runner: untyped
|
|
66
51
|
|
|
67
52
|
# rubocop:disable Metrics/ParameterLists
|
|
68
|
-
def initialize: (url: untyped, inputfile: untyped, output: untyped, cookies: untyped, headers: untyped, auth: untyped, ?headless: bool, ?port: ::Integer, ?wait_window_loaded: bool, ?wait_network_idle: bool, ?print_options: ::Hash[untyped, untyped], ?remote_browser_url: untyped?, ?network_log_format: ::Symbol, ?chrome_args: untyped) -> void
|
|
53
|
+
def initialize: (url: untyped, inputfile: untyped, output: untyped, cookies: untyped, headers: untyped, auth: untyped, ?headless: bool, ?port: ::Integer, ?wait_window_loaded: bool, ?wait_network_idle: bool, ?print_options: ::Hash[untyped, untyped], ?remote_browser_url: untyped?, ?network_log_format: ::Symbol, ?chrome_args: untyped, ?emulate_media: ::Hash[untyped, untyped]) -> void
|
|
69
54
|
|
|
70
55
|
def launch: () -> untyped
|
|
71
56
|
|
|
@@ -31,7 +31,8 @@ module Bidi2pdf
|
|
|
31
31
|
|
|
32
32
|
def on_page_loaded: (untyped _event) -> untyped
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
# A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
|
|
35
|
+
def on_network_idle: (untyped event) -> untyped
|
|
35
36
|
|
|
36
37
|
def on_console: (untyped event) -> untyped
|
|
37
38
|
|
data/sig/bidi2pdf/recipe.rbs
CHANGED
|
@@ -10,7 +10,7 @@ module Bidi2pdf
|
|
|
10
10
|
|
|
11
11
|
KNOWN_TOP_LEVEL_KEYS: ::Array["version" | "source" | "browser" | "headers" | "cookies" | "auth" | "actions" | "assert" | "print" | "output"]
|
|
12
12
|
|
|
13
|
-
KNOWN_ACTIONS: ::Array["wait_for" | "click" | "evaluate" | "inject_script" | "inject_style" | "set_viewport" | "wait_network_idle"]
|
|
13
|
+
KNOWN_ACTIONS: ::Array["wait_for" | "click" | "evaluate" | "inject_script" | "inject_style" | "set_viewport" | "wait_network_idle" | "emulate_media"]
|
|
14
14
|
|
|
15
15
|
PAGE_ASSERTIONS: ::Array["selector_exists" | "text_present" | "no_console_errors" | "no_network_failures" | "fonts_loaded"]
|
|
16
16
|
|
data/sig/bidi2pdf/schema.rbs
CHANGED
|
@@ -26,11 +26,11 @@ module Bidi2pdf
|
|
|
26
26
|
|
|
27
27
|
MANIFEST: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf render manifest", "description" => "--manifest FILE output", "type" => "object", "required" => ::Array["schema_version" | "bidi2pdf_version" | "created_at" | "input" | "output" | "navigation" | "render"], "properties" => { "schema_version" => { "const" => 1 }, "bidi2pdf_version" => { "type" => "string" }, "created_at" => { "type" => "string", "format" => "date-time" }, "input" => { "type" => "object", "required" => ::Array["type"], "properties" => { "type" => { "type" => "string", "enum" => ::Array["url" | "html_file" | "stdin"] }, "url" => { "type" => ::Array["string" | "null"] } } }, "output" => { "type" => "object", "properties" => { "path" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] } } }, "browser" => { "type" => "object" }, "navigation" => untyped, "render" => { "type" => "object", "properties" => { "duration_ms" => { "type" => ::Array["integer" | "null"] } } }, "console" => untyped, "network_failures" => untyped, "warnings" => untyped, "headers" => { "type" => "object", "description" => "present only when custom headers were given; secret-bearing values are redacted" } } }
|
|
28
28
|
|
|
29
|
-
EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "console" | "print" | "screenshot" | "result"] }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
|
|
29
|
+
EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "network_idle_timeout" | "console" | "print" | "screenshot" | "result"], "description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
|
|
30
30
|
|
|
31
31
|
RECIPE_SOURCE: { "description" => "exactly one of url, file, stdin", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["url"], "properties" => { "url" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["file"], "properties" => { "file" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["stdin"], "properties" => { "stdin" => { "const" => true } }, "additionalProperties" => false }] }
|
|
32
32
|
|
|
33
|
-
RECIPE_ACTIONS: { "description" => "one of the
|
|
33
|
+
RECIPE_ACTIONS: { "description" => "one of the 8 known actions - a thin dispatch over BrowserTab's own methods, see README's Agent and Automation Usage section", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["wait_for"], "additionalProperties" => false, "properties" => { "wait_for" => { "type" => "object", "description" => "exactly one of selector, paged_js, script; timeout is always optional", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" }, "timeout" => { "type" => "number" } } } | { "type" => "object", "required" => ::Array["paged_js"], "additionalProperties" => false, "properties" => { "paged_js" => { "type" => "boolean" }, "timeout" => { "type" => "number" } } } | { "type" => "object", "required" => ::Array["script"], "additionalProperties" => false, "properties" => { "script" => { "type" => "string" }, "timeout" => { "type" => "number" } } }] } } } | { "type" => "object", "required" => ::Array["click"], "additionalProperties" => false, "properties" => { "click" => { "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["evaluate"], "additionalProperties" => false, "properties" => { "evaluate" => { "type" => "object", "required" => ::Array["script"], "additionalProperties" => false, "properties" => { "script" => { "type" => "string" }, "assign" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["inject_script"], "additionalProperties" => false, "properties" => { "inject_script" => { "type" => "object", "additionalProperties" => false, "properties" => { "url" => { "type" => "string" }, "content" => { "type" => "string" }, "id" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["inject_style"], "additionalProperties" => false, "properties" => { "inject_style" => { "type" => "object", "additionalProperties" => false, "properties" => { "url" => { "type" => "string" }, "content" => { "type" => "string" }, "id" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["set_viewport"], "additionalProperties" => false, "properties" => { "set_viewport" => { "type" => "object", "required" => ::Array["width" | "height"], "additionalProperties" => false, "properties" => { "width" => { "type" => "number" }, "height" => { "type" => "number" }, "device_pixel_ratio" => { "type" => "number" } } } } } | { "type" => "object", "required" => ::Array["wait_network_idle"], "additionalProperties" => false, "properties" => { "wait_network_idle" => { "type" => "object", "additionalProperties" => false, "properties" => { "timeout" => { "type" => "number" } } } } } | { "type" => "object", "required" => ::Array["emulate_media"], "additionalProperties" => false, "properties" => { "emulate_media" => { "type" => "object", "additionalProperties" => false, "description" => "the CSS media type and media features the page is shown and printed with, from here on; media: screen prints the screen styles; on a narrow print page (layout width = page width minus the side margins, 9.525 cm without side margins = 360 CSS px) the mobile layout", "properties" => untyped } } }] }
|
|
34
34
|
|
|
35
35
|
RECIPE_ASSERT: { "description" => "one of the 8 known assertions. The presence-only ones (no_console_errors, no_network_failures, fonts_loaded, pdf_not_blank) never have their value read at run time - Recipe::Runner only checks the key is there - so they must be written as `true`; `false` or any other value is rejected as misleading rather than silently ignored. The last 3 assertions also require the pdf-reader gem at --validate time - a runtime fact this schema cannot express, see the top-level description", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["selector_exists"], "additionalProperties" => false, "properties" => { "selector_exists" => { "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["text_present"], "additionalProperties" => false, "properties" => { "text_present" => { "type" => "object", "required" => ::Array["text"], "additionalProperties" => false, "properties" => { "text" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["no_console_errors"], "additionalProperties" => false, "properties" => { "no_console_errors" => { "const" => true } } } | { "type" => "object", "required" => ::Array["no_network_failures"], "additionalProperties" => false, "properties" => { "no_network_failures" => { "const" => true } } } | { "type" => "object", "required" => ::Array["fonts_loaded"], "additionalProperties" => false, "properties" => { "fonts_loaded" => { "const" => true } } } | { "type" => "object", "required" => ::Array["page_count"], "additionalProperties" => false, "properties" => { "page_count" => { "oneOf" => ::Array[{ "type" => "integer" } | { "type" => "object", "properties" => { "min" => { "type" => "integer" }, "max" => { "type" => "integer" } }, "additionalProperties" => false }] } } } | { "type" => "object", "required" => ::Array["pdf_text_present"], "additionalProperties" => false, "properties" => { "pdf_text_present" => { "type" => "object", "required" => ::Array["text"], "additionalProperties" => false, "properties" => { "text" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["pdf_not_blank"], "additionalProperties" => false, "properties" => { "pdf_not_blank" => { "const" => true } } }] }
|
|
36
36
|
|
|
@@ -31,6 +31,8 @@ module Bidi2pdf
|
|
|
31
31
|
# @param [Boolean] wait_network_idle Whether to wait for the network to become idle. Defaults to false.
|
|
32
32
|
# @param [Hash] print_options Options for printing the page. Defaults to an empty hash.
|
|
33
33
|
# @param [Symbol] network_log_format The format for network logs. Defaults to :console.
|
|
34
|
+
# @param [Hash] emulate_media Media type and features for BrowserTab#emulate_media (media:,
|
|
35
|
+
# color_scheme:, reduced_motion:), applied before navigating. Defaults to none.
|
|
34
36
|
class SessionRunner
|
|
35
37
|
@session: untyped
|
|
36
38
|
|
|
@@ -54,6 +56,8 @@ module Bidi2pdf
|
|
|
54
56
|
|
|
55
57
|
@network_log_format: untyped
|
|
56
58
|
|
|
59
|
+
@emulate_media: untyped
|
|
60
|
+
|
|
57
61
|
@window: untyped
|
|
58
62
|
|
|
59
63
|
@tab: untyped
|
|
@@ -63,7 +67,7 @@ module Bidi2pdf
|
|
|
63
67
|
@uri: untyped
|
|
64
68
|
|
|
65
69
|
# rubocop: disable Metrics/ParameterLists
|
|
66
|
-
def initialize: (session: untyped, url: untyped, inputfile: untyped, output: untyped, ?cookies: ::Hash[untyped, untyped], ?headers: ::Hash[untyped, untyped], ?auth: ::Hash[untyped, untyped], ?wait_window_loaded: bool, ?wait_network_idle: bool, ?print_options: ::Hash[untyped, untyped], ?network_log_format: ::Symbol) -> void
|
|
70
|
+
def initialize: (session: untyped, url: untyped, inputfile: untyped, output: untyped, ?cookies: ::Hash[untyped, untyped], ?headers: ::Hash[untyped, untyped], ?auth: ::Hash[untyped, untyped], ?wait_window_loaded: bool, ?wait_network_idle: bool, ?print_options: ::Hash[untyped, untyped], ?network_log_format: ::Symbol, ?emulate_media: ::Hash[untyped, untyped]) -> void
|
|
67
71
|
|
|
68
72
|
def run: () -> untyped
|
|
69
73
|
|
data/sig/bidi2pdf/version.rbs
CHANGED