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,247 +0,0 @@
|
|
|
1
|
-
# Wrapped Element Click Implementation
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
|
|
5
|
-
When clicking on wrapped or multi-line text elements, using `getBoundingClientRect()` returns a single large bounding box that may have empty space in the center, causing clicks to miss the actual element.
|
|
6
|
-
|
|
7
|
-
## Example: Wrapped Link
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<div style="width: 10ch; word-wrap: break-word;">
|
|
11
|
-
<a href='#clicked'>123321</a>
|
|
12
|
-
</div>
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
The link text wraps into two lines:
|
|
16
|
-
```
|
|
17
|
-
123
|
|
18
|
-
321
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### getBoundingClientRect() Problem
|
|
22
|
-
|
|
23
|
-
Returns single large box:
|
|
24
|
-
```ruby
|
|
25
|
-
{x: 628.45, y: 62.47, width: 109.1, height: 49.73}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Click point (center): `(683, 87)` → **hits empty space between lines!**
|
|
29
|
-
|
|
30
|
-
### getClientRects() Solution
|
|
31
|
-
|
|
32
|
-
Returns multiple boxes for wrapped text:
|
|
33
|
-
```ruby
|
|
34
|
-
[
|
|
35
|
-
{x: 708.58, y: 62.47, width: 32.73, height: 22.67}, # "123"
|
|
36
|
-
{x: 628.45, y: 85.15, width: 32.73, height: 22.67} # "321"
|
|
37
|
-
]
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Click point (first box center): `(725, 73)` → **hits actual text!**
|
|
41
|
-
|
|
42
|
-
## Implementation
|
|
43
|
-
|
|
44
|
-
### clickable_box Method
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
def clickable_box
|
|
48
|
-
assert_not_disposed
|
|
49
|
-
|
|
50
|
-
# Get client rects - returns multiple boxes for wrapped elements
|
|
51
|
-
boxes = evaluate(<<~JS)
|
|
52
|
-
element => {
|
|
53
|
-
if (!(element instanceof Element)) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
return [...element.getClientRects()].map(rect => {
|
|
57
|
-
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
JS
|
|
61
|
-
|
|
62
|
-
return nil unless boxes&.is_a?(Array) && !boxes.empty?
|
|
63
|
-
|
|
64
|
-
# Intersect boxes with frame boundaries
|
|
65
|
-
intersect_bounding_boxes_with_frame(boxes)
|
|
66
|
-
|
|
67
|
-
# Find first box with valid dimensions
|
|
68
|
-
box = boxes.find { |rect| rect['width'] >= 1 && rect['height'] >= 1 }
|
|
69
|
-
return nil unless box
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
x: box['x'],
|
|
73
|
-
y: box['y'],
|
|
74
|
-
width: box['width'],
|
|
75
|
-
height: box['height']
|
|
76
|
-
}
|
|
77
|
-
end
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Viewport Clipping: intersectBoundingBoxesWithFrame
|
|
81
|
-
|
|
82
|
-
Clips element boxes to visible viewport boundaries:
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
def intersect_bounding_boxes_with_frame(boxes)
|
|
86
|
-
# Get document dimensions using element's evaluate
|
|
87
|
-
dimensions = evaluate(<<~JS)
|
|
88
|
-
element => {
|
|
89
|
-
return {
|
|
90
|
-
documentWidth: element.ownerDocument.documentElement.clientWidth,
|
|
91
|
-
documentHeight: element.ownerDocument.documentElement.clientHeight
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
JS
|
|
95
|
-
|
|
96
|
-
document_width = dimensions['documentWidth']
|
|
97
|
-
document_height = dimensions['documentHeight']
|
|
98
|
-
|
|
99
|
-
boxes.each do |box|
|
|
100
|
-
intersect_bounding_box(box, document_width, document_height)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def intersect_bounding_box(box, width, height)
|
|
105
|
-
# Clip width
|
|
106
|
-
box['width'] = [
|
|
107
|
-
box['x'] >= 0 ?
|
|
108
|
-
[width - box['x'], box['width']].min :
|
|
109
|
-
[width, box['width'] + box['x']].min,
|
|
110
|
-
0
|
|
111
|
-
].max
|
|
112
|
-
|
|
113
|
-
# Clip height
|
|
114
|
-
box['height'] = [
|
|
115
|
-
box['y'] >= 0 ?
|
|
116
|
-
[height - box['y'], box['height']].min :
|
|
117
|
-
[height, box['height'] + box['y']].min,
|
|
118
|
-
0
|
|
119
|
-
].max
|
|
120
|
-
|
|
121
|
-
# Ensure non-negative coordinates
|
|
122
|
-
box['x'] = [box['x'], 0].max
|
|
123
|
-
box['y'] = [box['y'], 0].max
|
|
124
|
-
end
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Why This Matters
|
|
128
|
-
|
|
129
|
-
### Use Cases for getClientRects()
|
|
130
|
-
|
|
131
|
-
1. **Wrapped text**: Multi-line links, buttons with text wrapping
|
|
132
|
-
2. **Inline elements**: `<span>` elements that span multiple lines
|
|
133
|
-
3. **Complex layouts**: Elements with transforms, rotations
|
|
134
|
-
|
|
135
|
-
### Algorithm Flow
|
|
136
|
-
|
|
137
|
-
1. **Get all client rects** for element (array of boxes)
|
|
138
|
-
2. **Clip to viewport** using intersectBoundingBox algorithm
|
|
139
|
-
3. **Find first valid box** (width >= 1 && height >= 1)
|
|
140
|
-
4. **Click center of that box**
|
|
141
|
-
|
|
142
|
-
## Puppeteer Reference
|
|
143
|
-
|
|
144
|
-
Based on [ElementHandle.ts#clickableBox](https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/api/ElementHandle.ts):
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
async #clickableBox(): Promise<BoundingBox | null> {
|
|
148
|
-
const boxes = await this.evaluate(element => {
|
|
149
|
-
if (!(element instanceof Element)) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
return [...element.getClientRects()].map(rect => {
|
|
153
|
-
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (!boxes?.length) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
await this.#intersectBoundingBoxesWithFrame(boxes);
|
|
162
|
-
|
|
163
|
-
// ... parent frame handling ...
|
|
164
|
-
|
|
165
|
-
const box = boxes.find(box => {
|
|
166
|
-
return box.width >= 1 && box.height >= 1;
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
return box || null;
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## Testing
|
|
174
|
-
|
|
175
|
-
### Test Asset
|
|
176
|
-
|
|
177
|
-
Official Puppeteer test asset: `spec/assets/wrappedlink.html`
|
|
178
|
-
|
|
179
|
-
```html
|
|
180
|
-
<div style="width: 10ch; word-wrap: break-word; transform: rotate(33deg);">
|
|
181
|
-
<a href='#clicked'>123321</a>
|
|
182
|
-
</div>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Critical**: Always use official test assets without modification!
|
|
186
|
-
|
|
187
|
-
### Test Case
|
|
188
|
-
|
|
189
|
-
```ruby
|
|
190
|
-
it 'should click wrapped links' do
|
|
191
|
-
with_test_state do |page:, server:, **|
|
|
192
|
-
page.goto("#{server.prefix}/wrappedlink.html")
|
|
193
|
-
page.click('a')
|
|
194
|
-
result = page.evaluate('window.__clicked')
|
|
195
|
-
expect(result).to be true
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Debugging Protocol Messages
|
|
201
|
-
|
|
202
|
-
Compare BiDi protocol messages with Puppeteer to verify coordinates:
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
# Ruby implementation
|
|
206
|
-
DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/click_spec.rb:138
|
|
207
|
-
|
|
208
|
-
# Look for input.performActions with click coordinates
|
|
209
|
-
# Verify they fall within actual element bounds
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Key Takeaways
|
|
213
|
-
|
|
214
|
-
1. **getClientRects() > getBoundingClientRect()** for clickable elements
|
|
215
|
-
2. **First valid box** is the click target (not center of bounding box)
|
|
216
|
-
3. **Viewport clipping** ensures clicks stay within visible area
|
|
217
|
-
4. **Test with official assets** - simplified versions hide edge cases
|
|
218
|
-
5. **Follow Puppeteer exactly** - algorithm has been battle-tested
|
|
219
|
-
|
|
220
|
-
## Performance
|
|
221
|
-
|
|
222
|
-
- `getClientRects()` is fast (native browser API)
|
|
223
|
-
- Intersection algorithm is O(n) where n = number of boxes (typically 1-3)
|
|
224
|
-
- No additional round-trips to browser
|
|
225
|
-
|
|
226
|
-
## Future: Parent Frame Support
|
|
227
|
-
|
|
228
|
-
For iframe support, add coordinate transformation:
|
|
229
|
-
|
|
230
|
-
```ruby
|
|
231
|
-
# TODO: Handle parent frames
|
|
232
|
-
frame = self.frame
|
|
233
|
-
while (parent_frame = frame.parent_frame)
|
|
234
|
-
# Adjust coordinates for parent frame offset
|
|
235
|
-
# boxes.each { |box| box['x'] += offset_x; box['y'] += offset_y }
|
|
236
|
-
end
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Files
|
|
240
|
-
|
|
241
|
-
- `lib/puppeteer/bidi/element_handle.rb`: clickable_box, intersect methods
|
|
242
|
-
- `spec/integration/click_spec.rb`: Test case for wrapped links
|
|
243
|
-
- `spec/assets/wrappedlink.html`: Official test asset (never modify!)
|
|
244
|
-
|
|
245
|
-
## Commit Reference
|
|
246
|
-
|
|
247
|
-
See commit: "Implement clickable_box with getClientRects() and viewport clipping"
|
data/CLAUDE.md
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# Puppeteer-BiDi Development Guide
|
|
2
|
-
|
|
3
|
-
## Project Overview
|
|
4
|
-
|
|
5
|
-
Port the WebDriver BiDi protocol portions of Puppeteer to Ruby, providing a standards-based tool for Firefox automation.
|
|
6
|
-
|
|
7
|
-
### Development Principles
|
|
8
|
-
|
|
9
|
-
- **BiDi-only**: Do not port CDP protocol-related code
|
|
10
|
-
- **Standards compliance**: Adhere to W3C WebDriver BiDi specification
|
|
11
|
-
- **Firefox optimization**: Maximize BiDi protocol capabilities
|
|
12
|
-
- **Ruby conventions**: Design Ruby-idiomatic interfaces
|
|
13
|
-
|
|
14
|
-
## Quick Reference
|
|
15
|
-
|
|
16
|
-
### Running Tests
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
# All integration tests (requires Firefox Nightly for full functionality)
|
|
20
|
-
bundle exec rspec spec/integration/
|
|
21
|
-
|
|
22
|
-
# Single test file
|
|
23
|
-
bundle exec rspec spec/integration/click_spec.rb
|
|
24
|
-
|
|
25
|
-
# Non-headless mode
|
|
26
|
-
HEADLESS=false bundle exec rspec spec/integration/
|
|
27
|
-
|
|
28
|
-
# Debug protocol messages
|
|
29
|
-
DEBUG_BIDI_COMMAND=1 bundle exec rspec spec/integration/click_spec.rb
|
|
30
|
-
|
|
31
|
-
# Use specific Firefox path (e.g., Nightly)
|
|
32
|
-
FIREFOX_PATH="/Applications/Firefox Nightly.app/Contents/MacOS/firefox" bundle exec rspec
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Note**: Some features (e.g., FileChooser) require Firefox Nightly. The browser launcher prioritizes Nightly automatically.
|
|
36
|
-
|
|
37
|
-
### Key Architecture
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
Upper Layer (Puppeteer::Bidi) - User-facing synchronous API
|
|
41
|
-
└── Page, Frame, ElementHandle, JSHandle
|
|
42
|
-
|
|
43
|
-
Core Layer (Puppeteer::Bidi::Core) - Async operations, returns Async::Task
|
|
44
|
-
└── Session, BrowsingContext, Realm
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Critical**: Upper layer methods must call `.wait` on all Core layer method calls. See [Two-Layer Architecture](CLAUDE/two_layer_architecture.md).
|
|
48
|
-
|
|
49
|
-
### Async Programming
|
|
50
|
-
|
|
51
|
-
This project uses **Async (Fiber-based)**, NOT concurrent-ruby (Thread-based).
|
|
52
|
-
|
|
53
|
-
- No Mutex needed (cooperative multitasking)
|
|
54
|
-
- Similar to JavaScript async/await
|
|
55
|
-
- See [Async Programming Guide](CLAUDE/async_programming.md)
|
|
56
|
-
|
|
57
|
-
## Development Workflow
|
|
58
|
-
|
|
59
|
-
1. Study Puppeteer's TypeScript implementation first
|
|
60
|
-
2. Understand BiDi protocol calls
|
|
61
|
-
3. Implement with proper deserialization
|
|
62
|
-
4. Port tests from Puppeteer
|
|
63
|
-
5. **Update `API_COVERAGE.md`** - Mark implemented methods as ✅ and update coverage count
|
|
64
|
-
6. See [Porting Puppeteer Guide](CLAUDE/porting_puppeteer.md)
|
|
65
|
-
|
|
66
|
-
## Coding Conventions
|
|
67
|
-
|
|
68
|
-
### Ruby
|
|
69
|
-
|
|
70
|
-
- Use Ruby 3.0+ features
|
|
71
|
-
- Follow RuboCop guidelines
|
|
72
|
-
- Class names: `PascalCase`, Methods: `snake_case`, Constants: `SCREAMING_SNAKE_CASE`
|
|
73
|
-
|
|
74
|
-
### Type Annotations (rbs-inline)
|
|
75
|
-
|
|
76
|
-
Use [rbs-inline](https://github.com/soutaro/rbs-inline) for type annotations in Ruby source files.
|
|
77
|
-
|
|
78
|
-
**Setup:**
|
|
79
|
-
- Add `# rbs_inline: enabled` magic comment at the top of the file
|
|
80
|
-
- Use Doc style syntax for type annotations
|
|
81
|
-
|
|
82
|
-
**Example:**
|
|
83
|
-
```ruby
|
|
84
|
-
# frozen_string_literal: true
|
|
85
|
-
# rbs_inline: enabled
|
|
86
|
-
|
|
87
|
-
class Example
|
|
88
|
-
attr_reader :name #: String
|
|
89
|
-
|
|
90
|
-
# @rbs name: String -- The name to set
|
|
91
|
-
# @rbs return: void
|
|
92
|
-
def initialize(name)
|
|
93
|
-
@name = name
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Query for an element matching the selector
|
|
97
|
-
# @rbs selector: String -- CSS selector to query
|
|
98
|
-
# @rbs return: ElementHandle? -- Matching element or nil
|
|
99
|
-
def query_selector(selector)
|
|
100
|
-
# ...
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**Conventions:**
|
|
106
|
-
- Use `A?` for nullable types (not `A | nil`)
|
|
107
|
-
- Use `A | B | nil` for union types with nil
|
|
108
|
-
- **Always include descriptions** with `--` separator: `# @rbs name: String -- the name`
|
|
109
|
-
- Public methods should have type annotations
|
|
110
|
-
- **Do NOT use `@rbs!` blocks** - RubyMine IDE doesn't recognize them
|
|
111
|
-
- **Use direct union types** instead of type aliases: `BrowserTarget | PageTarget | FrameTarget` not `target`
|
|
112
|
-
- **Do NOT use `**options` in public APIs** - RubyMine shows as `untyped`. Use explicit keyword arguments:
|
|
113
|
-
- Optional params: `param: nil`
|
|
114
|
-
- Boolean params with default: `headless: true` or `enabled: false`
|
|
115
|
-
- Internal/Core layer methods may still use `**options` for flexibility
|
|
116
|
-
|
|
117
|
-
**Generate RBS files:**
|
|
118
|
-
```bash
|
|
119
|
-
bundle exec rake rbs # Generates sig/**/*.rbs
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**Note:** `sig/` directory is gitignored. RBS files are generated automatically during `rake build`.
|
|
123
|
-
|
|
124
|
-
### Type Checking with Steep
|
|
125
|
-
|
|
126
|
-
Run type checking locally:
|
|
127
|
-
```bash
|
|
128
|
-
bundle exec rake rbs # Generate RBS files first
|
|
129
|
-
bundle exec steep check # Run type checker
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**Steepfile Configuration:**
|
|
133
|
-
- Currently configured with lenient `:hint` level diagnostics for gradual typing
|
|
134
|
-
- As type coverage improves, change to `:warning` or `:error` in `Steepfile`
|
|
135
|
-
|
|
136
|
-
**Special RBS Files (manually maintained, NOT gitignored):**
|
|
137
|
-
|
|
138
|
-
1. **`sig/_external.rbs`** - External dependency stubs
|
|
139
|
-
- Types for async gem (`Async`, `Kernel#Async`, `Kernel#Sync`, `Async::Task`, etc.)
|
|
140
|
-
- Standard library extensions (`Dir.mktmpdir`, `Time.parse`)
|
|
141
|
-
- Third-party types (`Singleton`, `Protocol::WebSocket`)
|
|
142
|
-
|
|
143
|
-
2. **`sig/_supplementary.rbs`** - Types rbs-inline cannot generate
|
|
144
|
-
- `extend self` pattern: Add `extend ModuleName` declaration
|
|
145
|
-
- Singleton pattern: Add `extend Singleton::SingletonClassMethods`
|
|
146
|
-
|
|
147
|
-
**Common Issues:**
|
|
148
|
-
|
|
149
|
-
1. **Type alias must be lowercase**: In RBS, `type target = ...` not `type Target = ...`
|
|
150
|
-
|
|
151
|
-
2. **`extend self` not recognized**: Add to `sig/_supplementary.rbs`:
|
|
152
|
-
```rbs
|
|
153
|
-
module Foo
|
|
154
|
-
extend Foo # Makes instance methods callable as singleton methods
|
|
155
|
-
end
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
3. **Singleton pattern**: Add to `sig/_supplementary.rbs`:
|
|
159
|
-
```rbs
|
|
160
|
-
class Bar
|
|
161
|
-
extend Singleton::SingletonClassMethods
|
|
162
|
-
end
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
4. **Missing external types**: Add stubs to `sig/_external.rbs`
|
|
166
|
-
|
|
167
|
-
### Testing
|
|
168
|
-
|
|
169
|
-
- Use RSpec for unit and integration tests
|
|
170
|
-
- Integration tests in `spec/integration/`
|
|
171
|
-
- Use `with_test_state` helper for browser reuse
|
|
172
|
-
|
|
173
|
-
### Test Assets
|
|
174
|
-
|
|
175
|
-
**CRITICAL**: Always use Puppeteer's official test assets without modification.
|
|
176
|
-
|
|
177
|
-
- Source: https://github.com/puppeteer/puppeteer/tree/main/test/assets
|
|
178
|
-
- Never modify files in `spec/assets/`
|
|
179
|
-
- Revert any experimental changes before PRs
|
|
180
|
-
|
|
181
|
-
## Detailed Documentation
|
|
182
|
-
|
|
183
|
-
See the [CLAUDE/](CLAUDE/) directory for detailed implementation guides:
|
|
184
|
-
|
|
185
|
-
### Architecture & Patterns
|
|
186
|
-
|
|
187
|
-
- **[Two-Layer Architecture](CLAUDE/two_layer_architecture.md)** - Core vs Upper layer, async patterns
|
|
188
|
-
- **[Async Programming](CLAUDE/async_programming.md)** - Fiber-based concurrency with socketry/async
|
|
189
|
-
- **[ReactorRunner](CLAUDE/reactor_runner.md)** - Using browser outside Sync blocks (at_exit, etc.)
|
|
190
|
-
- **[Porting Puppeteer](CLAUDE/porting_puppeteer.md)** - Best practices for implementing features
|
|
191
|
-
- **[Core Layer Gotchas](CLAUDE/core_layer_gotchas.md)** - EventEmitter/Disposable pitfalls, @disposed conflicts
|
|
192
|
-
|
|
193
|
-
### Implementation Details
|
|
194
|
-
|
|
195
|
-
- **[QueryHandler](CLAUDE/query_handler.md)** - CSS, XPath, text selector handling
|
|
196
|
-
- **[JavaScript Evaluation](CLAUDE/javascript_evaluation.md)** - `evaluate()` and `evaluate_handle()`
|
|
197
|
-
- **[JSHandle and ElementHandle](CLAUDE/jshandle_implementation.md)** - Object handle management
|
|
198
|
-
- **[Selector Evaluation](CLAUDE/selector_evaluation.md)** - `eval_on_selector` methods
|
|
199
|
-
- **[Click Implementation](CLAUDE/click_implementation.md)** - Mouse input and clicking
|
|
200
|
-
- **[Mouse Implementation](CLAUDE/mouse_implementation.md)** - wheel, reset, hover, BoundingBox/Point data classes
|
|
201
|
-
- **[Wrapped Element Click](CLAUDE/wrapped_element_click.md)** - getClientRects() for multi-line elements
|
|
202
|
-
- **[Navigation Waiting](CLAUDE/navigation_waiting.md)** - waitForNavigation patterns
|
|
203
|
-
- **[Frame Architecture](CLAUDE/frame_architecture.md)** - Parent-based frame hierarchy
|
|
204
|
-
- **[FileChooser](CLAUDE/file_chooser.md)** - File upload and dialog handling (requires Firefox Nightly)
|
|
205
|
-
- **[ExposeFunction](CLAUDE/expose_function_implementation.md)** - `exposeFunction` and `evaluateOnNewDocument` using BiDi script.message
|
|
206
|
-
- **[Error Handling](CLAUDE/error_handling.md)** - Custom exception types
|
|
207
|
-
|
|
208
|
-
### Testing
|
|
209
|
-
|
|
210
|
-
- **[Testing Strategy](CLAUDE/testing_strategy.md)** - Test organization and optimization
|
|
211
|
-
- **[RSpec pending vs skip](CLAUDE/rspec_pending_vs_skip.md)** - Documenting limitations
|
|
212
|
-
- **[Test Server Routes](CLAUDE/test_server_routes.md)** - Dynamic route handling
|
|
213
|
-
|
|
214
|
-
## Releasing
|
|
215
|
-
|
|
216
|
-
To release a new version:
|
|
217
|
-
|
|
218
|
-
1. Update the version number in `lib/puppeteer/bidi/version.rb`
|
|
219
|
-
2. Commit the change and push to main
|
|
220
|
-
3. Create and push a version tag:
|
|
221
|
-
```bash
|
|
222
|
-
git tag 1.2.3
|
|
223
|
-
git push origin 1.2.3
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
GitHub Actions will automatically build and publish the gem to RubyGems. Supported tag formats:
|
|
227
|
-
- `1.2.3` - stable release
|
|
228
|
-
- `1.2.3.alpha1` - alpha release
|
|
229
|
-
- `1.2.3.beta2` - beta release
|
|
230
|
-
|
|
231
|
-
**Note:** `RUBYGEMS_API_KEY` must be configured in repository secrets.
|
|
232
|
-
|
|
233
|
-
## References
|
|
234
|
-
|
|
235
|
-
- [WebDriver BiDi Specification](https://w3c.github.io/webdriver-bidi/)
|
|
236
|
-
- [Puppeteer Documentation](https://pptr.dev/)
|
|
237
|
-
- [Puppeteer Source Code](https://github.com/puppeteer/puppeteer)
|
|
238
|
-
- [puppeteer-ruby](https://github.com/YusukeIwaki/puppeteer-ruby) - CDP implementation reference
|