From 87ab743d84f158d02088373ab669a0d3f20afa05 Mon Sep 17 00:00:00 2001 From: Steven Schmid Date: Wed, 21 Jun 2023 15:18:35 +0200 Subject: [PATCH 1/2] Fix timing issue where a disconnected node is being requested & returned --- lib/ferrum/frame/runtime.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/ferrum/frame/runtime.rb b/lib/ferrum/frame/runtime.rb index 48752138..6ceecdf1 100644 --- a/lib/ferrum/frame/runtime.rb +++ b/lib/ferrum/frame/runtime.rb @@ -152,6 +152,10 @@ def handle_response(response) case response["subtype"] when "node" + # Ensure node is not requested & returned if it has been disconnected + # between execution and response handling (addresses timing issues). + raise NodeNotFoundError, "Node is disconnected" if disconnected?(object_id).dig("result", "value") + # We cannot store object_id in the node because page can be reloaded # and node destroyed so we need to retrieve it each time for given id. # Though we can try to subscribe to `DOM.childNodeRemoved` and @@ -241,6 +245,18 @@ def cyclic?(object_id) def cyclic_object CyclicObject.instance end + + def disconnected?(object_id) + @page.command( + "Runtime.callFunctionOn", + objectId: object_id, + functionDeclaration: <<~JS + function() { + return !this.isConnected; + } + JS + ) + end end end end From b8ccc1e309fb197b6d0519bc242495265e83fb50 Mon Sep 17 00:00:00 2001 From: Steven Schmid Date: Thu, 29 Jun 2023 10:02:54 +0200 Subject: [PATCH 2/2] Improve detection of disconnected nodes Previous solution had still some leeway for timing issues, leading to intermittent failing specs (albeit rarely) --- lib/ferrum/frame/runtime.rb | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/ferrum/frame/runtime.rb b/lib/ferrum/frame/runtime.rb index 6ceecdf1..3a0079e4 100644 --- a/lib/ferrum/frame/runtime.rb +++ b/lib/ferrum/frame/runtime.rb @@ -152,17 +152,19 @@ def handle_response(response) case response["subtype"] when "node" - # Ensure node is not requested & returned if it has been disconnected - # between execution and response handling (addresses timing issues). - raise NodeNotFoundError, "Node is disconnected" if disconnected?(object_id).dig("result", "value") - # We cannot store object_id in the node because page can be reloaded # and node destroyed so we need to retrieve it each time for given id. # Though we can try to subscribe to `DOM.childNodeRemoved` and # `DOM.childNodeInserted` in the future. node_id = @page.command("DOM.requestNode", objectId: object_id)["nodeId"] description = @page.command("DOM.describeNode", nodeId: node_id)["node"] - Node.new(self, @page.target_id, node_id, description) + node = Node.new(self, @page.target_id, node_id, description) + + # Ensure node has not been disconnected between execution and + # response handling (addresses timing issues). + raise NodeNotFoundError, "Node is disconnected" unless node.evaluate('this.isConnected') + + node when "array" reduce_props(object_id, []) do |memo, key, value| next(memo) unless Integer(key, exception: false) @@ -245,18 +247,6 @@ def cyclic?(object_id) def cyclic_object CyclicObject.instance end - - def disconnected?(object_id) - @page.command( - "Runtime.callFunctionOn", - objectId: object_id, - functionDeclaration: <<~JS - function() { - return !this.isConnected; - } - JS - ) - end end end end