-
-
Notifications
You must be signed in to change notification settings - Fork 784
Draft/toga-web-testing #3728
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
Closed
Closed
Draft/toga-web-testing #3728
Changes from 32 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
6f5a0b9
Squashed 'testbed/toga_web_testing/' content from commit c653c6b77
Stringer90 f42e968
Merge commit '6f5a0b9f9ed950af4b549c8361f81e3dd771f84e' as 'testbed/t…
Stringer90 2593c40
Purge pyc files from repo.
freakboy3742 65c02f3
Reorganize code to make better use of Briefcase.
freakboy3742 6b94754
Fix web-testbed/pyproject.toml. Changes made by pre-commit
Stringer90 f57616d
Remove page singleton method, using class injection now. Experimented…
Stringer90 c365377
Merge pull request #1 from Stringer90/web-workspace/remove-page-singl…
Stringer90 c914cd7
Added base proxy to be more dynamic
vt37 7b87ea9
Modify conftest to reflect recent commits
vt37 e147f07
Deleted base.py
vt37 99a4658
Changed the wait times for page to 7 secs
vt37 91373bf
Make Button probe non-dynamic. Other minor changes.
Stringer90 94609a3
Rename 'page_singleton.py' to 'playwright_page.py'.
Stringer90 600498e
Merge pull request #2 from Stringer90/web-workspace/dynamic-proxy
Stringer90 44e7dfc
Merge pull request #3 from Stringer90/workspace
vt37 7678c9e
Works with button test_text. Trying new proxy architecture with non-w…
Stringer90 87ab682
Last 2 button tests work.
Stringer90 666a2d2
Minor changes from recent feedback. Needs the 'return repr(str(value)…
Stringer90 b403792
Proxify BoxProxy. Non-widget objects use 'my_objs' dict. Tried integr…
Stringer90 485fd60
Merge pull request #4 from Stringer90/web-workspace/button-press-test
Stringer90 7e89ee8
Added serialize to test_cmd
vt37 214bee1
Added unwrap and fix repr(str() issue to fit with the new format
vt37 ce7eab5
Merge pull request #5 from Stringer90/web-workspace/serialize-test-cm…
vt37 61e365c
Removed BoxProxy from wire_page as unneeded
vt37 14ab27f
Added back the '[dependency-groups]' header
vt37 96aaf04
Merge pull request #6 from Stringer90/workspace
vt37 b5fb51f
Restructure of proxy architecure both on app and test suite side. Mer…
Stringer90 5c4f4ec
Put all proxy definitions in 'object_proxies'. Probe structure change…
Stringer90 61b2522
Fix merge conflict.
Stringer90 c6bc67a
Modify app.py so test_cmd exposed only if env TOGA_WEB_TESTING is true
vt37 1b46a5f
Added TOGA_WEB_TESTING to True when running Playwright
vt37 f0a9065
Merge pull request #8 from Stringer90/workspace
vt37 dfbfcad
Added script, toga class shimming, web test harness for app.py, and o…
Stringer90 fe87b15
Remove 'page_singleton.py' no-op. Integrate 'AttributeProxy' with '__…
Stringer90 86b5f51
Merge pull request #9 from Stringer90/web-workspace/script-and-shim
Stringer90 3da83f0
Fix recursive method calls in '_deserialise_payload()'.
Stringer90 10dc309
Added local policies and object caching
vt37 bbc2b1e
Merge pull request #10 from Stringer90/workspace
vt37 2c058a2
Change proxy line protocol from sending code strings to the web app t…
Stringer90 c6119bd
Merge pull request #11 from Stringer90/workspace
Stringer90 5f83d4b
Added callable source deserialization to enable runner to host valida…
vt37 bd7522f
Added four new test suites and updated conftest with new fixtures
vt37 6fc9f1f
Add widget to SHIMS and update apply() to allow call imports.
vt37 5bd78a7
Added new widgets and update base_proxy to serialize callables
vt37 22fbe52
Added new widget probes
vt37 2ddbea6
Merge pull request #12 from Stringer90/web-workspace/python-callables
vt37 c905acc
Merge pull request #13 from Stringer90/web-workspace/python-callables
vt37 354f8ae
Fixed pre-commit issues
vt37 8145f08
Merge pull request #14 from Stringer90/workspace
vt37 20d97ce
Added date/time envelopes and tests
vt37 0beea39
Merge pull request #15 from Stringer90/web-workspace/date-time
vt37 3506200
Merge pull request #16 from Stringer90/workspace
vt37 e435dbb
Added back 'encoding.py' to fix errors. Quieter script output.
Stringer90 e6a2367
Fixed the name placeholder issue
vt37 9121213
Merge pull request #17 from Stringer90/workspace
vt37 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| #!/usr/bin/env python | ||
| """Django's command-line utility for administrative tasks.""" | ||
|
|
||
| import os | ||
| import sys | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| This repository is dedicated to development, testing, and proof-of-concept work related to issue [3545](https://github.com/beeware/toga/issues/3545), which focuses on implementing testing for the web platform. | ||
|
|
||
| ## How We Run this Test Suite | ||
| 1. Open this directory. | ||
| 2. Create a Python 3.12 virtual environment and install test requirements: | ||
| - `python3.12 -m venv venv` | ||
| - `source venv/bin/activate` | ||
| - `pip install -U pip` | ||
| - `pip install --group test` | ||
| - `playwright install chromium` | ||
| 3. Run your Toga app as a web app. | ||
| - `briefcase run web` | ||
| 4. In a separate terminal, run the test suite: | ||
| - `source venv/bin/activate` | ||
| - `pytest tests` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| [project] | ||
| name = "testbed" | ||
| version = "0.0.1" | ||
|
|
||
| [dependency-groups] | ||
| test = [ | ||
| "briefcase", | ||
| "playwright == 1.51.0", | ||
| # "pytest==8.4.1", | ||
| "pytest==8.3.5", | ||
| # "pytest-asyncio==1.1.0", | ||
| "pytest-asyncio==0.26.0", | ||
| "pytest-playwright==0.7.0", | ||
| ] | ||
|
|
||
| [tool.briefcase] | ||
| project_name = "Toga Web Testbed" | ||
| bundle = "org.beeware.toga" | ||
| url = "https://beeware.org" | ||
| license = "BSD-3-Clause" | ||
| license-files = [ | ||
| "LICENSE", | ||
| ] | ||
| author = "Tiberius Yak" | ||
| author_email = "[email protected]" | ||
|
|
||
| [tool.briefcase.app.testbed] | ||
| formal_name = "Toga Testbed" | ||
| description = "A testbed for Toga visual tests" | ||
| icon = "icons/testbed" | ||
| sources = [ | ||
| "src/testbed", | ||
| ] | ||
| test_sources = [ | ||
| "tests", | ||
| ] | ||
| requires = [ | ||
| "../travertino", | ||
| "../core", | ||
| ] | ||
|
|
||
| [tool.briefcase.app.testbed.web] | ||
| requires = [ | ||
| "../web" | ||
| ] | ||
| style_framework = "Shoelace v2.3" | ||
|
|
||
| [tool.pytest.ini_options] | ||
| asyncio_mode = "auto" |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from testbed.app import main | ||
|
|
||
| if __name__ == "__main__": | ||
| main().main_loop() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import os | ||
| import types | ||
| from unittest.mock import Mock | ||
|
|
||
| import toga | ||
| from toga.style import Pack | ||
| from toga.style.pack import COLUMN | ||
|
|
||
| try: | ||
| import js | ||
| except ModuleNotFoundError: | ||
| js = None | ||
| try: | ||
| from pyodide.ffi import create_proxy, to_js | ||
| except ModuleNotFoundError: | ||
| pyodide = None | ||
|
|
||
|
|
||
| def _truthy(v) -> bool: | ||
| return str(v).strip().lower() in {"1", "true", "yes", "on"} | ||
|
|
||
|
|
||
| def _web_testing_enabled() -> bool: | ||
| if _truthy(os.getenv("TOGA_WEB_TESTING")): | ||
| return True | ||
|
|
||
| if js is not None: | ||
| try: | ||
| if _truthy(getattr(js.window, "TOGA_WEB_TESTING", "")): | ||
| return True | ||
| qs = str(getattr(js.window, "location", None).search or "") | ||
| # enable if ?toga_web_testing=1 in url | ||
| if "toga_web_testing" in qs.lower(): | ||
| return True | ||
| except Exception: | ||
| pass | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| class HelloWorld(toga.App): | ||
| def startup(self): | ||
| main_box = toga.Box(style=Pack(direction=COLUMN)) | ||
| self.label = toga.Label(id="myLabel", text="Test App - Toga Web Testing") | ||
|
|
||
| if _web_testing_enabled() and js is not None and create_proxy is not None: | ||
| self.my_objs = {} | ||
| js.window.test_cmd = create_proxy(self.cmd_test) | ||
|
|
||
| main_box.add(self.label) | ||
| self.main_window = toga.MainWindow(title=self.formal_name) | ||
| self.main_window.content = main_box | ||
| self.main_window.show() | ||
|
|
||
| def cmd_test(self, code): | ||
| env = {"self": self, "toga": toga, "my_objs": self.my_objs, "Mock": Mock} | ||
| local = {} | ||
| try: | ||
| exec(code, env, local) | ||
| result = local.get("result", env.get("result")) | ||
| envelope = self._serialise_payload(result) | ||
| return to_js(envelope, dict_converter=js.Object.fromEntries) | ||
| except Exception as e: | ||
| return to_js( | ||
| {"type": "error", "value": str(e)}, dict_converter=js.Object.fromEntries | ||
| ) | ||
|
|
||
| def _serialise_payload(self, x): | ||
| # primitives | ||
| if x is None: | ||
| return {"type": "none", "value": None} | ||
|
||
| if isinstance(x, bool): | ||
| return {"type": "bool", "value": x} | ||
| if isinstance(x, int): | ||
| return {"type": "int", "value": x} | ||
| if isinstance(x, float): | ||
| return {"type": "float", "value": x} | ||
| if isinstance(x, str): | ||
| return {"type": "str", "value": x} | ||
|
|
||
| # containers | ||
| if isinstance(x, list): | ||
| return {"type": "list", "items": [self._serialise_payload(i) for i in x]} | ||
| if isinstance(x, tuple): | ||
| return {"type": "tuple", "items": [self._serialise_payload(i) for i in x]} | ||
| if isinstance(x, dict): | ||
| items = [] | ||
| for k, v in x.items(): | ||
| if k is None: | ||
| key_env = {"type": "none", "value": None} | ||
| elif isinstance(k, bool): | ||
| key_env = {"type": "bool", "value": k} | ||
| elif isinstance(k, int): | ||
| key_env = {"type": "int", "value": k} | ||
| elif isinstance(k, float): | ||
| key_env = {"type": "float", "value": k} | ||
| elif isinstance(k, str): | ||
| key_env = {"type": "str", "value": k} | ||
| else: | ||
| key_env = {"type": "str", "value": str(k)} | ||
| items.append([key_env, self._serialise_payload(v)]) | ||
| return {"type": "dict", "items": items} | ||
|
|
||
| # references by id | ||
| obj_id = self._key_for(x) | ||
| is_callable = callable(x) or isinstance( | ||
| x, (types.FunctionType, types.MethodType) | ||
| ) | ||
| return {"type": "callable" if is_callable else "object", "id": obj_id} | ||
|
|
||
| def _key_for(self, x): | ||
| for k, v in self.my_objs.items(): | ||
| if v is x: | ||
| return k | ||
| # If not registered, register it | ||
| k = str(id(x)) | ||
| self.my_objs[k] = x | ||
| return k | ||
|
|
||
|
|
||
| def main(): | ||
| return HelloWorld() | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| from pytest import approx | ||
|
|
||
| TRANSPARENT = "transparent" | ||
|
|
||
|
|
||
| def assert_background_color(actual, expected): | ||
| # For platforms where alpha blending is manually implemented, the | ||
| # probe.background_color property returns a tuple consisting of: | ||
| # - The widget's background color | ||
| # - The widget's parent's background color | ||
| # - The widget's original alpha value - Required for deblending | ||
| if isinstance(actual, tuple): | ||
| actual_widget_bg, actual_parent_bg, actual_widget_bg_alpha = actual | ||
| if actual_widget_bg_alpha == 0: | ||
| # Since a color having an alpha value of 0 cannot be deblended. | ||
| # So, the deblended widget color would be equal to the parent color. | ||
| deblended_actual_widget_bg = actual_parent_bg | ||
| else: | ||
| deblended_actual_widget_bg = actual_widget_bg.unblend_over( | ||
| actual_parent_bg, actual_widget_bg_alpha | ||
| ) | ||
| if isinstance(expected, tuple): | ||
| expected_widget_bg, expected_parent_bg, expected_widget_bg_alpha = expected | ||
| if expected_widget_bg_alpha == 0: | ||
| # Since a color having an alpha value of 0 cannot be deblended. | ||
| # So, the deblended widget color would be equal to the parent color. | ||
| deblended_expected_widget_bg = expected_parent_bg | ||
| else: | ||
| deblended_expected_widget_bg = expected_widget_bg.unblend_over( | ||
| expected_parent_bg, expected_widget_bg_alpha | ||
| ) | ||
| assert_color(deblended_actual_widget_bg, deblended_expected_widget_bg) | ||
| # For comparison when expected is a single value object | ||
| else: | ||
| if (expected == TRANSPARENT) or ( | ||
| expected.a == 0 | ||
| # Since a color having an alpha value of 0 cannot be deblended to | ||
| # get the exact original color, as deblending in such cases would | ||
| # lead to a division by zero error. So, just check that widget and | ||
| # parent have the same color. | ||
| ): | ||
| assert_color(actual_widget_bg, actual_parent_bg) | ||
| elif expected.a != 1: | ||
| assert_color(deblended_actual_widget_bg, expected) | ||
| else: | ||
| assert_color(actual_widget_bg, expected) | ||
| # For other platforms | ||
| else: | ||
| assert_color(actual, expected) | ||
|
|
||
|
|
||
| def assert_color(actual, expected): | ||
| if expected in {None, TRANSPARENT}: | ||
| assert expected == actual | ||
| else: | ||
| if actual in {None, TRANSPARENT}: | ||
| assert expected == actual | ||
| else: | ||
| assert (actual.r, actual.g, actual.b, actual.a) == ( | ||
| expected.r, | ||
| expected.g, | ||
| expected.b, | ||
| approx(expected.a, abs=(1 / 255)), | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # from pytest import fixture, register_assert_rewrite, skip | ||
| # import toga | ||
|
|
||
| import pytest | ||
|
|
||
| from .tests_backend.playwright_page import BackgroundPage | ||
| from .tests_backend.proxies.app_proxy import AppProxy | ||
| from .tests_backend.proxies.base_proxy import BaseProxy | ||
| from .tests_backend.widgets.base import SimpleProbe | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def page(): | ||
| p = BackgroundPage() | ||
| return p | ||
|
|
||
|
|
||
| # Inject Playwright page object into | ||
| @pytest.fixture(scope="session", autouse=True) | ||
| def _wire_page(page): | ||
| BaseProxy.page_provider = staticmethod(lambda: page) | ||
| SimpleProbe.page_provider = staticmethod(lambda: page) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def app(): | ||
| return AppProxy() | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def main_window(app): | ||
| return app.main_window |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # A test object that can be used as data | ||
| class MyObject: | ||
| def __str__(self): | ||
| return "My Test Object" | ||
|
|
||
|
|
||
| # The text examples must both increase and decrease in size between examples to | ||
| # ensure that reducing the size of a label doesn't prevent future labels from | ||
| # increasing in size. | ||
| TEXTS = [ | ||
| "example", | ||
| "", | ||
| "a", | ||
| " ", | ||
| "ab", | ||
| "abc", | ||
| "hello world", | ||
| "hello\nworld", | ||
| "你好, wørłd!", | ||
| 1234, | ||
| MyObject(), | ||
| ] |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what you're looking for here is
vars(). That's a complete state of all variables that are visible in the current scope.