puppeteer-ruby 0.0.4 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +30 -0
  3. data/.rubocop.yml +4 -5
  4. data/README.md +3 -2
  5. data/docs/Puppeteer.html +2020 -0
  6. data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
  7. data/docs/Puppeteer/Browser.html +2148 -0
  8. data/docs/Puppeteer/BrowserContext.html +809 -0
  9. data/docs/Puppeteer/BrowserFetcher.html +214 -0
  10. data/docs/Puppeteer/BrowserRunner.html +914 -0
  11. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
  12. data/docs/Puppeteer/CDPSession.html +813 -0
  13. data/docs/Puppeteer/CDPSession/Error.html +124 -0
  14. data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
  15. data/docs/Puppeteer/Connection.html +960 -0
  16. data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
  17. data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
  18. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
  19. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
  20. data/docs/Puppeteer/ConsoleMessage.html +565 -0
  21. data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
  22. data/docs/Puppeteer/DOMWorld.html +2219 -0
  23. data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
  24. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
  25. data/docs/Puppeteer/DebugPrint.html +233 -0
  26. data/docs/Puppeteer/Device.html +470 -0
  27. data/docs/Puppeteer/Devices.html +139 -0
  28. data/docs/Puppeteer/ElementHandle.html +2542 -0
  29. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
  30. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
  31. data/docs/Puppeteer/ElementHandle/Point.html +492 -0
  32. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
  33. data/docs/Puppeteer/EmulationManager.html +454 -0
  34. data/docs/Puppeteer/EventCallbackable.html +433 -0
  35. data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
  36. data/docs/Puppeteer/ExecutionContext.html +998 -0
  37. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
  38. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
  39. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
  40. data/docs/Puppeteer/FileChooser.html +455 -0
  41. data/docs/Puppeteer/Frame.html +3677 -0
  42. data/docs/Puppeteer/FrameManager.html +2410 -0
  43. data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
  44. data/docs/Puppeteer/IfPresent.html +222 -0
  45. data/docs/Puppeteer/JSHandle.html +1352 -0
  46. data/docs/Puppeteer/Keyboard.html +1557 -0
  47. data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
  48. data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
  49. data/docs/Puppeteer/Launcher.html +237 -0
  50. data/docs/Puppeteer/Launcher/Base.html +385 -0
  51. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
  52. data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
  53. data/docs/Puppeteer/Launcher/Chrome.html +669 -0
  54. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
  55. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
  56. data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
  57. data/docs/Puppeteer/LifecycleWatcher.html +834 -0
  58. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
  59. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
  60. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
  61. data/docs/Puppeteer/Mouse.html +1105 -0
  62. data/docs/Puppeteer/Mouse/Button.html +136 -0
  63. data/docs/Puppeteer/NetworkManager.html +901 -0
  64. data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
  65. data/docs/Puppeteer/Page.html +5970 -0
  66. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  67. data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
  68. data/docs/Puppeteer/Page/ScriptTag.html +555 -0
  69. data/docs/Puppeteer/Page/StyleTag.html +448 -0
  70. data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
  71. data/docs/Puppeteer/RemoteObject.html +1087 -0
  72. data/docs/Puppeteer/Target.html +1336 -0
  73. data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
  74. data/docs/Puppeteer/Target/TargetInfo.html +729 -0
  75. data/docs/Puppeteer/TimeoutError.html +135 -0
  76. data/docs/Puppeteer/TimeoutSettings.html +496 -0
  77. data/docs/Puppeteer/TouchScreen.html +464 -0
  78. data/docs/Puppeteer/Viewport.html +837 -0
  79. data/docs/Puppeteer/WaitTask.html +637 -0
  80. data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
  81. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  82. data/docs/Puppeteer/WebSocket.html +673 -0
  83. data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
  84. data/docs/Puppeteer/WebSocketTransport.html +600 -0
  85. data/docs/Puppeteer/WebSocktTransportError.html +124 -0
  86. data/docs/_index.html +823 -0
  87. data/docs/class_list.html +51 -0
  88. data/docs/css/common.css +1 -0
  89. data/docs/css/full_list.css +58 -0
  90. data/docs/css/style.css +496 -0
  91. data/docs/file.README.html +123 -0
  92. data/docs/file_list.html +56 -0
  93. data/docs/frames.html +17 -0
  94. data/docs/index.html +123 -0
  95. data/docs/js/app.js +314 -0
  96. data/docs/js/full_list.js +216 -0
  97. data/docs/js/jquery.js +4 -0
  98. data/docs/method_list.html +4075 -0
  99. data/docs/top-level-namespace.html +126 -0
  100. data/lib/puppeteer.rb +16 -8
  101. data/lib/puppeteer/async_await_behavior.rb +6 -0
  102. data/lib/puppeteer/browser.rb +25 -6
  103. data/lib/puppeteer/browser_runner.rb +1 -1
  104. data/lib/puppeteer/cdp_session.rb +33 -11
  105. data/lib/puppeteer/connection.rb +1 -1
  106. data/lib/puppeteer/dom_world.rb +121 -104
  107. data/lib/puppeteer/element_handle.rb +186 -224
  108. data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
  109. data/lib/puppeteer/element_handle/box_model.rb +19 -0
  110. data/lib/puppeteer/element_handle/point.rb +26 -0
  111. data/lib/puppeteer/errors.rb +1 -3
  112. data/lib/puppeteer/execution_context.rb +36 -17
  113. data/lib/puppeteer/file_chooser.rb +29 -0
  114. data/lib/puppeteer/frame.rb +17 -11
  115. data/lib/puppeteer/frame_manager.rb +1 -3
  116. data/lib/puppeteer/js_handle.rb +3 -2
  117. data/lib/puppeteer/launcher.rb +0 -1
  118. data/lib/puppeteer/launcher/chrome.rb +48 -2
  119. data/lib/puppeteer/lifecycle_watcher.rb +3 -3
  120. data/lib/puppeteer/page.rb +121 -68
  121. data/lib/puppeteer/remote_object.rb +15 -1
  122. data/lib/puppeteer/target.rb +24 -24
  123. data/lib/puppeteer/version.rb +1 -1
  124. data/lib/puppeteer/viewport.rb +18 -0
  125. data/lib/puppeteer/wait_task.rb +183 -1
  126. data/lib/puppeteer/web_socket.rb +3 -1
  127. data/lib/puppeteer/web_socket_transport.rb +1 -1
  128. data/puppeteer-ruby.gemspec +4 -1
  129. metadata +145 -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 Fri Jun 12 12:38:32 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
