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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CLAUDE/README.md +158 -0
- data/CLAUDE/async_programming.md +158 -0
- data/CLAUDE/click_implementation.md +340 -0
- data/CLAUDE/core_layer_gotchas.md +136 -0
- data/CLAUDE/error_handling.md +232 -0
- data/CLAUDE/file_chooser.md +95 -0
- data/CLAUDE/frame_architecture.md +346 -0
- data/CLAUDE/javascript_evaluation.md +341 -0
- data/CLAUDE/jshandle_implementation.md +505 -0
- data/CLAUDE/keyboard_implementation.md +250 -0
- data/CLAUDE/mouse_implementation.md +140 -0
- data/CLAUDE/navigation_waiting.md +234 -0
- data/CLAUDE/porting_puppeteer.md +214 -0
- data/CLAUDE/query_handler.md +194 -0
- data/CLAUDE/rspec_pending_vs_skip.md +262 -0
- data/CLAUDE/selector_evaluation.md +198 -0
- data/CLAUDE/test_server_routes.md +263 -0
- data/CLAUDE/testing_strategy.md +236 -0
- data/CLAUDE/two_layer_architecture.md +180 -0
- data/CLAUDE/wrapped_element_click.md +247 -0
- data/CLAUDE.md +185 -0
- data/LICENSE.txt +21 -0
- data/README.md +488 -0
- data/Rakefile +21 -0
- data/lib/puppeteer/bidi/async_utils.rb +151 -0
- data/lib/puppeteer/bidi/browser.rb +285 -0
- data/lib/puppeteer/bidi/browser_context.rb +53 -0
- data/lib/puppeteer/bidi/browser_launcher.rb +240 -0
- data/lib/puppeteer/bidi/connection.rb +182 -0
- data/lib/puppeteer/bidi/core/README.md +169 -0
- data/lib/puppeteer/bidi/core/browser.rb +230 -0
- data/lib/puppeteer/bidi/core/browsing_context.rb +601 -0
- data/lib/puppeteer/bidi/core/disposable.rb +69 -0
- data/lib/puppeteer/bidi/core/errors.rb +64 -0
- data/lib/puppeteer/bidi/core/event_emitter.rb +83 -0
- data/lib/puppeteer/bidi/core/navigation.rb +128 -0
- data/lib/puppeteer/bidi/core/realm.rb +315 -0
- data/lib/puppeteer/bidi/core/request.rb +300 -0
- data/lib/puppeteer/bidi/core/session.rb +153 -0
- data/lib/puppeteer/bidi/core/user_context.rb +208 -0
- data/lib/puppeteer/bidi/core/user_prompt.rb +102 -0
- data/lib/puppeteer/bidi/core.rb +45 -0
- data/lib/puppeteer/bidi/deserializer.rb +132 -0
- data/lib/puppeteer/bidi/element_handle.rb +602 -0
- data/lib/puppeteer/bidi/errors.rb +42 -0
- data/lib/puppeteer/bidi/file_chooser.rb +52 -0
- data/lib/puppeteer/bidi/frame.rb +597 -0
- data/lib/puppeteer/bidi/http_response.rb +23 -0
- data/lib/puppeteer/bidi/injected.js +1 -0
- data/lib/puppeteer/bidi/injected_source.rb +21 -0
- data/lib/puppeteer/bidi/js_handle.rb +302 -0
- data/lib/puppeteer/bidi/keyboard.rb +265 -0
- data/lib/puppeteer/bidi/lazy_arg.rb +23 -0
- data/lib/puppeteer/bidi/mouse.rb +170 -0
- data/lib/puppeteer/bidi/page.rb +613 -0
- data/lib/puppeteer/bidi/query_handler.rb +397 -0
- data/lib/puppeteer/bidi/realm.rb +242 -0
- data/lib/puppeteer/bidi/serializer.rb +139 -0
- data/lib/puppeteer/bidi/target.rb +81 -0
- data/lib/puppeteer/bidi/task_manager.rb +44 -0
- data/lib/puppeteer/bidi/timeout_settings.rb +20 -0
- data/lib/puppeteer/bidi/transport.rb +129 -0
- data/lib/puppeteer/bidi/version.rb +7 -0
- data/lib/puppeteer/bidi/wait_task.rb +322 -0
- data/lib/puppeteer/bidi.rb +49 -0
- data/scripts/update_injected_source.rb +57 -0
- data/sig/puppeteer/bidi/browser.rbs +80 -0
- data/sig/puppeteer/bidi/element_handle.rbs +238 -0
- data/sig/puppeteer/bidi/frame.rbs +205 -0
- data/sig/puppeteer/bidi/js_handle.rbs +90 -0
- data/sig/puppeteer/bidi/page.rbs +247 -0
- data/sig/puppeteer/bidi.rbs +15 -0
- metadata +176 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puppeteer
|
|
4
|
+
module Bidi
|
|
5
|
+
module Core
|
|
6
|
+
# UserPrompt represents a user prompt (alert, confirm, prompt)
|
|
7
|
+
class UserPrompt < EventEmitter
|
|
8
|
+
include Disposable::DisposableMixin
|
|
9
|
+
|
|
10
|
+
# Create a user prompt instance
|
|
11
|
+
# @param browsing_context [BrowsingContext] The browsing context
|
|
12
|
+
# @param info [Hash] The userPromptOpened event data
|
|
13
|
+
# @return [UserPrompt] New user prompt instance
|
|
14
|
+
def self.from(browsing_context, info)
|
|
15
|
+
prompt = new(browsing_context, info)
|
|
16
|
+
prompt.send(:initialize_prompt)
|
|
17
|
+
prompt
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :browsing_context, :info, :result
|
|
21
|
+
|
|
22
|
+
def initialize(browsing_context, info)
|
|
23
|
+
super()
|
|
24
|
+
@browsing_context = browsing_context
|
|
25
|
+
@info = info
|
|
26
|
+
@reason = nil
|
|
27
|
+
@result = nil
|
|
28
|
+
@disposables = Disposable::DisposableStack.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if the prompt is closed
|
|
32
|
+
def closed?
|
|
33
|
+
!@reason.nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias disposed? closed?
|
|
37
|
+
|
|
38
|
+
# Check if the prompt has been handled
|
|
39
|
+
# Auto-handled prompts return true immediately
|
|
40
|
+
# @return [Boolean] Whether the prompt is handled
|
|
41
|
+
def handled?
|
|
42
|
+
handler = @info['handler']
|
|
43
|
+
return true if handler == 'accept' || handler == 'dismiss'
|
|
44
|
+
!@result.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Handle the user prompt
|
|
48
|
+
# @param accept [Boolean, nil] Whether to accept the prompt
|
|
49
|
+
# @param user_text [String, nil] Text to enter (for prompt dialogs)
|
|
50
|
+
# @return [Hash] Result of handling the prompt
|
|
51
|
+
def handle(accept: nil, user_text: nil)
|
|
52
|
+
raise UserPromptClosedError, @reason if closed?
|
|
53
|
+
|
|
54
|
+
params = { context: @info['context'] }
|
|
55
|
+
params[:accept] = accept unless accept.nil?
|
|
56
|
+
params[:userText] = user_text if user_text
|
|
57
|
+
|
|
58
|
+
session.send_command('browsingContext.handleUserPrompt', params)
|
|
59
|
+
|
|
60
|
+
# The result is set by the userPromptClosed event before this returns
|
|
61
|
+
@result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
protected
|
|
65
|
+
|
|
66
|
+
def perform_dispose
|
|
67
|
+
@reason ||= 'User prompt closed, probably because the browsing context was destroyed'
|
|
68
|
+
emit(:closed, { reason: @reason })
|
|
69
|
+
@disposables.dispose
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def session
|
|
76
|
+
@browsing_context.user_context.browser.session
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def initialize_prompt
|
|
80
|
+
# Listen for browsing context closure
|
|
81
|
+
@browsing_context.once(:closed) do |data|
|
|
82
|
+
dispose_prompt("User prompt closed: #{data[:reason]}")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Listen for prompt closed event
|
|
86
|
+
session.on('browsingContext.userPromptClosed') do |params|
|
|
87
|
+
next unless params['context'] == @browsing_context.id
|
|
88
|
+
|
|
89
|
+
@result = params
|
|
90
|
+
emit(:handled, params)
|
|
91
|
+
dispose_prompt('User prompt handled')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def dispose_prompt(reason)
|
|
96
|
+
@reason = reason
|
|
97
|
+
dispose
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Core module provides low-level BiDi protocol abstractions
|
|
4
|
+
# This layer sits above the WebSocket transport and provides
|
|
5
|
+
# object-oriented semantics around WebDriver BiDi's flat API.
|
|
6
|
+
#
|
|
7
|
+
# Design principles:
|
|
8
|
+
# - Required arguments are method parameters, optional ones are keyword arguments
|
|
9
|
+
# - Session is never exposed on public methods except on Browser
|
|
10
|
+
# - Follows WebDriver BiDi spec strictly, not Puppeteer's needs
|
|
11
|
+
# - Implements BiDi comprehensively but minimally
|
|
12
|
+
|
|
13
|
+
require 'puppeteer/bidi/core/errors'
|
|
14
|
+
require 'puppeteer/bidi/core/event_emitter'
|
|
15
|
+
require 'puppeteer/bidi/core/disposable'
|
|
16
|
+
require 'puppeteer/bidi/core/request'
|
|
17
|
+
require 'puppeteer/bidi/core/navigation'
|
|
18
|
+
require 'puppeteer/bidi/core/realm'
|
|
19
|
+
require 'puppeteer/bidi/core/session'
|
|
20
|
+
require 'puppeteer/bidi/core/browser'
|
|
21
|
+
require 'puppeteer/bidi/core/user_context'
|
|
22
|
+
require 'puppeteer/bidi/core/browsing_context'
|
|
23
|
+
require 'puppeteer/bidi/core/user_prompt'
|
|
24
|
+
|
|
25
|
+
module Puppeteer
|
|
26
|
+
module Bidi
|
|
27
|
+
# Core module containing low-level BiDi protocol classes
|
|
28
|
+
module Core
|
|
29
|
+
# This module provides:
|
|
30
|
+
# - EventEmitter: Event subscription and emission
|
|
31
|
+
# - Disposable: Resource management and cleanup
|
|
32
|
+
# - Session: BiDi session management
|
|
33
|
+
# - Browser: Browser instance management
|
|
34
|
+
# - UserContext: Isolated browsing contexts (incognito-like)
|
|
35
|
+
# - BrowsingContext: Individual tabs/windows/frames
|
|
36
|
+
# - Navigation: Navigation tracking
|
|
37
|
+
# - Realm: JavaScript execution contexts
|
|
38
|
+
# - WindowRealm: Window/iframe realms
|
|
39
|
+
# - DedicatedWorkerRealm: Dedicated worker realms
|
|
40
|
+
# - SharedWorkerRealm: Shared worker realms
|
|
41
|
+
# - Request: Network request management
|
|
42
|
+
# - UserPrompt: User prompt (alert/confirm/prompt) handling
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'set'
|
|
5
|
+
|
|
6
|
+
module Puppeteer
|
|
7
|
+
module Bidi
|
|
8
|
+
# Deserializer converts BiDi Script.RemoteValue to Ruby values
|
|
9
|
+
# Based on Puppeteer's Deserializer.ts
|
|
10
|
+
class Deserializer
|
|
11
|
+
class << self
|
|
12
|
+
# Deserialize a BiDi RemoteValue to Ruby value
|
|
13
|
+
# @param remote_value [Hash] BiDi RemoteValue
|
|
14
|
+
# @param realm [Core::Realm, nil] Realm for creating handles (optional)
|
|
15
|
+
# @return [Object] Ruby value or JSHandle/ElementHandle if realm provided
|
|
16
|
+
def deserialize(remote_value, realm = nil)
|
|
17
|
+
return remote_value unless remote_value.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
type = remote_value['type']
|
|
20
|
+
|
|
21
|
+
case type
|
|
22
|
+
when 'string'
|
|
23
|
+
remote_value['value']
|
|
24
|
+
when 'number'
|
|
25
|
+
deserialize_number(remote_value['value'])
|
|
26
|
+
when 'boolean'
|
|
27
|
+
remote_value['value']
|
|
28
|
+
when 'null'
|
|
29
|
+
nil
|
|
30
|
+
when 'undefined'
|
|
31
|
+
nil
|
|
32
|
+
when 'bigint'
|
|
33
|
+
# BiDi sends bigint as string, convert to Integer
|
|
34
|
+
remote_value['value'].to_i
|
|
35
|
+
when 'array'
|
|
36
|
+
# Deserialize array elements
|
|
37
|
+
return [] unless remote_value['value']
|
|
38
|
+
remote_value['value'].map { |item| deserialize(item, realm) }
|
|
39
|
+
when 'set'
|
|
40
|
+
# Deserialize set elements and return Ruby Set
|
|
41
|
+
return Set.new unless remote_value['value']
|
|
42
|
+
Set.new(remote_value['value'].map { |item| deserialize(item, realm) })
|
|
43
|
+
when 'object'
|
|
44
|
+
# Object is an array of [key, value] tuples
|
|
45
|
+
deserialize_object(remote_value['value'], realm)
|
|
46
|
+
when 'map'
|
|
47
|
+
# Map is an array of [key, value] tuples with non-string keys
|
|
48
|
+
deserialize_map(remote_value['value'], realm)
|
|
49
|
+
when 'regexp'
|
|
50
|
+
# Reconstruct Ruby Regexp
|
|
51
|
+
deserialize_regexp(remote_value['value'])
|
|
52
|
+
when 'date'
|
|
53
|
+
# Parse ISO 8601 datetime string
|
|
54
|
+
Time.parse(remote_value['value'])
|
|
55
|
+
when 'promise'
|
|
56
|
+
# Promise placeholder - return empty hash
|
|
57
|
+
{}
|
|
58
|
+
when 'node'
|
|
59
|
+
# DOM node - create ElementHandle if realm provided
|
|
60
|
+
if realm
|
|
61
|
+
ElementHandle.new(realm, remote_value)
|
|
62
|
+
else
|
|
63
|
+
# Without realm, return the remote value as-is
|
|
64
|
+
remote_value
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
# Unknown type - create JSHandle if realm provided
|
|
68
|
+
if realm
|
|
69
|
+
JSHandle.new(realm, remote_value)
|
|
70
|
+
else
|
|
71
|
+
# Without realm, return value as-is or nil
|
|
72
|
+
remote_value['value']
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Deserialize a BiDi number value (handles special strings)
|
|
80
|
+
def deserialize_number(value)
|
|
81
|
+
case value
|
|
82
|
+
when 'NaN'
|
|
83
|
+
Float::NAN
|
|
84
|
+
when 'Infinity'
|
|
85
|
+
Float::INFINITY
|
|
86
|
+
when '-Infinity'
|
|
87
|
+
-Float::INFINITY
|
|
88
|
+
when '-0'
|
|
89
|
+
-0.0
|
|
90
|
+
else
|
|
91
|
+
value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Deserialize BiDi object (array of [key, value] tuples) to Ruby Hash
|
|
96
|
+
def deserialize_object(tuples, realm)
|
|
97
|
+
return {} unless tuples
|
|
98
|
+
|
|
99
|
+
tuples.each_with_object({}) do |(key, value), hash|
|
|
100
|
+
# Keys in objects are always strings in BiDi
|
|
101
|
+
hash[key] = deserialize(value, realm)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Deserialize BiDi map (array of [key, value] tuples) to Ruby Hash
|
|
106
|
+
# Maps can have non-string keys, so we deserialize keys too
|
|
107
|
+
def deserialize_map(tuples, realm)
|
|
108
|
+
return {} unless tuples
|
|
109
|
+
|
|
110
|
+
tuples.each_with_object({}) do |pair, hash|
|
|
111
|
+
key = deserialize(pair[0], realm)
|
|
112
|
+
value = deserialize(pair[1], realm)
|
|
113
|
+
hash[key] = value
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Deserialize BiDi regexp to Ruby Regexp
|
|
118
|
+
def deserialize_regexp(value)
|
|
119
|
+
pattern = value['pattern']
|
|
120
|
+
flags_str = value['flags'] || ''
|
|
121
|
+
|
|
122
|
+
flags = 0
|
|
123
|
+
flags |= Regexp::IGNORECASE if flags_str.include?('i')
|
|
124
|
+
flags |= Regexp::MULTILINE if flags_str.include?('m')
|
|
125
|
+
flags |= Regexp::EXTENDED if flags_str.include?('x')
|
|
126
|
+
|
|
127
|
+
Regexp.new(pattern, flags)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|