Skip to content

Commit

Permalink
Add testdriver_features and use in Chrome
Browse files Browse the repository at this point in the history
* Add testdriver_features
* Choose ChromeDriverProtocol based on `testdriver_features` containing `bidi`
  • Loading branch information
sadym-chromium committed Nov 15, 2024
1 parent 3c2f2be commit 1dad89c
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
disabled: https://github.com/web-platform-tests/wpt/issues/47544
disabled:
if product != "chrome": @True
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
disabled:
if product != "chrome": @True
2 changes: 1 addition & 1 deletion infrastructure/webdriver/bidi/subscription.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<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.js?feature=bidi"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
promise_test(async () => {
Expand Down
22 changes: 22 additions & 0 deletions infrastructure/webdriver/bidi/subscription.window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// META: title=Test console log are present
// META: script=/resources/testdriver.js?feature=bidi

'use strict';

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 = test_driver.bidi.log.entry_added.once();
// Emit a console.log message.
// Note: Lint rule is disabled in `lint.ignore` file.
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 testdriver can subscribe and receive events");
1 change: 1 addition & 0 deletions lint.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ CONSOLE: webaudio/resources/audit.js:41

# Intentional use of console.*
CONSOLE: infrastructure/webdriver/bidi/subscription.html
CONSOLE: infrastructure/webdriver/bidi/subscription.window.js

# use of console in a public library - annotation-model ensures
# it is not actually used
Expand Down
23 changes: 23 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
var idCounter = 0;
let testharness_context = null;

const features = (() => {
function getFeatures(scriptSrc) {
try {
const url = new URL(scriptSrc);
return url.searchParams.getAll('feature');
} catch (e) {
return [];
}
}

return getFeatures(document?.currentScript?.src ?? '');
})();

function assertBidiIsEnabled(){
if (!features.includes('bidi')) {
throw new Error(
"`?feature=bidi` is missing when importing testdriver.js but the test is using WebDriver BiDi APIs");
}
}

function getInViewCenterPoint(rect) {
var left = Math.max(0, rect.left);
var right = Math.min(window.innerWidth, rect.right);
Expand Down Expand Up @@ -73,6 +93,7 @@
* @return {Promise<void>}
*/
subscribe: async function (params = {}) {
assertBidiIsEnabled();
return window.test_driver_internal.bidi.log.entry_added.subscribe(params);
},
/**
Expand All @@ -86,9 +107,11 @@
* remove the event listener.
*/
on: function (callback) {
assertBidiIsEnabled();
return window.test_driver_internal.bidi.log.entry_added.on(callback);
},
once: function () {
assertBidiIsEnabled();
return new Promise(resolve => {
const remove_handler = window.test_driver_internal.bidi.log.entry_added.on(
data => {
Expand Down
28 changes: 21 additions & 7 deletions tools/lint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,20 +523,34 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]
for element in source_file.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src]"):
src = element.attrib["src"]

def incorrect_path(script: Text, src: Text) -> bool:
return (script == src or
("/%s" % script in src and src != "/resources/%s" % script))
def is_path_correct(script: Text, src: Text,
allow_query_params: bool = False) -> bool:
if script == src:
# The src does not provide the full path.
return False

if incorrect_path("testharness.js", src):
if "/%s" % script not in src:
# The src is not related to the script.
return True

if src == "/resources/%s" % script:
# The src is properly included.
return True

# Check if the script is properly included with query parameters.
return allow_query_params and ("%s" % src).startswith(
"/resources/%s?" % script)

if not is_path_correct("testharness.js", src):
errors.append(rules.TestharnessPath.error(path))

if incorrect_path("testharnessreport.js", src):
if not is_path_correct("testharnessreport.js", src):
errors.append(rules.TestharnessReportPath.error(path))

if incorrect_path("testdriver.js", src):
if not is_path_correct("testdriver.js", src, True):
errors.append(rules.TestdriverPath.error(path))

if incorrect_path("testdriver-vendor.js", src):
if not is_path_correct("testdriver-vendor.js", src):
errors.append(rules.TestdriverVendorPath.error(path))

script_path = None
Expand Down
6 changes: 6 additions & 0 deletions tools/manifest/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ def timeout(self) -> Optional[Text]:
def pac(self) -> Optional[Text]:
return self._extras.get("pac")

@property
def testdriver_features(self) -> Optional[List[Text]]:
return self._extras.get("testdriver_features")

@property
def testdriver(self) -> Optional[Text]:
return self._extras.get("testdriver")
Expand All @@ -183,6 +187,8 @@ def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
rv[-1]["timeout"] = self.timeout
if self.pac is not None:
rv[-1]["pac"] = self.pac
if self.testdriver_features is not None:
rv[-1]["testdriver_features"] = self.testdriver_features
if self.testdriver:
rv[-1]["testdriver"] = self.testdriver
if self.jsshell:
Expand Down
56 changes: 54 additions & 2 deletions tools/manifest/sourcefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from io import BytesIO
from typing import (Any, BinaryIO, Callable, Deque, Dict, Iterable, List,
Optional, Pattern, Set, Text, Tuple, TypedDict, Union, cast)
from urllib.parse import urljoin
from urllib.parse import urlparse, parse_qs, urljoin

try:
from xml.etree import cElementTree as ElementTree
Expand Down Expand Up @@ -704,7 +704,15 @@ def testdriver_nodes(self) -> List[ElementTree.Element]:
"""List of ElementTree Elements corresponding to nodes representing a
testdriver.js script"""
assert self.root is not None
return self.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testdriver.js']")
# `xml.etree.ElementTree.findall` has a limited support of xPath, so
# explicit filter is required.
return [node for node in
self.root.findall(".//{http://www.w3.org/1999/xhtml}script")
if node.attrib.get('src',
"") == '/resources/testdriver.js'
or
node.attrib.get('src', "").startswith(
'/resources/testdriver.js?')]

@cached_property
def has_testdriver(self) -> Optional[bool]:
Expand All @@ -714,6 +722,46 @@ def has_testdriver(self) -> Optional[bool]:
return None
return bool(self.testdriver_nodes)

def ___get_testdriver_include_path(self):
if self.script_metadata:
for (meta, content) in self.script_metadata:
if meta.strip() == 'script' and content.startswith(
'/resources/testdriver.js'):
return content.strip()

if self.root is None:
return None

for node in self.testdriver_nodes:
if "src" in node.attrib:
return node.attrib.get("src")

return None


@cached_property
def testdriver_features(self) -> Optional[Text]:
"""
List of requested testdriver features.
"""

testdriver_include_url = self.___get_testdriver_include_path()

if testdriver_include_url is None:
return None

# Parse the URL
parsed_url = urlparse(testdriver_include_url)
# Extract query parameters
query_params = parse_qs(parsed_url.query)
# Get the values for the 'feature' parameter
feature_values = query_params.get('feature', [])

if len(feature_values) > 0:
return feature_values

return None

@cached_property
def reftest_nodes(self) -> List[ElementTree.Element]:
"""List of ElementTree Elements corresponding to nodes representing a
Expand Down Expand Up @@ -965,6 +1013,7 @@ def manifest_items(self) -> Tuple[Text, List[ManifestItem]]:
global_variant_url(self.rel_url, suffix) + variant,
timeout=self.timeout,
pac=self.pac,
testdriver_features=self.testdriver_features,
jsshell=jsshell,
script_metadata=self.script_metadata
)
Expand All @@ -983,6 +1032,7 @@ def manifest_items(self) -> Tuple[Text, List[ManifestItem]]:
test_url + variant,
timeout=self.timeout,
pac=self.pac,
testdriver_features=self.testdriver_features,
script_metadata=self.script_metadata
)
for variant in self.test_variants
Expand All @@ -999,6 +1049,7 @@ def manifest_items(self) -> Tuple[Text, List[ManifestItem]]:
test_url + variant,
timeout=self.timeout,
pac=self.pac,
testdriver_features=self.testdriver_features,
script_metadata=self.script_metadata
)
for variant in self.test_variants
Expand Down Expand Up @@ -1026,6 +1077,7 @@ def manifest_items(self) -> Tuple[Text, List[ManifestItem]]:
url,
timeout=self.timeout,
pac=self.pac,
testdriver_features=self.testdriver_features,
testdriver=testdriver,
script_metadata=self.script_metadata
))
Expand Down
10 changes: 7 additions & 3 deletions tools/manifest/tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,20 @@ def test_update_from_json_modified():
# Reload it from JSON
m = manifest.Manifest.from_json("/", json_str)

# Update it with timeout="long"
s2 = SourceFileWithTest("test1", "1"*40, item.TestharnessTest, timeout="long", pac="proxy.pac")
testdriver_features = ['feature_1', 'feature_2']

# Update timeout, pac and testdriver_features
s2 = SourceFileWithTest("test1", "1" * 40, item.TestharnessTest, timeout="long", pac="proxy.pac",
testdriver_features=testdriver_features)
tree, sourcefile_mock = tree_and_sourcefile_mocks([(s2, None, True)])
with mock.patch("tools.manifest.manifest.SourceFile", side_effect=sourcefile_mock):
m.update(tree)
json_str = m.to_json()
assert json_str == {
'items': {'testharness': {'test1': [
"1"*40,
(None, {'timeout': 'long', 'pac': 'proxy.pac'})
(None, {'timeout': 'long', 'pac': 'proxy.pac',
'testdriver_features': testdriver_features})
]}},
'url_base': '/',
'version': 8
Expand Down
53 changes: 53 additions & 0 deletions tools/manifest/tests/test_sourcefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,3 +960,56 @@ def test_page_ranges_invalid(page_ranges):
def test_hash():
s = SourceFile("/", "foo", "/", contents=b"Hello, World!")
assert "b45ef6fec89518d314f546fd6c3025367b721684" == s.hash


@pytest.mark.parametrize("file_name",
["html/test.worker.js", "html/test.window.js"])
def test_script_testdriver_missing_features(file_name):
contents = f"""// META: title=TEST_TITLE
// META: script=/resources/testdriver.js
test()""".encode("utf-8")

s = create(file_name, contents=contents)
item_type, items = s.manifest_items()
for item in items:
assert item.testdriver_features is None


@pytest.mark.parametrize("features",
[[], ['feature_1'], ['feature_1', 'feature_2']])
@pytest.mark.parametrize("file_name",
["html/test.worker.js", "html/test.window.js"])
def test_script_testdriver_features(file_name, features):
contents = f"""// META: title=TEST_TITLE
// META: script=/resources/testdriver.js?{"&".join('feature=' + f for f in features)}
test()""".encode("utf-8")

s = create(file_name, contents=contents)
item_type, items = s.manifest_items()
for item in items:
assert item.testdriver_features == (
features if len(features) > 0 else None)


def test_html_testdriver_missing_features():
contents = f"""
<!--Required to make test type `testharness` -->
<script src="/resources/testharness.js"></script>
<script src="/resources/testdriver.js?feature=bidi"></script>
""".encode("utf-8")

s = create("html/test.html", contents=contents)
s.testdriver_features == None


@pytest.mark.parametrize("features",
[[], ['feature_1'], ['feature_1', 'feature_2']])
def test_html_testdriver_features(features):
contents = f"""
<script src="/resources/testdriver.js?{"&".join('feature=' + f for f in features)}"></script>
<!--Required to make test type `testharness` -->
<script src="/resources/testharness.js?feature=bidi"></script>
""".encode("utf-8")

s = create("html/test.html", contents=contents)
s.testdriver_features == ['bidi']
19 changes: 18 additions & 1 deletion tools/wptrunner/wptrunner/browsers/chrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mozlog.structuredlog import StructuredLogger

from . import chrome_spki_certs
from .base import BrowserError
from .base import BrowserError, BrowserSettings
from .base import WebDriverBrowser, require_arg
from .base import NullBrowser # noqa: F401
from .base import OutputHandler
Expand Down Expand Up @@ -41,6 +41,8 @@
"update_properties": "update_properties",
"timeout_multiplier": "get_timeout_multiplier",}

from ..wpttest import Test


def debug_args(debug_info):
if debug_info.interactive:
Expand Down Expand Up @@ -214,6 +216,7 @@ def __init__(self,
super().__init__(logger, **kwargs)
self._leak_check = leak_check
self._actual_port = None
self._require_webdriver_bidi = None

def restart_on_test_type_change(self, new_test_type: str, old_test_type: str) -> bool:
# Restart the test runner when switch from/to wdspec tests. Wdspec test
Expand Down Expand Up @@ -262,6 +265,20 @@ def executor_browser(self):
browser_cls, browser_kwargs = super().executor_browser()
return browser_cls, {**browser_kwargs, "leak_check": self._leak_check}

@property
def require_webdriver_bidi(self) -> Optional[bool]:
return self._require_webdriver_bidi

def settings(self, test: Test) -> BrowserSettings:
""" Required to store `require_webdriver_bidi` in browser settings."""
settings = super().settings(test)
self._require_webdriver_bidi = test.testdriver_features is not None and 'bidi' in test.testdriver_features

return {
**settings,
"require_webdriver_bidi": self._require_webdriver_bidi
}


class ChromeDriverOutputHandler(OutputHandler):
PORT_RE = re.compile(rb'.*was started successfully on port (\d+)\.')
Expand Down
Loading

0 comments on commit 1dad89c

Please sign in to comment.