@@ -101,11 +102,14 @@ class Puppeteer::Browser
101
102
  )
102
103
  # assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
103
104
  @targets[target_info.target_id] = target
104
-
105
- target.on_initialize_succeeded do
105
+ if await target.initialized_promise
106
106
  emit_event 'Events.Browser.TargetCreated', target
107
107
  context.emit_event 'Events.BrowserContext.TargetCreated', target
108
108
  end
109
+
110
+ if_present(pending_target_info_changed_event.delete(target_info.target_id)) do |pending_event|
111
+ handle_target_info_changed(pending_event)
112
+ end
109
113
  end
110
114
 
111
115
 
@@ -113,10 +117,10 @@ class Puppeteer::Browser
113
117
  def handle_target_destroyed(event)
114
118
  target_id = event['targetId']
115
119
  target = @targets[target_id]
116
- target.handle_initialized(false)
120
+ target.ignore_initialize_callback_promise
117
121
  @targets.delete(target_id)
118
- target.handle_closed
119
- target.on_initialize_succeeded do
122
+ target.closed_callback
123
+ if await target.initialized_promise
120
124
  emit_event 'Events.Browser.TargetDestroyed', target
121
125
  target.browser_context.emit_event 'Events.BrowserContext.TargetDestroyed', target
122
126
  end
@@ -127,7 +131,18 @@ class Puppeteer::Browser
127
131
  target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
128
132
  target = @targets[target_info.target_id]
129
133
  if !target
130
- throw StandardError.new('target should exist before targetInfoChanged')
134
+ # targetCreated is sometimes notified after targetInfoChanged.
135
+ # We don't raise error. Instead, keep the event as a pending change,
136
+ # and handle it on handle_target_created.
137
+ #
138
+ # 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"}}}
139
+ # E, [2020-04-22T00:22:26.630448 #79646] ERROR -- : target should exist before targetInfoChanged (StandardError)
140
+ # 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"}}}
141
+ pending_target_info_changed_event[target_info.target_id] = event
142
+ return
143
+ # original implementation is:
144
+ #
145
+ # raise StandardError.new('target should exist before targetInfoChanged')
131
146
  end
132
147
  previous_url = target.url
133
148
  was_initialized = target.initialized?
@@ -138,6 +153,10 @@ class Puppeteer::Browser
138
153
  end
139
154
  end
140
155
 
156
+ private def pending_target_info_changed_event
157
+ @pending_target_info_changed_event ||= {}
158
+ end
159
+
141
160
  # @return [String]
142
161
  def websocket_endpoint
143
162
  @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: message['error']['message'],
