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

chore: validate API against upstream implementation #96

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions local-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 25 additions & 11 deletions playwright/playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Empty file added scripts/__init__.py
Empty file.
84 changes: 84 additions & 0 deletions scripts/validate_api.py
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 14 additions & 0 deletions tests/test_generate_sync_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import sys
from io import StringIO
from unittest.mock import patch

import pytest

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")
def test_generate_sync_api():
with patch("sys.stdout", new_callable=StringIO):
main()