puppeteer-bidi 0.0.1.beta1

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/CLAUDE/README.md +158 -0
  5. data/CLAUDE/async_programming.md +158 -0
  6. data/CLAUDE/click_implementation.md +340 -0
  7. data/CLAUDE/core_layer_gotchas.md +136 -0
  8. data/CLAUDE/error_handling.md +232 -0
  9. data/CLAUDE/file_chooser.md +95 -0
  10. data/CLAUDE/frame_architecture.md +346 -0
  11. data/CLAUDE/javascript_evaluation.md +341 -0
  12. data/CLAUDE/jshandle_implementation.md +505 -0
  13. data/CLAUDE/keyboard_implementation.md +250 -0
  14. data/CLAUDE/mouse_implementation.md +140 -0
  15. data/CLAUDE/navigation_waiting.md +234 -0
  16. data/CLAUDE/porting_puppeteer.md +214 -0
  17. data/CLAUDE/query_handler.md +194 -0
  18. data/CLAUDE/rspec_pending_vs_skip.md +262 -0
  19. data/CLAUDE/selector_evaluation.md +198 -0
  20. data/CLAUDE/test_server_routes.md +263 -0
  21. data/CLAUDE/testing_strategy.md +236 -0
  22. data/CLAUDE/two_layer_architecture.md +180 -0
  23. data/CLAUDE/wrapped_element_click.md +247 -0
  24. data/CLAUDE.md +185 -0
  25. data/LICENSE.txt +21 -0
  26. data/README.md +488 -0
  27. data/Rakefile +21 -0
  28. data/lib/puppeteer/bidi/async_utils.rb +151 -0
  29. data/lib/puppeteer/bidi/browser.rb +285 -0
  30. data/lib/puppeteer/bidi/browser_context.rb +53 -0
  31. data/lib/puppeteer/bidi/browser_launcher.rb +240 -0
  32. data/lib/puppeteer/bidi/connection.rb +182 -0
  33. data/lib/puppeteer/bidi/core/README.md +169 -0
  34. data/lib/puppeteer/bidi/core/browser.rb +230 -0
  35. data/lib/puppeteer/bidi/core/browsing_context.rb +601 -0
  36. data/lib/puppeteer/bidi/core/disposable.rb +69 -0
  37. data/lib/puppeteer/bidi/core/errors.rb +64 -0
  38. data/lib/puppeteer/bidi/core/event_emitter.rb +83 -0
  39. data/lib/puppeteer/bidi/core/navigation.rb +128 -0
  40. data/lib/puppeteer/bidi/core/realm.rb +315 -0
  41. data/lib/puppeteer/bidi/core/request.rb +300 -0
  42. data/lib/puppeteer/bidi/core/session.rb +153 -0
  43. data/lib/puppeteer/bidi/core/user_context.rb +208 -0
  44. data/lib/puppeteer/bidi/core/user_prompt.rb +102 -0
  45. data/lib/puppeteer/bidi/core.rb +45 -0
  46. data/lib/puppeteer/bidi/deserializer.rb +132 -0
  47. data/lib/puppeteer/bidi/element_handle.rb +602 -0
  48. data/lib/puppeteer/bidi/errors.rb +42 -0
  49. data/lib/puppeteer/bidi/file_chooser.rb +52 -0
  50. data/lib/puppeteer/bidi/frame.rb +597 -0
  51. data/lib/puppeteer/bidi/http_response.rb +23 -0
  52. data/lib/puppeteer/bidi/injected.js +1 -0
  53. data/lib/puppeteer/bidi/injected_source.rb +21 -0
  54. data/lib/puppeteer/bidi/js_handle.rb +302 -0
  55. data/lib/puppeteer/bidi/keyboard.rb +265 -0
  56. data/lib/puppeteer/bidi/lazy_arg.rb +23 -0
  57. data/lib/puppeteer/bidi/mouse.rb +170 -0
  58. data/lib/puppeteer/bidi/page.rb +613 -0
  59. data/lib/puppeteer/bidi/query_handler.rb +397 -0
  60. data/lib/puppeteer/bidi/realm.rb +242 -0
  61. data/lib/puppeteer/bidi/serializer.rb +139 -0
  62. data/lib/puppeteer/bidi/target.rb +81 -0
  63. data/lib/puppeteer/bidi/task_manager.rb +44 -0
  64. data/lib/puppeteer/bidi/timeout_settings.rb +20 -0
  65. data/lib/puppeteer/bidi/transport.rb +129 -0
  66. data/lib/puppeteer/bidi/version.rb +7 -0
  67. data/lib/puppeteer/bidi/wait_task.rb +322 -0
  68. data/lib/puppeteer/bidi.rb +49 -0
  69. data/scripts/update_injected_source.rb +57 -0
  70. data/sig/puppeteer/bidi/browser.rbs +80 -0
  71. data/sig/puppeteer/bidi/element_handle.rbs +238 -0
  72. data/sig/puppeteer/bidi/frame.rbs +205 -0
  73. data/sig/puppeteer/bidi/js_handle.rbs +90 -0
  74. data/sig/puppeteer/bidi/page.rbs +247 -0
  75. data/sig/puppeteer/bidi.rbs +15 -0
  76. metadata +176 -0
