Skip to content

Commit

Permalink
Squash #44649
Browse files Browse the repository at this point in the history
  • Loading branch information
sadym-chromium committed May 24, 2024
1 parent dc7931f commit 21176db
Show file tree
Hide file tree
Showing 8 changed files with 531 additions and 55 deletions.
25 changes: 25 additions & 0 deletions console/console-log-logged.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test console log are present</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
promise_test(async () => {
const some_message = "SOME MESSAGE";
// Subscribe to `log.entryAdded` BiDi events. This will not add a listener to the page.
await test_driver.bidi.log.entry_added.subscribe();
// Add a listener for the log.entryAdded event. This will not subscribe to the event, so the subscription is
// required before. The cleanup is done automatically after the test is finished.
const log_entry_promise = new Promise(resolve => test_driver.bidi.log.entry_added.on(resolve));
// Emit a console.log message.
console.log(some_message)
// Wait for the log.entryAdded event to be received.
const event = await log_entry_promise;
// Assert the log.entryAdded event has the expected message.
assert_equals(event.args.length, 1);
const event_message = event.args[0];
assert_equals(event_message.value, some_message);
}, "Assert log event is logged");
</script>
53 changes: 52 additions & 1 deletion resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,49 @@
return pointerInteractablePaintTree.indexOf(element) !== -1;
}


