Files
pyscript/core/tests/python/tests/test_websocket.py
Nicholas Tollervey a02ff691d2 pyscript API/docstring refactor with comprehensive tests (#2414)
* Revise display module. TODO: more comprehensive tests. Especially around mimebundles.

* Markdown corrections in example code in display.py docstrings.

* Minor adjustments and a much more comprehensive test-suite for the display module.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Updated docstring in __init__.py.

* Remove unused imports and black-ify the source.

* Refactor, docs and tests for the Event class in events.py.

* Refactored, simplified and documented @when decorator.

* Extensive test suite for @when decorator.

* Documentation and minor refactoring of the fetch.py module. TODO: Check tests.

* Refactored and more comprehensive tests for the fetch module.

* Add/clarify Event related interactions. Thanks @Neon22 for the suggestion.

* Refactor, document ffi.py module.

* More complete passing tests for ffi.py.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add docstrings to flatted.py. Since this is actually an external(ish) module, tests for it should be in the external repository from which this code is derived.

* Minor docstring cleanup in ffi.py.

* Added docstrings and clarifications to fs.py.

* Add very limited test suite for fs.py.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rename magic_js.py to context.py, add comprehensive docstrings, and rename certain internal things for readability and comprehension.

* Fix dict check in ffi.py.

* Rename test_js_modules to test_context.

* Fix test configuration aftert rename.

* Docs and refactor of media.py.

* Comprehensive tests for media.py.

* Refactor and docstrings for storage.py

* Appease the ruff gods.

* Further storage.py changes and a more complete test suite for storage.

* Refactor and docstrings for the util.py module. Fixed a problem with is_awaitable not handling async bound methods.

* More comprehensive test suite for util.py. Updated to latest upytest.

* A major refactoring, documenting and simplification of the web.py module substantially reducing it in size and complexity with only a few minor (edge) behavioural changes.

Softly breaking changes include:

- An element's classes are just a set.
- An element's styles are just a dict.
- Explicitly use `update_all` with ElementCollections (simpler and greater flexibility).
- Extract a child element by id with `my_container["#an-id"]`

* Updates and additions for a more comprehensive test suite for the web.py module. All code paths are exercised and checked.

* Black tidy-ups in test suite.

* Refactor and documentation for websocket.py module.

* Tests for websocket.py. Disabled due to playwright flakiness, but they all pass in a local browser.

* Refactor and documentation of workers.py module.

* Added tests for workers.py module. Updated related test suite to account for the new named worker in the test HTML.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Refactor away remaining "is not None" not caught before.

* Remove check-docstring-first because it interferes with the auto-generated documentation (where triple quoted strings are used to document module attributes).

* Careful Markdown changes so the docstrings render properly in the PyScript docs.

* Typo correction.

* More typo corrections and clarifications.

* Add clarification about SVG handling to _render_image docstring.

* Add DOM event options to the @when decorator (with new tests to exercise this functionality).

* Fixes default value for options if no options passed into @when.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-11 17:19:24 +00:00

280 lines
7.4 KiB
Python

"""
Exercise the pyscript.Websocket class.
"""
import asyncio
import upytest
from pyscript import WebSocket
# Websocket tests are disabled by default because they don't reliably work in
# playwright based tests. Feel free to set this to False to enable them when
# running tests locally in an actual browser (they all pass there).
SKIP_WEBSOCKET_TESTS = True
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_with_attributes():
"""
Event handlers assigned via object attributes.
The Websocket class should be able to connect to a websocket server and
send and receive messages.
Use of echo.websocket.org means:
1) When connecting it responds with a "Request served by" message.
2) When sending a message it echos it back.
"""
connected_flag = False
closed_flag = False
messages = []
ready_to_test = asyncio.Event()
def on_open(event):
nonlocal connected_flag
connected_flag = True
ws.send("Hello, world!") # A message to echo.
def on_message(event):
messages.append(event.data)
if len(messages) == 2: # We're done.
ws.close()
def on_close(event):
nonlocal closed_flag
closed_flag = True
ready_to_test.set() # Finished!
ws = WebSocket(url="wss://echo.websocket.org")
ws.onopen = on_open
ws.onmessage = on_message
ws.onclose = on_close
# Wait for everything to be finished.
await ready_to_test.wait()
assert connected_flag is True
assert len(messages) == 2
assert "request served by" in messages[0].lower()
assert messages[1] == "Hello, world!"
assert closed_flag is True
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_with_init():
"""
Event handlers assigned via __init__ arguments.
The Websocket class should be able to connect to a websocket server and
send and receive messages.
Use of echo.websocket.org means:
1) When connecting it responds with a "Request served by" message.
2) When sending a message it echos it back.
"""
connected_flag = False
closed_flag = False
messages = []
ready_to_test = asyncio.Event()
def on_open(event):
nonlocal connected_flag
connected_flag = True
ws.send("Hello, world!") # A message to echo.
def on_message(event):
messages.append(event.data)
if len(messages) == 2: # We're done.
ws.close()
def on_close(event):
nonlocal closed_flag
closed_flag = True
ready_to_test.set() # Finished!
ws = WebSocket(
url="wss://echo.websocket.org",
onopen=on_open,
onmessage=on_message,
onclose=on_close,
)
# Wait for everything to be finished.
await ready_to_test.wait()
assert connected_flag is True
assert len(messages) == 2
assert "request served by" in messages[0].lower()
assert messages[1] == "Hello, world!"
assert closed_flag is True
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_async_handlers():
"""
Async event handlers should work correctly.
"""
messages = []
ready_to_test = asyncio.Event()
async def on_open(event):
await asyncio.sleep(0)
ws.send("async test")
async def on_message(event):
await asyncio.sleep(0)
messages.append(event.data)
if len(messages) == 2:
ws.close()
async def on_close(event):
await asyncio.sleep(0)
ready_to_test.set()
ws = WebSocket(
url="wss://echo.websocket.org",
onopen=on_open,
onmessage=on_message,
onclose=on_close,
)
await ready_to_test.wait()
assert len(messages) == 2
assert messages[1] == "async test"
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_binary_data_conversion():
"""
WebSocket should convert binary data to memoryview for Python.
"""
messages = []
ready_to_test = asyncio.Event()
def on_open(event):
# Send binary data as bytearray.
binary_data = bytearray([0x48, 0x65, 0x6C, 0x6C, 0x6F])
ws.send(binary_data)
def on_message(event):
messages.append(event.data)
if len(messages) == 2:
ws.close()
def on_close(event):
ready_to_test.set()
ws = WebSocket(
url="wss://echo.websocket.org",
onopen=on_open,
onmessage=on_message,
onclose=on_close,
)
await ready_to_test.wait()
assert len(messages) == 2
# Verify wrapper converts binary to memoryview Python type.
assert isinstance(messages[1], memoryview)
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_send_bytes_conversion():
"""
WebSocket send should convert Python bytes to JS Uint8Array.
"""
messages = []
ready_to_test = asyncio.Event()
def on_open(event):
# Test that bytes are converted properly.
ws.send(bytes([0x41, 0x42, 0x43]))
def on_message(event):
messages.append(event.data)
if len(messages) == 2:
ws.close()
def on_close(event):
ready_to_test.set()
ws = WebSocket(
url="wss://echo.websocket.org",
onopen=on_open,
onmessage=on_message,
onclose=on_close,
)
await ready_to_test.wait()
assert len(messages) == 2
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_event_wrapper():
"""
WebSocketEvent wrapper should provide access to event properties.
"""
event_types = []
ready_to_test = asyncio.Event()
def on_open(event):
event_types.append(event.type)
ws.send("test")
def on_message(event):
event_types.append(event.type)
# Verify event wrapper exposes properties.
assert hasattr(event, "data")
assert hasattr(event, "type")
if len(event_types) >= 2:
ws.close()
def on_close(event):
event_types.append(event.type)
ready_to_test.set()
ws = WebSocket(
url="wss://echo.websocket.org",
onopen=on_open,
onmessage=on_message,
onclose=on_close,
)
await ready_to_test.wait()
assert "open" in event_types
assert "message" in event_types
assert "close" in event_types
@upytest.skip("Websocket tests are disabled.", skip_when=SKIP_WEBSOCKET_TESTS)
async def test_websocket_reassign_handler():
"""
Event handlers should be replaceable after creation.
"""
first_handler_called = False
second_handler_called = False
ready_to_test = asyncio.Event()
def first_handler(event):
nonlocal first_handler_called
first_handler_called = True
def second_handler(event):
nonlocal second_handler_called
second_handler_called = True
ws.close()
def on_open(event):
# Replace the message handler before any messages arrive.
ws.onmessage = second_handler
ws.send("test")
def on_close(event):
ready_to_test.set()
ws = WebSocket(url="wss://echo.websocket.org", onopen=on_open, onclose=on_close)
ws.onmessage = first_handler
await ready_to_test.wait()
# Verify that handler replacement worked.
assert first_handler_called is False
assert second_handler_called is True