puppeteer-ruby 0.45.6 → 0.50.0.alpha5
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/.rubocop.yml +1 -3
- data/AGENTS.md +169 -0
- data/CLAUDE/README.md +41 -0
- data/CLAUDE/architecture.md +253 -0
- data/CLAUDE/cdp_protocol.md +230 -0
- data/CLAUDE/concurrency.md +216 -0
- data/CLAUDE/porting_puppeteer.md +575 -0
- data/CLAUDE/rbs_type_checking.md +101 -0
- data/CLAUDE/spec_migration_plans.md +1041 -0
- data/CLAUDE/testing.md +278 -0
- data/CLAUDE.md +242 -0
- data/README.md +8 -0
- data/Rakefile +7 -0
- data/Steepfile +28 -0
- data/docs/api_coverage.md +105 -56
- data/lib/puppeteer/aria_query_handler.rb +3 -2
- data/lib/puppeteer/async_utils.rb +214 -0
- data/lib/puppeteer/browser.rb +98 -56
- data/lib/puppeteer/browser_connector.rb +18 -3
- data/lib/puppeteer/browser_context.rb +196 -3
- data/lib/puppeteer/browser_runner.rb +18 -10
- data/lib/puppeteer/cdp_session.rb +67 -23
- data/lib/puppeteer/chrome_target_manager.rb +65 -40
- data/lib/puppeteer/connection.rb +55 -36
- data/lib/puppeteer/console_message.rb +9 -1
- data/lib/puppeteer/console_patch.rb +47 -0
- data/lib/puppeteer/css_coverage.rb +5 -3
- data/lib/puppeteer/custom_query_handler.rb +80 -33
- data/lib/puppeteer/define_async_method.rb +31 -37
- data/lib/puppeteer/dialog.rb +47 -14
- data/lib/puppeteer/element_handle.rb +231 -62
- data/lib/puppeteer/emulation_manager.rb +1 -1
- data/lib/puppeteer/env.rb +1 -1
- data/lib/puppeteer/errors.rb +25 -2
- data/lib/puppeteer/event_callbackable.rb +15 -0
- data/lib/puppeteer/events.rb +4 -0
- data/lib/puppeteer/execution_context.rb +148 -3
- data/lib/puppeteer/file_chooser.rb +6 -0
- data/lib/puppeteer/frame.rb +162 -91
- data/lib/puppeteer/frame_manager.rb +69 -48
- data/lib/puppeteer/http_request.rb +114 -38
- data/lib/puppeteer/http_response.rb +24 -7
- data/lib/puppeteer/isolated_world.rb +64 -41
- data/lib/puppeteer/js_coverage.rb +5 -3
- data/lib/puppeteer/js_handle.rb +58 -16
- data/lib/puppeteer/keyboard.rb +30 -17
- data/lib/puppeteer/launcher/browser_options.rb +3 -1
- data/lib/puppeteer/launcher/chrome.rb +8 -5
- data/lib/puppeteer/launcher/launch_options.rb +7 -2
- data/lib/puppeteer/launcher.rb +4 -8
- data/lib/puppeteer/lifecycle_watcher.rb +38 -22
- data/lib/puppeteer/mouse.rb +273 -64
- data/lib/puppeteer/network_event_manager.rb +7 -0
- data/lib/puppeteer/network_manager.rb +393 -112
- data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
- data/lib/puppeteer/page.rb +568 -226
- data/lib/puppeteer/puppeteer.rb +171 -64
- data/lib/puppeteer/query_handler_manager.rb +112 -16
- data/lib/puppeteer/reactor_runner.rb +247 -0
- data/lib/puppeteer/remote_object.rb +127 -47
- data/lib/puppeteer/target.rb +74 -27
- data/lib/puppeteer/task_manager.rb +3 -1
- data/lib/puppeteer/timeout_helper.rb +6 -10
- data/lib/puppeteer/touch_handle.rb +39 -0
- data/lib/puppeteer/touch_screen.rb +72 -22
- data/lib/puppeteer/tracing.rb +3 -3
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +264 -101
- data/lib/puppeteer/web_socket.rb +2 -2
- data/lib/puppeteer/web_socket_transport.rb +91 -27
- data/lib/puppeteer/web_worker.rb +175 -0
- data/lib/puppeteer.rb +20 -4
- data/puppeteer-ruby.gemspec +15 -11
- data/sig/_external.rbs +8 -0
- data/sig/_supplementary.rbs +314 -0
- data/sig/puppeteer/browser.rbs +166 -0
- data/sig/puppeteer/cdp_session.rbs +64 -0
- data/sig/puppeteer/dialog.rbs +41 -0
- data/sig/puppeteer/element_handle.rbs +305 -0
- data/sig/puppeteer/execution_context.rbs +87 -0
- data/sig/puppeteer/frame.rbs +226 -0
- data/sig/puppeteer/http_request.rbs +214 -0
- data/sig/puppeteer/http_response.rbs +89 -0
- data/sig/puppeteer/js_handle.rbs +64 -0
- data/sig/puppeteer/keyboard.rbs +40 -0
- data/sig/puppeteer/mouse.rbs +113 -0
- data/sig/puppeteer/page.rbs +515 -0
- data/sig/puppeteer/puppeteer.rbs +98 -0
- data/sig/puppeteer/remote_object.rbs +78 -0
- data/sig/puppeteer/touch_handle.rbs +21 -0
- data/sig/puppeteer/touch_screen.rbs +35 -0
- data/sig/puppeteer/web_worker.rbs +83 -0
- metadata +116 -45
- data/CHANGELOG.md +0 -397
- data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
- data/lib/puppeteer/firefox_target_manager.rb +0 -157
- data/lib/puppeteer/launcher/firefox.rb +0 -453
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a20cad1d3b0ad0947041ff8086f0336e381d783f1f6edb1aee45263054f5b40
|
|
4
|
+
data.tar.gz: 70c27f24141dc2326762a419cd32c780fcb9c1b5474246e2dd339dd5246b2090
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89f6ed85a37682ced989ece6cddbac7ad9489bd6a1a31fb98f557f96527a8d298b572c078a6438d7030bd6e8c14d3b5a92042d4c1fd5caad1fc1a031335381a1
|
|
7
|
+
data.tar.gz: fe22e70a18f3319fcb7face324b2de32944f2ebfc0d594a8b33ad2bc95ceb2a0bd8a75bf63fa81369cd892fb8a125edf5c4221e0650476c618fef8455c794d0a
|
data/.rubocop.yml
CHANGED
|
@@ -80,6 +80,7 @@ Layout/IndentationWidth:
|
|
|
80
80
|
|
|
81
81
|
Layout/LeadingCommentSpace:
|
|
82
82
|
Enabled: true
|
|
83
|
+
AllowRBSInlineAnnotation: true
|
|
83
84
|
|
|
84
85
|
Layout/SpaceAfterColon:
|
|
85
86
|
Enabled: true
|
|
@@ -294,9 +295,6 @@ Style/AccessModifierDeclarations:
|
|
|
294
295
|
Enabled: true
|
|
295
296
|
EnforcedStyle: inline
|
|
296
297
|
|
|
297
|
-
Style/ClassAndModuleChildren:
|
|
298
|
-
EnforcedStyle: compact
|
|
299
|
-
|
|
300
298
|
Style/TrailingCommaInArguments:
|
|
301
299
|
Enabled: true
|
|
302
300
|
EnforcedStyleForMultiline: comma
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Start Here (Project-Specific Guidance)
|
|
4
|
+
|
|
5
|
+
- Read `CLAUDE.md` and `CLAUDE/` first; they define the CDP architecture, porting workflow, testing strategy, and concurrency plan.
|
|
6
|
+
- This repository is a CDP-based Ruby port of Puppeteer, **focused on Chrome/Chromium only**.
|
|
7
|
+
- CI covers Ruby 3.2, 3.3, 3.4 with latest Chrome.
|
|
8
|
+
|
|
9
|
+
### Technical Details
|
|
10
|
+
|
|
11
|
+
- **Ruby version**: Minimum is 3.2+
|
|
12
|
+
- **Concurrency**: Uses `socketry/async` (version 2.35.1+) for Fiber-based concurrency. See `CLAUDE/concurrency.md` for details.
|
|
13
|
+
|
|
14
|
+
## Project Structure & Module Organization
|
|
15
|
+
|
|
16
|
+
- `lib/puppeteer/`: core implementation (entry points: `puppeteer.rb`, `browser.rb`, `page.rb`, `frame.rb`, `element_handle.rb`, `connection.rb`, `cdp_session.rb`).
|
|
17
|
+
- `spec/integration/`: browser-driven specs; fixtures in `spec/assets/`.
|
|
18
|
+
- `spec/puppeteer/`: unit tests that do not require a browser.
|
|
19
|
+
- `docs/api_coverage.md`: API implementation status.
|
|
20
|
+
|
|
21
|
+
## Build, Test, and Development Commands
|
|
22
|
+
|
|
23
|
+
- Run all tests: `bundle exec rspec`
|
|
24
|
+
- Run a single file: `bundle exec rspec spec/integration/page_spec.rb`
|
|
25
|
+
- Debug (non-headless): `DEBUG=1 bundle exec rspec spec/integration/page_spec.rb`
|
|
26
|
+
- Lint: `bundle exec rubocop` (auto-fix: `bundle exec rubocop -a`)
|
|
27
|
+
|
|
28
|
+
### Useful Environment Variables
|
|
29
|
+
|
|
30
|
+
- `PUPPETEER_EXECUTABLE_PATH_RSPEC`: custom Chrome path
|
|
31
|
+
- `PUPPETEER_CHANNEL_RSPEC`: Chrome channel (for example `chrome-dev`)
|
|
32
|
+
- `PUPPETEER_NO_SANDBOX_RSPEC`: add `--no-sandbox` flag
|
|
33
|
+
|
|
34
|
+
## Coding Style & Naming Conventions
|
|
35
|
+
|
|
36
|
+
- Follow `.rubocop.yml`; prefer explicit keyword arguments for public APIs.
|
|
37
|
+
- Public APIs mirror Puppeteer naming but use Ruby `snake_case`.
|
|
38
|
+
- Custom errors inherit from `Puppeteer::Error`.
|
|
39
|
+
- Use `Puppeteer::AsyncUtils` for async operations; see `CLAUDE/concurrency.md` for patterns.
|
|
40
|
+
|
|
41
|
+
## Type Annotations (rbs-inline)
|
|
42
|
+
|
|
43
|
+
- Add `# rbs_inline: enabled` at the top of the file (after `# frozen_string_literal: true` if present).
|
|
44
|
+
- Use doc-style `# @rbs` annotations for parameters and return types.
|
|
45
|
+
- Always include descriptions with `--` (example: `# @rbs url: String -- Target URL`).
|
|
46
|
+
- Use `A?` for nullable types and `A | B | nil` for unions that include nil.
|
|
47
|
+
- Avoid `@rbs!` blocks (RubyMine doesn't recognize them).
|
|
48
|
+
- Avoid `**options` in public APIs; RubyMine shows it as `untyped`. Prefer explicit keyword args.
|
|
49
|
+
|
|
50
|
+
**Generate signatures:**
|
|
51
|
+
- `bundle exec rake rbs` (writes to `sig/`, which is gitignored)
|
|
52
|
+
- `bundle exec steep check` (after generating RBS)
|
|
53
|
+
|
|
54
|
+
**Manually maintained signatures (tracked):**
|
|
55
|
+
- `sig/_external.rbs` for external dependency stubs.
|
|
56
|
+
- `sig/_supplementary.rbs` for types rbs-inline cannot infer (e.g., `extend self`, singleton helpers).
|
|
57
|
+
|
|
58
|
+
**Example:**
|
|
59
|
+
```ruby
|
|
60
|
+
# frozen_string_literal: true
|
|
61
|
+
# rbs_inline: enabled
|
|
62
|
+
|
|
63
|
+
class Example
|
|
64
|
+
attr_reader :name #: String
|
|
65
|
+
|
|
66
|
+
# @rbs name: String -- The name to set
|
|
67
|
+
# @rbs return: void -- No return value
|
|
68
|
+
def initialize(name)
|
|
69
|
+
@name = name
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Testing Guidelines
|
|
75
|
+
|
|
76
|
+
- Integration tests are `spec/integration/` with `type: :puppeteer` helpers.
|
|
77
|
+
- Use `sinatra: true` for tests that need a local server.
|
|
78
|
+
- Use `match_golden` for screenshot comparisons where applicable.
|
|
79
|
+
|
|
80
|
+
## Agent Notes (Porting/Review)
|
|
81
|
+
|
|
82
|
+
### Source Code Porting
|
|
83
|
+
|
|
84
|
+
- When porting from upstream, use `packages/puppeteer-core/src/cdp/` as the primary source.
|
|
85
|
+
- Mirror upstream behavior, error messages, and option handling as closely as possible.
|
|
86
|
+
- Enable required CDP domains before relying on their events (see `CLAUDE/cdp_protocol.md`).
|
|
87
|
+
|
|
88
|
+
### Test Porting Guidelines
|
|
89
|
+
|
|
90
|
+
When porting tests from upstream `test/src/*.spec.ts` to `spec/integration/*_spec.rb`:
|
|
91
|
+
|
|
92
|
+
**Structure & Order**
|
|
93
|
+
- Keep `it` blocks in the **exact same order** as upstream
|
|
94
|
+
- Use the **same test names** (translated to Ruby style, e.g., `'should type into a textarea'`)
|
|
95
|
+
- Do NOT add extra `context`/`describe` wrappers unless upstream has them
|
|
96
|
+
- Do NOT add Ruby-specific tests in the middle; add them at the end if needed
|
|
97
|
+
|
|
98
|
+
**Ruby-Specific Tests → `*_ext_spec.rb`**
|
|
99
|
+
- When porting, separate Ruby-only features into `*_ext_spec.rb` files (e.g., `keyboard_ext_spec.rb`)
|
|
100
|
+
- Ruby-specific features include: block DSL (`page.keyboard { ... }`), `press('Shift') { press('Key') }` syntax
|
|
101
|
+
- Keep upstream-equivalent tests in the main spec file for easy comparison
|
|
102
|
+
- Example: `keyboard_spec.rb` (upstream port) + `keyboard_ext_spec.rb` (Ruby extensions)
|
|
103
|
+
|
|
104
|
+
**Test State Setup**
|
|
105
|
+
- Use `with_test_state` block instead of `include_context 'with test state'`
|
|
106
|
+
- Access test helpers via block arguments: `page:`, `server:`, `https_server:`, `browser:`, `browser_context:`
|
|
107
|
+
- Example:
|
|
108
|
+
```ruby
|
|
109
|
+
it 'should click button' do
|
|
110
|
+
with_test_state do |page:, server:, **|
|
|
111
|
+
page.goto("#{server.prefix}/input/button.html")
|
|
112
|
+
page.click('button')
|
|
113
|
+
expect(page.evaluate('() => globalThis.result')).to eq('Clicked')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Asset Files**
|
|
119
|
+
- `spec/assets/` files must be **identical** to upstream `test/assets/`
|
|
120
|
+
- Fetch assets directly: `wget https://raw.githubusercontent.com/puppeteer/puppeteer/main/test/assets/xxx`
|
|
121
|
+
- Do NOT hand-edit asset files; if upstream changes, re-fetch
|
|
122
|
+
|
|
123
|
+
**Code Translation**
|
|
124
|
+
- `page.evaluate(() => expr)` → `page.evaluate('() => expr')` (string form)
|
|
125
|
+
- `page.$('selector')` → `page.query_selector('selector')`
|
|
126
|
+
- `page.$$('selector')` → `page.query_selector_all('selector')`
|
|
127
|
+
- `page.$eval()` → `page.eval_on_selector()`
|
|
128
|
+
- `await expect(...)` assertions → RSpec `expect(...).to eq(...)`
|
|
129
|
+
- `toThrow`/`rejects.toThrow` → `raise_error` matcher
|
|
130
|
+
- Platform checks: `os.platform() !== 'darwin'` → `skip(...) unless Puppeteer.env.darwin?`
|
|
131
|
+
|
|
132
|
+
**JavaScript Object Comparisons**
|
|
133
|
+
- When comparing JS objects, use `eval_on_selector` or `evaluate` returning a hash
|
|
134
|
+
- Compare with Ruby hash: `expect(result).to eq({ 'key' => 'value' })`
|
|
135
|
+
- Note: JS object keys become string keys in Ruby hashes
|
|
136
|
+
|
|
137
|
+
**Upstream Test Location**
|
|
138
|
+
- Tests: `https://github.com/puppeteer/puppeteer/tree/main/test/src`
|
|
139
|
+
- Assets: `https://github.com/puppeteer/puppeteer/tree/main/test/assets`
|
|
140
|
+
|
|
141
|
+
See `CLAUDE/porting_puppeteer.md` for detailed examples.
|
|
142
|
+
- Update `docs/api_coverage.md` when new APIs are added.
|
|
143
|
+
- `CHANGELOG.md` is being retired; do not update it for new changes.
|
|
144
|
+
- Porting plan (CDP + async):
|
|
145
|
+
- Aim for Node.js Puppeteer fidelity, but use `socketry/async` (like puppeteer-bidi) instead of JS async/await.
|
|
146
|
+
- Use puppeteer-bidi as the Ruby porting reference: follow its approach and patterns.
|
|
147
|
+
- For `Puppeteer::Page`, mirror upstream `api/Page.ts` and `cdp/Page.ts`, similar to how puppeteer-bidi uses `api/Page.ts` + `bidi/Page.ts`.
|
|
148
|
+
- Follow puppeteer-bidi's `Page` patterns: wrap `:request` handlers to enqueue interception actions (WeakMap to keep `on/off` mapping), serialize interception with `Async::Semaphore`, and keep `Page` mostly as a thin delegator to `Frame`.
|
|
149
|
+
- Mirror upstream timeout/deferred behavior with `Async::Promise` + `AsyncUtils.async_timeout` (timeout 0 = infinite) and keep error messages aligned (e.g., file chooser/network idle).
|
|
150
|
+
- Screenshot parity: apply default option behavior and clip rounding (`normalize/round`), and convert clip coordinates when `captureBeyondViewport` is false (visualViewport offsets).
|
|
151
|
+
- Serialize screenshot operations (bidi uses `browserContext.waitForScreenshotOperations`); keep `ScreenshotTaskQueue`/guards to avoid overlapping captures.
|
|
152
|
+
- `evaluate_on_new_document`: build IIFE expressions and serialize args the same way as puppeteer-bidi's `build_evaluation_expression`/`serialize_arg_for_preload`.
|
|
153
|
+
- `wait_for_network_idle`: track inflight requests and reset idle timers on changes (mirrors Page.ts inflight counter logic).
|
|
154
|
+
- `wait_for_file_chooser`: resolve all pending waiters and keep timeout error messaging aligned (`Waiting for \`FileChooser\` failed: <ms> exceeded`).
|
|
155
|
+
- RxJS Observable flows are replaced with manual EventEmitter listeners + `Async::Promise` (no `fromEmitterEvent`/`merge`/`timeout`), so compose waits explicitly (e.g., `wait_for_navigation`, `set_content`).
|
|
156
|
+
- Use `AsyncUtils` (Barrier-based Promise.all/race + `async_timeout`) instead of RxJS `combineLatest`/`firstValueFrom`/`timeout`.
|
|
157
|
+
- `WaitTask`/`TaskManager` mirror Puppeteer's polling waits; no AbortSignal, use Async tasks + cancellation.
|
|
158
|
+
- `ReactorRunner` runs a dedicated Async reactor thread and proxies sync calls into it (wrap/unwrap).
|
|
159
|
+
- Use `Core::EventEmitter` + symbols instead of RxJS event streams; clean up listeners explicitly.
|
|
160
|
+
- Disposable patterns: `Core::Disposable::DisposableStack`/`DisposableMixin` stand in for JS DisposableStack/AsyncDisposableStack.
|
|
161
|
+
|
|
162
|
+
## Security & Configuration Tips
|
|
163
|
+
|
|
164
|
+
- Do not commit credentials or secrets.
|
|
165
|
+
- Be cautious with network-fetched/generated files; review diffs carefully.
|
|
166
|
+
|
|
167
|
+
## Release & CI
|
|
168
|
+
|
|
169
|
+
- Release/tagging workflow follows the current project practice (no new rules).
|
data/CLAUDE/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Detailed Implementation Documentation
|
|
2
|
+
|
|
3
|
+
This directory contains in-depth documentation for AI agents and developers working on puppeteer-ruby.
|
|
4
|
+
|
|
5
|
+
## Documentation Index
|
|
6
|
+
|
|
7
|
+
| Document | Description |
|
|
8
|
+
|----------|-------------|
|
|
9
|
+
| [architecture.md](./architecture.md) | CDP-based architecture and component relationships |
|
|
10
|
+
| [testing.md](./testing.md) | Testing strategies, patterns, and debugging |
|
|
11
|
+
| [cdp_protocol.md](./cdp_protocol.md) | Chrome DevTools Protocol usage |
|
|
12
|
+
| [concurrency.md](./concurrency.md) | Concurrency model and migration plans |
|
|
13
|
+
| [porting_puppeteer.md](./porting_puppeteer.md) | Guide for porting from TypeScript Puppeteer |
|
|
14
|
+
| [rbs_type_checking.md](./rbs_type_checking.md) | RBS type annotations and Steep type checking |
|
|
15
|
+
|
|
16
|
+
## Quick Navigation
|
|
17
|
+
|
|
18
|
+
### For New Feature Implementation
|
|
19
|
+
|
|
20
|
+
1. Read [porting_puppeteer.md](./porting_puppeteer.md) for workflow
|
|
21
|
+
2. Understand the feature's CDP calls in [cdp_protocol.md](./cdp_protocol.md)
|
|
22
|
+
3. Review [architecture.md](./architecture.md) for component relationships
|
|
23
|
+
|
|
24
|
+
### For Bug Fixes
|
|
25
|
+
|
|
26
|
+
1. Review [architecture.md](./architecture.md) to understand component relationships
|
|
27
|
+
2. Write tests following patterns in [testing.md](./testing.md)
|
|
28
|
+
3. Consider concurrency issues per [concurrency.md](./concurrency.md)
|
|
29
|
+
|
|
30
|
+
### For Code Review
|
|
31
|
+
|
|
32
|
+
1. Ensure alignment with [architecture.md](./architecture.md)
|
|
33
|
+
2. Verify test coverage per [testing.md](./testing.md)
|
|
34
|
+
3. Review concurrency patterns per [concurrency.md](./concurrency.md)
|
|
35
|
+
|
|
36
|
+
## Related Resources
|
|
37
|
+
|
|
38
|
+
- [CLAUDE.md](../CLAUDE.md) - High-level project summary
|
|
39
|
+
- [docs/api_coverage.md](../docs/api_coverage.md) - API implementation status
|
|
40
|
+
- [Puppeteer TypeScript source](https://github.com/puppeteer/puppeteer) - Reference implementation
|
|
41
|
+
- [Chrome DevTools Protocol docs](https://chromedevtools.github.io/devtools-protocol/) - CDP reference
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
This document describes the architecture of puppeteer-ruby.
|
|
4
|
+
|
|
5
|
+
## High-Level Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ User-Facing API │
|
|
10
|
+
│ Puppeteer.launch / Puppeteer.connect │
|
|
11
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
12
|
+
│
|
|
13
|
+
▼
|
|
14
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ Browser │
|
|
16
|
+
│ - Manages browser process lifecycle │
|
|
17
|
+
│ - Creates browser contexts (incognito sessions) │
|
|
18
|
+
│ - Tracks all targets (pages, workers, etc.) │
|
|
19
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ BrowserContext │
|
|
24
|
+
│ - Represents an incognito session or default context │
|
|
25
|
+
│ - Manages permissions, cookies, geolocation │
|
|
26
|
+
│ - Creates new pages within the context │
|
|
27
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ Page │
|
|
32
|
+
│ - Main user interaction point │
|
|
33
|
+
│ - Navigation, content, screenshots, PDF │
|
|
34
|
+
│ - Delegates to Frame, Keyboard, Mouse, etc. │
|
|
35
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
36
|
+
│
|
|
37
|
+
┌─────────────────┼─────────────────┐
|
|
38
|
+
▼ ▼ ▼
|
|
39
|
+
┌──────────┐ ┌──────────┐ ┌──────────────┐
|
|
40
|
+
│ Frame │ │ Keyboard │ │ Mouse │
|
|
41
|
+
│ │ │ │ │ │
|
|
42
|
+
└──────────┘ └──────────┘ └──────────────┘
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
┌──────────────────────────────────────────┐
|
|
46
|
+
│ IsolatedWorld │
|
|
47
|
+
│ - Execution context for JavaScript │
|
|
48
|
+
│ - Query selectors, evaluate JS │
|
|
49
|
+
└──────────────────────────────────────────┘
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
┌──────────────────────────────────────────┐
|
|
53
|
+
│ ElementHandle / JSHandle │
|
|
54
|
+
│ - References to DOM elements / JS objs │
|
|
55
|
+
│ - Click, type, evaluate, etc. │
|
|
56
|
+
└──────────────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Core Components
|
|
60
|
+
|
|
61
|
+
### Connection Layer
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
65
|
+
│ Connection │
|
|
66
|
+
│ lib/puppeteer/connection.rb │
|
|
67
|
+
│ │
|
|
68
|
+
│ - WebSocket connection to browser's DevTools endpoint │
|
|
69
|
+
│ - Manages message routing to correct CDPSession │
|
|
70
|
+
│ - Handles connection lifecycle │
|
|
71
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
72
|
+
│
|
|
73
|
+
▼
|
|
74
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
75
|
+
│ CDPSession │
|
|
76
|
+
│ lib/puppeteer/cdp_session.rb │
|
|
77
|
+
│ │
|
|
78
|
+
│ - Represents a CDP session to a specific target │
|
|
79
|
+
│ - send_message: Send CDP commands │
|
|
80
|
+
│ - on/once: Subscribe to CDP events │
|
|
81
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Target Management
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
88
|
+
│ ChromeTargetManager │
|
|
89
|
+
│ lib/puppeteer/chrome_target_manager.rb │
|
|
90
|
+
│ │
|
|
91
|
+
│ - Discovers and tracks all targets in browser │
|
|
92
|
+
│ - Creates Target objects for new pages, workers, etc. │
|
|
93
|
+
│ - Handles target attachment and auto-attach │
|
|
94
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Page Architecture
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Page
|
|
102
|
+
├── FrameManager # Manages all frames in the page
|
|
103
|
+
│ ├── Frame (main) # Main frame
|
|
104
|
+
│ └── Frame (child)... # Iframes
|
|
105
|
+
│ └── IsolatedWorld
|
|
106
|
+
│ ├── Main world (page's JS context)
|
|
107
|
+
│ └── Puppeteer utility world (isolated context)
|
|
108
|
+
│
|
|
109
|
+
├── NetworkManager # Request interception, network events
|
|
110
|
+
│ └── NetworkEventManager
|
|
111
|
+
│
|
|
112
|
+
├── EmulationManager # Viewport, device emulation
|
|
113
|
+
│
|
|
114
|
+
├── Keyboard # Keyboard input
|
|
115
|
+
├── Mouse # Mouse input
|
|
116
|
+
└── TouchScreen # Touch input
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Key Patterns
|
|
120
|
+
|
|
121
|
+
### Event Handling
|
|
122
|
+
|
|
123
|
+
Components use `EventCallbackable` mixin for event handling:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
class Page
|
|
127
|
+
include Puppeteer::EventCallbackable
|
|
128
|
+
|
|
129
|
+
def initialize
|
|
130
|
+
@frame_manager.on('load') do
|
|
131
|
+
emit_event('load')
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Usage
|
|
137
|
+
page.on('load') { puts 'Page loaded!' }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### CDP Command/Response Pattern
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# Send CDP command and wait for response
|
|
144
|
+
result = @client.send_message('Page.navigate', url: url)
|
|
145
|
+
|
|
146
|
+
# Subscribe to CDP events
|
|
147
|
+
@client.on('Network.requestWillBeSent') do |event|
|
|
148
|
+
handle_request(event)
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Resource Cleanup
|
|
153
|
+
|
|
154
|
+
Use `begin/ensure` blocks for cleanup:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
def screenshot(options = {})
|
|
158
|
+
original_viewport = @viewport
|
|
159
|
+
begin
|
|
160
|
+
self.viewport = options[:viewport] if options[:viewport]
|
|
161
|
+
# Take screenshot...
|
|
162
|
+
ensure
|
|
163
|
+
self.viewport = original_viewport if options[:viewport]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## File Organization
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
lib/puppeteer/
|
|
172
|
+
├── puppeteer.rb # Module with launch/connect methods
|
|
173
|
+
├── puppeteer/
|
|
174
|
+
│ ├── browser.rb # Browser class
|
|
175
|
+
│ ├── browser_context.rb # BrowserContext class
|
|
176
|
+
│ ├── page.rb # Page class (largest file)
|
|
177
|
+
│ ├── frame.rb # Frame class
|
|
178
|
+
│ ├── frame_manager.rb # FrameManager class
|
|
179
|
+
│ ├── element_handle.rb # ElementHandle class
|
|
180
|
+
│ ├── js_handle.rb # JSHandle class
|
|
181
|
+
│ ├── connection.rb # WebSocket connection
|
|
182
|
+
│ ├── cdp_session.rb # CDP session
|
|
183
|
+
│ ├── keyboard.rb # Keyboard input
|
|
184
|
+
│ ├── mouse.rb # Mouse input
|
|
185
|
+
│ ├── network_manager.rb # Network interception
|
|
186
|
+
│ ├── launcher/
|
|
187
|
+
│ │ ├── chrome.rb # Chrome-specific launch
|
|
188
|
+
│ └── ...
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Data Flow Examples
|
|
192
|
+
|
|
193
|
+
### Page Navigation
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
User: page.goto('https://example.com')
|
|
197
|
+
│
|
|
198
|
+
▼
|
|
199
|
+
Page#goto
|
|
200
|
+
│
|
|
201
|
+
├── FrameManager#navigate_frame
|
|
202
|
+
│ │
|
|
203
|
+
│ ├── CDPSession.send('Page.navigate', url: ...)
|
|
204
|
+
│ │
|
|
205
|
+
│ └── LifecycleWatcher.new(wait_until: 'load')
|
|
206
|
+
│ │
|
|
207
|
+
│ └── Waits for 'Page.loadEventFired' event
|
|
208
|
+
│
|
|
209
|
+
└── Returns HTTPResponse
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Element Click
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
User: element.click
|
|
216
|
+
│
|
|
217
|
+
▼
|
|
218
|
+
ElementHandle#click
|
|
219
|
+
│
|
|
220
|
+
├── scroll_into_view_if_needed
|
|
221
|
+
│ └── CDPSession.send('DOM.scrollIntoViewIfNeeded')
|
|
222
|
+
│
|
|
223
|
+
├── clickable_point
|
|
224
|
+
│ └── CDPSession.send('DOM.getContentQuads')
|
|
225
|
+
│
|
|
226
|
+
└── Page#mouse.click(x, y)
|
|
227
|
+
│
|
|
228
|
+
├── Mouse#move(x, y)
|
|
229
|
+
│ └── CDPSession.send('Input.dispatchMouseEvent', type: 'mouseMoved')
|
|
230
|
+
│
|
|
231
|
+
├── Mouse#down
|
|
232
|
+
│ └── CDPSession.send('Input.dispatchMouseEvent', type: 'mousePressed')
|
|
233
|
+
│
|
|
234
|
+
└── Mouse#up
|
|
235
|
+
└── CDPSession.send('Input.dispatchMouseEvent', type: 'mouseReleased')
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### JavaScript Evaluation
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
User: page.evaluate('() => document.title')
|
|
242
|
+
│
|
|
243
|
+
▼
|
|
244
|
+
Page#evaluate
|
|
245
|
+
│
|
|
246
|
+
└── Frame#evaluate
|
|
247
|
+
│
|
|
248
|
+
└── IsolatedWorld#evaluate
|
|
249
|
+
│
|
|
250
|
+
├── CDPSession.send('Runtime.callFunctionOn', ...)
|
|
251
|
+
│
|
|
252
|
+
└── RemoteObject.new(result).value
|
|
253
|
+
```
|