/**
* @namespace {test_driver}
*/
window.test_driver = {
/**
Represents `WebDriver BiDi <https://w3c.github.io/webdriver-bidi>`_ protocol.
*/
bidi: {
/**
* `log <https://w3c.github.io/webdriver-bidi/#module-log>`_ module.
*/
log: {
/**
* `log.entryAdded <https://w3c.github.io/webdriver-bidi/#event-log-entryAdded>`_ event.
*/
entry_added: {
/**
* Subscribe to the `log.entryAdded` event. This does not add actual listeners. To listen to the
* event, use `on` method.
* @param {{contexts?: null | (string | Window)[]}} props - Parameters for the subscription.
* * `contexts`: an array of window proxies or browsing context ids to listen to the event. If not
* provided, the event subscription is done for the current window's browsing context. `null` for
* the global subscription.
* @return {Promise<void>}
*/
subscribe: async function (props = {}) {
return window.test_driver_internal.bidi.log.entry_added.subscribe(props);
},
/**
* Add an event listener for the `log.entryAdded
* <https://w3c.github.io/webdriver-bidi/#event-log-entryAdded>`_ event. Make sure `subscribe` is
* called before using this method.
*
* @param callback {function(event): void} - The callback to be called when the event is fired.
* @returns {function(): void} - A function to call to remove the event listener.
*/
on: function (callback) {
return window.test_driver_internal.bidi.log.entry_added.on(callback);
},
}
}
},

/**
* Set the context in which testharness.js is loaded
*
Expand Down Expand Up @@ -1078,6 +1116,19 @@
*/
in_automation: false,

bidi: {
log: {
entry_added: {
subscribe: function () {
throw new Error("bidi.log.entry_added.subscribe is not implemented by testdriver-vendor.js");
},
on: function () {
throw new Error("bidi.log.entry_added.on is not implemented by testdriver-vendor.js");
}
}
}
},

async click(element, coords) {
if (this.in_automation) {
throw new Error("click() is not implemented by testdriver-vendor.js");
Expand Down
57 changes: 57 additions & 0 deletions tools/wptrunner/wptrunner/executors/asyncactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# mypy: allow-untyped-defs
import sys

from typing import Dict, List, Literal, Optional, Union


# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class WindowProxyProperties(Dict):
context: str


# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class WindowProxyRemoteValue(Dict):
"""
WebDriver BiDi browsing context descriptor.
"""
type: Literal["window"]
value: WindowProxyProperties


class BidiSessionSubscribeAction:
name = "bidi.session.subscribe"

# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class Payload(Dict):
"""
Payload for the "bidi.session.subscribe" action.
events: List of event names to subscribe to.
contexts: Optional list of browsing contexts to subscribe to. Each context can be either a BiDi serialized value,
or a string. The latter is considered as a browsing context id.
"""
events: List[str]
contexts: Optional[List[Union[str, WindowProxyRemoteValue]]]

def __init__(self, logger, protocol):
self.logger = logger
self.protocol = protocol

async def __call__(self, payload: Payload):
events = payload["events"]
contexts = None
if payload["contexts"] is not None:
contexts = []
for c in payload["contexts"]:
if isinstance(c, str):
contexts.append(c)
elif isinstance(c, dict) and "type" in c and c["type"] == "window":
contexts.append(c["value"]["context"])
else:
raise ValueError("Unexpected context type: %s" % c)
return await self.protocol.bidi_events.subscribe(events, contexts)


async_actions = [BidiSessionSubscribeAction]
59 changes: 59 additions & 0 deletions tools/wptrunner/wptrunner/executors/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mypy: allow-untyped-defs

import asyncio
import base64
import hashlib
import io
Expand All @@ -15,6 +16,7 @@

from . import pytestrunner
from .actions import actions
from .asyncactions import async_actions
from .protocol import Protocol, WdspecProtocol


Expand Down Expand Up @@ -788,6 +790,63 @@ def _send_message(self, cmd_id, message_type, status, message=None):
self.protocol.testdriver.send_message(cmd_id, message_type, status, message=message)


class AsyncCallbackHandler(CallbackHandler):
"""
Handle synchronous and asynchronous actions. Extends `CallbackHandler` with support of async actions.
"""

def __init__(self, logger, protocol, test_window, loop):
super().__init__(logger, protocol, test_window)
self.loop = loop
self.async_actions = {cls.name: cls(self.logger, self.protocol) for cls in async_actions}

def process_action(self, url, payload):
action = payload["action"]
if action in self.async_actions:
# Schedule async action to be processed in the event loop and return immediately.
self.logger.debug(f"Scheduling async action processing: {action}, {payload}")
self.loop.create_task(self._process_async_action(action, payload))
return False, None
else:
# Fallback to the default action processing, which will fail if the action is not implemented.
self.logger.debug(f"Processing synchronous action: {action}, {payload}")
return super().process_action(url, payload)

async def _process_async_action(self, action, payload):
"""
Process async action and send the result back to the test driver.
This method is analogous to `process_action` but is intended to be used with async actions in a task, so it does
not raise unexpected exceptions.
"""
async_action_handler = self.async_actions[action]
cmd_id = payload["id"]
try:
result = await async_action_handler(payload)
except AttributeError as e:
# If we fail to get an attribute from the protocol presumably that's a
# ProtocolPart we don't implement
# AttributeError got an obj property in Python 3.10, for older versions we
# fall back to looking at the error message.
if ((hasattr(e, "obj") and getattr(e, "obj") == self.protocol) or
f"'{self.protocol.__class__.__name__}' object has no attribute" in str(e)):
raise NotImplementedError from e
except self.unimplemented_exc:
self.logger.warning("Action %s not implemented" % action)
self._send_message(cmd_id, "complete", "error", f"Action {action} not implemented")
except self.expected_exc:
self.logger.debug(f"Action {action} failed with an expected exception")
self._send_message(cmd_id, "complete", "error", f"Action {action} failed")
except Exception:
self.logger.warning(f"Action {action} failed")
self._send_message(cmd_id, "complete", "error")
raise
else:
self.logger.debug(f"Action {action} completed with result {result}")
return_message = {"result": result}
self._send_message(cmd_id, "complete", "success", json.dumps(return_message))


class ActionContext:
def __init__(self, logger, protocol, context):
self.logger = logger
Expand Down
6 changes: 3 additions & 3 deletions tools/wptrunner/wptrunner/executors/executorchrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .executorwebdriver import (
WebDriverCrashtestExecutor,
WebDriverFedCMProtocolPart,
WebDriverProtocol,
WebDriverBidiProtocol,
WebDriverRefTestExecutor,
WebDriverRun,
WebDriverTestharnessExecutor,
Expand Down Expand Up @@ -200,13 +200,13 @@ def execute_cdp_command(self, command, params=None):
body=body)


class ChromeDriverProtocol(WebDriverProtocol):
class ChromeDriverProtocol(WebDriverBidiProtocol):
implements = [
ChromeDriverDevToolsProtocolPart,
ChromeDriverFedCMProtocolPart,
ChromeDriverPrintProtocolPart,
ChromeDriverTestharnessProtocolPart,
*(part for part in WebDriverProtocol.implements
*(part for part in WebDriverBidiProtocol.implements
if part.name != ChromeDriverTestharnessProtocolPart.name and
part.name != ChromeDriverFedCMProtocolPart.name)
]
Expand Down
Loading

0 comments on commit 21176db

Please sign in to comment.