From 1c366be4b6801628c85b260af0df22aea564f9c2 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 27 Jul 2020 01:24:20 +0200 Subject: [PATCH 1/3] chore: validate API against upstream implementation --- local-requirements.txt | 1 + playwright/playwright.py | 36 +++++++++----- scripts/__init__.py | 0 scripts/validate_api.py | 84 +++++++++++++++++++++++++++++++++ tests/test_generate_sync_api.py | 9 ++++ 5 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 scripts/__init__.py create mode 100644 scripts/validate_api.py create mode 100644 tests/test_generate_sync_api.py diff --git a/local-requirements.txt b/local-requirements.txt index caa0b8a95..dd8c34f0c 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -13,3 +13,4 @@ flake8==3.8.3 twine==3.2.0 pyOpenSSL==19.1.0 service_identity==18.1.0 +pyyaml==5.3.1 diff --git a/playwright/playwright.py b/playwright/playwright.py index b295a1454..5a4934f04 100644 --- a/playwright/playwright.py +++ b/playwright/playwright.py @@ -21,19 +21,33 @@ class Playwright(ChannelOwner): - chromium: BrowserType - firefox: BrowserType - webkit: BrowserType - selectors: Selectors - devices: Devices - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: super().__init__(scope, guid, initializer) - self.chromium = from_channel(initializer["chromium"]) - self.firefox = from_channel(initializer["firefox"]) - self.webkit = from_channel(initializer["webkit"]) - self.selectors = from_channel(initializer["selectors"]) - self.devices = { + self._chromium = from_channel(initializer["chromium"]) + self._firefox = from_channel(initializer["firefox"]) + self._webkit = from_channel(initializer["webkit"]) + self._selectors = from_channel(initializer["selectors"]) + self._devices = { device["name"]: device["descriptor"] for device in initializer["deviceDescriptors"] } + + @property + def chromium(self) -> BrowserType: + return self._chromium + + @property + def firefox(self) -> BrowserType: + return self._firefox + + @property + def webkit(self) -> BrowserType: + return self._webkit + + @property + def selectors(self) -> Selectors: + return self._selectors + + @property + def devices(self) -> Devices: + return self._devices diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/validate_api.py b/scripts/validate_api.py new file mode 100644 index 000000000..4050ab0e1 --- /dev/null +++ b/scripts/validate_api.py @@ -0,0 +1,84 @@ +import inspect +import logging +import os +import types +from pathlib import Path +from typing import Any, Dict, Optional + +import requests +import yaml + +from playwright.sync_base import mapping + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +_dirname = Path(os.path.dirname(os.path.abspath(__file__))) + + +def _get_protocol() -> Dict[str, Any]: + resp = requests.get( + "https://raw.githubusercontent.com/microsoft/playwright/master/src/rpc/protocol.yml" + ) + return yaml.load(resp.text, Loader=yaml.Loader) + + +def _validate( + mapping: Dict[str, Any], + class_name: str, + py_class: Any, + reference_class: Optional[Any] = None, +) -> None: + if not reference_class: + reference_class = py_class + members = {k: v for k, v in inspect.getmembers(py_class)} + if "commands" in mapping: + for command in mapping["commands"]: + if command not in members: + logging.warning("%s.%s is not existing", class_name, command) + continue + if not isinstance(members[command], types.FunctionType): + logging.warning("%s.%s has wrong function type", class_name, command) + continue + if "initializer" in mapping: + for initializer in mapping["initializer"]: + if initializer not in members: + logging.warning("%s.%s is not existing", class_name, initializer) + continue + if not isinstance(members[initializer], property): + logging.warning( + "%s.%s has wrong function type", class_name, initializer + ) + continue + + if "events" in mapping: + if not hasattr(reference_class, "Events"): + logging.warning( + "%s has no Events (%s is missing)", + class_name, + ",".join(mapping["events"].keys()), + ) + return + protocol_events = {v: k for k, v in vars(reference_class.Events).items()} + for event in mapping["events"]: + if event not in protocol_events: + logging.warning("Event %s of %s is not existing", event, class_name) + continue + pass + + +def main() -> None: + upstream_protocol = _get_protocol() + for async_class, sync_class in mapping.mapping.items(): + class_name = async_class.__name__ + if class_name not in upstream_protocol: + logger.warning("%s is not available in upstream protocol", class_name) + continue + protocol_mapping = upstream_protocol[class_name] + _validate(protocol_mapping, f"Sync{class_name}", sync_class, async_class) + _validate(protocol_mapping, class_name, async_class) + + +if __name__ == "__main__": + main() diff --git a/tests/test_generate_sync_api.py b/tests/test_generate_sync_api.py new file mode 100644 index 000000000..5ef4b49dd --- /dev/null +++ b/tests/test_generate_sync_api.py @@ -0,0 +1,9 @@ +from io import StringIO +from unittest.mock import patch + +from scripts.generate_sync_api import main + + +def test_generate_sync_api(): + with patch("sys.stdout", new_callable=StringIO): + main() From 917fddb5adf08dbd68e5ad5613f877176b5ab79a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 27 Jul 2020 01:32:38 +0200 Subject: [PATCH 2/3] fix generate script test --- tests/test_generate_sync_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_generate_sync_api.py b/tests/test_generate_sync_api.py index 5ef4b49dd..78da2feb9 100644 --- a/tests/test_generate_sync_api.py +++ b/tests/test_generate_sync_api.py @@ -1,9 +1,13 @@ +import sys from io import StringIO from unittest.mock import patch +import pytest + from scripts.generate_sync_api import main +@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher") def test_generate_sync_api(): with patch("sys.stdout", new_callable=StringIO): main() From 2cee7a8916c94772c6ec0d7d4dc1d298c6a03e5d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 27 Jul 2020 01:39:45 +0200 Subject: [PATCH 3/3] fix: it --- tests/test_generate_sync_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_generate_sync_api.py b/tests/test_generate_sync_api.py index 78da2feb9..995798c39 100644 --- a/tests/test_generate_sync_api.py +++ b/tests/test_generate_sync_api.py @@ -4,7 +4,8 @@ import pytest -from scripts.generate_sync_api import main +if sys.version_info >= (3, 8): + from scripts.generate_sync_api import main @pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher")