puppeteer-ruby 0.0.2

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +36 -0
  5. data/.travis.yml +7 -0
  6. data/Dockerfile +6 -0
  7. data/Gemfile +6 -0
  8. data/README.md +41 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +11 -0
  11. data/bin/setup +8 -0
  12. data/docker-compose.yml +15 -0
  13. data/example.rb +7 -0
  14. data/lib/puppeteer.rb +192 -0
  15. data/lib/puppeteer/async_await_behavior.rb +34 -0
  16. data/lib/puppeteer/browser.rb +240 -0
  17. data/lib/puppeteer/browser_context.rb +90 -0
  18. data/lib/puppeteer/browser_fetcher.rb +6 -0
  19. data/lib/puppeteer/browser_runner.rb +142 -0
  20. data/lib/puppeteer/cdp_session.rb +78 -0
  21. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  22. data/lib/puppeteer/connection.rb +254 -0
  23. data/lib/puppeteer/console_message.rb +24 -0
  24. data/lib/puppeteer/debug_print.rb +20 -0
  25. data/lib/puppeteer/device.rb +12 -0
  26. data/lib/puppeteer/devices.rb +885 -0
  27. data/lib/puppeteer/dom_world.rb +447 -0
  28. data/lib/puppeteer/element_handle.rb +433 -0
  29. data/lib/puppeteer/emulation_manager.rb +46 -0
  30. data/lib/puppeteer/errors.rb +4 -0
  31. data/lib/puppeteer/event_callbackable.rb +88 -0
  32. data/lib/puppeteer/execution_context.rb +230 -0
  33. data/lib/puppeteer/frame.rb +278 -0
  34. data/lib/puppeteer/frame_manager.rb +380 -0
  35. data/lib/puppeteer/if_present.rb +18 -0
  36. data/lib/puppeteer/js_handle.rb +142 -0
  37. data/lib/puppeteer/keyboard.rb +183 -0
  38. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  39. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  40. data/lib/puppeteer/launcher.rb +26 -0
  41. data/lib/puppeteer/launcher/base.rb +48 -0
  42. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  43. data/lib/puppeteer/launcher/chrome.rb +165 -0
  44. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  45. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  46. data/lib/puppeteer/lifecycle_watcher.rb +168 -0
  47. data/lib/puppeteer/mouse.rb +120 -0
  48. data/lib/puppeteer/network_manager.rb +122 -0
  49. data/lib/puppeteer/page.rb +1001 -0
  50. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  51. data/lib/puppeteer/remote_object.rb +124 -0
  52. data/lib/puppeteer/target.rb +150 -0
  53. data/lib/puppeteer/timeout_settings.rb +15 -0
  54. data/lib/puppeteer/touch_screen.rb +43 -0
  55. data/lib/puppeteer/version.rb +3 -0
  56. data/lib/puppeteer/viewport.rb +36 -0
  57. data/lib/puppeteer/wait_task.rb +6 -0
  58. data/lib/puppeteer/web_socket.rb +117 -0
  59. data/lib/puppeteer/web_socket_transport.rb +49 -0
  60. data/puppeteer-ruby.gemspec +29 -0
  61. metadata +213 -0
