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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/AGENTS.md +169 -0
  4. data/CLAUDE/README.md +41 -0
  5. data/CLAUDE/architecture.md +253 -0
  6. data/CLAUDE/cdp_protocol.md +230 -0
  7. data/CLAUDE/concurrency.md +216 -0
  8. data/CLAUDE/porting_puppeteer.md +575 -0
  9. data/CLAUDE/rbs_type_checking.md +101 -0
  10. data/CLAUDE/spec_migration_plans.md +1041 -0
  11. data/CLAUDE/testing.md +278 -0
  12. data/CLAUDE.md +242 -0
  13. data/README.md +8 -0
  14. data/Rakefile +7 -0
  15. data/Steepfile +28 -0
  16. data/docs/api_coverage.md +105 -56
  17. data/lib/puppeteer/aria_query_handler.rb +3 -2
  18. data/lib/puppeteer/async_utils.rb +214 -0
  19. data/lib/puppeteer/browser.rb +98 -56
  20. data/lib/puppeteer/browser_connector.rb +18 -3
  21. data/lib/puppeteer/browser_context.rb +196 -3
  22. data/lib/puppeteer/browser_runner.rb +18 -10
  23. data/lib/puppeteer/cdp_session.rb +67 -23
  24. data/lib/puppeteer/chrome_target_manager.rb +65 -40
  25. data/lib/puppeteer/connection.rb +55 -36
  26. data/lib/puppeteer/console_message.rb +9 -1
  27. data/lib/puppeteer/console_patch.rb +47 -0
  28. data/lib/puppeteer/css_coverage.rb +5 -3
  29. data/lib/puppeteer/custom_query_handler.rb +80 -33
  30. data/lib/puppeteer/define_async_method.rb +31 -37
  31. data/lib/puppeteer/dialog.rb +47 -14
  32. data/lib/puppeteer/element_handle.rb +231 -62
  33. data/lib/puppeteer/emulation_manager.rb +1 -1
  34. data/lib/puppeteer/env.rb +1 -1
  35. data/lib/puppeteer/errors.rb +25 -2
  36. data/lib/puppeteer/event_callbackable.rb +15 -0
  37. data/lib/puppeteer/events.rb +4 -0
  38. data/lib/puppeteer/execution_context.rb +148 -3
  39. data/lib/puppeteer/file_chooser.rb +6 -0
  40. data/lib/puppeteer/frame.rb +162 -91
  41. data/lib/puppeteer/frame_manager.rb +69 -48
  42. data/lib/puppeteer/http_request.rb +114 -38
  43. data/lib/puppeteer/http_response.rb +24 -7
  44. data/lib/puppeteer/isolated_world.rb +64 -41
  45. data/lib/puppeteer/js_coverage.rb +5 -3
  46. data/lib/puppeteer/js_handle.rb +58 -16
  47. data/lib/puppeteer/keyboard.rb +30 -17
  48. data/lib/puppeteer/launcher/browser_options.rb +3 -1
  49. data/lib/puppeteer/launcher/chrome.rb +8 -5
  50. data/lib/puppeteer/launcher/launch_options.rb +7 -2
  51. data/lib/puppeteer/launcher.rb +4 -8
  52. data/lib/puppeteer/lifecycle_watcher.rb +38 -22
  53. data/lib/puppeteer/mouse.rb +273 -64
  54. data/lib/puppeteer/network_event_manager.rb +7 -0
  55. data/lib/puppeteer/network_manager.rb +393 -112
  56. data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
  57. data/lib/puppeteer/page.rb +568 -226
  58. data/lib/puppeteer/puppeteer.rb +171 -64
  59. data/lib/puppeteer/query_handler_manager.rb +112 -16
  60. data/lib/puppeteer/reactor_runner.rb +247 -0
  61. data/lib/puppeteer/remote_object.rb +127 -47
  62. data/lib/puppeteer/target.rb +74 -27
  63. data/lib/puppeteer/task_manager.rb +3 -1
  64. data/lib/puppeteer/timeout_helper.rb +6 -10
  65. data/lib/puppeteer/touch_handle.rb +39 -0
  66. data/lib/puppeteer/touch_screen.rb +72 -22
  67. data/lib/puppeteer/tracing.rb +3 -3
  68. data/lib/puppeteer/version.rb +1 -1
  69. data/lib/puppeteer/wait_task.rb +264 -101
  70. data/lib/puppeteer/web_socket.rb +2 -2
  71. data/lib/puppeteer/web_socket_transport.rb +91 -27
  72. data/lib/puppeteer/web_worker.rb +175 -0
  73. data/lib/puppeteer.rb +20 -4
  74. data/puppeteer-ruby.gemspec +15 -11
  75. data/sig/_external.rbs +8 -0
  76. data/sig/_supplementary.rbs +314 -0
  77. data/sig/puppeteer/browser.rbs +166 -0
  78. data/sig/puppeteer/cdp_session.rbs +64 -0
  79. data/sig/puppeteer/dialog.rbs +41 -0
  80. data/sig/puppeteer/element_handle.rbs +305 -0
  81. data/sig/puppeteer/execution_context.rbs +87 -0
  82. data/sig/puppeteer/frame.rbs +226 -0
  83. data/sig/puppeteer/http_request.rbs +214 -0
  84. data/sig/puppeteer/http_response.rbs +89 -0
  85. data/sig/puppeteer/js_handle.rbs +64 -0
  86. data/sig/puppeteer/keyboard.rbs +40 -0
  87. data/sig/puppeteer/mouse.rbs +113 -0
  88. data/sig/puppeteer/page.rbs +515 -0
  89. data/sig/puppeteer/puppeteer.rbs +98 -0
  90. data/sig/puppeteer/remote_object.rbs +78 -0
  91. data/sig/puppeteer/touch_handle.rbs +21 -0
  92. data/sig/puppeteer/touch_screen.rbs +35 -0
  93. data/sig/puppeteer/web_worker.rbs +83 -0
  94. metadata +116 -45
  95. data/CHANGELOG.md +0 -397
  96. data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
  97. data/lib/puppeteer/firefox_target_manager.rb +0 -157
  98. data/lib/puppeteer/launcher/firefox.rb +0 -453
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba80a8570a4df72a141dff37a81899373827c461a5327311cc28cae8fc5be16f
4
- data.tar.gz: 7811d540f215426009b28906723ee03c93eb0504ccbfacd321d1c219be5105a1
3
+ metadata.gz: 2a20cad1d3b0ad0947041ff8086f0336e381d783f1f6edb1aee45263054f5b40
4
+ data.tar.gz: 70c27f24141dc2326762a419cd32c780fcb9c1b5474246e2dd339dd5246b2090
5
5
  SHA512:
6
- metadata.gz: f047d5dea4f9e7dc34c6578fa22887ef147be87f123f47c16d5b684ae9ac34cf6d2f3797f6d2513b039cf872bf02ff49d3e0505af7cfae98f5fa11df133f6315
7
- data.tar.gz: 580d0082d1e2bb05615d5eb472b5df5dff060c291bffd11a2ce08c16ca6592451f071f2b5192ab156141aa65c21d03071eb0af17162395b548dca7a056ab1fff
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
+ ```