Skip to content
Merged
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
6 changes: 3 additions & 3 deletions web-testbed/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import pytest

from .tests_backend.page_singleton import BackgroundPage
from .tests_backend.playwright_page import BackgroundPage

# In future, would only need to be AppProxy, MainWindowProxy, ProxyBase and
# a SimpleProbe/BaseProbe.
# Possibly only ProxyBase and SimpleProbe/BaseProbe.
from .tests_backend.proxies.app_proxy import AppProxy
from .tests_backend.proxies.base import ProxyBase
from .tests_backend.proxies.base_proxy import BaseProxy
from .tests_backend.proxies.box_proxy import BoxProxy
from .tests_backend.proxies.main_window_proxy import MainWindowProxy
from .tests_backend.widgets.button import ButtonProbe
Expand All @@ -27,7 +27,7 @@ def page():

@pytest.fixture(scope="session", autouse=True)
def _wire_page(page):
ProxyBase.page_provider = staticmethod(lambda: page)
BaseProxy.page_provider = staticmethod(lambda: page)
BoxProxy.page_provider = staticmethod(lambda: page)
MainWindowProxy.page_provider = staticmethod(lambda: page)
ButtonProbe.page_provider = staticmethod(lambda: page)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ async def _bootstrap(self):
self._context = await self._browser.new_context()
self._page = await self._context.new_page()

await self._page.goto("http://localhost:8080")
await self._page.goto("http://localhost:8080/")
# await self._page.goto(
# "http://localhost:8080", wait_until="load", timeout=30_000
# )
await self._page.wait_for_timeout(5000)
await self._page.wait_for_timeout(7000)

