puppeteer-ruby 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +30 -0
  3. data/.github/stale.yml +16 -0
  4. data/.rubocop.yml +4 -5
  5. data/README.md +4 -1
  6. data/docs/Puppeteer.html +2020 -0
  7. data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
  8. data/docs/Puppeteer/Browser.html +2150 -0
  9. data/docs/Puppeteer/BrowserContext.html +809 -0
  10. data/docs/Puppeteer/BrowserFetcher.html +214 -0
  11. data/docs/Puppeteer/BrowserRunner.html +914 -0
  12. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
  13. data/docs/Puppeteer/CDPSession.html +813 -0
  14. data/docs/Puppeteer/CDPSession/Error.html +124 -0
  15. data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
  16. data/docs/Puppeteer/Connection.html +960 -0
  17. data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
  18. data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
  19. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
  20. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
  21. data/docs/Puppeteer/ConsoleMessage.html +565 -0
  22. data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
  23. data/docs/Puppeteer/DOMWorld.html +2219 -0
  24. data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
  25. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
  26. data/docs/Puppeteer/DebugPrint.html +233 -0
  27. data/docs/Puppeteer/Device.html +470 -0
  28. data/docs/Puppeteer/Devices.html +139 -0
  29. data/docs/Puppeteer/ElementHandle.html +2224 -0
  30. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
  31. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
  32. data/docs/Puppeteer/ElementHandle/Point.html +481 -0
  33. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
  34. data/docs/Puppeteer/EmulationManager.html +454 -0
  35. data/docs/Puppeteer/EventCallbackable.html +433 -0
  36. data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
  37. data/docs/Puppeteer/ExecutionContext.html +998 -0
  38. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
  39. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
  40. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
  41. data/docs/Puppeteer/FileChooser.html +455 -0
  42. data/docs/Puppeteer/Frame.html +3677 -0
  43. data/docs/Puppeteer/FrameManager.html +2414 -0
  44. data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
  45. data/docs/Puppeteer/IfPresent.html +222 -0
  46. data/docs/Puppeteer/JSHandle.html +1352 -0
  47. data/docs/Puppeteer/Keyboard.html +1557 -0
  48. data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
  49. data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
  50. data/docs/Puppeteer/Launcher.html +237 -0
  51. data/docs/Puppeteer/Launcher/Base.html +385 -0
  52. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
  53. data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
  54. data/docs/Puppeteer/Launcher/Chrome.html +669 -0
  55. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
  56. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
  57. data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
  58. data/docs/Puppeteer/LifecycleWatcher.html +834 -0
  59. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
  60. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
  61. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
  62. data/docs/Puppeteer/Mouse.html +1105 -0
  63. data/docs/Puppeteer/Mouse/Button.html +136 -0
  64. data/docs/Puppeteer/NetworkManager.html +901 -0
  65. data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
  66. data/docs/Puppeteer/Page.html +5970 -0
  67. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  68. data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
  69. data/docs/Puppeteer/Page/ScriptTag.html +555 -0
  70. data/docs/Puppeteer/Page/StyleTag.html +448 -0
  71. data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
  72. data/docs/Puppeteer/RemoteObject.html +1016 -0
  73. data/docs/Puppeteer/Target.html +1384 -0
  74. data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
  75. data/docs/Puppeteer/Target/TargetInfo.html +729 -0
  76. data/docs/Puppeteer/TimeoutError.html +135 -0
  77. data/docs/Puppeteer/TimeoutSettings.html +496 -0
  78. data/docs/Puppeteer/TouchScreen.html +464 -0
  79. data/docs/Puppeteer/Viewport.html +757 -0
  80. data/docs/Puppeteer/WaitTask.html +637 -0
  81. data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
  82. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  83. data/docs/Puppeteer/WebSocket.html +673 -0
  84. data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
  85. data/docs/Puppeteer/WebSocketTransport.html +600 -0
  86. data/docs/Puppeteer/WebSocktTransportError.html +124 -0
  87. data/docs/_index.html +809 -0
  88. data/docs/class_list.html +51 -0
  89. data/docs/css/common.css +1 -0
  90. data/docs/css/full_list.css +58 -0
  91. data/docs/css/style.css +496 -0
  92. data/docs/file.README.html +123 -0
  93. data/docs/file_list.html +56 -0
  94. data/docs/frames.html +17 -0
  95. data/docs/index.html +123 -0
  96. data/docs/js/app.js +314 -0
  97. data/docs/js/full_list.js +216 -0
  98. data/docs/js/jquery.js +4 -0
  99. data/docs/method_list.html +3979 -0
  100. data/docs/top-level-namespace.html +126 -0
  101. data/lib/puppeteer.rb +16 -8
  102. data/lib/puppeteer/async_await_behavior.rb +6 -0
  103. data/lib/puppeteer/browser.rb +21 -1
  104. data/lib/puppeteer/browser_runner.rb +1 -1
  105. data/lib/puppeteer/cdp_session.rb +33 -11
  106. data/lib/puppeteer/connection.rb +1 -1
  107. data/lib/puppeteer/dom_world.rb +142 -121
  108. data/lib/puppeteer/element_handle.rb +223 -181
  109. data/lib/puppeteer/execution_context.rb +41 -17
  110. data/lib/puppeteer/file_chooser.rb +29 -0
  111. data/lib/puppeteer/frame.rb +23 -15
  112. data/lib/puppeteer/frame_manager.rb +7 -9
  113. data/lib/puppeteer/js_handle.rb +3 -3
  114. data/lib/puppeteer/keyboard.rb +1 -1
  115. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +4 -4
  116. data/lib/puppeteer/launcher.rb +0 -1
  117. data/lib/puppeteer/launcher/chrome.rb +48 -2
  118. data/lib/puppeteer/lifecycle_watcher.rb +9 -4
  119. data/lib/puppeteer/mouse.rb +10 -7
  120. data/lib/puppeteer/page.rb +134 -70
  121. data/lib/puppeteer/remote_object.rb +11 -1
  122. data/lib/puppeteer/version.rb +1 -1
  123. data/lib/puppeteer/wait_task.rb +183 -1
  124. data/puppeteer-ruby.gemspec +4 -1
  125. metadata +143 -4
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.24
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+ <dl>
75
+ <dt>Includes:</dt>
76
+ <dd><span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html" title="Puppeteer::ConcurrentRubyUtils (module)">Puppeteer::ConcurrentRubyUtils</a></span></dd>
77
+ </dl>
78
+
79
+
80
+
81
+
82
+
83
+
84
+ </div>
85
+
86
+ <h2>Defined Under Namespace</h2>
87
+ <p class="children">
88
+
89
+
90
+
91
+
92
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="Puppeteer.html" title="Puppeteer (class)">Puppeteer</a></span>
93
+
94
+
95
+ </p>
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+ <h2>Method Summary</h2>
111
+
112
+ <h3 class="inherited">Methods included from <span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html" title="Puppeteer::ConcurrentRubyUtils (module)">Puppeteer::ConcurrentRubyUtils</a></span></h3>
113
+ <p class="inherited"><span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html#await-instance_method" title="Puppeteer::ConcurrentRubyUtils#await (method)">#await</a></span>, <span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html#await_all-instance_method" title="Puppeteer::ConcurrentRubyUtils#await_all (method)">#await_all</a></span>, <span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html#await_any-instance_method" title="Puppeteer::ConcurrentRubyUtils#await_any (method)">#await_any</a></span>, <span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html#future-instance_method" title="Puppeteer::ConcurrentRubyUtils#future (method)">#future</a></span>, <span class='object_link'><a href="Puppeteer/ConcurrentRubyUtils.html#resolvable_future-instance_method" title="Puppeteer::ConcurrentRubyUtils#resolvable_future (method)">#resolvable_future</a></span></p>
114
+
115
+
116
+ </div>
117
+
118
+ <div id="footer">
119
+ Generated on Thu Jun 4 23:54:40 2020 by
120
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
121
+ 0.9.24 (ruby-2.6.3).
122
+ </div>
123
+
124
+ </div>
125
+ </body>
126
+ </html>
@@ -25,6 +25,7 @@ require 'puppeteer/devices'
25
25
  require 'puppeteer/dom_world'
