puppeteer-ruby 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 499744d122eeb8736a1466f17cf9ee09235f488fb19a95d3902da5529406dab0
4
- data.tar.gz: f6b27f599887d385c46914e1b255f9738fd7f946a0ff0a4f83a04075e50e092c
3
+ metadata.gz: 2764adee69056c36a0112866aa1f35a97ccb655ed81bc2977be9707efed7d121
4
+ data.tar.gz: 8919e7f0228b6dff08a1ee13906c97ad2d08552b1fd7652bc094b506bc9ac4e5
5
5
  SHA512:
6
- metadata.gz: 54ece5985462bc3f4f30c1a803fd43a5975845911657431aa33d78d70fba6e242d447869a03b3ad6cf925e0e94315db2df62b4d9d74d8b00e381b8d95326e311
7
- data.tar.gz: d7d3ab0af4b1bd69588b3998d0a47031d04d80fe3d0b7b55ec1f4b1a9b3ccd683c857faf06e103b972b9b59d63bda6fdc3e984f410720eeae17922176d518561
6
+ metadata.gz: c142553b4316d8cf7cf541e41283f01c982fc2f0ece3ffb1f87c258130035e3894062831364284fdfaa6c97771029687232955f7a4e64cb24e8d876aee36f08d
7
+ data.tar.gz: 5d59f78bb651c955617b0a0fee01b02b9e5aa4907d46d15cc79acf559db8f44d1a2a96ecd76571d3431737bfa8b9fd8a24a9a2ad80c5c8c0cc9754c42d1b75af
data/.github/stale.yml ADDED
@@ -0,0 +1,16 @@
1
+ # Number of days of inactivity before an issue becomes stale
2
+ daysUntilStale: 14
3
+ # Number of days of inactivity before a stale issue is closed
4
+ daysUntilClose: 7
5
+ # Issues with these labels will never be considered stale
6
+ exemptLabels:
7
+ - security
8
+ # Label to use when marking an issue as stale
9
+ staleLabel: inactive
10
+ # Comment to post when marking an issue as stale. Set to `false` to disable
11
+ markComment: >
12
+ This issue has been automatically marked as stale because it has not had
13
+ recent activity. It will be closed if no further activity occurs. Thank you
14
+ for your contributions.
15
+ # Comment to post when closing a stale issue. Set to `false` to disable
16
+ closeComment: false
data/README.md CHANGED
@@ -46,6 +46,8 @@ end
46
46
  ![puppeteer-ruby](https://user-images.githubusercontent.com/11763113/78505735-6e7f3000-77b0-11ea-9c82-9016828dd2a9.gif)
47
47
 
48
48
 
49
+ More usage examples can be found [here](https://github.com/YusukeIwaki/puppeteer-ruby-example)
50
+
49
51
  ## Contributing
50
52
 
51
53
  Bug reports and pull requests are welcome on GitHub at https://github.com/YusukeIwaki/puppeteer-ruby.
@@ -44,8 +44,8 @@ class Puppeteer::CDPSession
44
44
  callback.reject(
45
45
  Puppeteer::Connection::ProtocolError.new(
46
46
  method: callback.method,
47
- error_message: response['error']['message'],
48
- error_data: response['error']['data']))
47
+ error_message: message['error']['message'],
48
+ error_data: message['error']['data']))
49
49
  else
50
50
  callback.resolve(message['result'])
51
51
  end
@@ -12,6 +12,7 @@ class Puppeteer::DOMWorld
12
12
  @frame = frame
13
13
  @timeout_settings = timeout_settings
14
14
  @context_promise = resolvable_future
15
+ @pending_destroy = []
15
16
  @wait_tasks = Set.new
16
17
  @detached = false
17
18
  end
@@ -29,20 +30,24 @@ class Puppeteer::DOMWorld
29
30
 
30
31
  if context
31
32
  if @context_promise.fulfilled?
33
+ @pending_destroy << context._context_id
32
34
  @document = nil
33
35
  @context_promise = resolvable_future
34
- @pending_destroy_exists = true
35
36
  end
36
37
  @context_promise.fulfill(context)
37
38
  # for (const waitTask of this._waitTasks)
