Roll wpt tooling
This rolls wpt to latest commit at https://github.com/web-platform-tests/wpt. REMOTE-WPT-HEAD: 7dac8f99479ebe6835ab0eb38ce242cc0ad6be26 Cq-Include-Trybots: luci.chromium.try:linux-blink-rel Change-Id: I782c00d32bc6fc40c54da54f5b64a43a7d07aa5a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5160314 Auto-Submit: Jonathan Lee <jonathanjlee@google.com> Reviewed-by: Ben Pastene <bpastene@chromium.org> Reviewed-by: Weizhong Xia <weizhong@google.com> Commit-Queue: Weizhong Xia <weizhong@google.com> Cr-Commit-Position: refs/heads/main@{#1244210}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
93647d60dd
commit
55340421be
.vpython3
third_party
blink
tools
blinkpy
presubmit
web_tests
ChromeTestExpectations
platform
linux-chrome
external
wpt
webdriver
tests
bidi
browsing_context
network
continue_with_auth
wpt_tools
README.chromiumWPTIncludeList
wpt
resources
tools
lint
metadata
third_party
webdriver
webdriver
bidi
wpt
wptrunner
wptrunner
wptserve
wptserve
12
.vpython3
12
.vpython3
@ -92,13 +92,6 @@ wheel: <
|
||||
platform: "manylinux1_x86_64"
|
||||
>
|
||||
>
|
||||
wheel: <
|
||||
name: "infra/python/wheels/pyyaml/${vpython_platform}"
|
||||
version: "version:5.4.1.chromium.1"
|
||||
match_tag: <
|
||||
platform: "manylinux1_x86_64"
|
||||
>
|
||||
>
|
||||
wheel: <
|
||||
name: "infra/python/wheels/typing-inspect-py3"
|
||||
version: "version:0.7.1"
|
||||
@ -174,6 +167,11 @@ wheel: <
|
||||
platform: "linux_arm64"
|
||||
>
|
||||
>
|
||||
# `pyyaml` is shared between `//third_party/wpt_tools/wpt/wpt` and `pytype`.
|
||||
wheel: <
|
||||
name: "infra/python/wheels/pyyaml/${vpython_platform}"
|
||||
version: "version:5.4.1.chromium.1"
|
||||
>
|
||||
|
||||
# Used by various python unit tests.
|
||||
wheel: <
|
||||
|
@ -10,9 +10,9 @@ from typing import Optional
|
||||
def lint_wpt_root(input_api, output_api, repo_root: Optional[str] = None):
|
||||
"""Run `wpt lint` against the specified directory."""
|
||||
repo_root = repo_root or input_api.PresubmitLocalPath()
|
||||
wpt_executable = input_api.os_path.join(input_api.change.RepositoryRoot(),
|
||||
'third_party', 'wpt_tools', 'wpt',
|
||||
'wpt')
|
||||
wpt_root = input_api.os_path.join(input_api.change.RepositoryRoot(),
|
||||
'third_party', 'wpt_tools', 'wpt')
|
||||
wpt_executable = input_api.os_path.join(wpt_root, 'wpt')
|
||||
|
||||
# TODO(crbug.com/1406669): Changing a test file should also lint its
|
||||
# corresponding reference/*-expected.txt file, if any, because the
|
||||
@ -32,13 +32,17 @@ def lint_wpt_root(input_api, output_api, repo_root: Optional[str] = None):
|
||||
# that the file can be opened by name on Windows.
|
||||
with tempfile.NamedTemporaryFile('w+', newline='', delete=False) as f:
|
||||
for path in paths:
|
||||
f.write('%s\n' % path)
|
||||
f.write(f'{path}\n')
|
||||
paths_name = f.name
|
||||
args = [
|
||||
input_api.python3_executable,
|
||||
wpt_executable,
|
||||
# Third-party packages are vended through vpython instead of plain
|
||||
# virtualenv.
|
||||
f'--venv={wpt_root}',
|
||||
'--skip-venv-setup',
|
||||
'lint',
|
||||
'--repo-root=%s' % repo_root,
|
||||
f'--repo-root={repo_root}',
|
||||
# To avoid false positives, do not lint files not upstreamed from
|
||||
# Chromium.
|
||||
'--ignore-glob=*-expected.txt',
|
||||
@ -46,7 +50,7 @@ def lint_wpt_root(input_api, output_api, repo_root: Optional[str] = None):
|
||||
'--ignore-glob=*DIR_METADATA',
|
||||
'--ignore-glob=*OWNERS',
|
||||
'--ignore-glob=config.json',
|
||||
'--paths-file=%s' % paths_name,
|
||||
f'--paths-file={paths_name}',
|
||||
]
|
||||
|
||||
proc = input_api.subprocess.Popen(args,
|
||||
|
@ -608,6 +608,7 @@ crbug.com/626703 external/wpt/html/dom/elements/global-attributes/dir-auto-form-
|
||||
crbug.com/626703 external/wpt/svg/pservers/reftests/gradient-color-interpolation.svg [ Failure ]
|
||||
crbug.com/626703 external/wpt/webdriver/tests/bidi/network/continue_response/invalid.py [ Timeout ]
|
||||
crbug.com/626703 external/wpt/webdriver/tests/bidi/network/continue_with_auth/invalid.py [ Timeout ]
|
||||
crbug.com/626703 external/wpt/webdriver/tests/bidi/network/continue_with_auth/action.py [ Timeout ]
|
||||
crbug.com/626703 external/wpt/webdriver/tests/bidi/network/fail_request/invalid.py [ Timeout ]
|
||||
crbug.com/626703 external/wpt/css/css-page/page-orientation-on-landscape-001-print.html [ Failure ]
|
||||
crbug.com/626703 external/wpt/css/css-page/page-orientation-on-portrait-001-print.html [ Failure ]
|
||||
|
@ -1,24 +1,24 @@
|
||||
This is a wdspec test.
|
||||
[FAIL] test_find_by_locator[css-div]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_locator[xpath-//div]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_locator[innerText-foobarBARbaz]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_true_full_match_no_max_depth]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_false_full_match_no_max_depth]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_true_partial_match_no_max_depth]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_false_partial_match_no_max_depth]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_true_full_match_max_depth_zero]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_false_full_match_max_depth_zero]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_true_partial_match_max_depth_zero]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_find_by_inner_text[ignore_case_false_partial_match_max_depth_zero]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
Harness: the test ran to completion.
|
||||
|
@ -1,10 +1,10 @@
|
||||
This is a wdspec test.
|
||||
[FAIL] test_locate_nodes_in_sandbox
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_locate_same_node_in_different_sandboxes_returns_same_id
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_locate_same_node_in_default_sandbox_returns_same_id_as_sandbox
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_locate_same_node_in_different_sandboxes_with_root_ownership_returns_different_handles
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
Harness: the test ran to completion.
|
||||
|
@ -1,6 +1,6 @@
|
||||
This is a wdspec test.
|
||||
[FAIL] test_locate_nodes_serialization_options[open]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
[FAIL] test_locate_nodes_serialization_options[closed]
|
||||
AttributeError: 'BrowsingContext' object has no attribute 'locate_nodes'
|
||||
webdriver.bidi.error.UnsupportedOperationException: unsupported operation (Command 'browsingContext.locateNodes' not yet implemented.)
|
||||
Harness: the test ran to completion.
|
||||
|
@ -1,3 +1,3 @@
|
||||
This is a wdspec test.
|
||||
Harness Error. harness_status.status = 1 , harness_status.message =
|
||||
Harness Error. harness_status.status = 2 , harness_status.message =
|
||||
Harness: the test ran to completion.
|
||||
|
2
third_party/wpt_tools/README.chromium
vendored
2
third_party/wpt_tools/README.chromium
vendored
@ -1,7 +1,7 @@
|
||||
Name: web-platform-tests - Test Suites for Web Platform specifications
|
||||
Short Name: wpt
|
||||
URL: https://github.com/web-platform-tests/wpt/
|
||||
Version: a50aec6c90d6ce28629fa4eb5cbeba0d1d65c53b
|
||||
Version: 7dac8f99479ebe6835ab0eb38ce242cc0ad6be26
|
||||
License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
|
||||
Security Critical: no
|
||||
Shipped: no
|
||||
|
17
third_party/wpt_tools/WPTIncludeList
vendored
17
third_party/wpt_tools/WPTIncludeList
vendored
@ -60,6 +60,23 @@
|
||||
./tools/manifest/update.py
|
||||
./tools/manifest/utils.py
|
||||
./tools/manifest/vcs.py
|
||||
./tools/metadata/__init__.py
|
||||
./tools/metadata/meta/__init__.py
|
||||
./tools/metadata/meta/schema.py
|
||||
./tools/metadata/meta/tests/__init__.py
|
||||
./tools/metadata/meta/tests/test_schema.py
|
||||
./tools/metadata/schema.py
|
||||
./tools/metadata/tests/__init__.py
|
||||
./tools/metadata/tests/test_schema.py
|
||||
./tools/metadata/webfeatures/__init__.py
|
||||
./tools/metadata/webfeatures/schema.py
|
||||
./tools/metadata/webfeatures/tests/__init__.py
|
||||
./tools/metadata/webfeatures/tests/test_schema.py
|
||||
./tools/metadata/yaml/__init__.py
|
||||
./tools/metadata/yaml/load.py
|
||||
./tools/metadata/yaml/requirements.txt
|
||||
./tools/metadata/yaml/tests/__init__.py
|
||||
./tools/metadata/yaml/tests/test_file.py
|
||||
./tools/quic/__init__.py
|
||||
./tools/quic/commands.json
|
||||
./tools/quic/requirements.txt
|
||||
|
@ -357,6 +357,25 @@
|
||||
return window.test_driver_internal.set_window_rect(rect, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a rect with the size and position on the screen from the current window state.
|
||||
*
|
||||
* Matches the behaviour of the `Get Window Rect
|
||||
* <https://www.w3.org/TR/webdriver/#get-window-rect>`_
|
||||
* WebDriver command
|
||||
*
|
||||
* @param {WindowProxy} context - Browsing context in which
|
||||
* to run the call, or null for the current
|
||||
* browsing context.
|
||||
*
|
||||
* @returns {Promise} fulfilled after the window rect is returned, or rejected
|
||||
* in cases the WebDriver command returns errors. Returns a
|
||||
* `WindowRect <https://www.w3.org/TR/webdriver/#dfn-windowrect-object>`_
|
||||
*/
|
||||
get_window_rect: function(context=null) {
|
||||
return window.test_driver_internal.get_window_rect(context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a sequence of actions
|
||||
*
|
||||
@ -1082,6 +1101,10 @@
|
||||
throw new Error("set_window_rect() is not implemented by testdriver-vendor.js");
|
||||
},
|
||||
|
||||
async get_window_rect(context=null) {
|
||||
throw new Error("get_window_rect() is not implemented by testdriver-vendor.js");
|
||||
},
|
||||
|
||||
async action_sequence(actions, context=null) {
|
||||
throw new Error("action_sequence() is not implemented by testdriver-vendor.js");
|
||||
},
|
||||
|
@ -1,3 +1,12 @@
|
||||
{"lint":
|
||||
{"path": "lint.py", "script": "main", "parser": "create_parser", "help": "Run the lint",
|
||||
"virtualenv": false}}
|
||||
{
|
||||
"lint": {
|
||||
"path": "lint.py",
|
||||
"script": "main",
|
||||
"parser": "create_parser",
|
||||
"help": "Run the lint",
|
||||
"virtualenv": true,
|
||||
"requirements": [
|
||||
"../metadata/yaml/requirements.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
37
third_party/wpt_tools/wpt/tools/lint/lint.py
vendored
37
third_party/wpt_tools/wpt/tools/lint/lint.py
vendored
@ -31,6 +31,9 @@ from ..manifest.vcs import walk
|
||||
|
||||
from ..manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants
|
||||
|
||||
from ..metadata.yaml.load import load_data_to_dict
|
||||
from ..metadata.meta.schema import META_YML_FILENAME, MetaFile
|
||||
from ..metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME, WebFeaturesFile
|
||||
|
||||
# The Ignorelist is a two level dictionary. The top level is indexed by
|
||||
# error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of
|
||||
@ -663,6 +666,36 @@ def check_ahem_system_font(repo_root: Text, path: Text, f: IO[bytes]) -> List[ru
|
||||
return errors
|
||||
|
||||
|
||||
def check_meta_file(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
|
||||
if os.path.basename(path) != META_YML_FILENAME:
|
||||
return []
|
||||
try:
|
||||
MetaFile(load_data_to_dict(f))
|
||||
except Exception:
|
||||
return [rules.InvalidMetaFile.error(path)]
|
||||
return []
|
||||
|
||||
|
||||
def check_web_features_file(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
|
||||
if os.path.basename(path) != WEB_FEATURES_YML_FILENAME:
|
||||
return []
|
||||
try:
|
||||
web_features_file: WebFeaturesFile = WebFeaturesFile(load_data_to_dict(f))
|
||||
except Exception:
|
||||
return [rules.InvalidWebFeaturesFile.error(path)]
|
||||
errors = []
|
||||
base_dir = os.path.join(repo_root, os.path.dirname(path))
|
||||
files_in_directory = [
|
||||
f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))]
|
||||
for feature in web_features_file.features:
|
||||
if isinstance(feature.files, list):
|
||||
for file in feature.files:
|
||||
if not file.match_files(files_in_directory):
|
||||
errors.append(rules.MissingTestInWebFeaturesFile.error(path, (file)))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_path(repo_root: Text, path: Text) -> List[rules.Error]:
|
||||
"""
|
||||
Runs lints that check the file path.
|
||||
@ -847,7 +880,7 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def main(**kwargs: Any) -> int:
|
||||
def main(venv: Any = None, **kwargs: Any) -> int:
|
||||
|
||||
assert logger is not None
|
||||
if kwargs.get("json") and kwargs.get("markdown"):
|
||||
@ -984,7 +1017,7 @@ def lint(repo_root: Text,
|
||||
path_lints = [check_file_type, check_path_length, check_worker_collision, check_ahem_copy,
|
||||
check_mojom_js, check_tentative_directories, check_gitignore_file]
|
||||
file_lints = [check_regexp_line, check_parsed, check_python_ast, check_script_metadata,
|
||||
check_ahem_system_font]
|
||||
check_ahem_system_font, check_meta_file, check_web_features_file]
|
||||
|
||||
|
||||
def all_paths_lints() -> Any:
|
||||
|
17
third_party/wpt_tools/wpt/tools/lint/rules.py
vendored
17
third_party/wpt_tools/wpt/tools/lint/rules.py
vendored
@ -339,6 +339,23 @@ class TentativeDirectoryName(Rule):
|
||||
to_fix = "rename directory to be called 'tentative'"
|
||||
|
||||
|
||||
class InvalidMetaFile(Rule):
|
||||
name = "INVALID-META-FILE"
|
||||
description = "The META.yml is not a YAML file with the expected structure"
|
||||
|
||||
|
||||
class InvalidWebFeaturesFile(Rule):
|
||||
name = "INVALID-WEB-FEATURES-FILE"
|
||||
description = "The WEB_FEATURES.yml file contains an invalid structure"
|
||||
|
||||
|
||||
class MissingTestInWebFeaturesFile(Rule):
|
||||
name = "MISSING-WEB-FEATURES-FILE"
|
||||
description = collapse("""
|
||||
The WEB_FEATURES.yml file references a test that does not exist: '%s'
|
||||
""")
|
||||
|
||||
|
||||
class Regexp(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def pattern(self) -> bytes:
|
||||
|
0
third_party/wpt_tools/wpt/tools/metadata/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/meta/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/meta/__init__.py
vendored
Normal file
31
third_party/wpt_tools/wpt/tools/metadata/meta/schema.py
vendored
Normal file
31
third_party/wpt_tools/wpt/tools/metadata/meta/schema.py
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Set
|
||||
|
||||
from ..schema import SchemaValue, validate_dict
|
||||
|
||||
"""
|
||||
YAML filename for meta files
|
||||
"""
|
||||
META_YML_FILENAME = "META.yml"
|
||||
|
||||
@dataclass
|
||||
class MetaFile():
|
||||
"""documented structure of META files.
|
||||
Reference: https://github.com/web-platform-tests/wpt/pull/18434
|
||||
"""
|
||||
|
||||
"""a link to the specification covered by the tests in the directory"""
|
||||
spec: Optional[str] = None
|
||||
"""a list of GitHub account username belonging to people who are notified when pull requests
|
||||
modify files in the directory
|
||||
"""
|
||||
suggested_reviewers: Optional[List[str]] = None
|
||||
|
||||
_optional_keys: ClassVar[Set[str]] = {"spec", "suggested_reviewers"}
|
||||
|
||||
def __init__(self, obj: Dict[str, Any]):
|
||||
validate_dict(obj, optional_keys=MetaFile._optional_keys)
|
||||
self.spec = SchemaValue.from_union([SchemaValue.from_str, SchemaValue.from_none], obj.get("spec"))
|
||||
self.suggested_reviewers = SchemaValue.from_union(
|
||||
[lambda x: SchemaValue.from_list(SchemaValue.from_str, x), SchemaValue.from_none],
|
||||
obj.get("suggested_reviewers"))
|
0
third_party/wpt_tools/wpt/tools/metadata/meta/tests/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/meta/tests/__init__.py
vendored
Normal file
77
third_party/wpt_tools/wpt/tools/metadata/meta/tests/test_schema.py
vendored
Normal file
77
third_party/wpt_tools/wpt/tools/metadata/meta/tests/test_schema.py
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
# mypy: allow-untyped-defs
|
||||
|
||||
from dataclasses import asdict
|
||||
from ..schema import MetaFile
|
||||
|
||||
import pytest
|
||||
import re
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
(
|
||||
{
|
||||
"spec": "spec-value",
|
||||
"suggested_reviewers": ["reviewer_1", "reviewer_2"]
|
||||
},
|
||||
{
|
||||
"spec": "spec-value",
|
||||
"suggested_reviewers": ["reviewer_1", "reviewer_2"]
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
{
|
||||
"spec": "spec-value",
|
||||
},
|
||||
{
|
||||
"spec": "spec-value",
|
||||
"suggested_reviewers": None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
{
|
||||
"suggested_reviewers": ["reviewer_1", "reviewer_2"]
|
||||
},
|
||||
{
|
||||
"spec": None,
|
||||
"suggested_reviewers": ["reviewer_1", "reviewer_2"],
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
{},
|
||||
{"spec": None, "suggested_reviewers": None},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
{
|
||||
"spec": "spec-value",
|
||||
"suggested_reviewers": ["reviewer_1", 3]
|
||||
},
|
||||
None,
|
||||
ValueError,
|
||||
"Input value ['reviewer_1', 3] does not fit one of the expected values for the union"
|
||||
),
|
||||
(
|
||||
{
|
||||
"spec": "spec-value",
|
||||
"suggested_reviewers": ["reviewer_1", "reviewer_2"],
|
||||
"extra": "test"
|
||||
},
|
||||
None,
|
||||
ValueError,
|
||||
"Object contains invalid keys: ['extra']"
|
||||
),
|
||||
])
|
||||
def test_meta_file(input, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=re.escape(exception_message)):
|
||||
MetaFile(input)
|
||||
else:
|
||||
assert expected_result == asdict(MetaFile(input))
|
88
third_party/wpt_tools/wpt/tools/metadata/schema.py
vendored
Normal file
88
third_party/wpt_tools/wpt/tools/metadata/schema.py
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
from typing import Any, Callable, cast, Dict, Sequence, Set, Type, TypeVar, Union
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def validate_dict(obj: Any, required_keys: Set[str] = set(), optional_keys: Set[str] = set()) -> None:
|
||||
"""
|
||||
Validates the keys for a particular object
|
||||
This logic ensures:
|
||||
1. the obj is type dict
|
||||
2. That at a minimum the provided required_keys are present.
|
||||
Additionally, the logic checks for a set of optional_keys. With those two
|
||||
sets of keys, the logic will raise an error if there are extra keys in obj.
|
||||
:param obj: The object that will be checked.
|
||||
:param required_keys: Set of required keys that the obj should have.
|
||||
:param optional_keys: Set of optional keys that the obj should have.
|
||||
:return: `None` if obj does not have any extra keys.
|
||||
:raises ValueError: If there unexpected keys or missing required keys.
|
||||
"""
|
||||
if not isinstance(obj, dict):
|
||||
raise ValueError(f"Object is not a dictionary. Input: {obj}")
|
||||
extra_keys = set(obj.keys()) - required_keys - optional_keys
|
||||
missing_required_keys = required_keys - set(obj.keys())
|
||||
if extra_keys:
|
||||
raise ValueError(f"Object contains invalid keys: {sorted(extra_keys)}")
|
||||
if missing_required_keys:
|
||||
raise ValueError(f"Object missing required keys: {sorted(missing_required_keys)}")
|
||||
|
||||
|
||||
class SchemaValue():
|
||||
"""
|
||||
Set of helpers to convert raw input into an expected value for a given schema
|
||||
"""
|
||||
@staticmethod
|
||||
def from_dict(x: Any) -> Dict[str, Any]:
|
||||
if not isinstance(x, dict):
|
||||
raise ValueError(f"Input value {x} is not a dict")
|
||||
keys = x.keys()
|
||||
for key in keys:
|
||||
if not isinstance(key, str):
|
||||
raise ValueError(f"Input value {x} contains key {key} that is not a string")
|
||||
return cast(Dict[str, Any], x)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_str(x: Any) -> str:
|
||||
if not isinstance(x, str):
|
||||
raise ValueError(f"Input value {x} is not a string")
|
||||
return x
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_none(x: Any) -> None:
|
||||
if x is not None:
|
||||
raise ValueError(f"Input value {x} is not none")
|
||||
return x
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_union(fs:
|
||||
Sequence[Union[
|
||||
Callable[[Any], Sequence[T]],
|
||||
Callable[[Any], T],
|
||||
]],
|
||||
x: Any) -> Any:
|
||||
for f in fs:
|
||||
try:
|
||||
return f(x)
|
||||
except Exception:
|
||||
pass
|
||||
raise ValueError(f"Input value {x} does not fit one of the expected values for the union")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_list(f: Callable[[Any], T], x: Any) -> Sequence[T]:
|
||||
if not isinstance(x, list):
|
||||
raise ValueError(f"Input value {x} is not a list")
|
||||
return [f(y) for y in x]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_class(cls: Type[T]) -> Callable[[Any], T]:
|
||||
def class_converter(x: Any) -> T:
|
||||
try:
|
||||
# https://github.com/python/mypy/issues/10343
|
||||
return cls(x) # type: ignore [call-arg]
|
||||
except Exception:
|
||||
raise ValueError(f"Input value {x} could not be converted to {cls}")
|
||||
return class_converter
|
0
third_party/wpt_tools/wpt/tools/metadata/tests/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/tests/__init__.py
vendored
Normal file
94
third_party/wpt_tools/wpt/tools/metadata/tests/test_schema.py
vendored
Normal file
94
third_party/wpt_tools/wpt/tools/metadata/tests/test_schema.py
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
# mypy: allow-untyped-defs
|
||||
|
||||
from ..schema import SchemaValue, validate_dict
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
import pytest
|
||||
import re
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,kwargs,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
({}, {}, None, None, None),
|
||||
("2", {}, None, ValueError, "Object is not a dictionary. Input: 2"),
|
||||
({"extra": 3}, {}, None, ValueError, "Object contains invalid keys: ['extra']"),
|
||||
({"required": 1}, {"required_keys": {"required"}}, None, None, None),
|
||||
({"optional": 2}, {"optional_keys": {"optional"}}, None, None, None),
|
||||
({"extra": 3, "optional": 2}, {"optional_keys": {"optional"}}, None,
|
||||
ValueError, "Object contains invalid keys: ['extra']"),
|
||||
({"required": 1, "optional": 2}, {"required_keys": {"required"}, "optional_keys": {"optional"}}, None, None, None),
|
||||
({"optional": 2}, {"required_keys": {"required"}, "optional_keys": {"optional"}}, None,
|
||||
ValueError, "Object missing required keys: ['required']"),
|
||||
])
|
||||
def test_validate_dict(input, kwargs, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=re.escape(exception_message)):
|
||||
validate_dict(input, **kwargs)
|
||||
else:
|
||||
expected_result == validate_dict(input, **kwargs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FromDictTestDataClass:
|
||||
key: str
|
||||
|
||||
def __init__(self, input):
|
||||
self.key = input.get("key")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
({"key": "value"}, {"key": "value"}, None, None),
|
||||
({1: "value"}, None, ValueError, "Input value {1: 'value'} contains key 1 that is not a string"),
|
||||
(3, None, ValueError, "Input value 3 is not a dict")
|
||||
])
|
||||
def test_from_dict(input, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=exception_message):
|
||||
FromDictTestDataClass(SchemaValue.from_dict(input))
|
||||
else:
|
||||
assert expected_result == asdict(FromDictTestDataClass(SchemaValue.from_dict(input)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
("test", "test", None, None),
|
||||
(2, None, ValueError, "Input value 2 is not a string")
|
||||
])
|
||||
def test_from_str(input, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=exception_message):
|
||||
SchemaValue.from_str(input)
|
||||
else:
|
||||
assert expected_result == SchemaValue.from_str(input)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
(["1", "2"], ["1", "2"], None, None),
|
||||
(2, None, ValueError, "Input value 2 is not a list")
|
||||
])
|
||||
def test_from_list(input, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=exception_message):
|
||||
SchemaValue.from_list(SchemaValue.from_str, input)
|
||||
else:
|
||||
assert expected_result == SchemaValue.from_list(SchemaValue.from_str, input)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
("test", "test", None, None),
|
||||
(None, None, None, None),
|
||||
(2, None, ValueError, "Input value 2 does not fit one of the expected values for the union")
|
||||
])
|
||||
def test_from_union(input,expected_result, expected_exception_type, exception_message):
|
||||
union_input = [SchemaValue.from_str, SchemaValue.from_none]
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=exception_message):
|
||||
SchemaValue.from_union(union_input, input)
|
||||
else:
|
||||
assert expected_result == SchemaValue.from_union(union_input, input)
|
0
third_party/wpt_tools/wpt/tools/metadata/webfeatures/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/webfeatures/__init__.py
vendored
Normal file
86
third_party/wpt_tools/wpt/tools/metadata/webfeatures/schema.py
vendored
Normal file
86
third_party/wpt_tools/wpt/tools/metadata/webfeatures/schema.py
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from fnmatch import fnmatchcase
|
||||
from typing import Any, Dict, Sequence, Union
|
||||
|
||||
from ..schema import SchemaValue, validate_dict
|
||||
|
||||
"""
|
||||
YAML filename for meta files
|
||||
"""
|
||||
WEB_FEATURES_YML_FILENAME = "WEB_FEATURES.yml"
|
||||
|
||||
|
||||
class SpecialFileEnum(Enum):
|
||||
"""All files recursively"""
|
||||
RECURSIVE = "**"
|
||||
|
||||
|
||||
class FeatureFile(str):
|
||||
def match_files(self, base_filenames: Sequence[str]) -> Sequence[str]:
|
||||
"""
|
||||
Given the input base file names, returns the subset of base file names
|
||||
that match the given FeatureFile.
|
||||
If the FeatureFile contains any number of "*" characters, fnmatch is
|
||||
used check each file name.
|
||||
If the FeatureFile does not contain any "*" characters, the base file name
|
||||
must match the FeatureFile exactly
|
||||
:param base_filenames: The list of filenames to check against the FeatureFile
|
||||
:return: List of matching file names that match FeatureFile
|
||||
"""
|
||||
result = []
|
||||
# If our file name contains a wildcard, use fnmatch
|
||||
if "*" in self:
|
||||
for base_filename in base_filenames:
|
||||
if fnmatchcase(base_filename, self):
|
||||
result.append(base_filename)
|
||||
elif self.__str__() in base_filenames:
|
||||
result.append(self)
|
||||
return result
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeatureEntry:
|
||||
files: Union[Sequence[FeatureFile], SpecialFileEnum]
|
||||
"""The web-features key"""
|
||||
name: str
|
||||
|
||||
_required_keys = {"files", "name"}
|
||||
|
||||
def __init__(self, obj: Dict[str, Any]):
|
||||
"""
|
||||
Converts the provided dictionary to an instance of FeatureEntry
|
||||
:param obj: The object that will be converted to a FeatureEntry.
|
||||
:return: An instance of FeatureEntry
|
||||
:raises ValueError: If there are unexpected keys or missing required keys.
|
||||
"""
|
||||
validate_dict(obj, FeatureEntry._required_keys)
|
||||
self.files = SchemaValue.from_union([
|
||||
lambda x: SchemaValue.from_list(SchemaValue.from_class(FeatureFile), x),
|
||||
SpecialFileEnum], obj.get("files"))
|
||||
self.name = SchemaValue.from_str(obj.get("name"))
|
||||
|
||||
|
||||
def does_feature_apply_recursively(self) -> bool:
|
||||
if isinstance(self.files, SpecialFileEnum) and self.files == SpecialFileEnum.RECURSIVE:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebFeaturesFile:
|
||||
"""List of features"""
|
||||
features: Sequence[FeatureEntry]
|
||||
|
||||
_required_keys = {"features"}
|
||||
|
||||
def __init__(self, obj: Dict[str, Any]):
|
||||
"""
|
||||
Converts the provided dictionary to an instance of WebFeaturesFile
|
||||
:param obj: The object that will be converted to a WebFeaturesFile.
|
||||
:return: An instance of WebFeaturesFile
|
||||
:raises ValueError: If there are unexpected keys or missing required keys.
|
||||
"""
|
||||
validate_dict(obj, WebFeaturesFile._required_keys)
|
||||
self.features = SchemaValue.from_list(
|
||||
lambda raw_feature: FeatureEntry(SchemaValue.from_dict(raw_feature)), obj.get("features"))
|
0
third_party/wpt_tools/wpt/tools/metadata/webfeatures/tests/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/webfeatures/tests/__init__.py
vendored
Normal file
91
third_party/wpt_tools/wpt/tools/metadata/webfeatures/tests/test_schema.py
vendored
Normal file
91
third_party/wpt_tools/wpt/tools/metadata/webfeatures/tests/test_schema.py
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
# mypy: allow-untyped-defs
|
||||
|
||||
from dataclasses import asdict
|
||||
from ..schema import WebFeaturesFile, FeatureEntry, SpecialFileEnum, FeatureFile
|
||||
|
||||
import pytest
|
||||
import re
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result,expected_exception_type,exception_message",
|
||||
[
|
||||
(
|
||||
{
|
||||
"features": [
|
||||
{
|
||||
"name": "feature1",
|
||||
"files": ["file1", "file2"],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"features": [
|
||||
{
|
||||
"name": "feature1",
|
||||
"files": ["file1", "file2"],
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
{},
|
||||
None,
|
||||
ValueError,
|
||||
"Object missing required keys: ['features']"
|
||||
),
|
||||
(
|
||||
{
|
||||
"features": [
|
||||
{}
|
||||
]
|
||||
},
|
||||
None,
|
||||
ValueError,
|
||||
"Object missing required keys: ['files', 'name']"
|
||||
),
|
||||
])
|
||||
def test_web_features_file(input, expected_result, expected_exception_type, exception_message):
|
||||
if expected_exception_type:
|
||||
with pytest.raises(expected_exception_type, match=re.escape(exception_message)):
|
||||
WebFeaturesFile(input)
|
||||
else:
|
||||
assert expected_result == asdict(WebFeaturesFile(input))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected_result",
|
||||
[
|
||||
(
|
||||
FeatureEntry({"name": "test1", "files":["file1"]}),
|
||||
False
|
||||
),
|
||||
(
|
||||
FeatureEntry({"name": "test2", "files": SpecialFileEnum.RECURSIVE}),
|
||||
True
|
||||
),
|
||||
])
|
||||
def test_does_feature_apply_recursively(input, expected_result):
|
||||
assert input.does_feature_apply_recursively() == expected_result
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_feature,input_files,expected_result",
|
||||
[
|
||||
(
|
||||
FeatureFile("*"),
|
||||
["test.html", "TEST.HTML"],
|
||||
["test.html", "TEST.HTML"]
|
||||
),
|
||||
(
|
||||
FeatureFile("test.html"),
|
||||
["test.html", "TEST.HTML"],
|
||||
["test.html"]
|
||||
),
|
||||
(
|
||||
FeatureFile("test*.html"),
|
||||
["test.html", "test1.html", "TEST1.HTML", "test2.html", "test-2.html", "foo.html"],
|
||||
["test.html", "test1.html", "test2.html", "test-2.html"]
|
||||
),
|
||||
])
|
||||
def test_feature_file_match_files(input_feature, input_files, expected_result):
|
||||
assert input_feature.match_files(input_files) == expected_result
|
0
third_party/wpt_tools/wpt/tools/metadata/yaml/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/yaml/__init__.py
vendored
Normal file
11
third_party/wpt_tools/wpt/tools/metadata/yaml/load.py
vendored
Normal file
11
third_party/wpt_tools/wpt/tools/metadata/yaml/load.py
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
from typing import Any, Dict, IO
|
||||
from ..meta.schema import SchemaValue
|
||||
|
||||
import yaml
|
||||
|
||||
def load_data_to_dict(f: IO[bytes]) -> Dict[str, Any]:
|
||||
try:
|
||||
raw_data = yaml.safe_load(f)
|
||||
return SchemaValue.from_dict(raw_data)
|
||||
except Exception as e:
|
||||
raise e
|
1
third_party/wpt_tools/wpt/tools/metadata/yaml/requirements.txt
vendored
Normal file
1
third_party/wpt_tools/wpt/tools/metadata/yaml/requirements.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
pyyaml==6.0.1
|
0
third_party/wpt_tools/wpt/tools/metadata/yaml/tests/__init__.py
vendored
Normal file
0
third_party/wpt_tools/wpt/tools/metadata/yaml/tests/__init__.py
vendored
Normal file
31
third_party/wpt_tools/wpt/tools/metadata/yaml/tests/test_file.py
vendored
Normal file
31
third_party/wpt_tools/wpt/tools/metadata/yaml/tests/test_file.py
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# mypy: allow-untyped-defs
|
||||
|
||||
from ..load import load_data_to_dict
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
def test_load_data_to_dict():
|
||||
input_buffer = StringIO("""
|
||||
key:
|
||||
- value1
|
||||
- value2
|
||||
""")
|
||||
result = load_data_to_dict(input_buffer)
|
||||
assert result == {"key": ["value1", "value2"]}
|
||||
|
||||
def test_load_data_to_dict_not_dict():
|
||||
input_buffer = StringIO("""
|
||||
- key: 2
|
||||
""")
|
||||
with pytest.raises(ValueError):
|
||||
load_data_to_dict(input_buffer)
|
||||
|
||||
def test_load_data_to_dict_invalid_yaml():
|
||||
input_buffer = StringIO("""
|
||||
key: 1
|
||||
- test: value
|
||||
""")
|
||||
with pytest.raises(yaml.parser.ParserError):
|
||||
load_data_to_dict(input_buffer)
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2010-2019 Benjamin Peterson
|
||||
Copyright (c) 2010-2020 Benjamin Peterson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -29,7 +29,7 @@ import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.15.0"
|
||||
__version__ = "1.16.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
@ -71,6 +71,11 @@ else:
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
if PY34:
|
||||
from importlib.util import spec_from_loader
|
||||
else:
|
||||
spec_from_loader = None
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
@ -186,6 +191,11 @@ class _SixMetaPathImporter(object):
|
||||
return self
|
||||
return None
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if fullname in self.known_modules:
|
||||
return spec_from_loader(fullname, self)
|
||||
return None
|
||||
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
@ -223,6 +233,12 @@ class _SixMetaPathImporter(object):
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
def create_module(self, spec):
|
||||
return self.load_module(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
|
@ -35,6 +35,10 @@ class InvalidArgumentException(BidiException):
|
||||
error_code = "invalid argument"
|
||||
|
||||
|
||||
class InvalidSelectorException(BidiException):
|
||||
error_code = "invalid selector"
|
||||
|
||||
|
||||
class InvalidSessionIDError(BidiException):
|
||||
error_code = "invalid session id"
|
||||
|
||||
|
@ -3,9 +3,11 @@ from enum import Enum
|
||||
from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union
|
||||
|
||||
from ._module import BidiModule, command
|
||||
from .script import OwnershipModel, SerializationOptions
|
||||
from ..undefined import UNDEFINED, Undefined
|
||||
|
||||
|
||||
|
||||
class ElementOptions(Dict[str, Any]):
|
||||
def __init__(self, element: Mapping[str, Any]):
|
||||
self["type"] = "element"
|
||||
@ -129,6 +131,35 @@ class BrowsingContext(BidiModule):
|
||||
params["userText"] = user_text
|
||||
return params
|
||||
|
||||
@command
|
||||
def locate_nodes(self,
|
||||
context: str,
|
||||
locator: Mapping[str, Any],
|
||||
max_node_count: Optional[int] = None,
|
||||
ownership: Optional[OwnershipModel] = None,
|
||||
sandbox: Optional[str] = None,
|
||||
serialization_options: Optional[SerializationOptions] = None,
|
||||
start_nodes: Optional[List[Mapping[str, Any]]] = None) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {"context": context, "locator": locator}
|
||||
if max_node_count is not None:
|
||||
params["maxNodeCount"] = max_node_count
|
||||
if ownership is not None:
|
||||
params["ownership"] = ownership
|
||||
if sandbox is not None:
|
||||
params["sandbox"] = sandbox
|
||||
if serialization_options is not None:
|
||||
params["serializationOptions"] = serialization_options
|
||||
if start_nodes is not None:
|
||||
params["startNodes"] = start_nodes
|
||||
return params
|
||||
|
||||
@locate_nodes.result
|
||||
def _locate_nodes(self, result: Mapping[str, Any]) -> Any:
|
||||
assert result["nodes"] is not None
|
||||
assert isinstance(result["nodes"], List)
|
||||
|
||||
return result
|
||||
|
||||
@command
|
||||
def navigate(self,
|
||||
context: str,
|
||||
|
@ -3,6 +3,11 @@ from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union
|
||||
from ._module import BidiModule, command
|
||||
|
||||
|
||||
class AuthCredentials(Dict[str, Any]):
|
||||
def __init__(self, username: str, password: str):
|
||||
dict.__init__(self, type="password", username=username, password=password)
|
||||
|
||||
|
||||
class URLPatternPattern(Dict[str, Any]):
|
||||
def __init__(
|
||||
self,
|
||||
@ -57,11 +62,87 @@ class Network(BidiModule):
|
||||
assert result["intercept"] is not None
|
||||
return result["intercept"]
|
||||
|
||||
@command
|
||||
def continue_with_auth(
|
||||
self,
|
||||
request: str,
|
||||
action: str,
|
||||
credentials: Optional[AuthCredentials] = None
|
||||
) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {
|
||||
"request": request,
|
||||
"action": action,
|
||||
}
|
||||
|
||||
if action == "provideCredentials" and credentials is not None:
|
||||
params["credentials"] = credentials
|
||||
|
||||
return params
|
||||
|
||||
@command
|
||||
def continue_request(self,
|
||||
request: str,
|
||||
method: Optional[str] = None,
|
||||
url: Optional[str] = None) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {
|
||||
"request": request,
|
||||
}
|
||||
|
||||
if method is not None:
|
||||
params["method"] = method
|
||||
|
||||
if url is not None:
|
||||
params["url"] = url
|
||||
|
||||
# TODO: Add support for missing parameters: body, cookies, headers
|
||||
|
||||
return params
|
||||
|
||||
@command
|
||||
def continue_response(
|
||||
self,
|
||||
request: str,
|
||||
reason_phrase: Optional[str] = None,
|
||||
status_code: Optional[int] = None) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {
|
||||
"request": request,
|
||||
}
|
||||
|
||||
if reason_phrase is not None:
|
||||
params["reasonPhrase"] = reason_phrase
|
||||
|
||||
if status_code is not None:
|
||||
params["statusCode"] = status_code
|
||||
|
||||
# TODO: Add support for missing parameters: body, credentials, headers
|
||||
|
||||
return params
|
||||
|
||||
@command
|
||||
def fail_request(self, request: str) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {"request": request}
|
||||
return params
|
||||
|
||||
@command
|
||||
def provide_response(
|
||||
self,
|
||||
request: str,
|
||||
reason_phrase: Optional[str] = None,
|
||||
status_code: Optional[int] = None) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {
|
||||
"request": request,
|
||||
}
|
||||
|
||||
if reason_phrase is not None:
|
||||
params["reasonPhrase"] = reason_phrase
|
||||
|
||||
if status_code is not None:
|
||||
params["statusCode"] = status_code
|
||||
|
||||
# TODO: Add support for missing parameters: body, cookies, headers
|
||||
|
||||
return params
|
||||
|
||||
@command
|
||||
def remove_intercept(self, intercept: str) -> Mapping[str, Any]:
|
||||
params: MutableMapping[str, Any] = {"intercept": intercept}
|
||||
|
50
third_party/wpt_tools/wpt/tools/wpt/android.py
vendored
50
third_party/wpt_tools/wpt/tools/wpt/android.py
vendored
@ -3,8 +3,10 @@
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import requests
|
||||
from .wpt import venv_dir
|
||||
@ -79,6 +81,43 @@ def get_parser_start():
|
||||
return parser
|
||||
|
||||
|
||||
def install_fixed_emulator_version(logger, paths):
|
||||
# Downgrade to a pinned emulator version
|
||||
# See https://developer.android.com/studio/emulator_archive for what we're doing here
|
||||
from xml.etree import ElementTree
|
||||
|
||||
version = "32.1.15"
|
||||
urls = {"linux": "https://redirector.gvt1.com/edgedl/android/repository/emulator-linux_x64-10696886.zip"}
|
||||
|
||||
os_name = platform.system().lower()
|
||||
if os_name not in urls:
|
||||
logger.error(f"Don't know how to install old emulator for {os_name}, using latest version")
|
||||
# For now try with the latest version if this fails
|
||||
return
|
||||
|
||||
logger.info(f"Downgrading emulator to {version}")
|
||||
url = urls[os_name]
|
||||
|
||||
emulator_path = os.path.join(paths["sdk"], "emulator")
|
||||
latest_emulator_path = os.path.join(paths["sdk"], "emulator_latest")
|
||||
os.rename(emulator_path, latest_emulator_path)
|
||||
|
||||
download_and_extract(url, paths["sdk"])
|
||||
package_path = os.path.join(emulator_path, "package.xml")
|
||||
shutil.copyfile(os.path.join(latest_emulator_path, "package.xml"),
|
||||
package_path)
|
||||
|
||||
with open(package_path) as f:
|
||||
tree = ElementTree.parse(f)
|
||||
node = tree.find("localPackage").find("revision")
|
||||
assert len(node) == 3
|
||||
parts = version.split(".")
|
||||
for version_part, node in zip(parts, node):
|
||||
node.text = version_part
|
||||
with open(package_path, "wb") as f:
|
||||
tree.write(f, encoding="utf8")
|
||||
|
||||
|
||||
def get_paths(dest):
|
||||
os_name = platform.system().lower()
|
||||
|
||||
@ -257,10 +296,18 @@ def install(logger, dest=None, reinstall=False, prompt=True):
|
||||
|
||||
install_avd(logger, paths, prompt=prompt)
|
||||
|
||||
install_fixed_emulator_version(logger, paths)
|
||||
|
||||
emulator = get_emulator(paths)
|
||||
return emulator
|
||||
|
||||
|
||||
def cancel_start(thread_id):
|
||||
def cancel_func():
|
||||
raise signal.pthread_kill(thread_id, signal.SIGINT)
|
||||
return cancel_func
|
||||
|
||||
|
||||
def start(logger, dest=None, reinstall=False, prompt=True, device_serial=None):
|
||||
paths = get_paths(dest)
|
||||
|
||||
@ -274,7 +321,10 @@ def start(logger, dest=None, reinstall=False, prompt=True, device_serial=None):
|
||||
raise OSError
|
||||
|
||||
emulator.start()
|
||||
timer = threading.Timer(300, cancel_start(threading.get_ident()))
|
||||
timer.start()
|
||||
emulator.wait_for_start()
|
||||
timer.cancel()
|
||||
return emulator
|
||||
|
||||
|
||||
|
@ -88,7 +88,8 @@
|
||||
"help": "Start the x86 android emulator",
|
||||
"virtualenv": true,
|
||||
"requirements": [
|
||||
"requirements.txt"
|
||||
"requirements.txt",
|
||||
"requirements_android.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
54
third_party/wpt_tools/wpt/tools/wpt/run.py
vendored
54
third_party/wpt_tools/wpt/tools/wpt/run.py
vendored
@ -5,7 +5,7 @@ import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from shutil import which
|
||||
from shutil import copyfile, which
|
||||
from typing import ClassVar, Tuple, Type
|
||||
|
||||
wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
@ -387,14 +387,6 @@ class FirefoxAndroid(BrowserSetup):
|
||||
logger.info("Unable to find or install geckodriver, skipping wdspec tests")
|
||||
kwargs["test_types"].remove("wdspec")
|
||||
|
||||
for device_serial in kwargs["device_serial"]:
|
||||
if device_serial.startswith("emulator-"):
|
||||
# We're running on an emulator so ensure that's set up
|
||||
android.start(logger,
|
||||
reinstall=False,
|
||||
device_serial=device_serial,
|
||||
prompt=kwargs["prompt"])
|
||||
|
||||
if kwargs["adb_binary"] is None:
|
||||
if "ADB_PATH" not in os.environ:
|
||||
adb_path = os.path.join(android.get_paths(None)["sdk"],
|
||||
@ -405,19 +397,57 @@ class FirefoxAndroid(BrowserSetup):
|
||||
|
||||
self._logcat = AndroidLogcat(kwargs["adb_binary"], base_path=kwargs["logcat_dir"])
|
||||
|
||||
for device_serial in kwargs["device_serial"]:
|
||||
if device_serial.startswith("emulator-"):
|
||||
# We're running on an emulator so ensure that's set up
|
||||
android.start(logger,
|
||||
reinstall=False,
|
||||
device_serial=device_serial,
|
||||
prompt=kwargs["prompt"])
|
||||
|
||||
for device_serial in kwargs["device_serial"]:
|
||||
device = mozdevice.ADBDeviceFactory(adb=kwargs["adb_binary"],
|
||||
device=device_serial)
|
||||
self._logcat.start(device_serial)
|
||||
max_retries = 5
|
||||
last_exception = None
|
||||
if self.browser.apk_path:
|
||||
device.uninstall_app(app)
|
||||
device.install_app(self.browser.apk_path, timeout=600)
|
||||
for i in range(max_retries + 1):
|
||||
logger.info(f"Installing {app} on {device_serial} "
|
||||
f"attempt {i + 1}/{max_retries + 1}")
|
||||
try:
|
||||
# Temporarily replace mozdevice function with custom code
|
||||
# that passes in the `--no-incremental` option
|
||||
cmd = ["install", "--no-incremental", self.browser.apk_path]
|
||||
logger.debug(" ".join(cmd))
|
||||
data = device.command_output(cmd, timeout=120)
|
||||
if data.find("Success") == -1:
|
||||
raise mozdevice.ADBError(f"Install failed for {self.browser.apk_path}."
|
||||
f" Got: {data}")
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
else:
|
||||
break
|
||||
else:
|
||||
assert last_exception is not None
|
||||
raise WptrunError(f"Failed to install {app} on device {device_serial} "
|
||||
f"after {max_retries} retries") from last_exception
|
||||
elif not device.is_app_installed(app):
|
||||
raise WptrunError("app %s not installed on device %s" %
|
||||
(app, device_serial))
|
||||
raise WptrunError(f"app {app} not installed on device {device_serial}")
|
||||
|
||||
|
||||
def teardown(self):
|
||||
from . import android
|
||||
|
||||
if hasattr(self, "_logcat"):
|
||||
emulator_log = os.path.join(android.get_paths(None)["sdk"],
|
||||
".android",
|
||||
"emulator.log")
|
||||
if os.path.exists(emulator_log):
|
||||
dest_path = os.path.join(self._logcat.base_path, "emulator.log")
|
||||
copyfile(emulator_log, dest_path)
|
||||
|
||||
self._logcat.stop()
|
||||
|
||||
|
||||
|
@ -100,7 +100,7 @@ def executor_kwargs(logger, test_type, test_environment, run_info_data,
|
||||
# Shorten delay for Reporting <https://w3c.github.io/reporting/>.
|
||||
chrome_options["args"].append("--short-reporting-delay")
|
||||
# Point all .test domains to localhost for Chrome
|
||||
chrome_options["args"].append("--host-resolver-rules=MAP nonexistent.*.test ^NOTFOUND, MAP *.test 127.0.0.1")
|
||||
chrome_options["args"].append("--host-resolver-rules=MAP nonexistent.*.test ^NOTFOUND, MAP *.test 127.0.0.1, MAP *.test. 127.0.0.1")
|
||||
# Enable Secure Payment Confirmation for Chrome. This is normally disabled
|
||||
# on Linux as it hasn't shipped there yet, but in WPT we enable virtual
|
||||
# authenticator devices anyway for testing and so SPC works.
|
||||
|
@ -116,6 +116,15 @@ class SetWindowRectAction:
|
||||
rect = payload["rect"]
|
||||
self.protocol.window.set_rect(rect)
|
||||
|
||||
class GetWindowRectAction:
|
||||
name = "get_window_rect"
|
||||
|
||||
def __init__(self, logger, protocol):
|
||||
self.logger = logger
|
||||
self.protocol = protocol
|
||||
|
||||
def __call__(self, payload):
|
||||
return self.protocol.window.get_rect()
|
||||
|
||||
class ActionSequenceAction:
|
||||
name = "action_sequence"
|
||||
@ -300,16 +309,17 @@ class CancelFedCMDialogAction:
|
||||
self.logger.debug("Canceling FedCM dialog")
|
||||
return self.protocol.fedcm.cancel_fedcm_dialog()
|
||||
|
||||
class ConfirmIDPLoginAction:
|
||||
name = "confirm_idp_login"
|
||||
class ClickFedCMDialogButtonAction:
|
||||
name = "click_fedcm_dialog_button"
|
||||
|
||||
def __init__(self, logger, protocol):
|
||||
self.logger = logger
|
||||
self.protocol = protocol
|
||||
|
||||
def __call__(self, payload):
|
||||
self.logger.debug("Confirming IDP login")
|
||||
return self.protocol.fedcm.confirm_idp_login()
|
||||
dialog_button = payload["dialog_button"]
|
||||
self.logger.debug(f"Clicking FedCM dialog button: {dialog_button}")
|
||||
return self.protocol.fedcm.click_fedcm_dialog_button()
|
||||
|
||||
class SelectFedCMAccountAction:
|
||||
name = "select_fedcm_account"
|
||||
@ -443,6 +453,7 @@ actions = [ClickAction,
|
||||
SendKeysAction,
|
||||
MinimizeWindowAction,
|
||||
SetWindowRectAction,
|
||||
GetWindowRectAction,
|
||||
ActionSequenceAction,
|
||||
GenerateTestReportAction,
|
||||
SetPermissionAction,
|
||||
@ -456,7 +467,7 @@ actions = [ClickAction,
|
||||
SetSPCTransactionModeAction,
|
||||
SetRPHRegistrationModeAction,
|
||||
CancelFedCMDialogAction,
|
||||
ConfirmIDPLoginAction,
|
||||
ClickFedCMDialogButtonAction,
|
||||
SelectFedCMAccountAction,
|
||||
GetFedCMAccountListAction,
|
||||
GetFedCMDialogTitleAction,
|
||||
|
@ -497,6 +497,8 @@ class MarionetteWindowProtocolPart(WindowProtocolPart):
|
||||
def set_rect(self, rect):
|
||||
self.marionette.set_window_rect(rect["x"], rect["y"], rect["height"], rect["width"])
|
||||
|
||||
def get_rect(self):
|
||||
return self.marionette.window_rect
|
||||
|
||||
class MarionetteActionSequenceProtocolPart(ActionSequenceProtocolPart):
|
||||
def setup(self):
|
||||
|
@ -219,6 +219,9 @@ class SeleniumWindowProtocolPart(WindowProtocolPart):
|
||||
self.logger.info("Setting window rect")
|
||||
self.webdriver.window.rect = rect
|
||||
|
||||
def get_rect(self):
|
||||
self.logger.info("Getting window rect")
|
||||
return self.webdriver.window.rect
|
||||
|
||||
class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
|
||||
def setup(self):
|
||||
|
@ -257,6 +257,9 @@ class WebDriverWindowProtocolPart(WindowProtocolPart):
|
||||
self.logger.info("Restoring")
|
||||
self.webdriver.window.rect = rect
|
||||
|
||||
def get_rect(self):
|
||||
self.logger.info("Getting rect")
|
||||
return self.webdriver.window.rect
|
||||
|
||||
class WebDriverSendKeysProtocolPart(SendKeysProtocolPart):
|
||||
def setup(self):
|
||||
@ -379,6 +382,10 @@ class WebDriverFedCMProtocolPart(FedCMProtocolPart):
|
||||
def cancel_fedcm_dialog(self):
|
||||
return self.webdriver.send_session_command("POST", "fedcm/canceldialog")
|
||||
|
||||
def click_fedcm_dialog_button(self, dialog_button):
|
||||
body = {"dialogButton": dialog_button}
|
||||
return self.webdriver.send_session_command("POST", "fedcm/clickdialogbutton", body)
|
||||
|
||||
def select_fedcm_account(self, account_index):
|
||||
body = {"accountIndex": account_index}
|
||||
return self.webdriver.send_session_command("POST", "fedcm/selectaccount", body)
|
||||
|
@ -370,6 +370,11 @@ class WindowProtocolPart(ProtocolPart):
|
||||
"""Restores the window to the given rect."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_rect(self):
|
||||
"""Gets the current window rect."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def minimize(self):
|
||||
"""Minimizes the window and returns the previous rect."""
|
||||
@ -641,8 +646,10 @@ class FedCMProtocolPart(ProtocolPart):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def confirm_idp_login(self):
|
||||
"""Confirm IDP login"""
|
||||
def click_fedcm_dialog_button(self, dialog_button):
|
||||
"""Click a button on the FedCM dialog
|
||||
|
||||
:param str dialog_button: The dialog button to click"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
@ -204,6 +204,10 @@
|
||||
return create_action("set_window_rect", {rect, context});
|
||||
};
|
||||
|
||||
window.test_driver_internal.get_window_rect = function(context=null) {
|
||||
return create_action("get_window_rect", {context});
|
||||
};
|
||||
|
||||
window.test_driver_internal.send_keys = function(element, keys) {
|
||||
const selector = get_selector(element);
|
||||
const context = get_context(element);
|
||||
@ -277,8 +281,8 @@
|
||||
return create_action("cancel_fedcm_dialog", {context});
|
||||
};
|
||||
|
||||
window.test_driver_internal.confirm_idp_login = function(context = null) {
|
||||
return create_action("confirm_idp_login", {context});
|
||||
window.test_driver_internal.click_fedcm_dialog_button = function(dialog_button, context = null) {
|
||||
return create_action("click_fedcm_dialog_button", {dialog_button, context});
|
||||
};
|
||||
|
||||
window.test_driver_internal.select_fedcm_account = function(account_index, context = null) {
|
||||
|
@ -434,7 +434,7 @@ class TestRunnerManager(threading.Thread):
|
||||
f"and {len(skipped_tests) - 1} others"
|
||||
)
|
||||
for test in skipped_tests[1:]:
|
||||
self.logger.debug("Test left in the queue: {test[0].id!r}")
|
||||
self.logger.debug(f"Test left in the queue: {test[0].id!r}")
|
||||
|
||||
force_stop = (not isinstance(self.state, RunnerManagerState.stop) or
|
||||
self.state.force_stop)
|
||||
@ -679,7 +679,7 @@ class TestRunnerManager(threading.Thread):
|
||||
# Due to inherent race conditions in EXTERNAL-TIMEOUT, we might
|
||||
# receive multiple test_ended for a test (e.g. from both Executor
|
||||
# and TestRunner), in which case we ignore the duplicate message.
|
||||
self.logger.error("Received unexpected test_ended for %s" % test)
|
||||
self.logger.warning("Received unexpected test_ended for %s" % test)
|
||||
return
|
||||
if self.timer is not None:
|
||||
self.timer.cancel()
|
||||
|
@ -534,7 +534,7 @@ class Http2WebTestRequestHandler(BaseWebTestRequestHandler):
|
||||
dispatcher = request._dispatcher
|
||||
try:
|
||||
dispatcher.transfer_data(request)
|
||||
except StreamClosedError:
|
||||
except (StreamClosedError, ProtocolError):
|
||||
# work around https://github.com/web-platform-tests/wpt/issues/27786
|
||||
# The stream was already closed.
|
||||
queue.put(None)
|
||||
@ -546,7 +546,7 @@ class Http2WebTestRequestHandler(BaseWebTestRequestHandler):
|
||||
connection.end_stream(stream_id)
|
||||
data = connection.data_to_send()
|
||||
stream_handler.request.sendall(data)
|
||||
except StreamClosedError: # maybe the stream has already been closed
|
||||
except (StreamClosedError, ProtocolError): # maybe the stream has already been closed
|
||||
pass
|
||||
queue.put(None)
|
||||
|
||||
|
Reference in New Issue
Block a user