26
26
  require 'puppeteer/emulation_manager'
27
27
  require 'puppeteer/execution_context'
28
+ require 'puppeteer/file_chooser'
28
29
  require 'puppeteer/frame'
29
30
  require 'puppeteer/frame_manager'
30
31
  require 'puppeteer/js_handle'
@@ -55,9 +56,10 @@ class Puppeteer
55
56
 
56
57
  def instance
57
58
  @instance ||= Puppeteer.new(
58
- project_root: __dir__,
59
- preferred_revision: '706915',
60
- is_puppeteer_core: true)
59
+ project_root: __dir__,
60
+ preferred_revision: '706915',
61
+ is_puppeteer_core: true,
62
+ )
61
63
  end
62
64
  end
63
65
 
@@ -141,7 +143,12 @@ class Puppeteer
141
143
  default_viewport: default_viewport,
142
144
  slow_mo: slow_mo,
143
145
  }.compact
144
- launcher.connect(options)
146
+ browser = launcher.connect(options)
147
+ if block_given?
148
+ yield(browser)
149
+ else
150
+ browser
151
+ end
145
152
  end
146
153
 
147
154
  # @return {string}
@@ -151,10 +158,11 @@ class Puppeteer
151
158
 
152
159
  private def launcher
153
160
  @launcher ||= Puppeteer::Launcher.new(
154
- project_root: @project_root,
155
- preferred_revision: @preferred_revision,
156
- is_puppeteer_core: @is_puppeteer_core,
157
- product: @product_name)
161
+ project_root: @project_root,
162
+ preferred_revision: @preferred_revision,
163
+ is_puppeteer_core: @is_puppeteer_core,
164
+ product: @product_name,
165
+ )
158
166
  end