38
39
  # waitTask.rerun();
39
40
  else
40
- if @pending_destroy_exists
41
- @pending_destroy_exists = false
42
- else
43
- @document = nil
44
- @context_promise = resolvable_future
45
- end
41
+ raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
42
+ end
43
+ end
44
+
45
+ def delete_context(execution_context_id)
46
+ if @pending_destroy.include?(execution_context_id)
47
+ @pending_destroy.delete(execution_context_id)
48
+ else
49
+ @document = nil
50
+ @context_promise = resolvable_future
46
51
  end
47
52
  end
48
53
 
@@ -303,16 +308,15 @@ class Puppeteer::DOMWorld
303
308
  # }
304
309
  # }
305
310
 
306
- # /**
307
- # * @param {string} selector
308
- # * @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
309
- # */
310
- # async click(selector, options) {
311
- # const handle = await this.$(selector);
312
- # assert(handle, 'No node found for selector: ' + selector);
313
- # await handle.click(options);
314
- # await handle.dispose();
315
- # }
311
+ # @param selector [String]
312
+ # @param delay [Number]
313
+ # @param button [String] "left"|"right"|"middle"
314
+ # @param click_count [Number]
315
+ def click(selector, delay: nil, button: nil, click_count: nil)
316
+ handle = S(selector)
317
+ handle.click(delay: delay, button: button, click_count: click_count)
318
+ handle.dispose
319
+ end
316
320
 
317
321
  # /**
318
322
  # * @param {string} selector
@@ -28,62 +28,89 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
28
28
  end
29
29
  end
30
30
 
31
- # async _scrollIntoViewIfNeeded() {
32
- # const error = await this.evaluate(async(element, pageJavascriptEnabled) => {
33
- # if (!element.isConnected)
34
- # return 'Node is detached from document';
35
- # if (element.nodeType !== Node.ELEMENT_NODE)
36
- # return 'Node is not of type HTMLElement';
37
- # // force-scroll if page's javascript is disabled.
38
- # if (!pageJavascriptEnabled) {
39
- # element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
40
- # return false;
41
- # }
42
- # const visibleRatio = await new Promise(resolve => {
43
- # const observer = new IntersectionObserver(entries => {
44
- # resolve(entries[0].intersectionRatio);
45
- # observer.disconnect();
46
- # });
47
- # observer.observe(element);
48
- # });
49
- # if (visibleRatio !== 1.0)
50
- # element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
51
- # return false;
52
- # }, this._page._javascriptEnabled);
53
- # if (error)
54
- # throw new Error(error);
55
- # }
31
+ class ScrollIntoViewError < StandardError; end
32
+
33
+ def scroll_into_view_if_needed
34
+ js = <<~JAVASCRIPT
35
+ async(element, pageJavascriptEnabled) => {
36
+ if (!element.isConnected)
37
+ return 'Node is detached from document';
38
+ if (element.nodeType !== Node.ELEMENT_NODE)
39
+ return 'Node is not of type HTMLElement';
40
+ // force-scroll if page's javascript is disabled.
41
+ if (!pageJavascriptEnabled) {
42
+ element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
43
+ return false;
44
+ }
45
+ const visibleRatio = await new Promise(resolve => {
46
+ const observer = new IntersectionObserver(entries => {
47
+ resolve(entries[0].intersectionRatio);
48
+ observer.disconnect();
49
+ });
50
+ observer.observe(element);
51
+ });
52
+ if (visibleRatio !== 1.0)
53
+ element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
54
+ return false;
55
+ }
56
+ JAVASCRIPT
57
+ error = evaluate(js, @page.javascript_enabled) # returns String or false
58
+ if error
59
+ raise ScrollIntoViewError.new(error)
60
+ end
61
+ end
56
62
 
