mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Use the new testing machinery for test_examples (#676)
* WIP: start to use the PyScriptTests machinery to test the examples * factor test_hello_world out of test_examples * B011 forbids 'assert False' in tests because python -O remove asserts. Thank you, I knew that. * improve test_simple_clock and remove it from test_examples * test_altair * test_bokeh * rename * kill the parametrized test_example and write individual tests for each of them * test_kmeans it's slow, increase the timeout * improve these xfail * kill wait_for_load, no longer needed * write the name of the issue * add issue number * add a trick which I discovered to run test interactively * move the docstring inside the class
This commit is contained in:
@@ -66,6 +66,15 @@ class PyScriptTest:
|
||||
# this extra print is useful when using pytest -s, else we start printing
|
||||
# in the middle of the line
|
||||
print()
|
||||
#
|
||||
# if you use pytest --headed you can see the browser page while
|
||||
# playwright executes the tests. However, the page is closed very
|
||||
# quickly as soon as the test finishes. If you want to pause the test
|
||||
# to have time to inspect it manually, uncomment the next two
|
||||
# lines. The lines after the 'yield' will be executed during the
|
||||
# teardown, leaving the page open until you exit pdb.
|
||||
## yield
|
||||
## import pdb;pdb.set_trace()
|
||||
|
||||
def init_page(self, page):
|
||||
self.page = page
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
"""Each example requires the same three tests:
|
||||
|
||||
- Test that the initial markup loads properly (currently done by testing the <title>
|
||||
tag's content)
|
||||
- Testing that pyodide is loading properly
|
||||
- Testing that the page contains appropriate content after rendering
|
||||
|
||||
The single function iterates through the examples, instantiates one playwright browser
|
||||
session per example, and runs all three of each example's tests in that same browser
|
||||
session.
|
||||
"""
|
||||
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytest
|
||||
|
||||
from .support import ROOT
|
||||
|
||||
MAX_TEST_TIME = 30 # Number of seconds allowed for checking a testing condition
|
||||
TEST_TIME_INCREMENT = 0.25 # 1/4 second, the length of each iteration
|
||||
TEST_ITERATIONS = math.ceil(
|
||||
MAX_TEST_TIME / TEST_TIME_INCREMENT
|
||||
) # 120 iters of 1/4 second
|
||||
|
||||
# Content that is displayed in the page while pyodide loads
|
||||
LOADING_MESSAGES = [
|
||||
"Loading runtime...",
|
||||
"Runtime created...",
|
||||
"Initializing components...",
|
||||
"Initializing scripts...",
|
||||
]
|
||||
|
||||
EXAMPLES = [
|
||||
"altair",
|
||||
"bokeh",
|
||||
"bokeh_interactive",
|
||||
"d3",
|
||||
"folium",
|
||||
"hello_world",
|
||||
"matplotlib",
|
||||
"numpy_canvas_fractals",
|
||||
"panel",
|
||||
"panel_deckgl",
|
||||
"panel_kmeans",
|
||||
"panel_stream",
|
||||
"repl",
|
||||
"repl2",
|
||||
"simple_clock",
|
||||
"todo",
|
||||
"todo_pylist",
|
||||
"toga_freedom",
|
||||
"webgl_raycaster_index",
|
||||
]
|
||||
|
||||
TEST_PARAMS = {
|
||||
"altair": {
|
||||
"file": "examples/altair.html",
|
||||
"pattern": '<canvas.*?class=\\"marks\\".*?>',
|
||||
"title": "Altair",
|
||||
},
|
||||
"bokeh": {
|
||||
"file": "examples/bokeh.html",
|
||||
"pattern": '<div.*class=\\"bk\\".*>',
|
||||
"title": "Bokeh Example",
|
||||
},
|
||||
"bokeh_interactive": {
|
||||
"file": "examples/bokeh_interactive.html",
|
||||
"pattern": '<div.*?class=\\"bk\\".*?>',
|
||||
"title": "Bokeh Example",
|
||||
},
|
||||
"d3": {
|
||||
"file": "examples/d3.html",
|
||||
"pattern": "<svg.*?>",
|
||||
"title": "d3: JavaScript & PyScript visualizations side-by-side",
|
||||
},
|
||||
"folium": {
|
||||
"file": "examples/folium.html",
|
||||
"pattern": "<iframe srcdoc=",
|
||||
"title": "Folium",
|
||||
},
|
||||
"hello_world": {
|
||||
"file": "examples/hello_world.html",
|
||||
"pattern": "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+",
|
||||
"title": "PyScript Hello World",
|
||||
},
|
||||
"matplotlib": {
|
||||
"file": "examples/matplotlib.html",
|
||||
"pattern": "<img src=['\"]data:image",
|
||||
"title": "Matplotlib",
|
||||
},
|
||||
"numpy_canvas_fractals": {
|
||||
"file": "examples/numpy_canvas_fractals.html",
|
||||
"pattern": "<div.*?id=['\"](mandelbrot|julia|newton)['\"].*?>",
|
||||
"title": "Visualization of Mandelbrot, Julia and "
|
||||
"Newton sets with NumPy and HTML5 canvas",
|
||||
},
|
||||
"panel": {
|
||||
"file": "examples/panel.html",
|
||||
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
|
||||
"title": "Panel Example",
|
||||
},
|
||||
"panel_deckgl": {
|
||||
"file": "examples/panel_deckgl.html",
|
||||
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
|
||||
"title": "PyScript/Panel DeckGL Demo",
|
||||
},
|
||||
"panel_kmeans": {
|
||||
"file": "examples/panel_kmeans.html",
|
||||
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
|
||||
"title": "Pyscript/Panel KMeans Demo",
|
||||
},
|
||||
"panel_stream": {
|
||||
"file": "examples/panel_stream.html",
|
||||
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
|
||||
"title": "PyScript/Panel Streaming Demo",
|
||||
},
|
||||
"repl": {"file": "examples/repl.html", "pattern": "<py-repl.*?>", "title": "REPL"},
|
||||
"repl2": {
|
||||
"file": "examples/repl2.html",
|
||||
"pattern": "<py-repl.*?>",
|
||||
"title": "Custom REPL Example",
|
||||
},
|
||||
"simple_clock": {
|
||||
"file": "examples/simple_clock.html",
|
||||
"pattern": "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+",
|
||||
"title": "Simple Clock Demo",
|
||||
},
|
||||
"todo": {
|
||||
"file": "examples/todo.html",
|
||||
"pattern": "<input.*?id=['\"]new-task-content['\"].*?>",
|
||||
"title": "Todo App",
|
||||
},
|
||||
"todo_pylist": {
|
||||
"file": "examples/todo-pylist.html",
|
||||
"pattern": "<input.*?id=['\"]new-task-content['\"].*?>",
|
||||
"title": "Todo App",
|
||||
},
|
||||
"toga_freedom": {
|
||||
"file": "examples/toga/freedom.html",
|
||||
"pattern": "<(main|div).*?id=['\"]toga_\\d+['\"].*?>",
|
||||
"title": ["Loading...", "Freedom Units"],
|
||||
},
|
||||
"webgl_raycaster_index": {
|
||||
"file": "examples/webgl/raycaster/index.html",
|
||||
"pattern": "<canvas.*?>",
|
||||
"title": "Raycaster",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def wait_for_load(page):
|
||||
"""
|
||||
Assert that pyscript loading messages appear.
|
||||
"""
|
||||
pyodide_loading = False # Flag to be set to True when condition met
|
||||
|
||||
for _ in range(TEST_ITERATIONS):
|
||||
content = page.text_content("*")
|
||||
for message in LOADING_MESSAGES:
|
||||
if message in content:
|
||||
pyodide_loading = True
|
||||
if pyodide_loading:
|
||||
break
|
||||
time.sleep(TEST_TIME_INCREMENT)
|
||||
|
||||
assert pyodide_loading # nosec
|
||||
|
||||
|
||||
def wait_for_render(page, selector, pattern):
|
||||
"""
|
||||
Assert that rendering inserts data into the page as expected: search the
|
||||
DOM from within the timing loop for a string that is not present in the
|
||||
initial markup but should appear by way of rendering
|
||||
"""
|
||||
re_sub_content = re.compile(pattern)
|
||||
py_rendered = False # Flag to be set to True when condition met
|
||||
|
||||
for _ in range(TEST_ITERATIONS):
|
||||
content = page.inner_html(selector)
|
||||
if re_sub_content.search(content):
|
||||
py_rendered = True
|
||||
break
|
||||
time.sleep(TEST_TIME_INCREMENT)
|
||||
|
||||
assert py_rendered # nosec
|
||||
|
||||
|
||||
@pytest.mark.parametrize("example", EXAMPLES)
|
||||
def test_examples(example, http_server, page):
|
||||
# make sure that the http server serves from the right directory
|
||||
ROOT.join("pyscriptjs").chdir()
|
||||
|
||||
base_url = http_server
|
||||
example_path = urljoin(base_url, TEST_PARAMS[example]["file"])
|
||||
|
||||
page.goto(example_path, wait_until="commit")
|
||||
|
||||
title = page.title()
|
||||
|
||||
# STEP 1: Check page title proper initial loading of the example page
|
||||
expected_title = TEST_PARAMS[example]["title"]
|
||||
if isinstance(expected_title, list):
|
||||
# One example's title changes so expected_title is a list of possible
|
||||
# titles in that case
|
||||
assert title in expected_title # nosec
|
||||
else:
|
||||
assert title == expected_title # nosec
|
||||
|
||||
# STEP 2: Test that pyodide is loading via messages displayed during loading
|
||||
wait_for_load(page)
|
||||
|
||||
# Step 3: Wait for expected pattern to appear on page
|
||||
wait_for_render(page, "*", TEST_PARAMS[example]["pattern"])
|
||||
|
||||
|
||||
def test_simple_clock(http_server, page):
|
||||
example_path = urljoin(http_server, TEST_PARAMS["simple_clock"]["file"])
|
||||
|
||||
page.goto(example_path, wait_until="commit")
|
||||
|
||||
wait_for_load(page)
|
||||
|
||||
pattern = r"\d{2}/\d{2}/\d{4}, \d{2}:\d{2}:\d{2}"
|
||||
for _ in range(TEST_ITERATIONS):
|
||||
content = page.inner_html("#outputDiv2")
|
||||
if re.match(pattern, content) and int(content[-1]) in (0, 4, 8):
|
||||
assert page.inner_html("#outputDiv3") == "It's espresso time!"
|
||||
break
|
||||
else:
|
||||
time.sleep(TEST_TIME_INCREMENT)
|
||||
203
pyscriptjs/tests/test_zz_examples.py
Normal file
203
pyscriptjs/tests/test_zz_examples.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from .support import ROOT, PyScriptTest
|
||||
|
||||
MAX_TEST_TIME = 30 # Number of seconds allowed for checking a testing condition
|
||||
TEST_TIME_INCREMENT = 0.25 # 1/4 second, the length of each iteration
|
||||
TEST_ITERATIONS = math.ceil(
|
||||
MAX_TEST_TIME / TEST_TIME_INCREMENT
|
||||
) # 120 iters of 1/4 second
|
||||
|
||||
|
||||
def wait_for_render(page, selector, pattern):
|
||||
"""
|
||||
Assert that rendering inserts data into the page as expected: search the
|
||||
DOM from within the timing loop for a string that is not present in the
|
||||
initial markup but should appear by way of rendering
|
||||
"""
|
||||
re_sub_content = re.compile(pattern)
|
||||
py_rendered = False # Flag to be set to True when condition met
|
||||
|
||||
for _ in range(TEST_ITERATIONS):
|
||||
content = page.inner_html(selector)
|
||||
if re_sub_content.search(content):
|
||||
py_rendered = True
|
||||
break
|
||||
time.sleep(TEST_TIME_INCREMENT)
|
||||
|
||||
assert py_rendered # nosec
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("chdir")
|
||||
class TestExamples(PyScriptTest):
|
||||
"""
|
||||
Each example requires the same three tests:
|
||||
|
||||
- Test that the initial markup loads properly (currently done by
|
||||
testing the <title> tag's content)
|
||||
- Testing that pyscript is loading properly
|
||||
- Testing that the page contains appropriate content after rendering
|
||||
"""
|
||||
|
||||
@pytest.fixture()
|
||||
def chdir(self):
|
||||
# make sure that the http server serves from the right directory
|
||||
ROOT.join("pyscriptjs").chdir()
|
||||
|
||||
def test_hello_world(self):
|
||||
self.goto("examples/hello_world.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "PyScript Hello World"
|
||||
content = self.page.content()
|
||||
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
|
||||
assert re.search(pattern, content)
|
||||
|
||||
def test_simple_clock(self):
|
||||
self.goto("examples/simple_clock.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Simple Clock Demo"
|
||||
pattern = r"\d{2}/\d{2}/\d{4}, \d{2}:\d{2}:\d{2}"
|
||||
# run for 5 seconds to be sure that we see the page with "It's
|
||||
# espresso time!"
|
||||
for _ in range(5):
|
||||
content = self.page.inner_html("#outputDiv2")
|
||||
if re.match(pattern, content) and int(content[-1]) in (0, 4, 8):
|
||||
assert self.page.inner_html("#outputDiv3") == "It's espresso time!"
|
||||
break
|
||||
else:
|
||||
time.sleep(1)
|
||||
else:
|
||||
assert False, "Espresso time not found :("
|
||||
|
||||
def test_altair(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/altair.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Altair"
|
||||
wait_for_render(self.page, "*", '<canvas.*?class=\\"marks\\".*?>')
|
||||
|
||||
def test_bokeh(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/bokeh.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Bokeh Example"
|
||||
wait_for_render(self.page, "*", '<div.*class=\\"bk\\".*>')
|
||||
|
||||
def test_bokeh_interactive(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/bokeh_interactive.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Bokeh Example"
|
||||
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
|
||||
|
||||
def test_d3(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/d3.html")
|
||||
self.wait_for_pyscript()
|
||||
assert (
|
||||
self.page.title() == "d3: JavaScript & PyScript visualizations side-by-side"
|
||||
)
|
||||
wait_for_render(self.page, "*", "<svg.*?>")
|
||||
|
||||
def test_folium(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/folium.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Folium"
|
||||
wait_for_render(self.page, "*", "<iframe srcdoc=")
|
||||
|
||||
def test_matplotlib(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/matplotlib.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Matplotlib"
|
||||
wait_for_render(self.page, "*", "<img src=['\"]data:image")
|
||||
|
||||
def test_numpy_canvas_fractals(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/numpy_canvas_fractals.html")
|
||||
self.wait_for_pyscript()
|
||||
assert (
|
||||
self.page.title()
|
||||
== "Visualization of Mandelbrot, Julia and Newton sets with NumPy and HTML5 canvas"
|
||||
)
|
||||
wait_for_render(
|
||||
self.page, "*", "<div.*?id=['\"](mandelbrot|julia|newton)['\"].*?>"
|
||||
)
|
||||
|
||||
def test_panel(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/panel.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Panel Example"
|
||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||
|
||||
def test_panel_deckgl(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/panel_deckgl.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "PyScript/Panel DeckGL Demo"
|
||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||
|
||||
def test_panel_kmeans(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/panel_kmeans.html")
|
||||
self.wait_for_pyscript(timeout=120 * 1000)
|
||||
assert self.page.title() == "Pyscript/Panel KMeans Demo"
|
||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||
|
||||
@pytest.mark.xfail(reason="JsError: issue #677")
|
||||
def test_panel_stream(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/panel_stream.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "PyScript/Panel Streaming Demo"
|
||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||
|
||||
def test_repl(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/repl.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "REPL"
|
||||
wait_for_render(self.page, "*", "<py-repl.*?>")
|
||||
|
||||
def test_repl2(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/repl2.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Custom REPL Example"
|
||||
wait_for_render(self.page, "*", "<py-repl.*?>")
|
||||
|
||||
def test_todo(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/todo.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Todo App"
|
||||
wait_for_render(self.page, "*", "<input.*?id=['\"]new-task-content['\"].*?>")
|
||||
|
||||
@pytest.mark.xfail(reason="JsError, issue #673")
|
||||
def test_todo_pylist(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/todo-pylist.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Todo App"
|
||||
wait_for_render(self.page, "*", "<input.*?id=['\"]new-task-content['\"].*?>")
|
||||
|
||||
def test_toga_freedom(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/toga/freedom.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() in ["Loading...", "Freedom Units"]
|
||||
wait_for_render(self.page, "*", "<(main|div).*?id=['\"]toga_\\d+['\"].*?>")
|
||||
|
||||
@pytest.mark.xfail(reason="it never finishes loading, issue #678")
|
||||
def test_webgl_raycaster_index(self):
|
||||
# XXX improve this test
|
||||
self.goto("examples/webgl/raycaster/index.html")
|
||||
self.wait_for_pyscript()
|
||||
assert self.page.title() == "Raycaster"
|
||||
wait_for_render(self.page, "*", "<canvas.*?>")
|
||||
Reference in New Issue
Block a user