Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EXPERIMENTAL] Test iframe submission #450

Open
wants to merge 1 commit into
base: sadym/testdriver-bidi
Choose a base branch
from
Open
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
@@ -0,0 +1,41 @@
<!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>

<iframe src="support/iframe_sandbox_006.htm" sandbox="allow-forms"></iframe>
<script type="text/javascript">
promise_test(async () => {
const tree = await test_driver.bidi.browsing_context.get_tree({root: window});
const iframe = tree[0].children[0];
const submit_button_selector_result = await test_driver.bidi.browsing_context.locate_nodes({
context: iframe.context,
locator: {type: "css", value: "#submitButton"}
});
const submit_button_shared_id = submit_button_selector_result.nodes[0].sharedId;
await test_driver.bidi.input.click({
context: iframe.context,
origin: {
type: "element",
element: {
sharedId: submit_button_shared_id
}
}
});

// Wait is required to avoid false-negative.
// TODO: replace wait with a more efficient method.
await new Promise(resolve => setTimeout(resolve, 300));

const success_message_selector_result = await test_driver.bidi.browsing_context.locate_nodes({
context: iframe.context,
locator: {type: "innerText", value: "PASS!!!"}
});

assert_equals(success_message_selector_result.nodes.length, 1);
assert_equals(success_message_selector_result.nodes[0].value.attributes.style, 'color: Green');
}, "Allow form submission inside sandbox iframe when sandbox='allow-forms'.");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!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>

