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
data/CLAUDE/testing.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Testing Strategy
|
|
2
|
+
|
|
3
|
+
This document describes the testing approach for puppeteer-ruby.
|
|
4
|
+
|
|
5
|
+
## Test Organization
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
spec/
|
|
9
|
+
├── integration/ # Browser automation tests (type: :puppeteer)
|
|
10
|
+
│ ├── page_spec.rb
|
|
11
|
+
│ ├── element_handle_spec.rb
|
|
12
|
+
│ ├── click_spec.rb
|
|
13
|
+
│ └── ...
|
|
14
|
+
├── puppeteer/ # Unit tests (no browser required)
|
|
15
|
+
│ ├── devices_spec.rb
|
|
16
|
+
│ ├── launcher_spec.rb
|
|
17
|
+
│ └── ...
|
|
18
|
+
├── assets/ # Test HTML/JS/CSS files
|
|
19
|
+
├── golden_matcher.rb # Screenshot comparison matcher
|
|
20
|
+
├── spec_helper.rb # RSpec configuration
|
|
21
|
+
└── utils.rb # Test utilities
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Integration Tests
|
|
25
|
+
|
|
26
|
+
### Basic Structure
|
|
27
|
+
|
|
28
|
+
Integration tests use `type: :puppeteer` metadata which is automatically applied to files in `spec/integration/`:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
RSpec.describe 'Page#goto' do
|
|
32
|
+
# This file is in spec/integration/, so type: :puppeteer is applied
|
|
33
|
+
|
|
34
|
+
it 'navigates to a URL' do
|
|
35
|
+
page.goto('https://example.com')
|
|
36
|
+
expect(page.title).to eq('Example Domain')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Available Helpers
|
|
42
|
+
|
|
43
|
+
The `type: :puppeteer` metadata provides these helpers:
|
|
44
|
+
|
|
45
|
+
| Helper | Description |
|
|
46
|
+
|--------|-------------|
|
|
47
|
+
| `page` | Current `Puppeteer::Page` instance |
|
|
48
|
+
| `browser` | Browser instance (requires `puppeteer: :browser` metadata) |
|
|
49
|
+
| `browser_context` | BrowserContext (requires `browser_context: :incognito`) |
|
|
50
|
+
| `headless?` | Whether running in headless mode |
|
|
51
|
+
| `default_launch_options` | Launch options used for current test |
|
|
52
|
+
|
|
53
|
+
### Test with Sinatra Server
|
|
54
|
+
|
|
55
|
+
For tests requiring a web server, use `sinatra: true` metadata:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
RSpec.describe 'Page#goto', sinatra: true do
|
|
59
|
+
it 'navigates to local server' do
|
|
60
|
+
sinatra.get('/hello') { 'Hello World' }
|
|
61
|
+
|
|
62
|
+
page.goto("#{server_prefix}/hello")
|
|
63
|
+
expect(page.content).to include('Hello World')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Sinatra helpers:
|
|
69
|
+
|
|
70
|
+
| Helper | Description |
|
|
71
|
+
|--------|-------------|
|
|
72
|
+
| `sinatra` | Sinatra app instance |
|
|
73
|
+
| `server_prefix` | `http://localhost:4567` |
|
|
74
|
+
| `server_cross_process_prefix` | `http://127.0.0.1:4567` |
|
|
75
|
+
| `server_empty_page` | `http://localhost:4567/empty.html` |
|
|
76
|
+
|
|
77
|
+
### Test with Browser Instance
|
|
78
|
+
|
|
79
|
+
For tests that need direct browser access:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
RSpec.describe 'Browser', puppeteer: :browser do
|
|
83
|
+
it 'creates new pages' do
|
|
84
|
+
page1 = browser.new_page
|
|
85
|
+
page2 = browser.new_page
|
|
86
|
+
expect(browser.pages.length).to eq(2)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Incognito Context Tests
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
RSpec.describe 'BrowserContext', browser_context: :incognito do
|
|
95
|
+
it 'has isolated cookies' do
|
|
96
|
+
browser_context.set_cookie(name: 'test', value: 'value')
|
|
97
|
+
# Cookies are isolated to this context
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### OOPIF (Out-of-Process IFrame) Tests
|
|
103
|
+
|
|
104
|
+
For tests requiring cross-process iframes, use `enable_site_per_process_flag: true`:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
it 'clicks button in cross-process iframe', enable_site_per_process_flag: true do
|
|
108
|
+
with_test_state do |page:, server:, **|
|
|
109
|
+
page.goto(server.empty_page)
|
|
110
|
+
# Use cross_process_prefix (127.0.0.1) instead of prefix (localhost)
|
|
111
|
+
attach_frame(page, 'frame-id', "#{server.cross_process_prefix}/input/button.html")
|
|
112
|
+
|
|
113
|
+
frame = page.frames[1]
|
|
114
|
+
frame.click('button')
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This metadata launches Chrome with `--site-per-process` and `--host-rules=MAP * 127.0.0.1` flags.
|
|
120
|
+
|
|
121
|
+
## Running Tests
|
|
122
|
+
|
|
123
|
+
### Basic Commands
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Run all tests
|
|
127
|
+
bundle exec rspec
|
|
128
|
+
|
|
129
|
+
# Run specific file
|
|
130
|
+
bundle exec rspec spec/integration/page_spec.rb
|
|
131
|
+
|
|
132
|
+
# Run specific test by line number
|
|
133
|
+
bundle exec rspec spec/integration/page_spec.rb:42
|
|
134
|
+
|
|
135
|
+
# Run with documentation format
|
|
136
|
+
bundle exec rspec --format documentation
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Chrome Configuration
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Run with custom Chrome path
|
|
143
|
+
PUPPETEER_EXECUTABLE_PATH_RSPEC=/path/to/chrome bundle exec rspec
|
|
144
|
+
|
|
145
|
+
# Run with Chrome channel
|
|
146
|
+
PUPPETEER_CHANNEL_RSPEC=chrome-beta bundle exec rspec
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Debug Mode
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Non-headless mode (see the browser)
|
|
153
|
+
DEBUG=1 bundle exec rspec spec/integration/page_spec.rb
|
|
154
|
+
|
|
155
|
+
# With debug output
|
|
156
|
+
DEBUG=1 bundle exec rspec spec/integration/page_spec.rb 2>&1 | tee test.log
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Container/CI Mode
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Add --no-sandbox flag
|
|
163
|
+
PUPPETEER_NO_SANDBOX_RSPEC=true bundle exec rspec
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Screenshot Testing
|
|
167
|
+
|
|
168
|
+
### Golden Matcher
|
|
169
|
+
|
|
170
|
+
Use `match_golden` matcher for screenshot comparisons:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
RSpec.describe 'Page#screenshot' do
|
|
174
|
+
it 'captures page screenshot' do
|
|
175
|
+
page.goto(server_empty_page)
|
|
176
|
+
screenshot = page.screenshot
|
|
177
|
+
|
|
178
|
+
expect(screenshot).to match_golden('empty-page.png')
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Golden images are stored in `spec/golden/`.
|
|
184
|
+
|
|
185
|
+
### Updating Golden Images
|
|
186
|
+
|
|
187
|
+
When intentionally changing visual output:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Remove old golden and re-run test to generate new one
|
|
191
|
+
rm spec/golden/empty-page.png
|
|
192
|
+
bundle exec rspec spec/integration/screenshot_spec.rb
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Writing New Tests
|
|
196
|
+
|
|
197
|
+
### Guidelines
|
|
198
|
+
|
|
199
|
+
1. **One assertion per test when possible** - Easier to identify failures
|
|
200
|
+
2. **Use descriptive test names** - Should read like documentation
|
|
201
|
+
3. **Clean up resources** - Close pages, restore state in `after` blocks
|
|
202
|
+
4. **Minimize flakiness** - Use explicit waits, not sleeps
|
|
203
|
+
|
|
204
|
+
### Example Test Pattern
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
RSpec.describe 'ElementHandle#click', sinatra: true do
|
|
208
|
+
before do
|
|
209
|
+
sinatra.get('/button') do
|
|
210
|
+
<<~HTML
|
|
211
|
+
<button onclick="window.clicked = true">Click me</button>
|
|
212
|
+
HTML
|
|
213
|
+
end
|
|
214
|
+
page.goto("#{server_prefix}/button")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'clicks the button' do
|
|
218
|
+
button = page.query_selector('button')
|
|
219
|
+
button.click
|
|
220
|
+
|
|
221
|
+
result = page.evaluate('() => window.clicked')
|
|
222
|
+
expect(result).to eq(true)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'triggers click event' do
|
|
226
|
+
events = []
|
|
227
|
+
page.evaluate(<<~JS)
|
|
228
|
+
document.querySelector('button').addEventListener('click', () => {
|
|
229
|
+
window.clickEvent = true;
|
|
230
|
+
});
|
|
231
|
+
JS
|
|
232
|
+
|
|
233
|
+
page.click('button')
|
|
234
|
+
|
|
235
|
+
expect(page.evaluate('() => window.clickEvent')).to eq(true)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## CI Configuration
|
|
241
|
+
|
|
242
|
+
Tests run on GitHub Actions with matrix of:
|
|
243
|
+
|
|
244
|
+
- Ruby versions: 3.2, 3.3, 3.4
|
|
245
|
+
- Environments: Ubuntu with Chrome, Alpine with Chromium
|
|
246
|
+
|
|
247
|
+
See `.github/workflows/ci.yml` for details.
|
|
248
|
+
|
|
249
|
+
## Debugging Tips
|
|
250
|
+
|
|
251
|
+
### See What's Happening
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Run with visible browser
|
|
255
|
+
DEBUG=1 bundle exec rspec spec/integration/page_spec.rb
|
|
256
|
+
|
|
257
|
+
# Add screenshots for debugging
|
|
258
|
+
page.screenshot(path: 'debug.png')
|
|
259
|
+
|
|
260
|
+
# Log page content
|
|
261
|
+
puts page.content
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Slow Down Execution
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
# In spec_helper.rb or individual test
|
|
268
|
+
Puppeteer.launch(slow_mo: 100) do |browser|
|
|
269
|
+
# Actions are slowed by 100ms each
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Inspect CDP Messages
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Enable CDP debug logging
|
|
277
|
+
DEBUG=1 bundle exec rspec 2>&1 | grep -E "(SEND|RECV)"
|
|
278
|
+
```
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Puppeteer-Ruby Development Guide
|
|
2
|
+
|
|
3
|
+
> **IMPORTANT: Spec Migration Tracking**
|
|
4
|
+
>
|
|
5
|
+
> Test porting from Node.js Puppeteer to Ruby RSpec is tracked in **[CLAUDE/spec_migration_plans.md](./CLAUDE/spec_migration_plans.md)**.
|
|
6
|
+
>
|
|
7
|
+
> - Before porting tests: Check the migration plan for priorities and status
|
|
8
|
+
> - After porting tests: Update the migration plan to reflect progress
|
|
9
|
+
> - This file must be kept up-to-date with all spec migration work
|
|
10
|
+
|
|
11
|
+
This document provides essential guidance for AI agents working on the puppeteer-ruby codebase.
|
|
12
|
+
|
|
13
|
+
## Project Overview
|
|
14
|
+
|
|
15
|
+
puppeteer-ruby is a Ruby port of [Puppeteer](https://pptr.dev/), the Node.js browser automation library. It uses the Chrome DevTools Protocol (CDP) to automate Chrome/Chromium browsers.
|
|
16
|
+
|
|
17
|
+
### Core Principles
|
|
18
|
+
|
|
19
|
+
1. **CDP Protocol Focus**: All browser automation is done via CDP
|
|
20
|
+
2. **Chrome Specialization**: Focused on Chrome/Chromium automation
|
|
21
|
+
3. **API Compatibility**: Follow Puppeteer's API design closely, but use Ruby idioms
|
|
22
|
+
|
|
23
|
+
## Quick Reference
|
|
24
|
+
|
|
25
|
+
### Running Tests
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Run all tests
|
|
29
|
+
bundle exec rspec
|
|
30
|
+
|
|
31
|
+
# Run specific test file
|
|
32
|
+
bundle exec rspec spec/integration/page_spec.rb
|
|
33
|
+
|
|
34
|
+
# Run in debug mode (non-headless)
|
|
35
|
+
DEBUG=1 bundle exec rspec spec/integration/page_spec.rb
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **Note for Codex CLI**: When executing RSpec from Codex CLI, always use `rbenv exec`:
|
|
39
|
+
> ```bash
|
|
40
|
+
> rbenv exec bundle exec rspec spec/integration/click_spec.rb
|
|
41
|
+
> ```
|
|
42
|
+
|
|
43
|
+
### Key Environment Variables
|
|
44
|
+
|
|
45
|
+
| Variable | Description |
|
|
46
|
+
|----------|-------------|
|
|
47
|
+
| `PUPPETEER_EXECUTABLE_PATH_RSPEC` | Custom browser executable path |
|
|
48
|
+
| `PUPPETEER_CHANNEL_RSPEC` | Chrome channel (e.g., `chrome`, `chrome-beta`) |
|
|
49
|
+
| `DEBUG` | Set to `1` for debug output |
|
|
50
|
+
| `PUPPETEER_NO_SANDBOX_RSPEC` | Add `--no-sandbox` flag (for containers) |
|
|
51
|
+
|
|
52
|
+
### Code Quality
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run RuboCop
|
|
56
|
+
bundle exec rubocop
|
|
57
|
+
|
|
58
|
+
# Auto-fix RuboCop issues
|
|
59
|
+
bundle exec rubocop -a
|
|
60
|
+
|
|
61
|
+
# Run Steep type check
|
|
62
|
+
bundle exec steep check
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Architecture
|
|
66
|
+
|
|
67
|
+
The codebase follows a straightforward architecture:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
lib/puppeteer/
|
|
71
|
+
├── puppeteer.rb # Main entry point (Puppeteer.launch, Puppeteer.connect)
|
|
72
|
+
├── browser.rb # Browser instance management
|
|
73
|
+
├── browser_context.rb # Incognito/default context
|
|
74
|
+
├── page.rb # Page API (main user-facing class)
|
|
75
|
+
├── frame.rb # Frame handling
|
|
76
|
+
├── element_handle.rb # DOM element operations
|
|
77
|
+
├── js_handle.rb # JavaScript object handles
|
|
78
|
+
├── connection.rb # WebSocket connection to browser
|
|
79
|
+
├── cdp_session.rb # CDP session management
|
|
80
|
+
├── keyboard.rb # Keyboard input simulation
|
|
81
|
+
├── mouse.rb # Mouse input simulation
|
|
82
|
+
└── ...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Key Components
|
|
86
|
+
|
|
87
|
+
- **Connection**: Manages WebSocket connection to the browser's DevTools
|
|
88
|
+
- **CDPSession**: Sends CDP commands and receives events
|
|
89
|
+
- **FrameManager**: Tracks frames and their execution contexts
|
|
90
|
+
- **NetworkManager**: Handles request interception and network events
|
|
91
|
+
- **LifecycleWatcher**: Waits for navigation events (load, DOMContentLoaded, etc.)
|
|
92
|
+
|
|
93
|
+
## Code Standards
|
|
94
|
+
|
|
95
|
+
### Ruby Version & Style
|
|
96
|
+
|
|
97
|
+
- Minimum Ruby version: 3.2
|
|
98
|
+
- Follow RuboCop rules defined in `.rubocop.yml`
|
|
99
|
+
- Use explicit keyword arguments for public APIs
|
|
100
|
+
|
|
101
|
+
### API Naming Conventions
|
|
102
|
+
|
|
103
|
+
JavaScript Puppeteer methods use camelCase, Ruby methods use snake_case:
|
|
104
|
+
|
|
105
|
+
| Puppeteer (JS) | puppeteer-ruby |
|
|
106
|
+
|----------------|----------------|
|
|
107
|
+
| `page.waitForSelector()` | `page.wait_for_selector` |
|
|
108
|
+
| `page.setContent()` | `page.content=` or `page.set_content` |
|
|
109
|
+
| `element.boundingBox()` | `element.bounding_box` |
|
|
110
|
+
| `browser.newPage()` | `browser.new_page` |
|
|
111
|
+
|
|
112
|
+
### Error Classes
|
|
113
|
+
|
|
114
|
+
All custom errors inherit from `Puppeteer::Error`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
module Puppeteer
|
|
118
|
+
class Error < StandardError; end
|
|
119
|
+
class TimeoutError < Error; end
|
|
120
|
+
class FrameNotFoundError < Error; end
|
|
121
|
+
# etc.
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Testing Strategy
|
|
126
|
+
|
|
127
|
+
### Test Types
|
|
128
|
+
|
|
129
|
+
- **Unit tests**: `spec/puppeteer/` - Test individual classes without browser
|
|
130
|
+
- **Integration tests**: `spec/integration/` - Test with real browser
|
|
131
|
+
|
|
132
|
+
### Integration Test Setup
|
|
133
|
+
|
|
134
|
+
Integration tests use RSpec metadata:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
RSpec.describe 'Page', type: :puppeteer do
|
|
138
|
+
it 'navigates to a page' do
|
|
139
|
+
page.goto('https://example.com')
|
|
140
|
+
expect(page.title).to eq('Example Domain')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The `type: :puppeteer` metadata automatically:
|
|
146
|
+
- Launches Chrome before each test
|
|
147
|
+
- Provides `page` helper method
|
|
148
|
+
- Closes browser after test
|
|
149
|
+
|
|
150
|
+
## Porting from Puppeteer
|
|
151
|
+
|
|
152
|
+
When implementing new features, reference the TypeScript Puppeteer source:
|
|
153
|
+
|
|
154
|
+
1. Find the corresponding TypeScript file in [puppeteer/puppeteer](https://github.com/puppeteer/puppeteer)
|
|
155
|
+
2. Understand the CDP calls being made
|
|
156
|
+
3. Implement in Ruby following existing patterns
|
|
157
|
+
4. Port the relevant tests
|
|
158
|
+
5. Update `docs/api_coverage.md`
|
|
159
|
+
|
|
160
|
+
### CDP Command Pattern
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# TypeScript Puppeteer
|
|
164
|
+
await this._client.send('Page.navigate', { url });
|
|
165
|
+
|
|
166
|
+
# Ruby equivalent
|
|
167
|
+
@client.send_message('Page.navigate', url: url)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Concurrency Model
|
|
171
|
+
|
|
172
|
+
### Current State (socketry/async)
|
|
173
|
+
|
|
174
|
+
puppeteer-ruby uses Fiber-based concurrency with `socketry/async` (version 2.35.1+):
|
|
175
|
+
|
|
176
|
+
- `Async::Promise` - For async operations that complete later
|
|
177
|
+
- `Async` blocks - For running operations in Fiber context
|
|
178
|
+
- `Puppeteer::AsyncUtils.await_promise_all` - For waiting on multiple promises
|
|
179
|
+
- `Puppeteer::AsyncUtils.await_promise_race` - For waiting on any of multiple promises
|
|
180
|
+
- `Puppeteer::ReactorRunner` - Dedicated Async reactor thread for sync API wrapping
|
|
181
|
+
- Standard `Hash` with `Mutex` - For thread-safe callbacks and sessions
|
|
182
|
+
|
|
183
|
+
### Key Components
|
|
184
|
+
|
|
185
|
+
| Component | Purpose |
|
|
186
|
+
|-----------|---------|
|
|
187
|
+
| `Async::Promise` | Promise that can be resolved/rejected later |
|
|
188
|
+
| `AsyncUtils.await_promise_all` | Wait for multiple async operations |
|
|
189
|
+
| `AsyncUtils.await_promise_race` | Wait for first of multiple operations |
|
|
190
|
+
| `AsyncUtils.async_timeout` | Timeout wrapper for async operations |
|
|
191
|
+
| `ReactorRunner` | Bridges sync API calls into Async reactor |
|
|
192
|
+
|
|
193
|
+
### Async Method Pattern
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
class Page
|
|
197
|
+
# Synchronous version (blocks until complete)
|
|
198
|
+
def wait_for_selector(selector, timeout: nil)
|
|
199
|
+
# ...
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Async version (returns Async task)
|
|
203
|
+
define_async_method :async_wait_for_selector
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Development Workflow
|
|
208
|
+
|
|
209
|
+
### Before Submitting Changes
|
|
210
|
+
|
|
211
|
+
1. Run tests: `bundle exec rspec`
|
|
212
|
+
2. Run RuboCop: `bundle exec rubocop`
|
|
213
|
+
3. Run type check: `bundle exec steep check`
|
|
214
|
+
4. Update `docs/api_coverage.md` if adding new API methods
|
|
215
|
+
|
|
216
|
+
### Pull Request Guidelines
|
|
217
|
+
|
|
218
|
+
- **Language**: All Pull Requests must be written in English (title, description, and commit messages)
|
|
219
|
+
- **`gh` command**: Available for exploring GitHub issues, codes, or creating pull requests
|
|
220
|
+
- **PR Title**: Use a clear, concise title describing the change
|
|
221
|
+
- **PR Description**: Include a summary of changes and testing done
|
|
222
|
+
|
|
223
|
+
### Version Updates
|
|
224
|
+
|
|
225
|
+
When updating the version, **both files must be updated**:
|
|
226
|
+
- `lib/puppeteer/version.rb` - The canonical version constant
|
|
227
|
+
- `docs/api_coverage.md` - The version displayed in documentation
|
|
228
|
+
|
|
229
|
+
GitHub Actions automatically publishes to RubyGems when a version tag is pushed. Tag format is the version number without `v` prefix (e.g., `0.50.0.alpha3`, not `v0.50.0.alpha3`).
|
|
230
|
+
|
|
231
|
+
## Detailed Documentation
|
|
232
|
+
|
|
233
|
+
For in-depth information on specific topics, see the [CLAUDE/](./CLAUDE/) directory:
|
|
234
|
+
|
|
235
|
+
- [README.md](./CLAUDE/README.md) - Documentation index
|
|
236
|
+
- [architecture.md](./CLAUDE/architecture.md) - Detailed architecture overview
|
|
237
|
+
- [testing.md](./CLAUDE/testing.md) - Testing strategies and patterns
|
|
238
|
+
- [cdp_protocol.md](./CLAUDE/cdp_protocol.md) - CDP protocol details
|
|
239
|
+
- [concurrency.md](./CLAUDE/concurrency.md) - Concurrency patterns
|
|
240
|
+
- [porting_puppeteer.md](./CLAUDE/porting_puppeteer.md) - Guide for porting from TypeScript
|
|
241
|
+
- [rbs_type_checking.md](./CLAUDE/rbs_type_checking.md) - RBS type annotations and Steep type checking
|
|
242
|
+
- [spec_migration_plans.md](./CLAUDE/spec_migration_plans.md) - Test migration tracking and progress
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
[](https://badge.fury.io/rb/puppeteer-ruby)
|
|
2
2
|
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> The `main` branch is currently under **HEAVY DEVELOPMENT** for increased stability.
|
|
5
|
+
> If you need the latest stable release, please refer to the [ref-2025 tag](https://github.com/YusukeIwaki/puppeteer-ruby/tree/ref-2025).
|
|
6
|
+
|
|
3
7
|
# Puppeteer in Ruby
|
|
4
8
|
|
|
5
9
|
A Ruby port of [puppeteer](https://pptr.dev/).
|
|
@@ -228,6 +232,10 @@ end
|
|
|
228
232
|
|
|
229
233
|
https://yusukeiwaki.github.io/puppeteer-ruby-docs/
|
|
230
234
|
|
|
235
|
+
## Note on Firefox
|
|
236
|
+
|
|
237
|
+
This library supports **Chrome/Chromium only**. For Firefox automation, consider using [playwright-ruby-client](https://github.com/YusukeIwaki/playwright-ruby-client) or [puppeteer-bidi](https://github.com/YusukeIwaki/puppeteer-bidi).
|
|
238
|
+
|
|
231
239
|
## Contributing
|
|
232
240
|
|
|
233
241
|
Bug reports and pull requests are welcome on GitHub at https://github.com/YusukeIwaki/puppeteer-ruby.
|
data/Rakefile
CHANGED
data/Steepfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Steepfile for type checking
|
|
2
|
+
|
|
3
|
+
target :lib do
|
|
4
|
+
signature "sig"
|
|
5
|
+
|
|
6
|
+
check "lib"
|
|
7
|
+
|
|
8
|
+
# Standard library
|
|
9
|
+
library "base64"
|
|
10
|
+
library "json"
|
|
11
|
+
library "net-http"
|
|
12
|
+
library "uri"
|
|
13
|
+
|
|
14
|
+
configure_code_diagnostics do |hash|
|
|
15
|
+
hash[Steep::Diagnostic::Ruby::UnannotatedEmptyCollection] = :hint
|
|
16
|
+
hash[Steep::Diagnostic::Ruby::UnknownConstant] = :hint
|
|
17
|
+
hash[Steep::Diagnostic::Ruby::NoMethod] = :hint
|
|
18
|
+
hash[Steep::Diagnostic::Ruby::UnresolvedOverloading] = :hint
|
|
19
|
+
hash[Steep::Diagnostic::Ruby::IncompatibleAssignment] = :hint
|
|
20
|
+
hash[Steep::Diagnostic::Ruby::ArgumentTypeMismatch] = :hint
|
|
21
|
+
hash[Steep::Diagnostic::Ruby::ReturnTypeMismatch] = :hint
|
|
22
|
+
hash[Steep::Diagnostic::Ruby::BlockTypeMismatch] = :hint
|
|
23
|
+
hash[Steep::Diagnostic::Ruby::BreakTypeMismatch] = :hint
|
|
24
|
+
hash[Steep::Diagnostic::Ruby::ImplicitBreakValueMismatch] = :hint
|
|
25
|
+
hash[Steep::Diagnostic::Ruby::UnexpectedBlockGiven] = :hint
|
|
26
|
+
hash[Steep::Diagnostic::Ruby::UnexpectedPositionalArgument] = :hint
|
|
27
|
+
end
|
|
28
|
+
end
|