@@ -0,0 +1,505 @@
1
+ # JSHandle and ElementHandle Implementation
2
+
3
+ This document provides comprehensive details on implementing JSHandle and ElementHandle classes, including BiDi protocol parameters, debugging techniques, and common pitfalls.
4
+
5
+ ### Overview
6
+
7
+ JSHandle and ElementHandle are fundamental classes for interacting with JavaScript objects in the browser. This section documents the implementation details and debugging techniques learned during development.
8
+
9
+ ### Architecture
10
+
11
+ ```
12
+ Puppeteer::Bidi
13
+ ├── Serializer # Ruby → BiDi LocalValue
14
+ ├── Deserializer # BiDi RemoteValue → Ruby
15
+ ├── JSHandle # JavaScript object reference
16
+ └── ElementHandle # DOM element reference (extends JSHandle)
17
+ ```
18
+
19
+ ### Key Implementation Files
20
+
21
+ | File | Purpose | Lines |
22
+ |------|---------|-------|
23
+ | `lib/puppeteer/bidi/serializer.rb` | Centralized argument serialization | 136 |
24
+ | `lib/puppeteer/bidi/deserializer.rb` | Centralized result deserialization | 132 |
25
+ | `lib/puppeteer/bidi/js_handle.rb` | JavaScript object handles | 291 |
26
+ | `lib/puppeteer/bidi/element_handle.rb` | DOM element handles | 91 |
27
+
28
+ **Code reduction**: ~300 lines of duplicate serialization code eliminated from Frame and Page classes.
29
+
30
+ ### Critical BiDi Protocol Parameters
31
+
32
+ #### 1. resultOwnership - Handle Lifecycle Management
33
+
34
+ **Problem**: BiDi returns `{"type" => "object"}` without `handle` or `sharedId`, making it impossible to reference the object later.
35
+
36
+ **Root Cause**: Missing `resultOwnership` parameter in `script.callFunction` and `script.evaluate`.
37
+
38
+ **Solution**: Always set `resultOwnership: 'root'` when you need a handle:
39
+
40
+ ```ruby
41
+ # lib/puppeteer/bidi/core/realm.rb
42
+ def call_function(function_declaration, await_promise, **options)
43
+ # Critical: Use 'root' ownership to keep handles alive
44
+ unless options.key?(:resultOwnership)
45
+ options[:resultOwnership] = 'root'
46
+ end
47
+
48
+ session.send_command('script.callFunction', {
49
+ functionDeclaration: function_declaration,
50
+ awaitPromise: await_promise,
51
+ target: target,
52
+ **options
53
+ })
54
+ end
55
+ ```
56
+
57
+ **BiDi resultOwnership values**:
58
+ - `'root'`: Keep handle alive (garbage collection resistant)
59
+ - `'none'`: Don't return handle (for one-time evaluations)
60
+
61
+ **Important**: Don't confuse with `awaitPromise`:
62
+ - `awaitPromise`: Controls whether to wait for promises to resolve
63
+ - `resultOwnership`: Controls handle lifecycle (independent concern)
64
+
65
+ #### 2. serializationOptions - Control Serialization Depth
66
+
67
+ **When requesting handles**, set `maxObjectDepth: 0` to prevent deep serialization:
68
+
69
+ ```ruby
70
+ # When awaitPromise is false (returning handle):
71
+ options[:serializationOptions] = {
72
+ maxObjectDepth: 0, # Don't serialize, return handle
73
+ maxDomDepth: 0 # Don't serialize DOM children
74
+ }
75
+ ```
76
+
77
+ **Without serializationOptions**: BiDi may serialize the entire object graph, losing the handle reference.
78
+
79
+ ### Debugging Techniques
80
+
81
+ #### 1. Protocol Message Inspection
82
+
83
+ Use `DEBUG_BIDI_COMMAND=1` to see all BiDi protocol messages:
84
+
85
+ ```bash
86
+ DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/jshandle_spec.rb:24
87
+ ```
88
+
89
+ **Output**:
90
+ ```
91
+ [BiDi] Request script.callFunction: {
92
+ id: 1,
93
+ method: "script.callFunction",
94
+ params: {
95
+ functionDeclaration: "() => navigator",
96
+ awaitPromise: false,
97
+ target: {context: "..."},
98
+ resultOwnership: "root", # ← Check this!
99
+ serializationOptions: { # ← Check this!
100
+ maxObjectDepth: 0
101
+ }
102
+ }
103
+ }
104
+
105
+ [BiDi] Response for script.callFunction: {
106
+ type: "success",
107
+ result: {
108
+ type: "object",
109
+ handle: "6af2844f-..." # ← Should have handle!
110
+ }
111
+ }
112
+ ```
113
+
114
+ #### 2. Comparing with Puppeteer's Protocol Messages
115
+
116
+ **Workflow**:
117
+ 1. Clone Puppeteer repository: `git clone https://github.com/puppeteer/puppeteer`
118
+ 2. Set up Puppeteer: `npm install && npm run build`
119
+ 3. Enable protocol logging: `DEBUG_PROTOCOL=1 npm test -- test/src/jshandle.spec.ts`
120
+ 4. Compare messages side-by-side with Ruby implementation
121
+
122
+ **Example comparison**:
123
+ ```bash
124
+ # Puppeteer (TypeScript)
125
+ DEBUG_PROTOCOL=1 npm test -- test/src/jshandle.spec.ts -g "should accept object handle"
126
+
127
+ # Ruby implementation
128
+ DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/jshandle_spec.rb:24
129
+ ```
130
+
131
+ **Look for differences in**:
132
+ - Parameter names (camelCase vs snake_case)
133
+ - Missing parameters (resultOwnership, serializationOptions)
134
+ - Parameter values (arrays vs strings)
135
+
136
+ #### 3. Extracting Specific Protocol Messages
137
+
138
+ Use `grep` to filter specific BiDi methods:
139
+
140
+ ```bash
141
+ DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/jshandle_spec.rb:227 \
142
+ --format documentation 2>&1 | grep -A 5 "script\.disown"
143
+ ```
144
+
145
+ **Output**:
146
+ ```
147
+ [BiDi] Request script.disown: {
148
+ id: 6,
149
+ method: "script.disown",
150
+ params: {
151
+ target: {context: "..."},
152
+ handles: "6af2844f-..." # ← ERROR: Should be array!
153
+ }
154
+ }
155
+ ```
156
+
157
+ #### 4. Step-by-Step Protocol Flow Analysis
158
+
159
+ For complex issues, trace the entire flow:
160
+
161
+ ```ruby
162
+ # Add temporary debugging in code
163
+ def evaluate_handle(script, *args)
164
+ puts "1. Input script: #{script}"
165
+ puts "2. Serialized args: #{serialized_args.inspect}"
166
+
167
+ result = @realm.call_function(script, false, arguments: serialized_args)
168
+ puts "3. BiDi result: #{result.inspect}"
169
+
170
+ handle = JSHandle.from(result['result'], @realm)
171
+ puts "4. Created handle: #{handle.inspect}"
172
+
173
+ handle
174
+ end
175
+ ```
176
+
177
+ ### Common Pitfalls and Solutions
178
+
179
+ #### 1. Handle Parameters Must Be Arrays
180
+
181
+ **Problem**: BiDi error "Expected 'handles' to be an array, got [object String]"
182
+
183
+ **Root Cause**: `script.disown` expects `handles` parameter as array, but single string was passed:
184
+
185
+ ```ruby
186
+ # WRONG
187
+ @realm.disown(handle_id) # → {handles: "abc-123"}
188
+
189
+ # CORRECT
190
+ @realm.disown([handle_id]) # → {handles: ["abc-123"]}
191
+ ```
192
+
193
+ **Location**: `lib/puppeteer/bidi/js_handle.rb:57`
194
+
195
+ **Fix**:
196
+ ```ruby
197
+ def dispose
198
+ handle_id = id
199
+ @realm.disown([handle_id]) if handle_id # Wrap in array
200
+ end
201
+ ```
202
+
203
+ #### 2. Handle Not Returned from evaluate_handle
204
+
205
+ **Symptoms**:
206
+ - `remote_value['handle']` is `nil`
207
+ - BiDi returns `{"type" => "object"}` without handle
208
+ - Error: "Expected 'serializedKeyValueList' to be an array"
209
+
210
+ **Root Cause**: Missing `resultOwnership` and `serializationOptions` parameters
211
+
212
+ **Fix**: Add to `Core::Realm#call_function` (see section 1 above)
213
+
214
+ #### 3. Confusing awaitPromise with returnByValue
215
+
216
+ **Common mistake**: Thinking `awaitPromise` controls serialization
217
+
218
+ **Reality**:
219
+ - `awaitPromise`: Wait for promises? (`true`/`false`)
220
+ - `resultOwnership`: Return handle? (`'root'`/`'none'`)
221
+ - These are **independent** concerns!
222
+
223
+ **Example**:
224
+ ```ruby
225
+ # Want handle to a promise result? Use both!
226
+ call_function(script, true, resultOwnership: 'root') # await=true, handle=yes
227
+
228
+ # Want serialized promise result? Different!
229
+ call_function(script, true, resultOwnership: 'none') # await=true, serialize=yes
230
+ ```
231
+
232
+ #### 4. Date Serialization in json_value
233
+
234
+ **Problem**: Dates converted to strings instead of Time objects
235
+
236
+ **Wrong approach**:
237
+ ```ruby
238
+ # DON'T: Using JSON.stringify loses BiDi's native date type
239
+ result = evaluate('(value) => JSON.stringify(value)')
240
+ JSON.parse(result) # Date becomes string!
241
+ ```
242
+
243
+ **Correct approach**:
244
+ ```ruby
245
+ # DO: Use BiDi's built-in serialization
246
+ def json_value
247
+ evaluate('(value) => value') # BiDi handles dates natively
248
+ end
249
+ ```
250
+
251
+ **BiDi date format**:
252
+ ```ruby
253
+ # BiDi returns: {type: 'date', value: '2020-05-27T01:31:38.506Z'}
254
+ # Deserializer converts to: Time.parse('2020-05-27T01:31:38.506Z')
255
+ ```
256
+
257
+ ### Testing Strategy for Handle Implementation
258
+
259
+ #### Test Organization
260
+
261
+ ```
262
+ spec/integration/
263
+ ├── jshandle_spec.rb # 21 tests - JSHandle functionality
264
+ ├── queryselector_spec.rb # 8 tests - DOM querying
265
+ └── evaluation_spec.rb # Updated - ElementHandle arguments
266
+ ```
267
+
268
+ #### Test Coverage Checklist
269
+
270
+ When implementing handle-related features, ensure tests cover:
271
+
272
+ - ✅ Handle creation from primitives and objects
273
+ - ✅ Handle passing as function arguments
274
+ - ✅ Property access (single and multiple)
275
+ - ✅ JSON value serialization
276
+ - ✅ Special values (dates, circular references, undefined)
277
+ - ✅ Type conversion (`as_element`)
278
+ - ✅ String representation (`to_s`)
279
+ - ✅ Handle disposal and error handling
280
+ - ✅ DOM querying (single and multiple)
281
+ - ✅ Empty result handling
282
+
283
+ #### Running Handle Tests
284
+
285
+ ```bash
286
+ # All handle-related tests
287
+ bundle exec rspec spec/integration/jshandle_spec.rb spec/integration/queryselector_spec.rb
288
+
289
+ # With protocol debugging
290
+ DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/jshandle_spec.rb:24
291
+
292
+ # Specific test
293
+ bundle exec rspec spec/integration/jshandle_spec.rb:227 --format documentation
294
+ ```
295
+
296
+ ### Code Patterns and Best Practices
297
+
298
+ #### 1. Serializer Usage
299
+
300
+ **Always use Serializer** for argument preparation:
301
+
302
+ ```ruby
303
+ # Good
304
+ args = [element, selector].map { |arg| Serializer.serialize(arg) }
305
+ call_function(script, true, arguments: args)
306
+
307
+ # Bad - manual serialization (duplicates logic)
308
+ args = [
309
+ { type: 'object', handle: element.id },
310
+ { type: 'string', value: selector }
311
+ ]
312
+ ```
313
+
314
+ #### 2. Deserializer Usage
315
+
316
+ **Always use Deserializer** for result processing:
317
+
318
+ ```ruby
319
+ # Good
320
+ result = call_function(script, true)
321
+ Deserializer.deserialize(result['result'])
322
+
323
+ # Bad - manual deserialization (misses edge cases)
324
+ result['result']['value'] # Breaks for dates, handles, etc.
325
+ ```
326
+
327
+ #### 3. Factory Pattern for Handle Creation
328
+
329
+ **Use `JSHandle.from`** for polymorphic handle creation:
330
+
331
+ ```ruby
332
+ # Good - automatically creates ElementHandle for nodes
333
+ handle = JSHandle.from(remote_value, realm)
334
+
335
+ # Bad - manual type checking
336
+ if remote_value['type'] == 'node'
337
+ ElementHandle.new(realm, remote_value)
338
+ else
339
+ JSHandle.new(realm, remote_value)
340
+ end
341
+ ```
342
+
343
+ #### 4. Handle Disposal Pattern
344
+
345
+ **Always check disposal state** before operations:
346
+
347
+ ```ruby
348
+ def get_property(name)
349
+ raise 'JSHandle is disposed' if @disposed
350
+
351
+ # ... implementation
352
+ end
353
+ ```
354
+
355
+ ### Performance Considerations
356
+
357
+ #### Handle Lifecycle
358
+
359
+ **Handles consume browser memory** - dispose when no longer needed:
360
+
361
+ ```ruby
362
+ # Manual disposal
363
+ handle = page.evaluate_handle('window')
364
+ # ... use handle
365
+ handle.dispose
366
+
367
+ # Automatic disposal via block (future enhancement)
368
+ page.evaluate_handle('window') do |handle|
369
+ # handle automatically disposed after block
370
+ end
371
+ ```
372
+
373
+ #### Serialization vs Handle References
374
+
375
+ **Trade-off**:
376
+ - **Serialization** (`resultOwnership: 'none'`): One-time use, no memory overhead
377
+ - **Handle** (`resultOwnership: 'root'`): Reusable, requires disposal
378
+
379
+ ```ruby
380
+ # One-time evaluation - serialize
381
+ page.evaluate('document.title') # No handle created
382
+
383
+ # Reusable reference - handle
384
+ handle = page.evaluate_handle('document') # Keep for multiple operations
385
+ handle.evaluate('doc => doc.title')
386
+ handle.evaluate('doc => doc.body.innerHTML')
387
+ handle.dispose # Clean up
388
+ ```
389
+
390
+ ### ElementHandle Frame Access Pattern
391
+
392
+ **Following Puppeteer's architecture**, ElementHandle accesses the Page through its Frame:
393
+
394
+ ```typescript
395
+ // Puppeteer TypeScript
396
+ class BidiElementHandle extends ElementHandle {
397
+ override get frame(): BidiFrame {
398
+ return this.realm.environment; // realm.environment is the Frame
399
+ }
400
+ }
401
+ ```
402
+
403
+ **Ruby implementation**:
404
+ ```ruby
405
+ # lib/puppeteer/bidi/element_handle.rb
406
+ class ElementHandle < JSHandle
407
+ def frame
408
+ @realm.environment # WindowRealm#environment returns Frame
409
+ end
410
+
411
+ def type(text, delay: 0)
412
+ keyboard = Keyboard.new(frame.page, @realm.browsing_context)
413
+ # ...
414
+ end
415
+ end
416
+ ```
417
+
418
+ **Architecture**:
419
+ ```
420
+ ElementHandle
421
+ └─ @realm (WindowRealm)
422
+ └─ environment (Frame)
423
+ └─ page() → Page
424
+ ```
425
+
426
+ **Implementation steps**:
427
+
428
+ 1. **WindowRealm** stores Frame reference:
429
+ ```ruby
430
+ # lib/puppeteer/bidi/core/realm.rb
431
+ class WindowRealm < Realm
432
+ attr_accessor :environment # Holds Frame reference
433
+ end
434
+ ```
435
+
436
+ 2. **Frame** sets itself as realm's environment:
437
+ ```ruby
438
+ # lib/puppeteer/bidi/frame.rb
439
+ def initialize(parent, browsing_context)
440
+ @parent = parent
441
+ @browsing_context = browsing_context
442
+
443
+ # Set this frame as the environment
444
+ realm = @browsing_context.default_realm
445
+ realm.environment = self if realm.respond_to?(:environment=)
446
+ end
447
+ ```
448
+
449
+ 3. **ElementHandle** accesses Page via frame:
450
+ ```ruby
451
+ # lib/puppeteer/bidi/element_handle.rb
452
+ def frame
453
+ @realm.environment
454
+ end
455
+
456
+ # Use frame.page instead of workaround
457
+ keyboard = Keyboard.new(frame.page, @realm.browsing_context)
458
+ ```
459
+
460
+ **Benefits**:
461
+ - Matches Puppeteer's design exactly
462
+ - No temporary object creation
463
+ - Clear ownership hierarchy
464
+ - Type-safe frame access
465
+
466
+ ### Reference Implementation Mapping
467
+
468
+ | Puppeteer TypeScript | Ruby Implementation | Notes |
469
+ |---------------------|---------------------|-------|
470
+ | `BidiJSHandle.from()` | `JSHandle.from()` | Factory method |
471
+ | `BidiJSHandle#dispose()` | `JSHandle#dispose` | Handle cleanup |
472
+ | `BidiJSHandle#jsonValue()` | `JSHandle#json_value` | Uses evaluate trick |
473
+ | `BidiJSHandle#getProperty()` | `JSHandle#get_property` | Single property |
474
+ | `BidiJSHandle#getProperties()` | `JSHandle#get_properties` | Walks prototype chain |
475
+ | `BidiElementHandle#$()` | `ElementHandle#query_selector` | CSS selector |
476
+ | `BidiElementHandle#$$()` | `ElementHandle#query_selector_all` | Multiple elements |
477
+ | `BidiElementHandle#frame` | `ElementHandle#frame` | realm.environment |
478
+ | `BidiElementHandle#contentFrame()` | `ElementHandle#content_frame` | iframe/frame content |
479
+ | `BidiElementHandle#isVisible()` | `ElementHandle#visible?` | Visibility check |
480
+ | `BidiElementHandle#isHidden()` | `ElementHandle#hidden?` | Hidden check |
481
+ | `BidiElementHandle#toElement()` | `ElementHandle#to_element` | Tag name validation |
482
+ | `BidiElementHandle#boxModel()` | `ElementHandle#box_model` | CSS box model |
483
+ | `BidiSerializer.serialize()` | `Serializer.serialize()` | Centralized |
484
+ | `BidiDeserializer.deserialize()` | `Deserializer.deserialize()` | Centralized |
485
+
486
+ ### Future Enhancements
487
+
488
+ Potential improvements for handle implementation:
489
+
490
+ 1. **Automatic disposal**: Block-based API with automatic cleanup
491
+ 2. **Handle pooling**: Reuse handle IDs to reduce memory overhead
492
+ 3. **Lazy deserialization**: Defer conversion until value is accessed
493
+ 4. **Type hints**: RBS type definitions for better IDE support
494
+ 5. **Handle debugging**: Track handle creation/disposal for leak detection
495
+
496
+ ### Lessons Learned
497
+
498
+ 1. **Always compare protocol messages** with Puppeteer when debugging BiDi issues
499
+ 2. **resultOwnership is critical** for handle-based APIs - always set it explicitly
500
+ 3. **Don't confuse awaitPromise with serialization** - they control different aspects
501
+ 4. **BiDi arrays must be arrays** - wrapping single values is often necessary
502
+ 5. **Use Puppeteer's tricks** - like `evaluate('(value) => value')` for json_value
503
+ 6. **Test disposal thoroughly** - handle lifecycle bugs are subtle and common
504
+ 7. **Centralize serialization** - eliminates duplication and ensures consistency
505
+