Files
pyscript/pyscriptjs/tests/integration/test_02_output.py
Antonio Cuni f9194cc833 Refactor how py-script are executed, kill scriptQueue store, introduce pyExec (#881)
Yet another refactoring to untangle the old mess.
Highlights:

base.ts, pyscript.ts and pyrepl.ts were a tangled mess of code, in which each of them interacted with the others in non-obvious ways. Now PyScript is no longer a subclass of BaseEvalElement and it is much simpler. I removed code for handling the attributes std-out and std-err because they are no longer needed with the new display() logic.

The logic for executing python code is now in pyexec.ts: so we are decoupling the process of "finding" the python code (handled by the py-script web component) and the logic to actually execute it. This has many advantages, including the fact that it will be more easily usable by other components (e.g. pyrepl). Also, note that it's called pyexec and not pyeval: in the vast majority of cases in Python you have statements to execute, and almost never expressions to evaluate.

I killed the last remaining global store, scriptQueue tada. As a bonus effect, now we automatically do the correct thing when a <py-script> tag is dynamically added to the DOM (I added a test for it). I did not remove svelte from packages.json, because I don't fully understand the implications: there are various options which mention svelte in rollup.js and tsconfig.json, so it's probably better to kill it in its own PR.

pyexec.ts is also responsible of handling the default target for display() and correct handling/visualization of exceptions. I fixed/improved/added display/output tests in the process.
I also found a problem though, see issue #878, so I improved the test and marked it as xfail.

I removed BaseEvalElement as the superclass of most components. Now the only class which inherits from it is PyRepl. In a follow-up PR, I plan to merge them into a single class and do more cleanup.

During the refactoring, I killed guidGenerator: now instead of generating random py-* IDs which are very hard to read for humans, we generated py-internal-X IDs, where X is 0, 1, 2, 3, etc. This makes writing tests and debugging much easier.

I improved a lot our test machinery: it turns out that PR #829 broke the ability to use/view sourcemaps inside the playwright browser (at least on my machine).
For some reason chromium is unable to find sourcemaps if you use playwrights internal routing. So I reintroduced the http_server fixture which was removed by that PR, and added a pytest option --no-fake-server to use it instead, useful for debugging. By default we are still using the fakeserver though (which is faster and parallelizable).

Similarly, I added --dev which implies --headed and also automatically open chrome dev tools.
2022-10-23 23:31:50 +02:00

276 lines
8.4 KiB
Python

import re
import pytest
from .support import PyScriptTest
class TestOutput(PyScriptTest):
def test_simple_display(self):
self.pyscript_run(
"""
<py-script>
display('hello world')
</py-script>
"""
)
inner_html = self.page.content()
pattern = r'<div id="py-.*">hello world</div>'
assert re.search(pattern, inner_html)
@pytest.mark.xfail(reason="issue #878")
def test_consecutive_display(self):
self.pyscript_run(
"""
<py-script>
display('hello 1')
</py-script>
<p>hello 2</p>
<py-script>
display('hello 3')
</py-script>
"""
)
inner_text = self.page.inner_text("body")
lines = inner_text.splitlines()
lines = [line for line in lines if line != ""] # remove empty lines
assert lines == ["hello 1", "hello 2", "hello 3"]
@pytest.mark.xfail(reason="fix me")
def test_output_attribute(self):
self.pyscript_run(
"""
<py-script output="mydiv">
display('hello world')
</py-script>
<div id="mydiv"></div>
"""
)
mydiv = self.page.locator("#mydiv")
assert mydiv.inner_text() == "hello world"
def test_multiple_display_calls_same_tag(self):
self.pyscript_run(
"""
<py-script>
display('hello')
display('world')
</py-script>
"""
)
tag = self.page.locator("py-script")
lines = tag.inner_text().splitlines()
assert lines == ["hello", "world"]
def test_implicit_target_from_a_different_tag(self):
self.pyscript_run(
"""
<py-script id="py1">
def say_hello():
display('hello')
</py-script>
<py-script id="py2">
say_hello()
</py-script>
"""
)
py1 = self.page.locator("#py1")
py2 = self.page.locator("#py2")
assert py1.inner_text() == ""
assert py2.inner_text() == "hello"
def test_no_implicit_target(self):
self.pyscript_run(
"""
<py-script>
def display_hello():
# this fails because we don't have any implicit target
# from event handlers
display('hello')
</py-script>
<button id="my-button" py-onClick="display_hello()">Click me</button>
"""
)
self.page.locator("text=Click me").click()
text = self.page.text_content("body")
assert "hello" not in text
# currently the test infrastructure doesn't allow to easily assert that
# js exceptions were raised this is a workaround but we need a better fix.
# Antonio promised to write it
assert len(self._page_errors) == 1
console_text = self._page_errors
assert (
"Implicit target not allowed here. Please use display(..., target=...)"
in console_text[0].message
)
self._page_errors = []
def test_explicit_target_pyscript_tag(self):
self.pyscript_run(
"""
<py-script>
def display_hello():
display('hello', target='second-pyscript-tag')
</py-script>
<py-script id="second-pyscript-tag">
display_hello()
</py-script>
"""
)
text = self.page.locator("id=second-pyscript-tag").inner_text()
assert text == "hello"
def test_explicit_target_on_button_tag(self):
self.pyscript_run(
"""
<py-script>
def display_hello():
display('hello', target='my-button')
</py-script>
<button id="my-button" py-onClick="display_hello()">Click me</button>
"""
)
self.page.locator("text=Click me").click()
text = self.page.locator("id=my-button").inner_text()
assert "hello" in text
def test_explicit_different_target_from_call(self):
self.pyscript_run(
"""
<py-script id="first-pyscript-tag">
def display_hello():
display('hello', target='second-pyscript-tag')
</py-script>
<py-script id="second-pyscript-tag">
print('nothing to see here')
</py-script>
<py-script>
display_hello()
</py-script>
"""
)
text = self.page.locator("id=second-pyscript-tag").all_inner_texts()
assert "hello" in text
def test_append_true(self):
self.pyscript_run(
"""
<py-script>
display('hello world', append=True)
</py-script>
"""
)
inner_html = self.page.content()
pattern = r'<div id="py-.*">hello world</div>'
assert re.search(pattern, inner_html)
def test_append_false(self):
self.pyscript_run(
"""
<py-script>
display('hello world', append=False)
</py-script>
"""
)
inner_html = self.page.content()
pattern = r'<py-script id="py-.*">hello world</py-script>'
assert re.search(pattern, inner_html)
def test_display_multiple_values(self):
self.pyscript_run(
"""
<py-script>
hello = 'hello'
world = 'world'
display(hello, world)
</py-script>
"""
)
inner_text = self.page.inner_text("html")
assert inner_text == "hello\nworld"
def test_display_list_dict_tuple(self):
self.pyscript_run(
"""
<py-script>
l = ['A', 1, '!']
d = {'B': 2, 'List': l}
t = ('C', 3, '!')
display(l, d, t)
</py-script>
"""
)
inner_text = self.page.inner_text("html")
print(inner_text)
assert (
inner_text
== "['A', 1, '!']\n{'B': 2, 'List': ['A', 1, '!']}\n('C', 3, '!')"
)
def test_image_display(self):
self.pyscript_run(
"""
<py-config> packages = [ "matplotlib"] </py-config>
<py-script>
import matplotlib.pyplot as plt
xpoints = [3, 6, 9]
ypoints = [1, 2, 3]
plt.plot(xpoints, ypoints)
plt.show()
</py-script>
"""
)
inner_html = self.page.content()
pattern = r'<style id="matplotlib-figure-styles">'
assert re.search(pattern, inner_html)
def test_empty_HTML_and_console_output(self):
self.pyscript_run(
"""
<py-script>
print('print from python')
console.log('print from js')
console.error('error from js');
</py-script>
"""
)
inner_html = self.page.content()
assert re.search("", inner_html)
console_text = self.console.all.lines
assert "print from python" in console_text
assert "print from js" in console_text
assert "error from js" in console_text
def test_text_HTML_and_console_output(self):
self.pyscript_run(
"""
<py-script>
display('0')
print('print from python')
console.log('print from js')
console.error('error from js');
</py-script>
"""
)
inner_text = self.page.inner_text("html")
assert "0" == inner_text
console_text = self.console.all.lines
assert "print from python" in console_text
assert "print from js" in console_text
assert "error from js" in console_text
def test_console_line_break(self):
self.pyscript_run(
"""
<py-script>
print('1print\\n2print')
print('1console\\n2console')
</py-script>
"""
)
console_text = self.console.all.lines
assert console_text.index("1print") == (console_text.index("2print") - 1)
assert console_text.index("1console") == (console_text.index("2console") - 1)