Skip to content

Commit

Permalink
Merge pull request #449 from sadym-chromium/sadym/browsing_context_id
Browse files Browse the repository at this point in the history
Sadym/browsing context
  • Loading branch information
sadym-chromium authored Apr 15, 2024
2 parents 497271a + 248d683 commit 32bade7
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 26 deletions.
12 changes: 8 additions & 4 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@
*/
entry_added: {
/**
* Subscribe to the `log.entryAdded` event. This does not actual listeners. To listen to the event,
* use `on` method.
* 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 () {
return window.test_driver_internal.bidi.log.entry_added.subscribe();
subscribe: async function (props = {}) {
return window.test_driver_internal.bidi.log.entry_added.subscribe(props);
},
/**
* Add an event listener for the `log.entryAdded
Expand Down
46 changes: 44 additions & 2 deletions tools/wptrunner/wptrunner/executors/asyncactions.py
Original file line number Diff line number Diff line change
@@ -1,15 +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):
return await self.protocol.bidi_events.subscribe(payload["events"], payload["contexts"])
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]
10 changes: 5 additions & 5 deletions tools/wptrunner/wptrunner/executors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,25 +800,25 @@ def __init__(self, logger, protocol, test_window, loop):

def process_action(self, url, payload):
action = payload["action"]
cmd_id = payload["id"]
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}")
self.loop.create_task(self._process_async_action(action, cmd_id, payload))
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, cmd_id, 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:
Expand Down
57 changes: 47 additions & 10 deletions tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ async def unsubscribe_all(self):
await self.webdriver.bidi_session.session.unsubscribe(events=events, contexts=contexts)

def add_event_listener(self, fn, event=None):
print("adding event listener %s" % event)
self.logger.info("adding event listener %s" % event)
return self.webdriver.bidi_session.add_event_listener(name=event, fn=fn)


Expand All @@ -146,15 +146,12 @@ def setup(self):
self.webdriver = self.parent.webdriver

async def async_call_function(self, script, context, args=None):
result = await self.webdriver.bidi_session.script.call_function(
return await self.webdriver.bidi_session.script.call_function(
function_declaration=script,
arguments=args,
target={
"context": context if context else self.current_window},
await_promise=True)
if result["type"] != "string":
raise Exception("Unexpected result")
return json.loads(result["value"])


class WebDriverTestharnessProtocolPart(TestharnessProtocolPart):
Expand Down Expand Up @@ -716,6 +713,7 @@ async def process_bidi_event(method, params):

while True:
test_driver_message = self._get_next_message(protocol, url, test_window)
self.logger.debug("Receive message from testdriver: %s" % test_driver_message)

# As of 2019-03-29, WebDriver does not define expected behavior for
# cases where the browser crashes during script execution:
Expand Down Expand Up @@ -757,6 +755,43 @@ async def process_bidi_event(method, params):

return rv

def bidi_deserialize(self, bidi_value):
"""
Deserialize the BiDi value to the Python value, keeping non-common data typs (window) in BiDi format. The result
can have collisions with the classic values.
:param bidi_value: BiDi value to deserialize.
"""
if isinstance(bidi_value, str):
return bidi_value
if isinstance(bidi_value, int):
return bidi_value
if not isinstance(bidi_value, dict):
raise ValueError("Unexpected bidi value: %s" % bidi_value)
if bidi_value["type"] == "null":
return None
if bidi_value["type"] == "boolean":
return bidi_value["value"]
if bidi_value["type"] == "number":
# TODO: extend with edge case values, like `Infinity`.
return bidi_value["value"]
if bidi_value["type"] == "string":
return bidi_value["value"]
if bidi_value["type"] == "array":
result = []
for item in bidi_value["value"]:
result.append(self.bidi_deserialize(item))
return result
if bidi_value["type"] == "object":
result = {}
for item in bidi_value["value"]:
result[self.bidi_deserialize(item[0])] = self.bidi_deserialize(item[1])
return result
if bidi_value["type"] == "window":
return bidi_value
# TODO: probably should return bidi value as-is, like `window` instead of raising exception. Keep for now to
# check any regression in classic values.
raise ValueError("Unexpected bidi value: %s" % bidi_value)

def _get_next_message(self, protocol, url, test_window):
"""
Get the next message from the test_driver. If the protocol supports bidi scripts, the messages are processed
Expand All @@ -772,7 +807,7 @@ def _get_next_message(self, protocol, url, test_window):
# coroutine is finished as well.
wrapped_script = """async function(...args){
return new Promise((resolve, reject) => {
args.push((value)=>resolve(JSON.stringify(value)));
args.push(resolve);
(async function(){
%s
}).apply(null, args);
Expand All @@ -784,15 +819,17 @@ def _get_next_message(self, protocol, url, test_window):
"value": strip_server(url)
}

return protocol.loop.run_until_complete(protocol.bidi_script.async_call_function(
# `run_until_complete` allows processing BiDi events in the same loop while waiting for the next message.
message = protocol.loop.run_until_complete(protocol.bidi_script.async_call_function(
wrapped_script, context=test_window,
args=[bidi_url_argument]))
# The message is in WebDriver BiDi format. Deserialize it.
deserialized_message = self.bidi_deserialize(message)
return deserialized_message
else:
# If `bidi_script` is not available, use the classic WebDriver async script execution. This will
# block the event loop until the test_driver send a message.
return protocol.base.execute_script(self.script_resume, asynchronous=True, context=test_window,
args=[strip_server(url)])

return protocol.base.execute_script(self.script_resume, asynchronous=True, args=[strip_server(url)])

class WebDriverRefTestExecutor(RefTestExecutor):
protocol_cls = WebDriverProtocol
Expand Down
2 changes: 1 addition & 1 deletion tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ async def subscribe(self, events, contexts):
"""Subscribe to events.
:param list events: The list of events names to subscribe to.
:param list contexts: The list of contexts to subscribe to. None for global subscription"""
:param list contexts: The list of contexts to subscribe to. None for global subscription."""
pass

@abstractmethod
Expand Down
15 changes: 11 additions & 4 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,21 @@
return pending_promise;
};

const subscribe = function(events, contexts) {
return create_action("bidi.session.subscribe", {events, contexts});
const subscribe = function (props) {
return create_action("bidi.session.subscribe", {
// Default to subscribing to the window's events.
contexts: [window],
...props
});
};

window.test_driver_internal.in_automation = true;

window.test_driver_internal.bidi.log.entry_added.subscribe = function () {
return subscribe(["log.entryAdded"], null)
window.test_driver_internal.bidi.log.entry_added.subscribe = function (props) {
return subscribe({
...(props ?? {}),
events: ["log.entryAdded"]
})
};

window.test_driver_internal.bidi.log.entry_added.on = function (callback) {
Expand Down

0 comments on commit 32bade7

Please sign in to comment.