57
- # /**
58
- # * @return {!Promise<!{x: number, y: number}>}
59
- # */
60
- # async _clickablePoint() {
61
- # const [result, layoutMetrics] = await Promise.all([
62
- # this._client.send('DOM.getContentQuads', {
63
- # objectId: this._remoteObject.objectId
64
- # }).catch(debugError),
65
- # this._client.send('Page.getLayoutMetrics'),
66
- # ]);
67
- # if (!result || !result.quads.length)
68
- # throw new Error('Node is either not visible or not an HTMLElement');
69
- # // Filter out quads that have too small area to click into.
70
- # const {clientWidth, clientHeight} = layoutMetrics.layoutViewport;
71
- # const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1);
72
- # if (!quads.length)
73
- # throw new Error('Node is either not visible or not an HTMLElement');
74
- # // Return the middle point of the first quad.
75
- # const quad = quads[0];
76
- # let x = 0;
77
- # let y = 0;
78
- # for (const point of quad) {
79
- # x += point.x;
80
- # y += point.y;
81
- # }
82
- # return {
83
- # x: x / 4,
84
- # y: y / 4
85
- # };
86
- # }
63
+ class Point
64
+ def initialize(x:, y:)
65
+ @x = x
66
+ @y = y
67
+ end
68
+
69
+ def +(other)
70
+ Point.new(
71
+ x: @x + other.x,
72
+ y: @y + other.y,
73
+ )
74
+ end
75
+
76
+ def /(num)
77
+ Point.new(
78
+ x: @x / num,
79
+ y: @y / num,
80
+ )
81
+ end
82
+
83
+ attr_reader :x, :y
84
+ end
85
+
86
+ class ElementNotVisibleError < StandardError
87
+ def initialize
88
+ super("Node is either not visible or not an HTMLElement")
89
+ end
90
+ end
91
+
92
+ def clickable_point
93
+ result = @remote_object.content_quads(@client)
94
+ if !result || result["quads"].empty?
95
+ raise ElementNotVisibleError.new
96
+ end
97
+
98
+ # Filter out quads that have too small area to click into.
99
+ layout_metrics = @client.send_message('Page.getLayoutMetrics')
100
+ client_width = layout_metrics["layoutViewport"]["clientWidth"]
101
+ client_height = layout_metrics["layoutViewport"]["clientHeight"]
102
+
103
+ quads = result["quads"].
104
+ map { |quad| from_protocol_quad(quad) }.
105
+ map { |quad| intersect_quad_with_viewport(quad, client_width, client_height) }.
106
+ select { |quad| compute_quad_area(quad) > 1 }
107
+ if quads.empty?
108
+ raise ElementNotVisibleError.new
109
+ end
110
+
111
+ # Return the middle point of the first quad.
112
+ quads.first.reduce(:+) / 4
113
+ end
87
114
 
88
115
  # /**
89
116
  # * @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
@@ -94,31 +121,26 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
94
121
  # }).catch(error => debugError(error));
95
122
  # }
96
123
 
97
- # /**
98
- # * @param {!Array<number>} quad
99
- # * @return {!Array<{x: number, y: number}>}
100
- # */
101
- # _fromProtocolQuad(quad) {
102
- # return [
103
- # {x: quad[0], y: quad[1]},
104
- # {x: quad[2], y: quad[3]},
105
- # {x: quad[4], y: quad[5]},
106
- # {x: quad[6], y: quad[7]}
107
- # ];
108
- # }
124
+ # @param quad [Array<number>]
125
+ # @return [Array<Point>]
126
+ private def from_protocol_quad(quad)
127
+ quad.each_slice(2).map do |x, y|
128
+ Point.new(x: x, y: y)
129
+ end
130
+ end
109
131
 
110
- # /**
111
- # * @param {!Array<{x: number, y: number}>} quad
112
- # * @param {number} width
113
- # * @param {number} height
114
- # * @return {!Array<{x: number, y: number}>}
115
- # */
116
- # _intersectQuadWithViewport(quad, width, height) {
117
- # return quad.map(point => ({
118
- # x: Math.min(Math.max(point.x, 0), width),
119
- # y: Math.min(Math.max(point.y, 0), height),
120
- # }));
121
- # }
132
+ # @param quad [Array<Point>]
133
+ # @param width [number]
134
+ # @param height [number]
135
+ # @return [Array<Point>]
136
+ private def intersect_quad_with_viewport(quad, width, height)
137
+ quad.map do |point|
138
+ Point.new(
139
+ x: [[point.x, 0].max, width].min,
140
+ y: [[point.y, 0].max, height].min,
141
+ )
142
+ end
143
+ end
122
144
 
