Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
[subscription.html]
expected:
if product != "chrome": ERROR
[Assert testdriver can subscribe globally]
expected:
# TODO(https://github.com/web-platform-tests/wpt/issues/56985):
# Investigate root cause
if product == "chrome": FAIL
107 changes: 61 additions & 46 deletions tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,54 +621,31 @@ def release(self):
class WebDriverTestDriverProtocolPart(TestDriverProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver

def run(self, url, script_resume, test_window=None):
# If protocol implements `bidi_events`, remove all the existing subscriptions.
# Required for detecting relevant `browsingContext.userPromptOpened` events.
self._test_window = self.parent.base.current_window
# Exceptions occurred outside the main loop. Uses to store exceptions happened in async code
# to communicate the failure to the test runner. Reset after each test.
self._unexpected_exceptions = []
if hasattr(self.parent, 'bidi_events'):
# Use protocol loop to run the async cleanup.
self.parent.loop.run_until_complete(self.parent.bidi_events.unsubscribe_all())
# If protocol implements `bidi_events`, forward all the events to test_driver. This has
# to be done only once on setup to prevent events duplications.
self.parent.bidi_events.add_event_listener(None, self._process_bidi_event)

def run(self, url, script_resume, test_window=None):
if test_window is None:
test_window = self.parent.base.current_window
self._test_window = self.parent.base.current_window
else:
self._test_window = test_window

# Exceptions occurred outside the main loop.
unexpected_exceptions = []
# Reset exceptions list.
self._unexpected_exceptions = []

if hasattr(self.parent, 'bidi_events'):
# If protocol implements `bidi_events`, forward all the events to test_driver.
async def process_bidi_event(method, params):
try:
self.logger.debug(f"Received bidi event: {method}, {params}")
if hasattr(self.parent, 'bidi_browsing_context') and method == "browsingContext.userPromptOpened" and \
params["context"] == test_window:
# User prompts of the test window are handled separately. In classic
# implementation, this user prompt always causes an exception when
# `protocol.testdriver.get_next_message()` is called. In BiDi it's not the
# case, as the BiDi protocol allows sending commands even with the user
# prompt opened. However, the user prompt can block the testdriver JS
# execution and cause a dead loop. To overcome this issue, the user prompt
# of the test window is always dismissed and the test is failing.
try:
await self.parent.bidi_browsing_context.handle_user_prompt(params["context"])
except Exception as e:
if "no such alert" in str(e):
# The user prompt is already dismissed by WebDriver BiDi server. Ignore the exception.
pass
else:
# The exception is unexpected. Re-raising it to handle it in the main loop.
raise e
raise Exception("Unexpected user prompt in test window: %s" % params)
else:
self.send_message(-1, "event", method, json.dumps({
"params": params,
"method": method}))
except Exception as e:
# As the event listener is async, the exceptions should be added to the list to be processed in the
# main loop.
self.logger.error("BiDi event processing failed: %s" % e)
unexpected_exceptions.append(e)

self.parent.bidi_events.add_event_listener(None, process_bidi_event)
# Remove all the existing subscriptions. Use protocol loop to run the async cleanup.
self.parent.loop.run_until_complete(self.parent.bidi_events.unsubscribe_all())
# As long as test runner requires JS execution on the test page, if the alert blocks the
# page, the communication to the test page is blocked. To prevent it, we need to keep
# track of the user prompts.
self.parent.loop.run_until_complete(self.parent.bidi_events.subscribe(['browsingContext.userPromptOpened'], None))

# If possible, support async actions.
Expand All @@ -680,9 +657,9 @@ async def process_bidi_event(method, params):
self.webdriver.url = url

while True:
if len(unexpected_exceptions) > 0:
if len(self._unexpected_exceptions) > 0:
# TODO: what to do if there are more then 1 unexpected exceptions?
raise unexpected_exceptions[0]
raise self._unexpected_exceptions[0]

test_driver_message = self.get_next_message(url, script_resume, test_window)
self.logger.debug("Receive message from testdriver: %s" % test_driver_message)
Expand Down Expand Up @@ -717,9 +694,9 @@ async def process_bidi_event(method, params):
# Use protocol loop to run the async cleanup.
self.parent.loop.run_until_complete(self.parent.bidi_events.unsubscribe_all())

if len(unexpected_exceptions) > 0:
if len(self._unexpected_exceptions) > 0:
# TODO: what to do if there are more then 1 unexpected exceptions?
raise unexpected_exceptions[0]
raise self._unexpected_exceptions[0]

return rv

Expand Down Expand Up @@ -770,6 +747,44 @@ def _get_next_message_bidi(self, url, script_resume, test_window):
deserialized_message = bidi_deserialize(message)
return deserialized_message

async def _process_bidi_event(self, method, params):
"""
Forwards WebDriver BiDi session's events to testdriver.js. Also automatically handles user
prompts to prevent deadlocks. Any exceptions are added to `self._unexpected_exceptions`.
"""
try:
self.logger.debug(f"Received bidi event: {method}, {params}")
if hasattr(self.parent, 'bidi_browsing_context') and \
method == "browsingContext.userPromptOpened" and \
params["context"] == self._test_window:
# Handle user prompts in the test window. In the classic implementation, an open
# user prompt always causes an exception when
# `protocol.testdriver.get_next_message()` is called. In WebDriver BiDi, this is not
# the case, as the protocol allows sending commands even when a user prompt is open.
# However, the prompt can block `testdriver.js` execution, causing a deadlock. To
# prevent this, we automatically dismiss the prompt in the test window and fail the
# test.
try:
await self.parent.bidi_browsing_context.handle_user_prompt(params["context"])
except Exception as e:
if "no such alert" in str(e):
# The user prompt is already dismissed by WebDriver BiDi server. Ignore the
# exception.
pass
else:
# The exception is unexpected. Re-raising it to handle it in the main loop.
raise e
raise Exception("Unexpected user prompt in test window: %s" % params)
else:
self.send_message(-1, "event", method, json.dumps({
"params": params,
"method": method}))
except Exception as e:
# As the event listener is async, the exceptions should be added to the list to be
# processed in the main loop.
self.logger.error("BiDi event processing failed: %s" % e)
self._unexpected_exceptions.append(e)

def send_message(self, cmd_id, message_type, status, message=None):
self.webdriver.execute_script(
self._format_send_message_script(cmd_id, message_type, status, message))
Expand Down