puppeteer-ruby 0.0.3 → 0.0.8
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/.circleci/config.yml +30 -0
- data/.github/stale.yml +16 -0
- data/.rubocop.yml +4 -5
- data/README.md +4 -1
- data/docs/Puppeteer.html +2020 -0
- data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
- data/docs/Puppeteer/Browser.html +2150 -0
- data/docs/Puppeteer/BrowserContext.html +809 -0
- data/docs/Puppeteer/BrowserFetcher.html +214 -0
- data/docs/Puppeteer/BrowserRunner.html +914 -0
- data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
- data/docs/Puppeteer/CDPSession.html +813 -0
- data/docs/Puppeteer/CDPSession/Error.html +124 -0
- data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
- data/docs/Puppeteer/Connection.html +960 -0
- data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
- data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
- data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
- data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
- data/docs/Puppeteer/ConsoleMessage.html +565 -0
- data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
- data/docs/Puppeteer/DOMWorld.html +2219 -0
- data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
- data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
- data/docs/Puppeteer/DebugPrint.html +233 -0
- data/docs/Puppeteer/Device.html +470 -0
- data/docs/Puppeteer/Devices.html +139 -0
- data/docs/Puppeteer/ElementHandle.html +2224 -0
- data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
- data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
- data/docs/Puppeteer/ElementHandle/Point.html +481 -0
- data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
- data/docs/Puppeteer/EmulationManager.html +454 -0
- data/docs/Puppeteer/EventCallbackable.html +433 -0
- data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
- data/docs/Puppeteer/ExecutionContext.html +998 -0
- data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
- data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
- data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
- data/docs/Puppeteer/FileChooser.html +455 -0
- data/docs/Puppeteer/Frame.html +3677 -0
- data/docs/Puppeteer/FrameManager.html +2414 -0
- data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
- data/docs/Puppeteer/IfPresent.html +222 -0
- data/docs/Puppeteer/JSHandle.html +1352 -0
- data/docs/Puppeteer/Keyboard.html +1557 -0
- data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
- data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
- data/docs/Puppeteer/Launcher.html +237 -0
- data/docs/Puppeteer/Launcher/Base.html +385 -0
- data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
- data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
- data/docs/Puppeteer/Launcher/Chrome.html +669 -0
- data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
- data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
- data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
- data/docs/Puppeteer/LifecycleWatcher.html +834 -0
- data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
- data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
- data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
- data/docs/Puppeteer/Mouse.html +1105 -0
- data/docs/Puppeteer/Mouse/Button.html +136 -0
- data/docs/Puppeteer/NetworkManager.html +901 -0
- data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
- data/docs/Puppeteer/Page.html +5970 -0
- data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
- data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
- data/docs/Puppeteer/Page/ScriptTag.html +555 -0
- data/docs/Puppeteer/Page/StyleTag.html +448 -0
- data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
- data/docs/Puppeteer/RemoteObject.html +1016 -0
- data/docs/Puppeteer/Target.html +1384 -0
- data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
- data/docs/Puppeteer/Target/TargetInfo.html +729 -0
- data/docs/Puppeteer/TimeoutError.html +135 -0
- data/docs/Puppeteer/TimeoutSettings.html +496 -0
- data/docs/Puppeteer/TouchScreen.html +464 -0
- data/docs/Puppeteer/Viewport.html +757 -0
- data/docs/Puppeteer/WaitTask.html +637 -0
- data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
- data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
- data/docs/Puppeteer/WebSocket.html +673 -0
- data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
- data/docs/Puppeteer/WebSocketTransport.html +600 -0
- data/docs/Puppeteer/WebSocktTransportError.html +124 -0
- data/docs/_index.html +809 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +496 -0
- data/docs/file.README.html +123 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +123 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +3979 -0
- data/docs/top-level-namespace.html +126 -0
- data/lib/puppeteer.rb +16 -8
- data/lib/puppeteer/async_await_behavior.rb +6 -0
- data/lib/puppeteer/browser.rb +21 -1
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/cdp_session.rb +33 -11
- data/lib/puppeteer/connection.rb +1 -1
- data/lib/puppeteer/dom_world.rb +142 -121
- data/lib/puppeteer/element_handle.rb +223 -181
- data/lib/puppeteer/execution_context.rb +41 -17
- data/lib/puppeteer/file_chooser.rb +29 -0
- data/lib/puppeteer/frame.rb +23 -15
- data/lib/puppeteer/frame_manager.rb +7 -9
- data/lib/puppeteer/js_handle.rb +3 -3
- data/lib/puppeteer/keyboard.rb +1 -1
- data/lib/puppeteer/keyboard/us_keyboard_layout.rb +4 -4
- data/lib/puppeteer/launcher.rb +0 -1
- data/lib/puppeteer/launcher/chrome.rb +48 -2
- data/lib/puppeteer/lifecycle_watcher.rb +9 -4
- data/lib/puppeteer/mouse.rb +10 -7
- data/lib/puppeteer/page.rb +134 -70
- data/lib/puppeteer/remote_object.rb +11 -1
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +183 -1
- data/puppeteer-ruby.gemspec +4 -1
- metadata +143 -4
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'mime/types'
|
|
2
|
+
|
|
1
3
|
class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
2
4
|
include Puppeteer::IfPresent
|
|
3
5
|
using Puppeteer::AsyncAwaitBehavior
|
|
@@ -28,62 +30,80 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# if (error)
|
|
54
|
-
# throw new Error(error);
|
|
55
|
-
# }
|
|
33
|
+
class ScrollIntoViewError < StandardError; end
|
|
34
|
+
|
|
35
|
+
def scroll_into_view_if_needed
|
|
36
|
+
js = <<~JAVASCRIPT
|
|
37
|
+
async(element, pageJavascriptEnabled) => {
|
|
38
|
+
if (!element.isConnected)
|
|
39
|
+
return 'Node is detached from document';
|
|
40
|
+
if (element.nodeType !== Node.ELEMENT_NODE)
|
|
41
|
+
return 'Node is not of type HTMLElement';
|
|
42
|
+
|
|
43
|
+
element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
JAVASCRIPT
|
|
47
|
+
error = evaluate(js, @page.javascript_enabled) # returns String or false
|
|
48
|
+
if error
|
|
49
|
+
raise ScrollIntoViewError.new(error)
|
|
50
|
+
end
|
|
51
|
+
# clickpoint is often calculated before scrolling is completed.
|
|
52
|
+
# So, just sleep about 10 frames
|
|
53
|
+
sleep 0.16
|
|
54
|
+
end
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
56
|
+
class Point
|
|
57
|
+
def initialize(x:, y:)
|
|
58
|
+
@x = x
|
|
59
|
+
@y = y
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def +(other)
|
|
63
|
+
Point.new(
|
|
64
|
+
x: @x + other.x,
|
|
65
|
+
y: @y + other.y,
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def /(num)
|
|
70
|
+
Point.new(
|
|
71
|
+
x: @x / num,
|
|
72
|
+
y: @y / num,
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
attr_reader :x, :y
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class ElementNotVisibleError < StandardError
|
|
80
|
+
def initialize
|
|
81
|
+
super("Node is either not visible or not an HTMLElement")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def clickable_point
|
|
86
|
+
result = @remote_object.content_quads(@client)
|
|
87
|
+
if !result || result["quads"].empty?
|
|
88
|
+
raise ElementNotVisibleError.new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Filter out quads that have too small area to click into.
|
|
92
|
+
layout_metrics = @client.send_message('Page.getLayoutMetrics')
|
|
93
|
+
client_width = layout_metrics["layoutViewport"]["clientWidth"]
|
|
94
|
+
client_height = layout_metrics["layoutViewport"]["clientHeight"]
|
|
95
|
+
|
|
96
|
+
quads = result["quads"].
|
|
97
|
+
map { |quad| from_protocol_quad(quad) }.
|
|
98
|
+
map { |quad| intersect_quad_with_viewport(quad, client_width, client_height) }.
|
|
99
|
+
select { |quad| compute_quad_area(quad) > 1 }
|
|
100
|
+
if quads.empty?
|
|
101
|
+
raise ElementNotVisibleError.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Return the middle point of the first quad.
|
|
105
|
+
quads.first.reduce(:+) / 4
|
|
106
|
+
end
|
|
87
107
|
|
|
88
108
|
# /**
|
|
89
109
|
# * @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
|
|
@@ -94,31 +114,26 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
|
94
114
|
# }).catch(error => debugError(error));
|
|
95
115
|
# }
|
|
96
116
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# {x: quad[2], y: quad[3]},
|
|
105
|
-
# {x: quad[4], y: quad[5]},
|
|
106
|
-
# {x: quad[6], y: quad[7]}
|
|
107
|
-
# ];
|
|
108
|
-
# }
|
|
117
|
+
# @param quad [Array<number>]
|
|
118
|
+
# @return [Array<Point>]
|
|
119
|
+
private def from_protocol_quad(quad)
|
|
120
|
+
quad.each_slice(2).map do |x, y|
|
|
121
|
+
Point.new(x: x, y: y)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
109
124
|
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
# @param quad [Array<Point>]
|
|
126
|
+
# @param width [number]
|
|
127
|
+
# @param height [number]
|
|
128
|
+
# @return [Array<Point>]
|
|
129
|
+
private def intersect_quad_with_viewport(quad, width, height)
|
|
130
|
+
quad.map do |point|
|
|
131
|
+
Point.new(
|
|
132
|
+
x: [[point.x, 0].max, width].min,
|
|
133
|
+
y: [[point.y, 0].max, height].min,
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
122
137
|
|
|
123
138
|
# async hover() {
|
|
124
139
|
# await this._scrollIntoViewIfNeeded();
|
|
@@ -126,83 +141,93 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
|
126
141
|
# await this._page.mouse.move(x, y);
|
|
127
142
|
# }
|
|
128
143
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
# @param delay [Number]
|
|
145
|
+
# @param button [String] "left"|"right"|"middle"
|
|
146
|
+
# @param click_count [Number]
|
|
147
|
+
def click(delay: nil, button: nil, click_count: nil)
|
|
148
|
+
scroll_into_view_if_needed
|
|
149
|
+
point = clickable_point
|
|
150
|
+
@page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
|
|
151
|
+
end
|
|
137
152
|
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
# assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
|
145
|
-
# return this.evaluate((element, values) => {
|
|
146
|
-
# if (element.nodeName.toLowerCase() !== 'select')
|
|
147
|
-
# throw new Error('Element is not a <select> element.');
|
|
148
|
-
|
|
149
|
-
# const options = Array.from(element.options);
|
|
150
|
-
# element.value = undefined;
|
|
151
|
-
# for (const option of options) {
|
|
152
|
-
# option.selected = values.includes(option.value);
|
|
153
|
-
# if (option.selected && !element.multiple)
|
|
154
|
-
# break;
|
|
155
|
-
# }
|
|
156
|
-
# element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
157
|
-
# element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
158
|
-
# return options.filter(option => option.selected).map(option => option.value);
|
|
159
|
-
# }, values);
|
|
160
|
-
# }
|
|
153
|
+
# @param delay [Number]
|
|
154
|
+
# @param button [String] "left"|"right"|"middle"
|
|
155
|
+
# @param click_count [Number]
|
|
156
|
+
async def async_click(delay: nil, button: nil, click_count: nil)
|
|
157
|
+
click(delay: delay, button: button, click_count: click_count)
|
|
158
|
+
end
|
|
161
159
|
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>');
|
|
168
|
-
# // These imports are only needed for `uploadFile`, so keep them
|
|
169
|
-
# // scoped here to avoid paying the cost unnecessarily.
|
|
170
|
-
# const path = require('path');
|
|
171
|
-
# const mime = require('mime-types');
|
|
172
|
-
# const fs = require('fs');
|
|
173
|
-
# const readFileAsync = helper.promisify(fs.readFile);
|
|
174
|
-
|
|
175
|
-
# const promises = filePaths.map(filePath => readFileAsync(filePath));
|
|
176
|
-
# const files = [];
|
|
177
|
-
# for (let i = 0; i < filePaths.length; i++) {
|
|
178
|
-
# const buffer = await promises[i];
|
|
179
|
-
# const filePath = path.basename(filePaths[i]);
|
|
180
|
-
# const file = {
|
|
181
|
-
# name: filePath,
|
|
182
|
-
# content: buffer.toString('base64'),
|
|
183
|
-
# mimeType: mime.lookup(filePath),
|
|
184
|
-
# };
|
|
185
|
-
# files.push(file);
|
|
186
|
-
# }
|
|
187
|
-
# await this.evaluateHandle(async(element, files) => {
|
|
188
|
-
# const dt = new DataTransfer();
|
|
189
|
-
# for (const item of files) {
|
|
190
|
-
# const response = await fetch(`data:${item.mimeType};base64,${item.content}`);
|
|
191
|
-
# const file = new File([await response.blob()], item.name);
|
|
192
|
-
# dt.items.add(file);
|
|
193
|
-
# }
|
|
194
|
-
# element.files = dt.files;
|
|
195
|
-
# element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
196
|
-
# element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
197
|
-
# }, files);
|
|
198
|
-
# }
|
|
160
|
+
# @return [Array<String>]
|
|
161
|
+
def select(*values)
|
|
162
|
+
if nonstring = values.find { |value| !value.is_a?(String) }
|
|
163
|
+
raise ArgumentError.new("Values must be strings. Found value \"#{nonstring}\" of type \"#{nonstring.class}\"")
|
|
164
|
+
end
|
|
199
165
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
166
|
+
fn = <<~JAVASCRIPT
|
|
167
|
+
(element, values) => {
|
|
168
|
+
if (element.nodeName.toLowerCase() !== 'select') {
|
|
169
|
+
throw new Error('Element is not a <select> element.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const options = Array.from(element.options);
|
|
173
|
+
element.value = undefined;
|
|
174
|
+
for (const option of options) {
|
|
175
|
+
option.selected = values.includes(option.value);
|
|
176
|
+
if (option.selected && !element.multiple) {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
181
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
182
|
+
return options.filter(option => option.selected).map(option => option.value);
|
|
183
|
+
}
|
|
184
|
+
JAVASCRIPT
|
|
185
|
+
evaluate(fn, values)
|
|
186
|
+
end
|
|
205
187
|
|
|
188
|
+
# @param file_paths [Array<String>]
|
|
189
|
+
def upload_file(*file_paths)
|
|
190
|
+
is_multiple = evaluate("el => el.multiple")
|
|
191
|
+
if !is_multiple && file_paths.length >= 2
|
|
192
|
+
raise ArgumentError.new('Multiple file uploads only work with <input type=file multiple>')
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if error_path = file_paths.find { |file_path| !File.exist?(file_path) }
|
|
196
|
+
raise ArgmentError.new("#{error_path} does not exist or is not readable")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
backend_node_id = @remote_object.node_info(@client)["node"]["backendNodeId"]
|
|
200
|
+
|
|
201
|
+
# The zero-length array is a special case, it seems that DOM.setFileInputFiles does
|
|
202
|
+
# not actually update the files in that case, so the solution is to eval the element
|
|
203
|
+
# value to a new FileList directly.
|
|
204
|
+
if file_paths.empty?
|
|
205
|
+
fn = <<~JAVASCRIPT
|
|
206
|
+
(element) => {
|
|
207
|
+
element.files = new DataTransfer().files;
|
|
208
|
+
|
|
209
|
+
// Dispatch events for this case because it should behave akin to a user action.
|
|
210
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
211
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
212
|
+
}
|
|
213
|
+
JAVASCRIPT
|
|
214
|
+
await this.evaluate(fn)
|
|
215
|
+
else
|
|
216
|
+
@remote_object.set_file_input_files(@client, file_paths, backend_node_id)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def tap(&block)
|
|
221
|
+
return super(&block) if block
|
|
222
|
+
|
|
223
|
+
scroll_into_view_if_needed
|
|
224
|
+
point = clickable_point
|
|
225
|
+
@page.touchscreen.tap(point.x, point.y)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
async def async_tap
|
|
229
|
+
tap
|
|
230
|
+
end
|
|
206
231
|
|
|
207
232
|
def focus
|
|
208
233
|
evaluate('element => element.focus()')
|
|
@@ -373,47 +398,57 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
|
373
398
|
result
|
|
374
399
|
end
|
|
375
400
|
|
|
401
|
+
# `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
|
402
|
+
# @param selector [String]
|
|
403
|
+
# @param page_function [String]
|
|
404
|
+
# @return [Object]
|
|
405
|
+
async def async_Seval(selector, page_function, *args)
|
|
406
|
+
Seval(selector, page_function, *args)
|
|
407
|
+
end
|
|
408
|
+
|
|
376
409
|
# `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
|
377
410
|
# @param selector [String]
|
|
378
411
|
# @param page_function [String]
|
|
379
412
|
# @return [Object]
|
|
380
413
|
def SSeval(selector, page_function, *args)
|
|
381
414
|
handles = evaluate_handle(
|
|
382
|
-
|
|
383
|
-
|
|
415
|
+
'(element, selector) => Array.from(element.querySelectorAll(selector))',
|
|
416
|
+
selector,
|
|
417
|
+
)
|
|
384
418
|
result = handles.evaluate(page_function, *args)
|
|
385
419
|
handles.dispose
|
|
386
420
|
|
|
387
421
|
result
|
|
388
422
|
end
|
|
389
423
|
|
|
390
|
-
#
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
424
|
+
# `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
|
425
|
+
# @param selector [String]
|
|
426
|
+
# @param page_function [String]
|
|
427
|
+
# @return [Object]
|
|
428
|
+
async def async_SSeval(selector, page_function, *args)
|
|
429
|
+
SSeval(selector, page_function, *args)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
|
433
|
+
# @param expression [String]
|
|
434
|
+
# @return [Array<ElementHandle>]
|
|
435
|
+
def Sx(expression)
|
|
436
|
+
fn = <<~JAVASCRIPT
|
|
437
|
+
(element, expression) => {
|
|
438
|
+
const document = element.ownerDocument || element;
|
|
439
|
+
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
|
|
440
|
+
const array = [];
|
|
441
|
+
let item;
|
|
442
|
+
while ((item = iterator.iterateNext()))
|
|
443
|
+
array.push(item);
|
|
444
|
+
return array;
|
|
445
|
+
}
|
|
446
|
+
JAVASCRIPT
|
|
447
|
+
handles = evaluate_handle(fn, expression)
|
|
448
|
+
properties = handles.properties
|
|
449
|
+
handles.dispose
|
|
450
|
+
properties.values.map(&:as_element).compact
|
|
451
|
+
end
|
|
417
452
|
|
|
418
453
|
# /**
|
|
419
454
|
# * @returns {!Promise<boolean>}
|
|
@@ -430,4 +465,11 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
|
430
465
|
# return visibleRatio > 0;
|
|
431
466
|
# });
|
|
432
467
|
# }
|
|
468
|
+
|
|
469
|
+
# @param quad [Array<Point>]
|
|
470
|
+
private def compute_quad_area(quad)
|
|
471
|
+
# Compute sum of all directed areas of adjacent triangles
|
|
472
|
+
# https://en.wikipedia.org/wiki/Polygon#Simple_polygons
|
|
473
|
+
quad.zip(quad.rotate).map { |p1, p2| (p1.x * p2.y - p2.x * p1.y) / 2 }.reduce(:+).abs
|
|
474
|
+
end
|
|
433
475
|
end
|