puppeteer-bidi 0.0.3.beta1 → 0.0.3.beta2
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/lib/puppeteer/bidi/browser.rb +15 -0
- data/lib/puppeteer/bidi/version.rb +1 -1
- data/sig/puppeteer/bidi/browser.rbs +3 -0
- metadata +1 -24
- data/CLAUDE/README.md +0 -158
- data/CLAUDE/async_programming.md +0 -158
- data/CLAUDE/click_implementation.md +0 -340
- data/CLAUDE/core_layer_gotchas.md +0 -136
- data/CLAUDE/error_handling.md +0 -232
- data/CLAUDE/expose_function_implementation.md +0 -271
- data/CLAUDE/file_chooser.md +0 -95
- data/CLAUDE/frame_architecture.md +0 -346
- data/CLAUDE/javascript_evaluation.md +0 -341
- data/CLAUDE/jshandle_implementation.md +0 -505
- data/CLAUDE/keyboard_implementation.md +0 -250
- data/CLAUDE/mouse_implementation.md +0 -140
- data/CLAUDE/navigation_waiting.md +0 -234
- data/CLAUDE/porting_puppeteer.md +0 -234
- data/CLAUDE/query_handler.md +0 -194
- data/CLAUDE/reactor_runner.md +0 -111
- data/CLAUDE/rspec_pending_vs_skip.md +0 -262
- data/CLAUDE/selector_evaluation.md +0 -198
- data/CLAUDE/test_server_routes.md +0 -263
- data/CLAUDE/testing_strategy.md +0 -236
- data/CLAUDE/two_layer_architecture.md +0 -180
- data/CLAUDE/wrapped_element_click.md +0 -247
- data/CLAUDE.md +0 -238
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
# JavaScript Evaluation Implementation
|
|
2
|
-
|
|
3
|
-
This document details the implementation of JavaScript evaluation in Page and Frame classes, including script detection logic, argument serialization, and result deserialization.
|
|
4
|
-
|
|
5
|
-
### Page.evaluate and Frame.evaluate
|
|
6
|
-
|
|
7
|
-
The `evaluate` method supports both JavaScript expressions and functions with proper argument serialization.
|
|
8
|
-
|
|
9
|
-
#### Detection Logic
|
|
10
|
-
|
|
11
|
-
The implementation distinguishes between three types of JavaScript code:
|
|
12
|
-
|
|
13
|
-
1. **Expressions**: Simple JavaScript code
|
|
14
|
-
2. **Functions**: Arrow functions or function declarations (use `script.callFunction`)
|
|
15
|
-
3. **IIFE**: Immediately Invoked Function Expressions (use `script.evaluate`)
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
# Expression - uses script.evaluate
|
|
19
|
-
page.evaluate('7 * 3') # => 21
|
|
20
|
-
|
|
21
|
-
# Function - uses script.callFunction
|
|
22
|
-
page.evaluate('(a, b) => a + b', 3, 4) # => 7
|
|
23
|
-
|
|
24
|
-
# IIFE - uses script.evaluate (not script.callFunction)
|
|
25
|
-
page.evaluate('(() => document.title)()') # => "Page Title"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
#### IIFE Detection Pattern
|
|
29
|
-
|
|
30
|
-
**Critical**: IIFE must be detected and treated as expressions:
|
|
31
|
-
|
|
32
|
-
```ruby
|
|
33
|
-
# Check if it's an IIFE - ends with () after the function body
|
|
34
|
-
is_iife = script_trimmed.match?(/\)\s*\(\s*\)\s*\z/)
|
|
35
|
-
|
|
36
|
-
# Only treat as function if not IIFE
|
|
37
|
-
is_function = !is_iife && (
|
|
38
|
-
script_trimmed.match?(/\A\s*(?:async\s+)?(?:\(.*?\)|[a-zA-Z_$][\w$]*)\s*=>/) ||
|
|
39
|
-
script_trimmed.match?(/\A\s*(?:async\s+)?function\s*\w*\s*\(/)
|
|
40
|
-
)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Why this matters**: IIFE like `(() => {...})()` looks like a function but must be evaluated as an expression. Using `script.callFunction` on IIFE causes syntax errors.
|
|
44
|
-
|
|
45
|
-
#### Argument Serialization
|
|
46
|
-
|
|
47
|
-
Arguments are serialized to BiDi `LocalValue` format:
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
# Special numbers
|
|
51
|
-
{ type: 'number', value: 'NaN' }
|
|
52
|
-
{ type: 'number', value: 'Infinity' }
|
|
53
|
-
{ type: 'number', value: '-0' }
|
|
54
|
-
|
|
55
|
-
# Collections
|
|
56
|
-
{ type: 'array', value: [...] }
|
|
57
|
-
{ type: 'object', value: [[key, value], ...] }
|
|
58
|
-
{ type: 'map', value: [[key, value], ...] }
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### Result Deserialization
|
|
62
|
-
|
|
63
|
-
BiDi returns results in special format that must be deserialized:
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
# BiDi response format
|
|
67
|
-
{
|
|
68
|
-
"type" => "success",
|
|
69
|
-
"realm" => "...",
|
|
70
|
-
"result" => {
|
|
71
|
-
"type" => "number",
|
|
72
|
-
"value" => 42
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Extract and deserialize
|
|
77
|
-
actual_result = result['result'] || result
|
|
78
|
-
deserialize_result(actual_result) # => 42
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
#### Exception Handling
|
|
82
|
-
|
|
83
|
-
Exceptions from JavaScript are returned in the result, not thrown by BiDi:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
if result['type'] == 'exception'
|
|
87
|
-
exception_details = result['exceptionDetails']
|
|
88
|
-
text = exception_details['text'] # "ReferenceError: notExistingObject is not defined"
|
|
89
|
-
raise text
|
|
90
|
-
end
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Core::Realm Return Values
|
|
94
|
-
|
|
95
|
-
**Important**: Core::Realm methods return the **complete BiDi result**, not just the value:
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
98
|
-
# Core::Realm.call_function returns:
|
|
99
|
-
{
|
|
100
|
-
"type" => "success" | "exception",
|
|
101
|
-
"realm" => "...",
|
|
102
|
-
"result" => {...} | nil,
|
|
103
|
-
"exceptionDetails" => {...} | nil
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
# NOT result['result'] (this was a bug that was fixed)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Testing Strategy
|
|
110
|
-
|
|
111
|
-
#### Integration Tests Organization
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
spec/
|
|
115
|
-
├── unit/ # Fast unit tests (future)
|
|
116
|
-
├── integration/ # Browser automation tests
|
|
117
|
-
│ ├── examples/ # Example-based tests
|
|
118
|
-
│ │ └── screenshot_spec.rb
|
|
119
|
-
│ └── screenshot_spec.rb # Feature test suites
|
|
120
|
-
├── assets/ # Test HTML/CSS/JS files
|
|
121
|
-
│ ├── grid.html
|
|
122
|
-
│ ├── scrollbar.html
|
|
123
|
-
│ ├── empty.html
|
|
124
|
-
│ └── digits/*.png
|
|
125
|
-
├── golden-firefox/ # Reference images
|
|
126
|
-
│ └── screenshot-*.png
|
|
127
|
-
└── support/ # Test utilities
|
|
128
|
-
├── test_server.rb
|
|
129
|
-
└── golden_comparator.rb
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
#### Implemented Screenshot Tests
|
|
133
|
-
|
|
134
|
-
All 12 tests ported from [Puppeteer's screenshot.spec.ts](https://github.com/puppeteer/puppeteer/blob/main/test/src/screenshot.spec.ts):
|
|
135
|
-
|
|
136
|
-
1. **should work** - Basic screenshot functionality
|
|
137
|
-
2. **should clip rect** - Clipping specific region
|
|
138
|
-
3. **should get screenshot bigger than the viewport** - Offscreen clip with captureBeyondViewport
|
|
139
|
-
4. **should clip bigger than the viewport without "captureBeyondViewport"** - Viewport coordinate transformation
|
|
140
|
-
5. **should run in parallel** - Thread-safe parallel screenshots on single page
|
|
141
|
-
6. **should take fullPage screenshots** - Full page with document origin
|
|
142
|
-
7. **should take fullPage screenshots without captureBeyondViewport** - Full page with viewport resize
|
|
143
|
-
8. **should run in parallel in multiple pages** - Concurrent screenshots across multiple pages
|
|
144
|
-
9. **should work with odd clip size on Retina displays** - Odd pixel dimensions (11x11)
|
|
145
|
-
10. **should return base64** - Base64 encoding verification
|
|
146
|
-
11. **should take fullPage screenshots when defaultViewport is null** - No explicit viewport
|
|
147
|
-
12. **should restore to original viewport size** - Viewport restoration after fullPage
|
|
148
|
-
|
|
149
|
-
Run tests:
|
|
150
|
-
```bash
|
|
151
|
-
bundle exec rspec spec/integration/screenshot_spec.rb
|
|
152
|
-
# Expected: 12 examples, 0 failures (completes in ~8 seconds with optimized spec_helper)
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
#### Test Performance Optimization
|
|
156
|
-
|
|
157
|
-
**Critical**: Integration tests are ~19x faster with browser reuse strategy.
|
|
158
|
-
|
|
159
|
-
##### Before Optimization (Per-test Browser Launch)
|
|
160
|
-
```ruby
|
|
161
|
-
def with_test_state(**options)
|
|
162
|
-
server = TestServer::Server.new
|
|
163
|
-
server.start
|
|
164
|
-
|
|
165
|
-
with_browser(**options) do |browser| # New browser per test!
|
|
166
|
-
context = browser.default_browser_context
|
|
167
|
-
page = browser.new_page
|
|
168
|
-
yield(page: page, server: server, browser: browser, context: context)
|
|
169
|
-
end
|
|
170
|
-
ensure
|
|
171
|
-
server.stop
|
|
172
|
-
end
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
**Performance**: ~195 seconds for 35 tests (browser launch overhead × 35)
|
|
176
|
-
|
|
177
|
-
##### After Optimization (Shared Browser)
|
|
178
|
-
```ruby
|
|
179
|
-
# In spec_helper.rb
|
|
180
|
-
config.before(:suite) do
|
|
181
|
-
if RSpec.configuration.files_to_run.any? { |f| f.include?('spec/integration') }
|
|
182
|
-
$shared_browser = Puppeteer::Bidi.launch_browser_instance(headless: headless_mode?)
|
|
183
|
-
$shared_test_server = TestServer::Server.new
|
|
184
|
-
$shared_test_server.start
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def with_test_state(**options)
|
|
189
|
-
if $shared_browser && options.empty?
|
|
190
|
-
# Create new page (tab) per test
|
|
191
|
-
page = $shared_browser.new_page
|
|
192
|
-
context = $shared_browser.default_browser_context
|
|
193
|
-
|
|
194
|
-
begin
|
|
195
|
-
yield(page: page, server: $shared_test_server, browser: $shared_browser, context: context)
|
|
196
|
-
ensure
|
|
197
|
-
page.close unless page.closed? # Clean up tab
|
|
198
|
-
end
|
|
199
|
-
else
|
|
200
|
-
# Fall back to per-test browser for custom options
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
**Performance**: ~10 seconds for 35 tests (1 browser launch + 35 tab creations)
|
|
206
|
-
|
|
207
|
-
##### Performance Results
|
|
208
|
-
|
|
209
|
-
| Test Suite | Before | After | Improvement |
|
|
210
|
-
|------------|--------|-------|-------------|
|
|
211
|
-
| **evaluation_spec (23 tests)** | 127s | **7.17s** | **17.7x faster** |
|
|
212
|
-
| **screenshot_spec (12 tests)** | 68s | **8.47s** | **8.0x faster** |
|
|
213
|
-
| **Combined (35 tests)** | 195s | **10.33s** | **18.9x faster** 🚀 |
|
|
214
|
-
|
|
215
|
-
**Key Benefits**:
|
|
216
|
-
- Browser launch only once per suite
|
|
217
|
-
- Each test gets fresh page (tab) for isolation
|
|
218
|
-
- Cleanup handled automatically
|
|
219
|
-
- Backward compatible (custom options fall back to per-test browser)
|
|
220
|
-
|
|
221
|
-
#### Environment Variables
|
|
222
|
-
|
|
223
|
-
```bash
|
|
224
|
-
HEADLESS=false # Run browser in non-headless mode for debugging
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Debugging Techniques
|
|
228
|
-
|
|
229
|
-
#### 1. Save Screenshots for Inspection
|
|
230
|
-
|
|
231
|
-
```ruby
|
|
232
|
-
# In golden_comparator.rb
|
|
233
|
-
def save_screenshot(screenshot_base64, filename)
|
|
234
|
-
output_dir = File.join(__dir__, '../output')
|
|
235
|
-
FileUtils.mkdir_p(output_dir)
|
|
236
|
-
File.binwrite(File.join(output_dir, filename),
|
|
237
|
-
Base64.decode64(screenshot_base64))
|
|
238
|
-
end
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
#### 2. Compare Images Pixel-by-Pixel
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
cat > /tmp/compare.rb << 'EOF'
|
|
245
|
-
require 'chunky_png'
|
|
246
|
-
|
|
247
|
-
golden = ChunkyPNG::Image.from_file('spec/golden-firefox/screenshot.png')
|
|
248
|
-
actual = ChunkyPNG::Image.from_file('spec/output/debug.png')
|
|
249
|
-
|
|
250
|
-
diff_count = 0
|
|
251
|
-
(0...golden.height).each do |y|
|
|
252
|
-
(0...golden.width).each do |x|
|
|
253
|
-
if golden[x, y] != actual[x, y]
|
|
254
|
-
diff_count += 1
|
|
255
|
-
puts "Diff at (#{x}, #{y})" if diff_count <= 10
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
puts "Total: #{diff_count} pixels differ"
|
|
260
|
-
EOF
|
|
261
|
-
ruby /tmp/compare.rb
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
#### 3. Debug BiDi Responses
|
|
265
|
-
|
|
266
|
-
```ruby
|
|
267
|
-
# Temporarily add debugging
|
|
268
|
-
result = @browsing_context.default_realm.evaluate(script, true)
|
|
269
|
-
puts "BiDi result: #{result.inspect}"
|
|
270
|
-
deserialize_result(result)
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Common Pitfalls and Solutions
|
|
274
|
-
|
|
275
|
-
#### 1. BiDi Protocol Differences
|
|
276
|
-
|
|
277
|
-
**Problem:** BiDi `origin` parameter behavior differs from expectations
|
|
278
|
-
|
|
279
|
-
**Solution:** Consult BiDi spec and test both `'document'` and `'viewport'` origins
|
|
280
|
-
|
|
281
|
-
```ruby
|
|
282
|
-
# document: Absolute coordinates in full page
|
|
283
|
-
# viewport: Relative to current viewport
|
|
284
|
-
options[:origin] = capture_beyond_viewport ? 'document' : 'viewport'
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
#### 2. Image Comparison Failures
|
|
288
|
-
|
|
289
|
-
**Problem:** Golden images don't match exactly (1-2 pixel differences)
|
|
290
|
-
|
|
291
|
-
**Solution:** Implement tolerance in comparison
|
|
292
|
-
|
|
293
|
-
```ruby
|
|
294
|
-
# Allow small rendering differences (±1 RGB per channel)
|
|
295
|
-
compare_with_golden(screenshot, 'golden.png', pixel_threshold: 1)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
#### 3. Viewport State Management
|
|
299
|
-
|
|
300
|
-
**Problem:** Viewport not restored after fullPage screenshot
|
|
301
|
-
|
|
302
|
-
**Solution:** Use `ensure` block
|
|
303
|
-
|
|
304
|
-
```ruby
|
|
305
|
-
begin
|
|
306
|
-
set_viewport(full_page_dimensions)
|
|
307
|
-
screenshot = capture_screenshot(...)
|
|
308
|
-
ensure
|
|
309
|
-
set_viewport(original_viewport) if original_viewport
|
|
310
|
-
end
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### 4. Thread Safety
|
|
314
|
-
|
|
315
|
-
**Problem:** Parallel screenshots cause race conditions
|
|
316
|
-
|
|
317
|
-
**Solution:** BiDi protocol handles this naturally - test with threads
|
|
318
|
-
|
|
319
|
-
```ruby
|
|
320
|
-
threads = (0...3).map do |i|
|
|
321
|
-
Thread.new { page.screenshot(clip: {...}) }
|
|
322
|
-
end
|
|
323
|
-
screenshots = threads.map(&:value)
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Documentation References
|
|
327
|
-
|
|
328
|
-
**Essential reading for implementation:**
|
|
329
|
-
|
|
330
|
-
1. **WebDriver BiDi Spec**: https://w3c.github.io/webdriver-bidi/
|
|
331
|
-
2. **Puppeteer Source**: https://github.com/puppeteer/puppeteer
|
|
332
|
-
3. **Puppeteer BiDi Tests**: https://github.com/puppeteer/puppeteer/tree/main/test/src
|
|
333
|
-
4. **Firefox BiDi Impl**: Check Firefox implementation notes for quirks
|
|
334
|
-
|
|
335
|
-
**Reference implementation workflow:**
|
|
336
|
-
1. Find corresponding Puppeteer test in `test/src/`
|
|
337
|
-
2. Read TypeScript implementation in `packages/puppeteer-core/src/`
|
|
338
|
-
3. Check BiDi spec for protocol details
|
|
339
|
-
4. Implement Ruby version maintaining same logic
|
|
340
|
-
5. Download golden images and verify pixel-perfect match (with tolerance)
|
|
341
|
-
|