<iframe src="support/iframe_sandbox_007.htm" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
<script type="text/javascript">
promise_test(async () => {
const tree = await test_driver.bidi.browsing_context.get_tree({root: window});
const iframe = tree[0].children[0];
const submit_button_selector_result = await test_driver.bidi.browsing_context.locate_nodes({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the API is nice and I like it very much that you can use window interchangeably with browsingContext

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the same approach can be used for the element itself vs it's sharedId

context: iframe.context,
locator: {type: "css", value: "#submitButton"}
});
const submit_button_shared_id = submit_button_selector_result.nodes[0].sharedId;
await test_driver.bidi.input.click({
context: iframe.context,
origin: {
type: "element",
element: {
sharedId: submit_button_shared_id
}
}
});

// Wait is required to avoid false-positive passes.
// TODO: replace wait with a more efficient method.
await new Promise(resolve => setTimeout(resolve, 300));

const success_message_selector_result = await test_driver.bidi.browsing_context.locate_nodes({
context: iframe.context,
locator: {type: "innerText", value: "PASS!!!"}
});

assert_equals(success_message_selector_result.nodes.length, 0);
}, "Block form submission inside iframe with sandbox attribute.");
</script>
37 changes: 37 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@
Represents `WebDriver BiDi <https://w3c.github.io/webdriver-bidi>`_ protocol.
*/
bidi: {
/**
* `browsingContext <https://w3c.github.io/webdriver-bidi/#module-browsingContext>`_ module.
*/
browsing_context: {
/**
* `browsingContext.getTree <https://w3c.github.io/webdriver-bidi/#command-browsingContext-getTree>`_ command.
* @param {{max_depth?: number, root?: (string | Window)}} props - Parameters for the command.
* @return {Promise<void>}
*/
get_tree: async function(props) {
return window.test_driver_internal.bidi.browsing_context.get_tree(props);
},
locate_nodes: async function(props) {
return window.test_driver_internal.bidi.browsing_context.locate_nodes(props);
}
},
/**
* `input <https://w3c.github.io/webdriver-bidi/#module-input>`_ module.
*/
input: {
click: async function(props) {
return window.test_driver_internal.bidi.input.click(props);
}
},
/**
* `log <https://w3c.github.io/webdriver-bidi/#module-log>`_ module.
*/
Expand Down Expand Up @@ -1075,6 +1099,19 @@
in_automation: false,

bidi: {
browsing_context: {
get_tree: function () {
throw new Error("bidi.browsing_context.get_tree is not implemented by testdriver-vendor.js");
},
locate_nodes: function () {
throw new Error("bidi.browsing_context.locate_nodes is not implemented by testdriver-vendor.js");
}
},
input: {
click: function () {
throw new Error("bidi.browsing_context.get_tree is not implemented by testdriver-vendor.js");
},
},
log: {
entry_added: {
subscribe: function () {
Expand Down
86 changes: 84 additions & 2 deletions tools/wptrunner/wptrunner/executors/asyncactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys

from typing import Dict, List, Literal, Optional, Union
from typing_extensions import NotRequired, TypedDict


# TODO: check if type annotation is supported by all the required versions of Python.
Expand All @@ -12,14 +13,91 @@ class WindowProxyProperties(Dict):

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


class BidiBrowsingContextGetTreeAction:
name = "bidi.browsing_context.get_tree"

# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class Payload(TypedDict):
max_depth: NotRequired[int]
root: NotRequired[Union[WindowProxyRemoteValue, str]]

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

async def __call__(self, payload: Payload):
root = None
if "root" in payload:
root = payload["root"]
if isinstance(root, dict) and "type" in root and root["type"] == "window":
root = root["value"]["context"]
return await self.protocol.bidi_browsing_context.get_tree(root)


class BidiBrowsingContextLocateNodesAction:
name = "bidi.browsing_context.locate_nodes"

# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class Payload(TypedDict):
context: Union[WindowProxyRemoteValue, str]
locator: List[Dict]

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

async def __call__(self, payload: Payload):
context = payload["context"]
if isinstance(context, dict) and "type" in context and context["type"] == "window":
context = context["value"]["context"]
return await self.protocol.bidi_browsing_context.locate_nodes(context, payload["locator"])


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


class BidiInputPerformAction:
name = "bidi.input.perform_actions"

# TODO: check if type annotation is supported by all the required versions of Python.
# noinspection PyCompatibility
class Payload(TypedDict):
context: Union[str, WindowProxyRemoteValue]
actions: List[Dict]

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

async def __call__(self, payload: Payload):
"""
:param payload: https://w3c.github.io/webdriver-bidi/#command-input-performActions
:return:
"""
context = payload["context"]
if isinstance(context, dict) and "type" in context and context["type"] == "window":
context = context["value"]["context"]

return await self.protocol.bidi_input.perform_actions(payload["actions"], context)


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

Expand Down Expand Up @@ -54,4 +132,8 @@ async def __call__(self, payload: Payload):
return await self.protocol.bidi_events.subscribe(events, contexts)


async_actions = [BidiSessionSubscribeAction]
async_actions = [
BidiBrowsingContextGetTreeAction,
BidiBrowsingContextLocateNodesAction,
BidiInputPerformAction,
BidiSessionSubscribeAction]
33 changes: 32 additions & 1 deletion tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
RPHRegistrationsProtocolPart,
FedCMProtocolPart,
VirtualSensorProtocolPart,
BidiBrowsingContextProtocolPart,
BidiEventsProtocolPart,
BidiInputProtocolPart,
BidiScriptProtocolPart,
merge_dicts)

Expand Down Expand Up @@ -109,6 +111,33 @@ def wait(self):
return False


class WebDriverBidiBrowsingContextProtocolPart(BidiBrowsingContextProtocolPart):
def __init__(self, parent):
super().__init__(parent)
self.webdriver = None

def setup(self):
self.webdriver = self.parent.webdriver

async def get_tree(self, root=None, max_depth=None):
return await self.webdriver.bidi_session.browsing_context.get_tree(root=root, max_depth=max_depth)

async def locate_nodes(self, context, locator):
return await self.webdriver.bidi_session.browsing_context.locate_nodes(context=context, locator=locator)


class WebDriverBidiInputProtocolPart(BidiInputProtocolPart):
def __init__(self, parent):
super().__init__(parent)
self.webdriver = None

def setup(self):
self.webdriver = self.parent.webdriver

async def perform_actions(self, actions, context):
return await self.webdriver.bidi_session.input.perform_actions(context=context, actions=actions)


class WebDriverBidiEventsProtocolPart(BidiEventsProtocolPart):
_subscriptions = []

Expand Down Expand Up @@ -569,7 +598,9 @@ def after_connect(self):

class WebDriverBidiProtocol(WebDriverProtocol):
enable_bidi = True
implements = [WebDriverBidiEventsProtocolPart,
implements = [WebDriverBidiBrowsingContextProtocolPart,
WebDriverBidiEventsProtocolPart,
WebDriverBidiInputProtocolPart,
WebDriverBidiScriptProtocolPart,
*(part for part in WebDriverProtocol.implements)
]
Expand Down
37 changes: 35 additions & 2 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import traceback
from http.client import HTTPConnection
from typing import Any, Awaitable, Callable, Optional, Mapping
from typing import ClassVar, List, Type, Any, Awaitable, Callable, Optional, Mapping

from abc import ABCMeta, abstractmethod
from typing import ClassVar, List, Type


def merge_dicts(target, source):
Expand Down Expand Up @@ -322,6 +321,40 @@ def get_computed_role(self, element):
pass


class BidiBrowsingContextProtocolPart(ProtocolPart):
"""Protocol part for managing BiDi events"""
__metaclass__ = ABCMeta
name = "bidi_browsing_context"

@abstractmethod
async def get_tree(self, root=None, max_depth=None):
"""
Get the tree of browsing contexts.
:param root: The root of the tree. If `None`, returns all the browsing contexts.
:param max_depth: The maximum depth of the tree to return. If `None`, returns the entire tree.
:return: The tree of browsing contexts.
"""
pass

@abstractmethod
async def locate_nodes(self, context, locator):
pass


class BidiInputProtocolPart(ProtocolPart):
"""Protocol part for managing BiDi events"""
__metaclass__ = ABCMeta
name = "bidi_input"

@abstractmethod
async def perform_actions(self, actions, context):
"""
Perform a sequence of actions in a specific context as specified in
https://w3c.github.io/webdriver-bidi/#command-input-performActions
"""
pass


class BidiEventsProtocolPart(ProtocolPart):
"""Protocol part for managing BiDi events"""
__metaclass__ = ABCMeta
Expand Down
31 changes: 30 additions & 1 deletion tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,36 @@

window.test_driver_internal.in_automation = true;

window.test_driver_internal.bidi.log.entry_added.subscribe = function (props) {
window.test_driver_internal.bidi.browsing_context.get_tree = function (props) {
return create_action("bidi.browsing_context.get_tree", props);
}
window.test_driver_internal.bidi.browsing_context.locate_nodes = function (props) {
return create_action("bidi.browsing_context.locate_nodes", props);
}

window.test_driver_internal.bidi.input.click = function (props) {
const actions = [{
type: "pointer",
id: "main_mouse",
actions: [{
"type": "pointerMove",
"x": 0,
"y": 0,
"origin": props.origin
}, {
"type": "pointerDown",
"button": 0,
}, {
"type": "pointerUp",
"button": 0,
}]
}]

return create_action("bidi.input.perform_actions", {context: props.context, actions});
}

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