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.
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