123
145
  # async hover() {
124
146
  # await this._scrollIntoViewIfNeeded();
@@ -126,6 +148,15 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
126
148
  # await this._page.mouse.move(x, y);
127
149
  # }
128
150
 
151
+ # @param delay [Number]
152
+ # @param button [String] "left"|"right"|"middle"
153
+ # @param click_count [Number]
154
+ def click(delay: nil, button: nil, click_count: nil)
155
+ scroll_into_view_if_needed
156
+ point = clickable_point
157
+ @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
158
+ end
159
+
129
160
  # /**
130
161
  # * @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
131
162
  # */
@@ -430,4 +461,11 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
430
461
  # return visibleRatio > 0;
431
462
  # });
432
463
  # }
464
+
465
+ # @param quad [Array<Point>]
466
+ private def compute_quad_area(quad)
467
+ # Compute sum of all directed areas of adjacent triangles
468
+ # https://en.wikipedia.org/wiki/Polygon#Simple_polygons
469
+ quad.zip(quad.rotate).map { |p1, p2| (p1.x * p2.y - p2.x * p1.y) / 2 }.reduce(:+).abs
470
+ end
433
471
  end
@@ -16,6 +16,11 @@ class Puppeteer::ExecutionContext
16
16
 
17
17
  attr_reader :client, :world
18
18
 
19
+ # only used in DomWorld#delete_context
20
+ def _context_id
21
+ @context_id
22
+ end
23
+
19
24
  # @return [Puppeteer::Frame]
20
25
  def frame
21
26
  if_present(@world) do |world|
@@ -143,8 +143,10 @@ class Puppeteer::Frame
143
143
  @main_world.add_style_tag(style_tag)
144
144
  end
145
145
 
146
- # @param {string} selector
147
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
146
+ # @param selector [String]
147
+ # @param delay [Number]
148
+ # @param button [String] "left"|"right"|"middle"
149
+ # @param click_count [Number]
148
150
  def click(selector, delay: nil, button: nil, click_count: nil)
149
151
  @secondary_world.click(selector, delay: delay, button: button, click_count: click_count)
150
152
  end
@@ -268,8 +270,8 @@ class Puppeteer::Frame
268
270
 
269
271
  def detach
270
272
  @detached = true
271
- # this._mainWorld._detach();
272
- # this._secondaryWorld._detach();
273
+ @main_world.detach
274
+ @secondary_world.detach
273
275
  if @parent_frame
274
276
  @parent_frame._child_frames.delete(self)
275
277
  end
@@ -206,12 +206,12 @@ class Puppeteer::FrameManager
206
206
  # @param {string} frameId
207
207
  # @param {?string} parentFrameId
208
208
  def handle_frame_attached(frame_id, parent_frame_id)
209
- return if @frames.has_key?[frame_id]
209
+ return if @frames.has_key?(frame_id)
210
210
  if !parent_frame_id
211
211
  raise ArgymentError.new('parent_frame_id must not be nil')
212
212
  end
213
213
  parent_frame = @frames[parent_frame_id]
214
- frame = Frame.new(self, @client, parent_frame, frame_id)
214
+ frame = Puppeteer::Frame.new(self, @client, parent_frame, frame_id)
215
215
  @frames[frame_id] = frame
216
216
 
217
217
  emit_event 'Events.FrameManager.FrameAttached', frame
@@ -332,12 +332,12 @@ class Puppeteer::FrameManager
332
332
  @context_id_to_context.delete(execution_context_id)
333
333
  @context_id_created.delete(execution_context_id)
334
334
  if context.world
335
- context.world.context = nil
335
+ context.world.delete_context(execution_context_id)
336
336
  end
337
337
  end
338
338
 
339
339
  def handle_execution_contexts_cleared
340
- # executionContextCleared is often notified after executionContextCreated.
340
+ # executionContextsCleared is often notified after executionContextCreated.
341
341
  # D, [2020-04-06T01:47:03.101227 #13823] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>5, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"71C347B70848B89DDDEFAA8AB5B0BC92"}}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