159
167
 
160
168
  # @return {string}
@@ -11,6 +11,9 @@ module Puppeteer::AsyncAwaitBehavior
11
11
  define_method(method_name) do |*args|
12
12
  Concurrent::Promises.future do
13
13
  original_method.bind(self).call(*args)
14
+ rescue => err
15
+ Logger.new(STDERR).warn(err)
16
+ raise err
14
17
  end
15
18
  end
16
19
  rescue NameError
@@ -24,6 +27,9 @@ module Puppeteer::AsyncAwaitBehavior
24
27
  define_singleton_method(method_name) do |*args|
25
28
  Concurrent::Promises.future do
26
29
  original_method.call(*args)
30
+ rescue => err
31
+ Logger.new(STDERR).warn(err)
32
+ raise err
27
33
  end
28
34
  end
29
35
  end
@@ -4,6 +4,7 @@ require 'timeout'
4
4
  class Puppeteer::Browser
5
5
  include Puppeteer::DebugPrint
6
6
  include Puppeteer::EventCallbackable
7
+ include Puppeteer::IfPresent
7
8
  using Puppeteer::AsyncAwaitBehavior
8
9
 
9
10
  # @param {!Puppeteer.Connection} connection
@@ -106,6 +107,10 @@ class Puppeteer::Browser
106
107
  emit_event 'Events.Browser.TargetCreated', target
107
108
  context.emit_event 'Events.BrowserContext.TargetCreated', target
108
109
  end
110
+
111
+ if_present(pending_target_info_changed_event.delete(target_info.target_id)) do |pending_event|
112
+ handle_target_info_changed(pending_event)
113
+ end
109
114
  end
110
115
 
111
116
 
@@ -127,7 +132,18 @@ class Puppeteer::Browser
127
132
  target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
128
133
  target = @targets[target_info.target_id]
129
134
  if !target
