diff --git a/playwright/__init__.py b/playwright/__init__.py index 1ae7a7a9b..7db2a1221 100644 --- a/playwright/__init__.py +++ b/playwright/__init__.py @@ -20,6 +20,7 @@ firefox = playwright_object.firefox webkit = playwright_object.webkit devices = playwright_object.devices +selectors = playwright_object.selectors browser_types = playwright_object.browser_types Error = helper.Error TimeoutError = helper.TimeoutError @@ -30,6 +31,7 @@ "firefox", "webkit", "devices", + "selectors", "Error", "TimeoutError", ] diff --git a/playwright/js_handle.py b/playwright/js_handle.py index 7b38427ed..e8f2ded21 100644 --- a/playwright/js_handle.py +++ b/playwright/js_handle.py @@ -73,10 +73,10 @@ async def getProperty(self, name: str) -> "JSHandle": return from_channel(await self._channel.send("getProperty", dict(name=name))) async def getProperties(self) -> Dict[str, "JSHandle"]: - map = dict() - for property in await self._channel.send("getPropertyList"): - map[property["name"]] = from_channel(property["value"]) - return map + return { + prop["name"]: from_channel(prop["value"]) + for prop in await self._channel.send("getPropertyList") + } def asElement(self) -> Optional["ElementHandle"]: return None diff --git a/playwright/object_factory.py b/playwright/object_factory.py index a3d5a310b..e019d30a3 100644 --- a/playwright/object_factory.py +++ b/playwright/object_factory.py @@ -28,6 +28,7 @@ from playwright.network import Request, Response, Route from playwright.page import BindingCall, Page from playwright.playwright import Playwright +from playwright.selectors import Selectors from playwright.worker import Worker @@ -73,4 +74,6 @@ def create_remote_object( return Route(scope, guid, initializer) if type == "worker": return Worker(scope, guid, initializer) + if type == "selectors": + return Selectors(scope, guid, initializer) return DummyObject(scope, guid, initializer) diff --git a/playwright/playwright.py b/playwright/playwright.py index 7239ab2ac..85aa6207f 100644 --- a/playwright/playwright.py +++ b/playwright/playwright.py @@ -16,6 +16,7 @@ from playwright.browser_type import BrowserType from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.selectors import Selectors class Playwright(ChannelOwner): @@ -24,9 +25,11 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None self.chromium: BrowserType = from_channel(initializer["chromium"]) self.firefox: BrowserType = from_channel(initializer["firefox"]) self.webkit: BrowserType = from_channel(initializer["webkit"]) - self.devices = dict() - for device in initializer["deviceDescriptors"]: - self.devices[device["name"]] = device["descriptor"] + self.selectors: Selectors = from_channel(initializer["selectors"]) + self.devices = { + device["name"]: device["descriptor"] + for device in initializer["deviceDescriptors"] + } self.browser_types: Dict[str, BrowserType] = dict( chromium=self.chromium, webkit=self.webkit, firefox=self.firefox ) diff --git a/playwright/selectors.py b/playwright/selectors.py new file mode 100644 index 000000000..ee2f24cc8 --- /dev/null +++ b/playwright/selectors.py @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Optional + +from playwright.connection import ChannelOwner, ConnectionScope +from playwright.element_handle import ElementHandle + + +class Selectors(ChannelOwner): + def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: + super().__init__(scope, guid, initializer) + + async def register( + self, name: str, source: str = "", path: str = None, contentScript: bool = False + ) -> None: + if path: + with open(path, "r") as file: + source = file.read() + await self._channel.send( + "register", + dict(name=name, source=source, options={"contentScript": contentScript}), + ) + + async def _createSelector(self, name: str, handle: ElementHandle) -> Optional[str]: + return await self._channel.send( + "createSelector", dict(name=name, handle=handle._channel) + ) diff --git a/tests/assets/sectionselectorengine.js b/tests/assets/sectionselectorengine.js new file mode 100644 index 000000000..e831d03ce --- /dev/null +++ b/tests/assets/sectionselectorengine.js @@ -0,0 +1,10 @@ +({ + create(root, target) { + }, + query(root, selector) { + return root.querySelector('section'); + }, + queryAll(root, selector) { + return Array.from(root.querySelectorAll('section')); + } +}) diff --git a/tests/conftest.py b/tests/conftest.py index c49014b20..7441acc39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -42,6 +42,11 @@ def event_loop(): loop.close() +@pytest.fixture(scope="session") +def selectors(): + return playwright.selectors + + @pytest.fixture(scope="session") def browser_type(browser_name: str): return playwright.browser_types[browser_name] diff --git a/tests/test_queryselector.py b/tests/test_queryselector.py new file mode 100644 index 000000000..a3bfb6fb4 --- /dev/null +++ b/tests/test_queryselector.py @@ -0,0 +1,154 @@ +import os + +import pytest + +from playwright.helper import Error +from playwright.page import Page + + +async def test_selectors_register_should_work(selectors, page: Page, utils): + await utils.register_selector_engine( + selectors, + "tag", + """{ + create(root, target) { + return target.nodeName; + }, + query(root, selector) { + return root.querySelector(selector); + }, + queryAll(root, selector) { + return Array.from(root.querySelectorAll(selector)); + } + }""", + ) + await page.setContent("