342
342
  # D, [2020-04-06T01:47:03.101439 #13823] DEBUG -- : RECV << {"method"=>"Page.frameNavigated", "params"=>{"frame"=>{"id"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "loaderId"=>"80338225D035AC96BAE8F6D4E81C7D51", "url"=>"https://github.com/search?q=puppeteer", "securityOrigin"=>"https://github.com", "mimeType"=>"text/html"}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
343
343
  # D, [2020-04-06T01:47:03.101325 #13823] DEBUG -- : RECV << {"method"=>"Target.targetInfoChanged", "params"=>{"targetInfo"=>{"targetId"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "type"=>"page", "title"=>"https://github.com/search?q=puppeteer", "url"=>"https://github.com/search?q=puppeteer", "attached"=>true, "browserContextId"=>"AF37BC660284CE1552B4ECB147BE9305"}}}
@@ -346,9 +346,9 @@ class Puppeteer::FrameManager
346
346
  # To avoid the problem, just skip recent created ids.
347
347
  now = Time.now
348
348
  context_ids_to_skip = @context_id_created.select { |k, v| now - v < 1 }.keys
349
- @context_id_to_context.reject { |k, v| context_ids_to_skip.include?(k) }.each_value do |context|
349
+ @context_id_to_context.reject { |k, v| context_ids_to_skip.include?(k) }.each do |execution_context_id, context|
350
350
  if context.world
351
- context.world.context = nil
351
+ context.world.delete_context(execution_context_id)
352
352
  end
353
353
  end
354
354
  @context_id_to_context.select! { |k, v| context_ids_to_skip.include?(k) }
@@ -116,7 +116,6 @@ class Puppeteer::JSHandle
116
116
  nil
117
117
  end
118
118
 
119
- # @return [Future]
120
119
  def dispose
121
120
  return if @disposed
122
121
 
@@ -178,6 +178,6 @@ class Puppeteer::Keyboard
178
178
  # @param key [String]
179
179
  # @return [Future]
180
180
  async def async_press(key, delay: nil)
181
- press(key, delay)
181
+ press(key, delay: delay)
182
182
  end
183
183
  end
@@ -41,10 +41,10 @@ class Puppeteer::Keyboard
41
41
  'Backspace': KeyDefinition.new({ 'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace' }),
42
42
  'Tab': KeyDefinition.new({ 'keyCode': 9, 'code': 'Tab', 'key': 'Tab' }),