130
- throw StandardError.new('target should exist before targetInfoChanged')
135
+ # targetCreated is sometimes notified after targetInfoChanged.
136
+ # We don't raise error. Instead, keep the event as a pending change,
137
+ # and handle it on handle_target_created.
138
+ #
139
+ # D, [2020-04-22T00:22:26.630328 #79646] DEBUG -- : RECV << {"method"=>"Target.targetInfoChanged", "params"=>{"targetInfo"=>{"targetId"=>"8068CED48357B9557EEC85AA62165A8E", "type"=>"iframe", "title"=>"", "url"=>"", "attached"=>true, "browserContextId"=>"7895BFB24BF22CE40584808713D96E8D"}}}
140
+ # E, [2020-04-22T00:22:26.630448 #79646] ERROR -- : target should exist before targetInfoChanged (StandardError)
141
+ # D, [2020-04-22T00:22:26.630648 #79646] DEBUG -- : RECV << {"method"=>"Target.targetCreated", "params"=>{"targetInfo"=>{"targetId"=>"8068CED48357B9557EEC85AA62165A8E", "type"=>"iframe", "title"=>"", "url"=>"", "attached"=>false, "browserContextId"=>"7895BFB24BF22CE40584808713D96E8D"}}}
142
+ pending_target_info_changed_event[target_info.target_id] = event
143
+ return
144
+ # original implementation is:
145
+ #
146
+ # raise StandardError.new('target should exist before targetInfoChanged')
131
147
  end
132
148
  previous_url = target.url
133
149
  was_initialized = target.initialized?
@@ -138,6 +154,10 @@ class Puppeteer::Browser
138
154
  end
139
155
  end
140
156
 
157
+ private def pending_target_info_changed_event
158
+ @pending_target_info_changed_event ||= {}
159
+ end
160
+
141
161
  # @return [String]
142
162
  def websocket_endpoint
143
163
  @connection.url
@@ -147,7 +147,7 @@ class Puppeteer::BrowserRunner
147
147
  end
148
148
 
149
149
  private def wait_for_ws_endpoint(browser_process, timeout, preferred_revision)
150
- Timeout.timeout(timeout / 1000) do
150
+ Timeout.timeout(timeout / 1000.0) do
151
151
  loop do
152
152
  line = browser_process.stderr.readline
153
153
  /^DevTools listening on (ws:\/\/.*)$/.match(line) do |m|
@@ -1,4 +1,5 @@
1
1
  class Puppeteer::CDPSession
2
+ include Puppeteer::DebugPrint
2
3
  include Puppeteer::EventCallbackable
3
4
  using Puppeteer::AsyncAwaitBehavior
4
5
 
@@ -12,6 +13,7 @@ class Puppeteer::CDPSession
12
13
  @connection = connection
13
14
  @target_type = target_type
14
15
  @session_id = session_id
16
+ @pending_messages = {}
15
17
  end
16
18
 
17
19
  attr_reader :connection
@@ -32,7 +34,13 @@ class Puppeteer::CDPSession
32
34
  end
33
35
  id = @connection.raw_send(message: { sessionId: @session_id, method: method, params: params })
34
36
  promise = resolvable_future
35
- @callbacks[id] = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
37
+ callback = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
38
+ if pending_message = @pending_messages.delete(id)
39
+ debug_puts "Pending message (id: #{id}) is handled"
40
+ callback_with_message(callback, pending_message)
41
+ else
42
+ @callbacks[id] = callback
43
+ end
36
44
  promise
37
45
  end
38
46
 
@@ -40,23 +48,37 @@ class Puppeteer::CDPSession
40
48
  def handle_message(message)
41
49
  if message['id']
42
50
  if callback = @callbacks.delete(message['id'])
43
- if message['error']
44
- callback.reject(
45
- Puppeteer::Connection::ProtocolError.new(
46
- method: callback.method,
47
- error_message: response['error']['message'],
48
- error_data: response['error']['data']))
49
- else
50
- callback.resolve(message['result'])
51
- end
51
+ callback_with_message(callback, message)
52
52
  else
53
- raise Error.new("unknown id: #{message['id']}")
53
+ debug_puts "unknown id: #{id}. Store it into pending message"
54
+
55
+ # RECV is often notified before SEND.
56
+ # Wait about 10 frames before throwing an error.
57
+ message_id = message['id']
58
+ @pending_messages[message_id] = message
59
+ Concurrent::Promises.schedule(0.16, message_id) do |id|
60
+ if @pending_messages.delete(id)
61
+ raise Error.new("unknown id: #{id}")
62
+ end
63
+ end
54
64
  end