48
- error_data: message['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
@@ -19,6 +19,11 @@ class Puppeteer::DOMWorld
19
19
 
20
20
  attr_reader :frame
21
21
 
22
+ # only used in Puppeteer::WaitTask#initialize
23
+ def _wait_tasks
24
+ @wait_tasks
25
+ end
26
+
22
27
  # @param {?Puppeteer.ExecutionContext} context
23
28
  def context=(context)
24
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"}
@@ -35,8 +40,7 @@ class Puppeteer::DOMWorld
35
40
  @context_promise = resolvable_future
36
41
  end
37
42
  @context_promise.fulfill(context)
38
- # for (const waitTask of this._waitTasks)
39
- # waitTask.rerun();
43
+ @wait_tasks.each(&:async_rerun)
40
44
  else
41
45
  raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
42
46
  end
@@ -55,7 +59,7 @@ class Puppeteer::DOMWorld
55
59
  @context_promise.resolved?
56
60
  end
57
61
 
58
- private def detach
62
+ def detach
59
63
  @detached = true
60
64
  @wait_tasks.each do |wait_task|
61
65
  wait_task.terminate(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
@@ -93,8 +97,21 @@ class Puppeteer::DOMWorld
93
97
  document.S(selector)
94
98
  end
95
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
+
96
113
  private def document
97
- @document ||= execution_context.evaluate_handle('document').as_element
114
+ @document ||= evaluate_document.as_element
98
115
  end
99
116
 
100
117
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
@@ -338,58 +355,47 @@ class Puppeteer::DOMWorld
338
355
  # await handle.dispose();
339
356
  # }
340
357
 
341
- # /**
342
- # * @param {string} selector
343
- # * @param {!Array<string>} values
344
- # * @return {!Promise<!Array<string>>}
345
- # */
346
- # async select(selector, ...values) {
347
- # const handle = await this.$(selector);
348
- # assert(handle, 'No node found for selector: ' + selector);
349
- # const result = await handle.select(...values);
350
- # await handle.dispose();
351
- # return result;
352
- # }
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
353
364
 
354
- # /**
355
- # * @param {string} selector
356
- # */
357
- # async tap(selector) {
358
- # const handle = await this.$(selector);
359
- # assert(handle, 'No node found for selector: ' + selector);
360
- # await handle.tap();
361
- # await handle.dispose();
362
- # }
365
+ result
366
+ end
363
367
 
364
- # /**
365
- # * @param {string} selector
366
- # * @param {string} text
367
- # * @param {{delay: (number|undefined)}=} options
368
- # */
369
- # async type(selector, text, options) {
370
- # const handle = await this.$(selector);
371
- # assert(handle, 'No node found for selector: ' + selector);
372
- # await handle.type(text, options);
373
- # await handle.dispose();
374
- # }
368
+ # @param selector [String]
369
+ def tap(selector)
370
+ handle = S(selector)
371
+ handle.tap
372
+ handle.dispose
373
+ end
375
374
 
376
- # /**
377
- # * @param {string} selector
378
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
379
- # * @return {!Promise<?Puppeteer.ElementHandle>}
380
- # */
381
- # waitForSelector(selector, options) {
382
- # return this._waitForSelectorOrXPath(selector, false, options);
383
- # }
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
384
383
 
385
- # /**
386
- # * @param {string} xpath
387
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
388
- # * @return {!Promise<?Puppeteer.ElementHandle>}
389
- # */
390
- # waitForXPath(xpath, options) {
391
- # return this._waitForSelectorOrXPath(xpath, true, options);
392
- # }
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
393
399
 
394
400
  # /**
395
401
  # * @param {Function|string} pageFunction
@@ -411,57 +417,68 @@ class Puppeteer::DOMWorld
411
417
  # return this.evaluate(() => document.title);
412
418
  # }
413
419
 
414
- # /**
415
- # * @param {string} selectorOrXPath
416
- # * @param {boolean} isXPath
417
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
418
- # * @return {!Promise<?Puppeteer.ElementHandle>}
419
- # */
420
- # async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
421
- # const {
422
- # visible: waitForVisible = false,
423
- # hidden: waitForHidden = false,
424
- # timeout = this._timeoutSettings.timeout(),
425
- # } = options;
426
- # const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
427
- # const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
428
- # const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
429
- # const handle = await waitTask.promise;
430
- # if (!handle.asElement()) {
431
- # await handle.dispose();
432
- # return null;
433
- # }
434
- # 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
435
453
 
436
- # /**
437
- # * @param {string} selectorOrXPath
438
- # * @param {boolean} isXPath
439
- # * @param {boolean} waitForVisible
440
- # * @param {boolean} waitForHidden
441
- # * @return {?Node|boolean}
442
- # */
443
- # function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
444
- # const node = isXPath
445
- # ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
446
- # : document.querySelector(selectorOrXPath);
447
- # if (!node)
448
- # return waitForHidden;
449
- # if (!waitForVisible && !waitForHidden)
450
- # return node;
451
- # const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
452
-
453
- # const style = window.getComputedStyle(element);
454
- # const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
455
- # const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
456
- # return success ? node : null;
457
-
458
- # /**
459
- # * @return {boolean}
460
- # */
461
- # function hasVisibleBoundingBox() {
462
- # const rect = element.getBoundingClientRect();
463
- # return !!(rect.top || rect.bottom || rect.width || rect.height);
464
- # }
465
- # }
466
- # }
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
467
484
  end