8000 Basic tests (#355) · sarvjeetdev/pyscript@59fc667 · GitHub
[go: up one dir, main page]

Skip to content

Commit 59fc667

Browse files
cleonardpre-commit-ci[bot]verhulstmfpliger
authored
Basic tests (pyscript#355)
* Added microsoft channel and playwright as dependency * Added test-setup to run * Basic tests for each example in examples/index.html * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_webgl_raycaster_index.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_folium.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_folium.py * Update test_folium.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_bokeh.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Whitelisting assertion statements in test files * Updated bare execept statements with ImportError * Flake8 compliance * Updated message * Removed 'make test-setup' * Removed @echo from make test * Uncommented Toga test * Removed __test_all__.py file * Removing unnecessary files * Removed individual test files * conftest.py with all data for running tests * Consolidated test file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed pre-commit flake8 issues * flake8 issue * add playwright installation to setup * Testing parameterization and webserver to serve examples locally * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Flake8 compliance * More flake8 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Michael Verhulst <michael@terminallabs.com> Co-authored-by: Fabio Pliger <fabio.pliger@gmail.com>
1 parent 0d946f8 commit 59fc667

File tree

5 files changed

+250
-1
lines changed

5 files changed

+250
-1
lines changed

pyscriptjs/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ conda_run := conda run -p $(env)
1313
setup:
1414
@if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi
1515
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
16+
$(conda_run) playwright install
1617

1718
clean:
1819
find . -name \*.py[cod] -delete
@@ -32,7 +33,6 @@ build:
3233
npm run build
3334

3435
test:
35-
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
3636
$(conda_run) pytest -vv $(ARGS) tests/ --log-cli-level=warning
3737

3838
test-py:

pyscriptjs/environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
channels:
22
- defaults
33
- conda-forge
4+
- microsoft
45
dependencies:
56
- python=3.9
67
- pip=20.2.2
@@ -10,3 +11,4 @@ dependencies:
1011
- isort
1112
- codespell
1213
- pre-commit
14+
- playwright

pyscriptjs/tests/__init__.py

Whitespace-only changes.

pyscriptjs/tests/conftest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""All data required for testing examples"""
2+
import os
3+
import threading
4+
from http.server import HTTPServer as SuperHTTPServer
5+
from http.server import SimpleHTTPRequestHandler
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
my_path = Path.cwd() / "examples"
11+
os.chdir(my_path)
12+
13+
14+
class HTTPServer(SuperHTTPServer):
15+
"""
16+
Class for wrapper to run SimpleHTTPServer on Thread.
17+
Ctrl +Only Thread remains dead when terminated with C.
18+
Keyboard Interrupt passes.
19+
"""
20+
21+
def run(self):
22+
try:
23+
self.serve_forever()
24+
except KeyboardInterrupt:
25+
pass
26+
finally:
27+
self.server_close()
28+
29+
30+
@pytest.fixture(scope="session")
31+
def http_server():
32+
host, port = "127.0.0.1", 8080
33+
base_url = f"http://{host}:{port}"
34+
35+
# serve_Run forever under thread
36+
server = HTTPServer((host, port), SimpleHTTPRequestHandler)
37+
thread = threading.Thread(None, server.run)
38+
thread.start()
39+
40+
yield base_url # Transition to test here
41+
42+
# End thread
43+
server.shutdown()
44+
thread.join()

pyscriptjs/tests/test_examples.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""Each example requires the same three tests:
2+
3+
- Test that the initial markup loads properly (currently done by testing the <title>
4+
tag's content)
5+
- Testing that pyodide is loading properly
6+
- Testing that the page contains appropriate content after rendering
7+
8+
The single function iterates through the examples, instantiates one playwright browser
9+
session per example, and runs all three of each example's tests in that same browser
10+
session.
11+
"""
12+
13+
import math
14+
import re
15+
import time
16+
from urllib.parse import urljoin
17+
18+
import pytest
19+
from playwright.sync_api import sync_playwright
20+
21+
MAX_TEST_TIME = 30 # Number of seconds allowed for checking a testing condition
22+
TEST_TIME_INCREMENT = 0.25 # 1/4 second, the length of each iteration
23+
TEST_ITERATIONS = math.ceil(
24+
MAX_TEST_TIME / TEST_TIME_INCREMENT
25+
) # 120 iters of 1/4 second
26+
27+
# Content that is displayed in the page while pyodide loads
28+
LOADING_MESSAGES = [
29+
"Loading runtime...",
30+
"Runtime created...",
31+
"Initializing components...",
32+
"Initializing scripts...",
33+
]
34+
35+
EXAMPLES = [
36+
"altair",
37+
"bokeh",
38+
"bokeh_interactive",
39+
"d3",
40+
"folium",
41+
"hello_world",
42+
"matplotlib",
43+
"numpy_canvas_fractals",
44+
"panel",
45+
"panel_deckgl",
46+
"panel_kmeans",
47+
"panel_stream",
48+
"repl",
49+
"repl2",
50+
"simple_clock",
51+
"todo",
52+
"todo_pylist",
53+
"toga_freedom",
54+
"webgl_raycaster_index",
55+
]
56+
57+
TEST_PARAMS = {
58+
"altair": {
59+
"file": "altair.html",
60+
"pattern": '<canvas.*?class=\\"marks\\".*?>',
61+
"title": "Altair",
62+
},
63+
"bokeh": {
64+
"file": "bokeh.html",
65+
"pattern": '<div.*class=\\"bk\\".*>',
66+
"title": "Bokeh Example",
67+
},
68+
"bokeh_interactive": {
69+
"file": "bokeh_interactive.html",
70+
"pattern": '<div.*?class=\\"bk\\".*?>',
71+
"title": "Bokeh Example",
72+
},
73+
"d3": {
74+
"file": "d3.html",
75+
"pattern": "<svg.*?>",
76+
"title": "d3: JavaScript & PyScript visualizations side-by-side",
77+
},
78+
"folium": {"file": "folium.html", "pattern": "<iframe srcdoc=", "title": "Folium"},
79+
"hello_world": {
80+
"file": "hello_world.html",
81+
"pattern": "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+",
82+
"title": "PyScript Hello World",
83+
},
84+
"matplotlib": {
85+
"file": "matplotlib.html",
86+
"pattern": "<img src=['\"]data:image",
87+
"title": "Matplotlib",
88+
},
89+
"numpy_canvas_fractals": {
90+
"file": "numpy_canvas_fractals.html",
91+
"pattern": "<div.*?id=['\"](mandelbrot|julia|newton)['\"].*?>",
92+
"title": "Visualization of Mandelbrot, Julia and "
93+
"Newton sets with NumPy and HTML5 canvas",
94+
},
95+
"panel": {
96+
"file": "panel.html",
97+
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
98+
"title": "Panel Example",
99+
},
100+
"panel_deckgl": {
101+
"file": "panel_deckgl.html",
102+
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
103+
"title": "PyScript/Panel DeckGL Demo",
104+
},
105+
"panel_kmeans": {
106+
"file": "panel_kmeans.html",
107+
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
108+
"title": "Pyscript/Panel KMeans Demo",
109+
},
110+
"panel_stream": {
111+
"file": "panel_stream.html",
112+
"pattern": "<div.*?class=['\"]bk-root['\"].*?>",
113+
"title": "PyScript/Panel Streaming Demo",
114+
},
115+
"repl": {"file": "repl.html", "pattern": "<py-repl.*?>", "title": "REPL"},
116+
"repl2": {
117+
"file": "repl2.html",
118+
"pattern": "<py-repl.*?>",
119+
"title": "Custom REPL Example",
120+
},
121+
"simple_clock": {
122+
"file": "simple_clock.html",
123+
"pattern": "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+",
124+
"title": "Simple Clock Demo",
125+
},
126+
"todo": {
127+
"file": "todo.html",
128+
"pattern": "<input.*?id=['\"]new-task-content['\"].*?>",
129+
"title": "Todo App",
130+
},
131+
"todo_pylist": {
132+
"file": "todo-pylist.html",
133+
"pattern": "<input.*?id=['\"]new-task-content['\"].*?>",
134+
"title": "Todo App",
135+
},
136+
"toga_freedom": {
137+
"file": "toga/freedom.html",
138+
"pattern": "<(main|div).*?id=['\"]toga_\\d+['\"].*?>",
139+
"title": ["Loading...", "Freedom Units"],
140+
},
141+
"webgl_raycaster_index": {
142+
"file": "webgl/raycaster/index.html",
143+
"pattern": "<canvas.*?>",
144+
"title": "Raycaster",
145+
},
146+
}
147+
148+
149+
@pytest.mark.parametrize("example", EXAMPLES)
150+
def test_examples(example, http_server):
151+
152+
base_url = http_server
153+
example_path = urljoin(base_url, TEST_PARAMS[example]["file"])
154+
155+
# Invoke playwright
156+
with sync_playwright() as p:
157+
browser = p.chromium.launch()
158+
page = browser.new_page()
159+
page.goto(example_path)
160+
161+
# STEP 1: Check page title proper initial loading of the example page
162+
163+
expected_title = TEST_PARAMS[example]["title"]
164+
if isinstance(expected_title, list):
165+
# One example's title changes so expected_title is a list of possible
166+
# titles in that case
167+
assert page.title() in expected_title # nosec
168+
else:
169+
assert page.title() == expected_title # nosec
170+
171+
# STEP 2: Test that pyodide is loading via messages displayed during loading
172+
173+
pyodide_loading = False # Flag to be set to True when condition met
174+
175+
for _ in range(TEST_ITERATIONS):
176+
time.sleep(TEST_TIME_INCREMENT)
177+
content = page.text_content("*")
178+
for message in LOADING_MESSAGES:
179+
if message in content:
180+
pyodide_loading = True
181+
if pyodide_loading:
182+
break
183+
184+
assert pyodide_loading # nosec
185+
186+
# STEP 3:
187+
# Assert that rendering inserts data into the page as expected: search the
188+
# DOM from within the timing loop for a string that is not present in the
189+
# initial markup but should appear by way of rendering
190+
191+
re_sub_content = re.compile(TEST_PARAMS[example]["pattern"])
192+
py_rendered = False # Flag to be set to True when condition met
193+
194+
for _ in range(TEST_ITERATIONS):
195+
time.sleep(TEST_TIME_INCREMENT)
196+
content = page.inner_html("*")
197+
if re_sub_content.search(content):
198+
py_rendered = True
199+
break
200+
201+
assert py_rendered # nosec
202+
203+
browser.close()

0 commit comments

Comments
 (0)
0