55
65
  else
56
66
  emit_event message['method'], message['params']
57
67
  end
58
68
  end
59
69
 
70
+ private def callback_with_message(callback, message)
71
+ if message['error']
72
+ callback.reject(
73
+ Puppeteer::Connection::ProtocolError.new(
74
+ method: callback.method,
75
+ error_message: message['error']['message'],
76
+ error_data: message['error']['data']))
77
+ else
78
+ callback.resolve(message['result'])
79
+ end
80
+ end
81
+
60
82
  def detach
61
83
  if !@connection
62
84
  raise Error.new("Session already detarched. Most likely the #{@target_type} has been closed.")
@@ -172,7 +172,7 @@ class Puppeteer::Connection
172
172
  session_id = message['params']['sessionId']
173
173
  session = @sessions[session_id]
174
174
  if session
175
- session._onClosed
175
+ session.handle_closed
176
176
  @sessions.delete(session_id)
177
177
  end
178
178
  end
@@ -12,12 +12,18 @@ 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
18
19
 
19
20
  attr_reader :frame
20
21
 
22
+ # only used in Puppeteer::WaitTask#initialize
23
+ def _wait_tasks
24
+ @wait_tasks
25
+ end
26
+
21
27
  # @param {?Puppeteer.ExecutionContext} context
22
28
  def context=(context)
23
29
  # D, [2020-04-12T22:45:03.938754 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>3, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"3AD7F1E82BCBA88BFE31D03BC49FF6CB"}}}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
@@ -29,20 +35,23 @@ class Puppeteer::DOMWorld
29
35
 
30
36
  if context
31
37
  if @context_promise.fulfilled?
38
+ @pending_destroy << context._context_id
32
39
  @document = nil
33
40
  @context_promise = resolvable_future
34
- @pending_destroy_exists = true
35
41
  end
36
42
  @context_promise.fulfill(context)
37
- # for (const waitTask of this._waitTasks)
38
- # waitTask.rerun();
43
+ @wait_tasks.each(&:async_rerun)
39
44
  else
40
- if @pending_destroy_exists
41
- @pending_destroy_exists = false
42
- else
43
- @document = nil
44
- @context_promise = resolvable_future
45
- end
45
+ raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
46
+ end
47
+ end
48
+
49
+ def delete_context(execution_context_id)
50
+ if @pending_destroy.include?(execution_context_id)
51
+ @pending_destroy.delete(execution_context_id)
52
+ else
53
+ @document = nil
54
+ @context_promise = resolvable_future
46
55
  end
47
56
  end
48
57
 
@@ -50,7 +59,7 @@ class Puppeteer::DOMWorld
50
59
  @context_promise.resolved?
51
60
  end
52
61
 
53
- private def detach
62
+ def detach
54
63
  @detached = true
55
64
  @wait_tasks.each do |wait_task|