@@ -0,0 +1,46 @@
1
+ class Puppeteer::EmulationManager
2
+ using Puppeteer::AsyncAwaitBehavior
3
+
4
+ # @param {!Puppeteer.CDPSession} client
5
+ def initialize(client)
6
+ @client = client
7
+ @emulating_mobile = false
8
+ @has_touch = false
9
+ end
10
+
11
+ # @param viewport [Puppeteer::Viewport]
12
+ # @return [true|false]
13
+ def emulate_viewport(viewport)
14
+ mobile = viewport.mobile?
15
+ width = viewport.width
16
+ height = viewport.height
17
+ device_scale_factor = viewport.device_scale_factor
18
+ # /** @type {Protocol.Emulation.ScreenOrientation} */
19
+ # const screenOrientation = viewport.isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
20
+ has_touch = viewport.has_touch?
21
+
22
+ await_all(
23
+ @client.async_send_message('Emulation.setDeviceMetricsOverride',
24
+ mobile: mobile,
25
+ width: width,
26
+ height: height,
27
+ deviceScaleFactor: device_scale_factor,
28
+ # screenOrientation: screen_orientation,
29
+ ),
30
+ @client.async_send_message('Emulation.setTouchEmulationEnabled',
31
+ enabled: has_touch,
32
+ ),
33
+ )
34
+
35
+ reload_needed = @emulating_mobile != mobile || @hasTouch != has_touch;
36
+ @emulating_mobile = mobile
37
+ @has_touch = has_touch
38
+ return reload_needed
39
+ end
40
+
41
+ # @param viewport [Puppeteer::Viewport]
42
+ # @return [Future<true|false>]
43
+ async def async_emulate_viewport(viewport)
44
+ emulate_viewport(viewport)
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ # ref: https://github.com/puppeteer/puppeteer/blob/master/lib/Errors.js
2
+ class Puppeteer::TimeoutError < StandardError ; end
3
+
4
+ class Puppeteer::WebSocktTransportError < StandardError ; end
@@ -0,0 +1,88 @@
1
+ require 'securerandom'
2
+
3
+ module Puppeteer::EventCallbackable
4
+ class EventListeners
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @listeners = {}
9
+ end
10
+
11
+ # @return [String] Listener ID
12
+ def add(&block)
13
+ id = SecureRandom.hex(8)
14
+ @listeners[id] = block
15
+ id
16
+ end
17
+
18
+ # @param id [String] Listener ID returned on #add
19
+ def delete(id)
20
+ @listeners.delete(id)
21
+ end
22
+
23
+ # @implement Enumerable#each
24
+ def each(&block)
25
+ @listeners.values.each(&block)
26
+ end
27
+ end
28
+
29
+ def add_event_listener(event_name, &block)
30
+ @event_listeners ||= {}
31
+ (@event_listeners[event_name] ||= EventListeners.new).add(&block)
32
+ end
33
+
34
+ def remove_event_listener(*id_args)
35
+ (@event_listeners ||= {}).each do |event_name, listeners|
36
+ id_args.each do |id|
37
+ listeners.delete(id)
38
+ end
39
+ end
40
+ end
41
+
42
+ def on_event(event_name, &block)
43
+ @event_callbackable_handlers ||= {}
44
+ @event_callbackable_handlers[event_name] = block
45
+ end
46
+
47
+ def emit_event(event_name, *args, **kwargs)
48
+ @event_callbackable_handlers ||= {}
49
+ @event_listeners ||= {}
50
+
51
+ if kwargs.empty?
52
+ # In Ruby's specification (version < 2.7),
53
+ # `method(:x).call(*args, **kwargs)` is equivalent to `x(*args, {})`
54
+ # It often causes unexpected ArgumentError.
55
+ #
56
+ # ----------------
57
+ # def greet
58
+ # puts 'Hello!'
59
+ # end
60
+ #
61
+ # def call_me(*args, **kwargs)
62
+ # greet(*args, **kwargs) # => 'Hello!'
63
+ #
64
+ # method(:greet).call(*args, **kwargs) # => `greet': wrong number of arguments (given 1, expected 0) (ArgumentError)
65
+ # end
66
+ #
67
+ # call_me
68
+ # ----------------
69
+ #
70
+ # This behavior is really annoying, and should be avoided, because we often want to set event handler as below:
71
+ #
72
+ # `on_event 'Some.Event.awesome', &method(:handle_awesome_event)`
73
+ #
74
+ # So Let's avoid it by checking kwargs.
75
+ @event_callbackable_handlers[event_name]&.call(*args)
76
+ @event_listeners[event_name]&.each do |proc|
77
+ proc.call(*args)
78
+ end
79
+ else
80
+ @event_callbackable_handlers[event_name]&.call(*args, **kwargs)
81
+ @event_listeners[event_name]&.each do |proc|
82
+ proc.call(*args, **kwargs)
83
+ end
84
+ end
85
+
86
+ event_name
87
+ end
88
+ end
@@ -0,0 +1,230 @@
1
+ class Puppeteer::ExecutionContext
2
+ include Puppeteer::IfPresent
3
+ using Puppeteer::AsyncAwaitBehavior
4
+
5
+ EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__'
6
+ SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m
7
+
8
+ # @param client [Puppeteer::CDPSession]
9
+ # @param context_payload [Hash]
10
+ # @param world [Puppeteer::DOMWorld?]
11
+ def initialize(client, context_payload, world)
12
+ @client = client
13
+ @world = world
14
+ @context_id = context_payload['id']
15
+ end
16
+
17
+ attr_reader :client, :world
18
+
19
+ # @return [Puppeteer::Frame]
20
+ def frame
21
+ if_present(@world) do |world|
22
+ world.frame
23
+ end
24
+ end
25
+
26
+ # @param page_function [String]
27
+ # @return [Object]
28
+ def evaluate(page_function, *args)
29
+ evaluate_internal(true, page_function, *args)
30
+ end
31
+
32
+ # @param page_function [String]
33
+ # @return [Puppeteer::JSHandle]
34
+ def evaluate_handle(page_function, *args)
35
+ evaluate_internal(false, page_function, *args)
36
+ end
37
+
38
+ class JavaScriptExpression
39
+ def initialize(execution_context, expression, return_by_value)
40
+ @execution_context = execution_context
41
+ @expression = expression
42
+ @return_by_value = return_by_value
43
+ end
44
+
45
+ # @param client [Puppeteer::CDPSession]
46
+ # @param context_id [String]
47
+ # @return [Object|JSHandle]
48
+ def evaluate_with(client:, context_id:)
49
+ result = client.send_message('Runtime.evaluate',
50
+ expression: expression_with_source_url,
51
+ contextId: context_id,
52
+ returnByValue: @return_by_value,
53
+ awaitPromise: true,
54
+ userGesture: true,
55
+ )
56
+ # }).catch(rewriteError);
57
+
58
+ exception_details = result["exceptionDetails"]
59
+ if exception_details
60
+ raise EvaluationError.new("Evaluation failed: #{exception_details}")
61
+ end
62
+
63
+ remote_object = Puppeteer::RemoteObject.new(result['result'])
64
+ if @return_by_value
65
+ remote_object.value
66
+ else
67
+ Puppeteer::JSHandle.create(
68
+ context: @execution_context,
69
+ remote_object: remote_object,
70
+ )
71
+ end
72
+ end
73
+
74
+ private def suffix
75
+ "//# sourceURL=#{EVALUATION_SCRIPT_URL}"
76
+ end
77
+
78
+ private def expression_with_source_url
79
+ if SOURCE_URL_REGEX.match?(@expression)
80
+ @expression
81
+ else
82
+ "#{@expression}\n#{suffix}"
83
+ end
84
+ end
85
+ end
86
+
87
+ class JavaScriptFunction
88
+ include Puppeteer::IfPresent
89
+
90
+ def initialize(execution_context, expression, args, return_by_value)
91
+ @execution_context = execution_context
92
+ @expression = expression
93
+ @return_by_value = return_by_value
94
+ @args = args
95
+ end
96
+
97
+ # @param client [Puppeteer::CDPSession]
98
+ # @param context_id [String]
99
+ # @return [Object|JSHandle]
100
+ def evaluate_with(client:, context_id:)
101
+ # `function` can be omitted in JS after ES2015.
102
+ # https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer
103
+ #
104
+ # Original puppeteer implementation take it into consideration.
105
+ # But we don't support the syntax here.
106
+
107
+ result = client.send_message('Runtime.callFunctionOn',
108
+ functionDeclaration: "#{@expression}\n#{suffix}\n",
109
+ executionContextId: context_id,
110
+ arguments: converted_args,
111
+ returnByValue: @return_by_value,
112
+ awaitPromise: true,
113
+ userGesture: true,
114
+ ) #.catch(rewriteError);
115
+
116
+ exception_details = result["exceptionDetails"]
117
+ remote_object = Puppeteer::RemoteObject.new(result["result"])
118
+
119
+ if exception_details
120
+ raise EvaluationError.new("Evaluation failed: #{exceptionDetails}")
121
+ end
122
+
123
+ if @return_by_value
124
+ remote_object.value
125
+ else
126
+ Puppeteer::JSHandle.create(
127
+ context: @execution_context,
128
+ remote_object: remote_object,
129
+ )
130
+ end
131
+ end
132
+
133
+ private def converted_args
134
+ # if (typeof arg === 'bigint') // eslint-disable-line valid-typeof
135
+ # return { unserializableValue: `${arg.toString()}n` };
136
+ # if (Object.is(arg, -0))
137
+ # return { unserializableValue: '-0' };
138
+ # if (Object.is(arg, Infinity))
139
+ # return { unserializableValue: 'Infinity' };
140
+ # if (Object.is(arg, -Infinity))
141
+ # return { unserializableValue: '-Infinity' };
142
+ # if (Object.is(arg, NaN))
143
+ # return { unserializableValue: 'NaN' };
144
+ @args.map do |arg|
145
+ if arg && arg.is_a?(Puppeteer::JSHandle)
146
+ if arg.context != @execution_context
147
+ raise EvaluationError.new('JSHandles can be evaluated only in the context they were created!')
148
+ elsif arg.disposed?
149
+ raise EvaluationError.new('JSHandles is disposed!')
150
+ end
151
+
152
+ arg.remote_object.converted_arg
153
+ else
154
+ { value: arg }
155
+ end
156
+ end
157
+ end
158
+
159
+ # /**
160
+ # * @param {!Error} error
161
+ # * @return {!Protocol.Runtime.evaluateReturnValue}
162
+ # */
163
+ # function rewriteError(error) {
164
+ # if (error.message.includes('Object reference chain is too long'))
165
+ # return {result: {type: 'undefined'}};
166
+ # if (error.message.includes('Object couldn\'t be returned by value'))
167
+ # return {result: {type: 'undefined'}};
168
+
169
+ # if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed'))
170
+ # throw new Error('Execution context was destroyed, most likely because of a navigation.');
171
+ # throw error;
172
+ # }
173
+
174
+ private def suffix
175
+ "//# sourceURL=#{EVALUATION_SCRIPT_URL}"
176
+ end
177
+ end
178
+
179
+ class EvaluationError < StandardError ; end
180
+
181
+ # @param return_by_value [Boolean]
182
+ # @param page_function [String]
183
+ # @return [Object|Puppeteer::JSHandle]
184
+ private def evaluate_internal(return_by_value, page_function, *args)
185
+ # `function` can be omitted in JS after ES2015.
186
+ # https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer
187
+ # But we don't support the syntax here.
188
+ js_object =
189
+ if ["=>", "async", "function"].any? { |keyword| page_function.include?(keyword) }
190
+ JavaScriptFunction.new(self, page_function, args, return_by_value)
191
+ else
192
+ JavaScriptExpression.new(self, page_function, return_by_value)
193
+ end
194
+
195
+ js_object.evaluate_with(
196
+ client: @client,
197
+ context_id: @context_id,
198
+ )
199
+ end
200
+
201
+ # /**
202
+ # * @param {!JSHandle} prototypeHandle
203
+ # * @return {!Promise<!JSHandle>}
204
+ # */
205
+ # async queryObjects(prototypeHandle) {
206
+ # assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
207
+ # assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
208
+ # const response = await this._client.send('Runtime.queryObjects', {
209
+ # prototypeObjectId: prototypeHandle._remoteObject.objectId
210
+ # });
211
+ # return createJSHandle(this, response.objects);
212
+ # }
213
+
214
+ # /**
215
+ # * @param {Puppeteer.ElementHandle} elementHandle
216
+ # * @return {Promise<Puppeteer.ElementHandle>}
217
+ # */
218
+ # async _adoptElementHandle(elementHandle) {
219
+ # assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
220
+ # assert(this._world, 'Cannot adopt handle without DOMWorld');
221
+ # const nodeInfo = await this._client.send('DOM.describeNode', {
222
+ # objectId: elementHandle._remoteObject.objectId,
223
+ # });
224
+ # const {object} = await this._client.send('DOM.resolveNode', {
225
+ # backendNodeId: nodeInfo.node.backendNodeId,
226
+ # executionContextId: this._contextId,
227
+ # });
228
+ # return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
229
+ # }
230
+ end
@@ -0,0 +1,278 @@
1
+ class Puppeteer::Frame
2
+ # @param {!FrameManager} frameManager
3
+ # @param {!Puppeteer.CDPSession} client
4
+ # @param {?Frame} parentFrame
5
+ # @param {string} frameId
6
+ def initialize(frame_manager, client, parent_frame, frame_id)
7
+ @frame_manager = frame_manager
8
+ @client = client
9
+ @parent_frame = parent_frame
10
+ @id = frame_id
11
+ @detached = false
12
+
13
+ @loader_id = ''
14
+ @lifecycle_events = Set.new
15
+ @main_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
16
+ @secondary_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
17
+ @child_frames = Set.new
18
+ if parent_frame
19
+ parent_frame._child_frames << self
20
+ end
21
+ end
22
+
23
+ attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world
24
+
25
+ # @param url [String]
26
+ # @param rederer [String]
27
+ # @param timeout [number|nil]
28
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
29
+ # @return [Puppeteer::Response]
30
+ def goto(url, referer: nil, timeout: nil, wait_until: nil)
31
+ @frame_manager.navigate_frame(self, url, referer: referer, timeout: timeout, wait_until: wait_until)
32
+ end
33
+
34
+ # @param timeout [number|nil]
35
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
36
+ def wait_for_navigation(timeout: nil, wait_until: nil)
37
+ @frame_manager.wait_for_frame_navigation(self, timeout: timeout, wait_until: wait_until)
38
+ end
39
+
40
+ def execution_context
41
+ @main_world.execution_context
42
+ end
43
+
44
+ # @param {Function|string} pageFunction
45
+ # @return {!Promise<!Puppeteer.JSHandle>}
46
+ def evaluate_handle(page_function, *args)
47
+ @main_world.evaluate_handle(page_function, *args)
48
+ end
49
+
50
+ # @param {Function|string} pageFunction
51
+ # @param {!Array<*>} args
52
+ def evaluate(page_function, *args)
53
+ @main_world.evaluate(page_function, *args)
54
+ end
55
+
56
+ # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
57
+ # @param {string} selector
58
+ # @return {!Promise<?Puppeteer.ElementHandle>}
59
+ def S(selector)
60
+ @main_world.S(selector)
61
+ end
62
+
63
+
64
+ # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
65
+ # @param {string} expression
66
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
67
+ def Sx(expression)
68
+ @main_world.Sx(expression)
69
+ end
70
+
71
+
72
+ # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
73
+ # @param {string} selector
74
+ # @param {Function|string} pageFunction
75
+ # @param {!Array<*>} args
76
+ # @return {!Promise<(!Object|undefined)>}
77
+ def Seval(selector, page_function, *args)
78
+ @main_world.Seval(selector, page_function, *args)
79
+ end
80
+
81
+ # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
82
+ # @param {string} selector
83
+ # @param {Function|string} pageFunction
84
+ # @param {!Array<*>} args
85
+ # @return {!Promise<(!Object|undefined)>}
86
+ def SSeval(selector, page_function, *args)
87
+ @main_world.SSeval(selector, page_function, *args)
88
+ end
89
+
90
+ # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
91
+ # @param {string} selector
92
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
93
+ def SS(selector)
94
+ @main_world.SS(selector)
95
+ end
96
+
97
+ def content
98
+ @secondary_world.content
99
+ end
100
+
101
+ # @param {string} html
102
+ # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
103
+ def set_content(html, timeout: nil, wait_until: nil)
104
+ @secondary_world.set_content(html, timeout: timeout, wait_until: wait_until)
105
+ end
106
+
107
+ # @return [String]
108
+ def name
109
+ @name || ''
110
+ end
111
+
112
+ # @return [String]
113
+ def url
114
+ @url
115
+ end
116
+
117
+ # @return [Frame?]
118
+ def parent_frame
119
+ @parent_frame
120
+ end
121
+
122
+ protected def _child_frames
123
+ @child_frames
124
+ end
125
+
126
+ def child_frames
127
+ @child_frames.dup
128
+ end
129
+
130
+ def detached?
131
+ @detached
132
+ end
133
+
134
+ # @param style_tag [Puppeteer::Page::ScriptTag]
135
+ # @return {!Promise<!ElementHandle>}
136
+ def add_script_tag(script_tag)
137
+ @main_world.add_script_tag(script_tag)
138
+ end
139
+
140
+ # @param style_tag [Puppeteer::Page::StyleTag]
141
+ # @return {!Promise<!ElementHandle>}
142
+ def add_style_tag(style_tag)
143
+ @main_world.add_style_tag(style_tag)
144
+ end
145
+
146
+ # @param {string} selector
147
+ # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
148
+ def click(selector, delay: nil, button: nil, click_count: nil)
149
+ @secondary_world.click(selector, delay: delay, button: button, click_count: click_count)
150
+ end
151
+
152
+ # @param {string} selector
153
+ def focus(selector)
154
+ @secondary_world.focus(selector)
155
+ end
156
+
157
+ # @param {string} selector
158
+ def hover(selector)
159
+ @secondary_world.hover(selector)
160
+ end
161
+
162
+ # @param {string} selector
163
+ # @param {!Array<string>} values
164
+ # @return {!Promise<!Array<string>>}
165
+ def select(selector, *values)
166
+ @secondary_world.select(selector, *values)
167
+ end
168
+
169
+ # @param {string} selector
170
+ def tap(selector)
171
+ @secondary_world.tap(selector)
172
+ end
173
+
174
+ # @param {string} selector
175
+ # @param {string} text
176
+ # @param {{delay: (number|undefined)}=} options
177
+ def type(selector, text, delay: nil)
178
+ @main_world.type(selector, text, delay: delay)
179
+ end
180
+
181
+ # /**
182
+ # * @param {(string|number|Function)} selectorOrFunctionOrTimeout
183
+ # * @param {!Object=} options
184
+ # * @param {!Array<*>} args
185
+ # * @return {!Promise<?Puppeteer.JSHandle>}
186
+ # */
187
+ # waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
188
+ # const xPathPattern = '//';
189
+
190
+ # if (helper.isString(selectorOrFunctionOrTimeout)) {
191
+ # const string = /** @type {string} */ (selectorOrFunctionOrTimeout);
192
+ # if (string.startsWith(xPathPattern))
193
+ # return this.waitForXPath(string, options);
194
+ # return this.waitForSelector(string, options);
195
+ # }
196
+ # if (helper.isNumber(selectorOrFunctionOrTimeout))
197
+ # return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
198
+ # if (typeof selectorOrFunctionOrTimeout === 'function')
199
+ # return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
200
+ # return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
201
+ # }
202
+
203
+ # @param {string} selector
204
+ # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
205
+ # @return {!Promise<?Puppeteer.ElementHandle>}
206
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
207
+ handle = @secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
208
+ if !handle
209
+ return nil
210
+ end
211
+ main_execution_context = @main_world.execution_context
212
+ result = main_execution_context.adopt_element_handle(handle)
213
+ handle.dispose
214
+ return result
215
+ end
216
+
217
+ # @param {string} xpath
218
+ # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
219
+ # @return {!Promise<?Puppeteer.ElementHandle>}
220
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
221
+ handle = @secondary_world.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
222
+ if !handle
223
+ return nil
224
+ end
225
+ main_execution_context = @main_world.execution_context
226
+ result = main_execution_context.adopt_element_handle(handle)
227
+ handle.dispose
228
+ return result
229
+ end
230
+
231
+ # @param {Function|string} pageFunction
232
+ # @param {!{polling?: string|number, timeout?: number}=} options
233
+ # @param {!Array<*>} args
234
+ # @return {!Promise<!Puppeteer.JSHandle>}
235
+ def wait_for_function(page_function, options = {}, *args)
236
+ @main_world.wait_for_function(page_function, options, *args)
237
+ end
238
+
239
+ def title
240
+ @secondary_world.title
241
+ end
242
+
243
+ # @param frame_payload [Hash]
244
+ def navigated(frame_payload)
245
+ @name = frame_payload['name']
246
+ # TODO(lushnikov): remove this once requestInterception has loaderId exposed.
247
+ @navigation_url = frame_payload['url']
248
+ @url = frame_payload['url']
249
+ end
250
+
251
+ # @param url [String]
252
+ def navigated_within_document(url)
253
+ @url = url
254
+ end
255
+
256
+ def handle_lifecycle_event(loader_id, name)
257
+ if name == 'init'
258
+ @loader_id = loader_id
259
+ @lifecycle_events.clear
260
+ end
261
+ @lifecycle_events << name
262
+ end
263
+
264
+ def handle_loading_stopped
265
+ @lifecycle_events << 'DOMContentLoaded'
266
+ @lifecycle_events << 'load'
267
+ end
268
+
269
+ def detach
270
+ @detached = true
271
+ # this._mainWorld._detach();
272
+ # this._secondaryWorld._detach();
273
+ if @parent_frame
274
+ @parent_frame._child_frames.delete(self)
275
+ end
276
+ @parent_frame = nil
277
+ end
278
+ end