await self._page.evaluate(
"(code) => window.test_cmd(code)", "self.my_widgets = {}"
Expand Down
41 changes: 0 additions & 41 deletions web-testbed/tests/tests_backend/proxies/base.py

This file was deleted.

86 changes: 86 additions & 0 deletions web-testbed/tests/tests_backend/proxies/base_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
class BaseProxy:
page_provider = staticmethod(lambda: None)

def _page(self):
return type(self).page_provider()

def __init__(self, widget_id: str):
object.__setattr__(self, "_id", widget_id)

@property
def id(self) -> str:
return object.__getattribute__(self, "_id")

@property
def js_ref(self) -> str:
return f"self.my_widgets[{repr(self.id)}]"

@classmethod
def from_id(cls, widget_id: str) -> "BaseProxy":
return cls(widget_id)

def _is_function(self, name: str) -> bool:
prop = repr(name)
code = (
f"_obj = {self.js_ref}\n"
f"_attr = getattr(_obj, {prop})\n"
f"result = callable(_attr)"
)
return bool(self._page().eval_js("(code) => window.test_cmd(code)", code))

def _encode_value(self, value) -> str:
# other proxy, pass by reference
if isinstance(value, BaseProxy):
return value.js_ref
# if plain primitives, embed as python literal
if isinstance(value, (str, int, float, bool)) or value is None:
return repr(value)
# everything else use text form (what Toga expects for .text, etc)
return repr(str(value))

def __setattr__(self, name, value):
prop = repr(name)

if name.startswith("_"):
return object.__setattr__(self, name, value)
if name == "id":
raise AttributeError("Proxy 'id' is read-only")

rhs = self._encode_value(value)

code = f"setattr({self.js_ref}, {prop}, {rhs})"
self._page().eval_js("(code) => window.test_cmd(code)", code)

def __getattr__(self, name):
prop = repr(name)

# If it's a function on the remote side, return a Python wrapper
if self._is_function(name):

def _method(*args):
parts = [self._encode_value(a) for a in args]
args_py = ", ".join(parts)
code = (
f"_obj = {self.js_ref}\n"
f"_fn = getattr(_obj, {prop})\n"
f"result = _fn({args_py})"
)
return self._page().eval_js("(code) => window.test_cmd(code)", code)

return _method

# else plain property get
code = f"result = getattr({self.js_ref}, {prop})"
return self._page().eval_js("(code) => window.test_cmd(code)", code)

def add_to_main_window(self):
self._page().eval_js(
"(code) => window.test_cmd(code)",
f"self.main_window.content.add({self.js_ref})",
)

def __repr__(self):
return f"<WidgetProxy id={self.id}>"

def __str__(self):
return f"WidgetProxy({self.id})"
2 changes: 2 additions & 0 deletions web-testbed/tests/tests_backend/proxies/box_proxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class BoxProxy:
# Currently only for use in the 'probe' pytest fixture.

"""Proxy for toga.Box(children=[...])."""

page_provider = staticmethod(lambda: None)
Expand Down
16 changes: 6 additions & 10 deletions web-testbed/tests/tests_backend/proxies/button_proxy.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
from .base import ProxyBase
from .base_proxy import BaseProxy


class ButtonProxy(ProxyBase):
def __init__(self):
object.__setattr__(self, "_inited", False)

class ButtonProxy(BaseProxy):
def __init__(self, text="Hello"):
code = (
"new_widget = toga.Button('Hello')\n"
f"new_widget = toga.Button({repr(text)})\n"
"self.my_widgets[new_widget.id] = new_widget\n"
"result = new_widget.id"
)
widget_id = self._page().eval_js("(code) => window.test_cmd(code)", code)

object.__setattr__(self, "id", widget_id)
object.__setattr__(self, "_inited", True)
wid = self._page().eval_js("(code) => window.test_cmd(code)", code)
super().__init__(wid)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class MainWindowProxy:
"""Proxy that can get/set content. Content must be a BoxProxy."""
"""Minimal proxy that can get/set content. Content must be a BoxProxy."""

page_provider = staticmethod(lambda: None)

Expand Down
56 changes: 24 additions & 32 deletions web-testbed/tests/tests_backend/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,29 @@ def __init__(self, widget):
object.__setattr__(self, "id", widget.id)
object.__setattr__(self, "dom_id", f"toga_{widget.id}")

def __getattr__(self, name):
@property
def text(self):
page = self._page()
return page.run_coro(lambda p: p.locator(f"#{self.dom_id}").text_content())

match name:
case "text":
# Was inner_text, but it trims leading/trailing spaces and removes only
# whitespace.
return page.run_coro(
lambda p: p.locator(f"#{self.dom_id}").text_content()
)
case "height":
box = page.run_coro(
lambda p: p.locator(f"#{self.dom_id}").bounding_box()
)
return None if box is None else box["height"]

return "No match"
# raise AttributeError(name)

# Alternate Method - Keep just in case
"""
sel = f"#{self.dom_id}"

if name == "text":
async def _text(page):
return await page.locator(sel).inner_text()
return w.run_coro(_text)

if name == "height":
async def _height(page):
box = await page.locator(sel).bounding_box()
return None if box is None else box["height"]
return w.run_coro(_height)
"""
@property
def height(self):
page = self._page()
box = page.run_coro(lambda p: p.locator(f"#{self.dom_id}").bounding_box())
return None if box is None else box["height"]

# Alternate Method (non-lambda)
"""
sel = f"#{self.dom_id}"

if name == "text":
async def _text(page):
return await page.locator(sel).inner_text()
return w.run_coro(_text)

if name == "height":
async def _height(page):
box = await page.locator(sel).bounding_box()
return None if box is None else box["height"]
return w.run_coro(_height)
"""
18 changes: 0 additions & 18 deletions web-testbed/tests/widgets/test_button.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# Handled differently in real testing with get_module()
from pytest import approx, fixture

# from ..tests_backend.widgets.button import ButtonProbe
# from ..tests_backend.proxies.button_proxy import ButtonProxy
from tests.data import TEXTS
from tests.tests_backend.proxies.button_proxy import ButtonProxy

Expand All @@ -29,17 +25,3 @@ async def test_text(widget, probe):
assert probe.text == expected
# GTK rendering can result in a very minor change in button height
assert probe.height == approx(initial_height, abs=1)


# async def test_text_change(widget, probe):
# initial_height = probe.height
#
# widget.text = "new text"
#
# assert isinstance(widget.text, str)
# expected = str("new text").split("\n")[0]
#
# assert widget.text == expected
# assert probe.text == expected
#
# assert probe.height == approx(initial_height, abs=1)