56
65
  wait_task.terminate(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
@@ -88,8 +97,21 @@ class Puppeteer::DOMWorld
88
97
  document.S(selector)
89
98
  end
90
99
 
100
+ class DocumentEvaluationError < StandardError; end
101
+
102
+ private def evaluate_document
103
+ # sometimes execution_context.evaluate_handle('document') returns null object.
104
+ # D, [2020-04-24T02:17:51.023631 #220] DEBUG -- : RECV << {"id"=>20, "result"=>{"result"=>{"type"=>"object", "subtype"=>"null", "value"=>nil}}, "sessionId"=>"78E9CF1E14D81294E320E7C20E5CDE06"}
105
+ # retry if so.
106
+ 5.times do
107
+ handle = execution_context.evaluate_handle('document')
108
+ return handle if handle.is_a?(Puppeteer::ElementHandle)
109
+ end
110
+ raise DocumentEvaluationError.new("'document' object cannot be evaluated as an Element")
111
+ end
112
+
91
113
  private def document
92
- @document ||= execution_context.evaluate_handle('document').as_element
114
+ @document ||= evaluate_document.as_element
93
115
  end
94
116
 
95
117
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
@@ -303,16 +325,15 @@ class Puppeteer::DOMWorld
303
325
  # }
304
326
  # }
305
327
 
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
- # }
328
+ # @param selector [String]
329
+ # @param delay [Number]
330
+ # @param button [String] "left"|"right"|"middle"
331
+ # @param click_count [Number]
332
+ def click(selector, delay: nil, button: nil, click_count: nil)
333
+ handle = S(selector)
334
+ handle.click(delay: delay, button: button, click_count: click_count)
335
+ handle.dispose
336
+ end
316
337
 
317
338
  # /**
318
339
  # * @param {string} selector
@@ -334,58 +355,47 @@ class Puppeteer::DOMWorld
334
355
  # await handle.dispose();
335
356
  # }
336
357
 
337
- # /**
338
- # * @param {string} selector
339
- # * @param {!Array<string>} values
340
- # * @return {!Promise<!Array<string>>}
341
- # */
342
- # async select(selector, ...values) {
343
- # const handle = await this.$(selector);
344
- # assert(handle, 'No node found for selector: ' + selector);
345
- # const result = await handle.select(...values);
346
- # await handle.dispose();
347
- # return result;
348
- # }
358
+ # @param selector [String]
359
+ # @return [Array<String>]
360
+ def select(selector, *values)
361
+ handle = S(selector)
362
+ result = handle.select(*values)
363
+ handle.dispose
349
364
 
350
- # /**
351
- # * @param {string} selector
352
- # */
353
- # async tap(selector) {
354
- # const handle = await this.$(selector);
355
- # assert(handle, 'No node found for selector: ' + selector);
356
- # await handle.tap();
357
- # await handle.dispose();
358
- # }
365
+ result
366
+ end
359
367
 
360
- # /**
361
- # * @param {string} selector
362
- # * @param {string} text
363
- # * @param {{delay: (number|undefined)}=} options
364
- # */
365
- # async type(selector, text, options) {
366
- # const handle = await this.$(selector);
367
- # assert(handle, 'No node found for selector: ' + selector);
368
- # await handle.type(text, options);
369
- # await handle.dispose();
370
- # }
368
+ # @param selector [String]
369
+ def tap(selector)
370
+ handle = S(selector)
371
+ handle.tap
372
+ handle.dispose
373
+ end
371
374
 
372
- # /**
373
- # * @param {string} selector
374
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
375
- # * @return {!Promise<?Puppeteer.ElementHandle>}
376
- # */
377
- # waitForSelector(selector, options) {
378
- # return this._waitForSelectorOrXPath(selector, false, options);
379
- # }
375
+ # @param selector [String]
376
+ # @param text [String]
377
+ # @param delay [Number]
378
+ def type_text(selector, text, delay: nil)
379
+ handle = S(selector)
380
+ handle.type_text(text, delay: delay)
381
+ handle.dispose
382
+ end
380
383
 
381
- # /**
382
- # * @param {string} xpath
383
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
384
- # * @return {!Promise<?Puppeteer.ElementHandle>}
385
- # */
386
- # waitForXPath(xpath, options) {
387
- # return this._waitForSelectorOrXPath(xpath, true, options);
388
- # }
384
+ # @param selector [String]
385
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
386
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
387
+ # @param timeout [Integer]
388
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
389
+ wait_for_selector_or_xpath(selector, false, visible: visible, hidden: hidden, timeout: timeout)
390
+ end
391
+
392
+ # @param xpath [String]
393
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
394
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
395
+ # @param timeout [Integer]
396
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
397
+ wait_for_selector_or_xpath(xpath, true, visible: visible, hidden: hidden, timeout: timeout)
398
+ end
389
399
 
390
400
  # /**
391
401
  # * @param {Function|string} pageFunction
@@ -407,57 +417,68 @@ class Puppeteer::DOMWorld
407
417
  # return this.evaluate(() => document.title);
408
418
  # }
409
419
 
410
- # /**
411
- # * @param {string} selectorOrXPath
412
- # * @param {boolean} isXPath
413
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
414
- # * @return {!Promise<?Puppeteer.ElementHandle>}
415
- # */
416
- # async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
417
- # const {
418
- # visible: waitForVisible = false,
419
- # hidden: waitForHidden = false,
420
- # timeout = this._timeoutSettings.timeout(),
421
- # } = options;
422
- # const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
423
- # const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
424
- # const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
425
- # const handle = await waitTask.promise;
426
- # if (!handle.asElement()) {
427
- # await handle.dispose();
428
- # return null;
429
- # }
430
- # return handle.asElement();
420
+ # @param selector_or_xpath [String]
421
+ # @param is_xpath [Boolean]
422
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
423
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
424
+ # @param timeout [Integer]
425
+ private def wait_for_selector_or_xpath(selector_or_xpath, is_xpath, visible: nil, hidden: nil, timeout: nil)
426
+ option_wait_for_visible = visible || false
427
+ option_wait_for_hidden = hidden || false
428
+ option_timeout = timeout || @timeout_settings.timeout
429
+
430
+ polling =
431
+ if option_wait_for_visible || option_wait_for_hidden
432
+ 'raf'
433
+ else
434
+ 'mutation'
435
+ end
436
+ title = "#{is_xpath ? :XPath : :selector} #{selector_or_xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
437
+
438
+ wait_task = Puppeteer::WaitTask.new(
439
+ dom_world: self,
440
+ predicate_body: "return (#{PREDICATE})(...args)",
441
+ title: title,
442
+ polling: polling,
443
+ timeout: option_timeout,
444
+ args: [selector_or_xpath, is_xpath, option_wait_for_visible, option_wait_for_hidden],
445
+ )
446
+ handle = wait_task.await_promise
447
+ unless handle.as_element
448
+ handle.dispose
449
+ return nil
450
+ end
451
+ handle.as_element
452
+ end
431
453
 
432
- # /**
433
- # * @param {string} selectorOrXPath
434
- # * @param {boolean} isXPath
435
- # * @param {boolean} waitForVisible
436
- # * @param {boolean} waitForHidden
437
- # * @return {?Node|boolean}
438
- # */
439
- # function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
440
- # const node = isXPath
441
- # ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
442
- # : document.querySelector(selectorOrXPath);
443
- # if (!node)
444
- # return waitForHidden;
445
- # if (!waitForVisible && !waitForHidden)
446
- # return node;
447
- # const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
448
-
449
- # const style = window.getComputedStyle(element);
450
- # const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
451
- # const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
452
- # return success ? node : null;
453
-
454
- # /**
455
- # * @return {boolean}
456
- # */
457
- # function hasVisibleBoundingBox() {
458
- # const rect = element.getBoundingClientRect();
459
- # return !!(rect.top || rect.bottom || rect.width || rect.height);
460
- # }
461
- # }
462
- # }
454
+ PREDICATE = <<~JAVASCRIPT
455
+ /**
456
+ * @param {string} selectorOrXPath
457
+ * @param {boolean} isXPath
458
+ * @param {boolean} waitForVisible
459
+ * @param {boolean} waitForHidden
460
+ * @return {?Node|boolean}
461
+ */
462
+ function _(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
463
+ const node = isXPath
464
+ ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
465
+ : document.querySelector(selectorOrXPath);
466
+ if (!node)
467
+ return waitForHidden;
468
+ if (!waitForVisible && !waitForHidden)
469
+ return node;
470
+ const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
471
+ const style = window.getComputedStyle(element);
472
+ const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
473
+ const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
474
+ return success ? node : null;
475
+ /**
476
+ * @return {boolean}
477
+ */
478
+ function hasVisibleBoundingBox() {
479
+ const rect = element.getBoundingClientRect();
480
+ return !!(rect.top || rect.bottom || rect.width || rect.height);
481
+ }
482
+ }
483
+ JAVASCRIPT
463
484
  end