43
43
  'Numpad5': KeyDefinition.new({ 'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3 }),
44
- 'NumpadEnter': KeyDefinition.new({ 'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3 }),
45
- 'Enter': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
46
- '\r': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
47
- '\n': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
44
+ 'NumpadEnter': KeyDefinition.new({ 'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': "\r", 'location': 3 }),
45
+ 'Enter': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
46
+ "\r": KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
47
+ "\n": KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
48
48
  'ShiftLeft': KeyDefinition.new({ 'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1 }),
49
49
  'ShiftRight': KeyDefinition.new({ 'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2 }),
50
50
  'ControlLeft': KeyDefinition.new({ 'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1 }),
@@ -50,6 +50,11 @@ class Puppeteer::LifecycleWatcher
50
50
  end
51
51
  end
52
52
 
53
+ class FrameDetachedError < StandardError
54
+ def initialize
55
+ super('Navigating frame was detached')
56
+ end
57
+ end
53
58
  class TerminatedError < StandardError; end
54
59
 
55
60
  # * @param {!Puppeteer.FrameManager} frameManager
@@ -92,7 +97,7 @@ class Puppeteer::LifecycleWatcher
92
97
  # @param frame [Puppeteer::Frame]
93
98
  def handle_frame_detached(frame)
94
99
  if @frame == frame
95
- # this._terminationCallback.call(null, new Error('Navigating frame was detached'));
100
+ @termination_promise.reject(FrameDetachedError.new)
96
101
  return
97
102
  end
98
103
  check_lifecycle_complete
@@ -37,8 +37,8 @@ class Puppeteer::Mouse
37
37
  @client.send_message('Input.dispatchMouseEvent',
38
38
  type: 'mouseMoved',
39
39
  button: @button,
40
- x: from_x + (@x - from_x) * n / steps,
41
- y: from_y + (@y - from_y) * n / steps,
40
+ x: from_x + (@x - from_x) * n / move_steps,
41
+ y: from_y + (@y - from_y) * n / move_steps,
42
42
  modifiers: @keyboard.modifiers,
43
43
  )
44
44
  end
@@ -56,16 +56,19 @@ class Puppeteer::Mouse
56
56
  # @param y [number]
57
57
  # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
58
58
  def click(x, y, delay: nil, button: nil, click_count: nil)
59
+ # await_all(async_move, async_down, async_up) often breaks the order of CDP commands.
60
+ # D, [2020-04-15T17:09:47.895895 #88683] DEBUG -- : RECV << {"id"=>23, "result"=>{"layoutViewport"=>{"pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667}, "visualViewport"=>{"offsetX"=>0, "offsetY"=>0, "pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667, "scale"=>1, "zoom"=>1}, "contentSize"=>{"x"=>0, "y"=>0, "width"=>375, "height"=>2007}}, "sessionId"=>"0B09EA5E18DEE403E525B3E7FCD7E225"}
61
+ # D, [2020-04-15T17:09:47.898422 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseReleased","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":24}
62
+ # D, [2020-04-15T17:09:47.899711 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mousePressed","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":25}
63
+ # D, [2020-04-15T17:09:47.900237 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseMoved","button":"left","x":187,"y":283,"modifiers":0},"id":26}
64
+ # So we execute move in advance.
65
+ move(x, y)
59
66
  if delay
60
- await_all(
61
- async_move(x, y),
62
- async_down(button: button, click_count: click_count),
63
- )
67
+ down(button: button, click_count: click_count)
64
68
  sleep(delay / 1000.0)
65
69
  up(button: button, click_count: click_count)
66
70
  else
67
71
  await_all(
68
- async_move(x, y),
69
72
  async_down(button: button, click_count: click_count),
70
73
  async_up(button: button, click_count: click_count),
71
74
  )
@@ -173,7 +173,7 @@ class Puppeteer::Page
173
173
  # await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy});
174
174
  # }
175
175
 
176
- attr_reader :target
176
+ attr_reader :javascript_enabled, :target
177
177
 
178
178
  def browser
179
179
  @target.browser
@@ -932,12 +932,23 @@ class Puppeteer::Page
932
932
 
933
933
  attr_reader :mouse
934
934
 
935
- # @param {string} selector
936
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
935
+ # @param selector [String]
936
+ # @param delay [Number]
937
+ # @param button [String] "left"|"right"|"middle"
938
+ # @param click_count [Number]
937
939
  def click(selector, delay: nil, button: nil, click_count: nil)
938
940
  main_frame.click(selector, delay: delay, button: button, click_count: click_count)
939
941
  end
940
942
 
943
+ # @param selector [String]
944
+ # @param delay [Number]
945
+ # @param button [String] "left"|"right"|"middle"
946
+ # @param click_count [Number]
947
+ # @return [Future]
948
+ async def async_click(selector, delay: nil, button: nil, click_count: nil)
949
+ click(selector, delay: delay, button: button, click_count: click_count)
950
+ end
951
+
941
952
  # @param {string} selector
942
953
  def focus(selector)
943
954
  main_frame.focus(selector)
@@ -57,6 +57,11 @@ class Puppeteer::RemoteObject
57
57
  client.send_message('DOM.describeNode', objectId: @object_id)
58
58
  end
59
59
 
60
+ # used in ElementHandle#clickable_point
61
+ def content_quads(client)
62
+ client.send_message('DOM.getContentQuads', objectId: @object_id)
63
+ end
64
+
60
65
  # helper#valueFromRemoteObject
61
66
  def value
62
67
  if @unserializable_value
@@ -1,3 +1,3 @@
1
1
  class Puppeteer
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppeteer-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2020-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -130,6 +130,7 @@ extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
132
  - ".circleci/config.yml"
133
+ - ".github/stale.yml"
133
134
  - ".gitignore"
134
135
  - ".rspec"
135
136
  - ".rubocop.yml"