@pyscript/core 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/core.js +3 -3
- package/dist/core.js.map +1 -1
- package/docs/README.md +3 -12
- package/package.json +3 -3
- package/src/config.js +32 -24
- package/src/core.js +1 -1
- package/src/stdlib/pyscript/__init__.py +34 -0
- package/src/stdlib/pyscript/display.py +154 -0
- package/src/stdlib/pyscript/event_handling.py +45 -0
- package/src/stdlib/pyscript/magic_js.py +32 -0
- package/src/stdlib/pyscript/util.py +22 -0
- package/src/stdlib/pyscript.js +9 -5
- package/src/stdlib/pyweb/pydom.py +314 -0
- package/tests/integration/__init__.py +0 -0
- package/tests/integration/conftest.py +184 -0
- package/tests/integration/support.py +1038 -0
- package/tests/integration/test_00_support.py +495 -0
- package/tests/integration/test_01_basic.py +353 -0
- package/tests/integration/test_02_display.py +452 -0
- package/tests/integration/test_03_element.py +303 -0
- package/tests/integration/test_assets/line_plot.png +0 -0
- package/tests/integration/test_assets/tripcolor.png +0 -0
- package/tests/integration/test_async.py +197 -0
- package/tests/integration/test_event_handling.py +193 -0
- package/tests/integration/test_importmap.py +66 -0
- package/tests/integration/test_interpreter.py +98 -0
- package/tests/integration/test_plugins.py +419 -0
- package/tests/integration/test_py_config.py +294 -0
- package/tests/integration/test_py_repl.py +663 -0
- package/tests/integration/test_py_terminal.py +270 -0
- package/tests/integration/test_runtime_attributes.py +64 -0
- package/tests/integration/test_script_type.py +121 -0
- package/tests/integration/test_shadow_root.py +33 -0
- package/tests/integration/test_splashscreen.py +124 -0
- package/tests/integration/test_stdio_handling.py +370 -0
- package/tests/integration/test_style.py +47 -0
- package/tests/integration/test_warnings_and_banners.py +32 -0
- package/tests/integration/test_zz_examples.py +419 -0
- package/tests/integration/test_zzz_docs_snippets.py +305 -0
- package/types/stdlib/pyscript.d.ts +8 -4
@@ -0,0 +1,663 @@
|
|
1
|
+
import platform
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from .support import PyScriptTest, skip_worker
|
6
|
+
|
7
|
+
pytest.skip(
|
8
|
+
reason="FIX LATER: pyscript NEXT doesn't support the REPL yet",
|
9
|
+
allow_module_level=True,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
class TestPyRepl(PyScriptTest):
|
14
|
+
def _replace(self, py_repl, newcode):
|
15
|
+
"""
|
16
|
+
Clear the editor and write new code in it.
|
17
|
+
WARNING: this assumes that the textbox has already the focus
|
18
|
+
"""
|
19
|
+
# clear the editor, write new code
|
20
|
+
if "macOS" in platform.platform():
|
21
|
+
self.page.keyboard.press("Meta+A")
|
22
|
+
else:
|
23
|
+
self.page.keyboard.press("Control+A")
|
24
|
+
|
25
|
+
self.page.keyboard.press("Backspace")
|
26
|
+
self.page.keyboard.type(newcode)
|
27
|
+
|
28
|
+
def test_repl_loads(self):
|
29
|
+
self.pyscript_run(
|
30
|
+
"""
|
31
|
+
<py-repl></py-repl>
|
32
|
+
"""
|
33
|
+
)
|
34
|
+
py_repl = self.page.query_selector("py-repl .py-repl-box")
|
35
|
+
assert py_repl
|
36
|
+
|
37
|
+
def test_execute_preloaded_source(self):
|
38
|
+
"""
|
39
|
+
Unfortunately it tests two things at once, but it's impossible to write a
|
40
|
+
smaller test. I think this is the most basic test that we can write.
|
41
|
+
|
42
|
+
We test that:
|
43
|
+
1. the source code that we put in the tag is loaded inside the editor
|
44
|
+
2. clicking the button executes it
|
45
|
+
"""
|
46
|
+
self.pyscript_run(
|
47
|
+
"""
|
48
|
+
<py-repl>
|
49
|
+
print('hello from py-repl')
|
50
|
+
</py-repl>
|
51
|
+
"""
|
52
|
+
)
|
53
|
+
py_repl = self.page.locator("py-repl")
|
54
|
+
src = py_repl.locator("div.cm-content").inner_text()
|
55
|
+
assert "print('hello from py-repl')" in src
|
56
|
+
py_repl.locator("button").click()
|
57
|
+
self.page.wait_for_selector("py-terminal")
|
58
|
+
assert self.console.log.lines[-1] == "hello from py-repl"
|
59
|
+
|
60
|
+
def test_execute_code_typed_by_the_user(self):
|
61
|
+
self.pyscript_run(
|
62
|
+
"""
|
63
|
+
<py-repl></py-repl>
|
64
|
+
"""
|
65
|
+
)
|
66
|
+
py_repl = self.page.locator("py-repl")
|
67
|
+
py_repl.type('print("hello")')
|
68
|
+
py_repl.locator("button").click()
|
69
|
+
self.page.wait_for_selector("py-terminal")
|
70
|
+
assert self.console.log.lines[-1] == "hello"
|
71
|
+
|
72
|
+
def test_execute_on_shift_enter(self):
|
73
|
+
self.pyscript_run(
|
74
|
+
"""
|
75
|
+
<py-repl>
|
76
|
+
print("hello world")
|
77
|
+
</py-repl>
|
78
|
+
"""
|
79
|
+
)
|
80
|
+
self.page.wait_for_selector("py-repl .py-repl-run-button")
|
81
|
+
self.page.keyboard.press("Shift+Enter")
|
82
|
+
self.page.wait_for_selector("py-terminal")
|
83
|
+
|
84
|
+
assert self.console.log.lines[-1] == "hello world"
|
85
|
+
|
86
|
+
# Shift-enter should not add a newline to the editor
|
87
|
+
assert self.page.locator(".cm-line").count() == 1
|
88
|
+
|
89
|
+
@skip_worker("FIXME: display()")
|
90
|
+
def test_display(self):
|
91
|
+
self.pyscript_run(
|
92
|
+
"""
|
93
|
+
<py-repl>
|
94
|
+
display('hello world')
|
95
|
+
</py-repl>
|
96
|
+
"""
|
97
|
+
)
|
98
|
+
py_repl = self.page.locator("py-repl")
|
99
|
+
py_repl.locator("button").click()
|
100
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
101
|
+
assert out_div.inner_text() == "hello world"
|
102
|
+
|
103
|
+
@skip_worker("TIMEOUT")
|
104
|
+
def test_show_last_expression(self):
|
105
|
+
"""
|
106
|
+
Test that we display() the value of the last expression, as you would
|
107
|
+
expect by a REPL
|
108
|
+
"""
|
109
|
+
self.pyscript_run(
|
110
|
+
"""
|
111
|
+
<py-repl>
|
112
|
+
42
|
113
|
+
</py-repl>
|
114
|
+
"""
|
115
|
+
)
|
116
|
+
py_repl = self.page.locator("py-repl")
|
117
|
+
py_repl.locator("button").click()
|
118
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
119
|
+
assert out_div.inner_text() == "42"
|
120
|
+
|
121
|
+
@skip_worker("TIMEOUT")
|
122
|
+
def test_show_last_expression_with_output(self):
|
123
|
+
"""
|
124
|
+
Test that we display() the value of the last expression, as you would
|
125
|
+
expect by a REPL
|
126
|
+
"""
|
127
|
+
self.pyscript_run(
|
128
|
+
"""
|
129
|
+
<div id="repl-target"></div>
|
130
|
+
<py-repl output="repl-target">
|
131
|
+
42
|
132
|
+
</py-repl>
|
133
|
+
"""
|
134
|
+
)
|
135
|
+
py_repl = self.page.locator("py-repl")
|
136
|
+
py_repl.locator("button").click()
|
137
|
+
out_div = py_repl.locator("div.py-repl-output")
|
138
|
+
assert out_div.all_inner_texts()[0] == ""
|
139
|
+
|
140
|
+
out_div = self.page.wait_for_selector("#repl-target")
|
141
|
+
assert out_div.inner_text() == "42"
|
142
|
+
|
143
|
+
@skip_worker("FIXME: display()")
|
144
|
+
def test_run_clears_previous_output(self):
|
145
|
+
"""
|
146
|
+
Check that we clear the previous output of the cell before executing it
|
147
|
+
again
|
148
|
+
"""
|
149
|
+
self.pyscript_run(
|
150
|
+
"""
|
151
|
+
<py-repl>
|
152
|
+
display('hello world')
|
153
|
+
</py-repl>
|
154
|
+
"""
|
155
|
+
)
|
156
|
+
py_repl = self.page.locator("py-repl")
|
157
|
+
self.page.keyboard.press("Shift+Enter")
|
158
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
159
|
+
assert out_div.inner_text() == "hello world"
|
160
|
+
# clear the editor, write new code, execute
|
161
|
+
self._replace(py_repl, "display('another output')")
|
162
|
+
self.page.keyboard.press("Shift+Enter")
|
163
|
+
# test runner can be too fast, the line below should wait for output to change
|
164
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
165
|
+
assert out_div.inner_text() == "another output"
|
166
|
+
|
167
|
+
def test_python_exception(self):
|
168
|
+
"""
|
169
|
+
See also test01_basic::test_python_exception, since it's very similar
|
170
|
+
"""
|
171
|
+
self.pyscript_run(
|
172
|
+
"""
|
173
|
+
<py-repl>
|
174
|
+
raise Exception('this is an error')
|
175
|
+
</py-repl>
|
176
|
+
"""
|
177
|
+
)
|
178
|
+
py_repl = self.page.locator("py-repl")
|
179
|
+
py_repl.locator("button").click()
|
180
|
+
self.page.wait_for_selector(".py-error")
|
181
|
+
#
|
182
|
+
# check that we sent the traceback to the console
|
183
|
+
tb_lines = self.console.error.lines[-1].splitlines()
|
184
|
+
assert tb_lines[0] == "[pyexec] Python exception:"
|
185
|
+
assert tb_lines[1] == "Traceback (most recent call last):"
|
186
|
+
assert tb_lines[-1] == "Exception: this is an error"
|
187
|
+
#
|
188
|
+
# check that we show the traceback in the page
|
189
|
+
err_pre = py_repl.locator("div.py-repl-output > pre.py-error")
|
190
|
+
tb_lines = err_pre.inner_text().splitlines()
|
191
|
+
assert tb_lines[0] == "Traceback (most recent call last):"
|
192
|
+
assert tb_lines[-1] == "Exception: this is an error"
|
193
|
+
#
|
194
|
+
self.check_py_errors("this is an error")
|
195
|
+
|
196
|
+
@skip_worker("FIXME: display()")
|
197
|
+
def test_multiple_repls(self):
|
198
|
+
"""
|
199
|
+
Multiple repls showing in the correct order in the page
|
200
|
+
"""
|
201
|
+
self.pyscript_run(
|
202
|
+
"""
|
203
|
+
<py-repl data-testid=="first"> display("first") </py-repl>
|
204
|
+
<py-repl data-testid=="second"> display("second") </py-repl>
|
205
|
+
"""
|
206
|
+
)
|
207
|
+
first_py_repl = self.page.get_by_text("first")
|
208
|
+
first_py_repl.click()
|
209
|
+
self.page.keyboard.press("Shift+Enter")
|
210
|
+
self.page.wait_for_selector("#py-internal-0-repl-output")
|
211
|
+
assert self.page.inner_text("#py-internal-0-repl-output") == "first"
|
212
|
+
|
213
|
+
second_py_repl = self.page.get_by_text("second")
|
214
|
+
second_py_repl.click()
|
215
|
+
self.page.keyboard.press("Shift+Enter")
|
216
|
+
self.page.wait_for_selector("#py-internal-1-repl-output")
|
217
|
+
assert self.page.inner_text("#py-internal-1-repl-output") == "second"
|
218
|
+
|
219
|
+
@skip_worker("FIXME: display()")
|
220
|
+
def test_python_exception_after_previous_output(self):
|
221
|
+
self.pyscript_run(
|
222
|
+
"""
|
223
|
+
<py-repl>
|
224
|
+
display('hello world')
|
225
|
+
</py-repl>
|
226
|
+
"""
|
227
|
+
)
|
228
|
+
py_repl = self.page.locator("py-repl")
|
229
|
+
self.page.keyboard.press("Shift+Enter")
|
230
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
231
|
+
assert out_div.inner_text() == "hello world"
|
232
|
+
#
|
233
|
+
# clear the editor, write new code, execute
|
234
|
+
self._replace(py_repl, "0/0")
|
235
|
+
self.page.keyboard.press("Shift+Enter")
|
236
|
+
# test runner can be too fast, the line below should wait for output to change
|
237
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
238
|
+
assert "hello world" not in out_div.inner_text()
|
239
|
+
assert "ZeroDivisionError" in out_div.inner_text()
|
240
|
+
#
|
241
|
+
self.check_py_errors("ZeroDivisionError")
|
242
|
+
|
243
|
+
@skip_worker("FIXME: js.document")
|
244
|
+
def test_hide_previous_error_after_successful_run(self):
|
245
|
+
"""
|
246
|
+
this tests the fact that a new error div should be created once there's an
|
247
|
+
error but also that it should disappear automatically once the error
|
248
|
+
is fixed
|
249
|
+
"""
|
250
|
+
self.pyscript_run(
|
251
|
+
"""
|
252
|
+
<py-repl>
|
253
|
+
raise Exception('this is an error')
|
254
|
+
</py-repl>
|
255
|
+
"""
|
256
|
+
)
|
257
|
+
py_repl = self.page.locator("py-repl")
|
258
|
+
self.page.keyboard.press("Shift+Enter")
|
259
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
260
|
+
assert "this is an error" in out_div.inner_text()
|
261
|
+
#
|
262
|
+
self._replace(py_repl, "display('hello')")
|
263
|
+
self.page.keyboard.press("Shift+Enter")
|
264
|
+
# test runner can be too fast, the line below should wait for output to change
|
265
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
266
|
+
assert out_div.inner_text() == "hello"
|
267
|
+
#
|
268
|
+
self.check_py_errors("this is an error")
|
269
|
+
|
270
|
+
def test_output_attribute_does_not_exist(self):
|
271
|
+
"""
|
272
|
+
If we try to use an attribute which doesn't exist, we display an error
|
273
|
+
instead
|
274
|
+
"""
|
275
|
+
self.pyscript_run(
|
276
|
+
"""
|
277
|
+
<py-repl output="I-dont-exist">
|
278
|
+
print('I will not be executed')
|
279
|
+
</py-repl>
|
280
|
+
"""
|
281
|
+
)
|
282
|
+
py_repl = self.page.locator("py-repl")
|
283
|
+
py_repl.locator("button").click()
|
284
|
+
|
285
|
+
banner = self.page.wait_for_selector(".py-warning")
|
286
|
+
|
287
|
+
banner_content = banner.inner_text()
|
288
|
+
expected = (
|
289
|
+
'output = "I-dont-exist" does not match the id of any element on the page.'
|
290
|
+
)
|
291
|
+
assert banner_content == expected
|
292
|
+
|
293
|
+
@skip_worker("TIMEOUT")
|
294
|
+
def test_auto_generate(self):
|
295
|
+
self.pyscript_run(
|
296
|
+
"""
|
297
|
+
<py-repl auto-generate="true">
|
298
|
+
</py-repl>
|
299
|
+
"""
|
300
|
+
)
|
301
|
+
py_repls = self.page.locator("py-repl")
|
302
|
+
outputs = py_repls.locator("div.py-repl-output")
|
303
|
+
assert py_repls.count() == 1
|
304
|
+
assert outputs.count() == 1
|
305
|
+
#
|
306
|
+
# evaluate the py-repl, and wait for the newly generated one
|
307
|
+
self.page.keyboard.type("'hello'")
|
308
|
+
self.page.keyboard.press("Shift+Enter")
|
309
|
+
self.page.locator('py-repl[exec-id="1"]').wait_for()
|
310
|
+
assert py_repls.count() == 2
|
311
|
+
assert outputs.count() == 2
|
312
|
+
#
|
313
|
+
# now we type something else: the new py-repl should have the focus
|
314
|
+
self.page.keyboard.type("'world'")
|
315
|
+
self.page.keyboard.press("Shift+Enter")
|
316
|
+
self.page.locator('py-repl[exec-id="2"]').wait_for()
|
317
|
+
assert py_repls.count() == 3
|
318
|
+
assert outputs.count() == 3
|
319
|
+
#
|
320
|
+
# check that the code and the outputs are in order
|
321
|
+
out_texts = [el.inner_text() for el in self.iter_locator(outputs)]
|
322
|
+
assert out_texts == ["hello", "world", ""]
|
323
|
+
|
324
|
+
@skip_worker("FIXME: display()")
|
325
|
+
def test_multiple_repls_mixed_display_order(self):
|
326
|
+
"""
|
327
|
+
Displaying several outputs that don't obey the order in which the original
|
328
|
+
repl displays were created using the auto_generate attr
|
329
|
+
"""
|
330
|
+
self.pyscript_run(
|
331
|
+
"""
|
332
|
+
<py-repl auto-generate="true" data-testid=="first"> display("root first") </py-repl>
|
333
|
+
<py-repl auto-generate="true" data-testid=="second"> display("root second") </py-repl>
|
334
|
+
"""
|
335
|
+
)
|
336
|
+
|
337
|
+
second_py_repl = self.page.get_by_text("root second")
|
338
|
+
second_py_repl.click()
|
339
|
+
self.page.keyboard.press("Shift+Enter")
|
340
|
+
self.page.wait_for_selector("#py-internal-1-repl-output")
|
341
|
+
self.page.keyboard.type("display('second children')")
|
342
|
+
self.page.keyboard.press("Shift+Enter")
|
343
|
+
self.page.wait_for_selector("#py-internal-1-1-repl-output")
|
344
|
+
|
345
|
+
first_py_repl = self.page.get_by_text("root first")
|
346
|
+
first_py_repl.click()
|
347
|
+
self.page.keyboard.press("Shift+Enter")
|
348
|
+
self.page.wait_for_selector("#py-internal-0-repl-output")
|
349
|
+
self.page.keyboard.type("display('first children')")
|
350
|
+
self.page.keyboard.press("Shift+Enter")
|
351
|
+
self.page.wait_for_selector("#py-internal-0-1-repl-output")
|
352
|
+
|
353
|
+
assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children"
|
354
|
+
assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children"
|
355
|
+
|
356
|
+
@skip_worker("FIXME: display()")
|
357
|
+
def test_repl_output_attribute(self):
|
358
|
+
# Test that output attribute sends stdout to the element
|
359
|
+
# with the given ID, but not display()
|
360
|
+
self.pyscript_run(
|
361
|
+
"""
|
362
|
+
<div id="repl-target"></div>
|
363
|
+
<py-repl output="repl-target">
|
364
|
+
print('print from py-repl')
|
365
|
+
display('display from py-repl')
|
366
|
+
</py-repl>
|
367
|
+
|
368
|
+
"""
|
369
|
+
)
|
370
|
+
|
371
|
+
py_repl = self.page.locator("py-repl")
|
372
|
+
py_repl.locator("button").click()
|
373
|
+
|
374
|
+
target = self.page.wait_for_selector("#repl-target")
|
375
|
+
assert "print from py-repl" in target.inner_text()
|
376
|
+
|
377
|
+
out_div = self.page.wait_for_selector("#py-internal-0-repl-output")
|
378
|
+
assert out_div.inner_text() == "display from py-repl"
|
379
|
+
|
380
|
+
self.assert_no_banners()
|
381
|
+
|
382
|
+
@skip_worker("FIXME: js.document")
|
383
|
+
def test_repl_output_display_async(self):
|
384
|
+
# py-repls running async code are not expected to
|
385
|
+
# send display to element element
|
386
|
+
self.pyscript_run(
|
387
|
+
"""
|
388
|
+
<div id="repl-target"></div>
|
389
|
+
<py-script>
|
390
|
+
import asyncio
|
391
|
+
import js
|
392
|
+
|
393
|
+
async def print_it():
|
394
|
+
await asyncio.sleep(1)
|
395
|
+
print('print from py-repl')
|
396
|
+
|
397
|
+
|
398
|
+
async def display_it():
|
399
|
+
display('display from py-repl')
|
400
|
+
await asyncio.sleep(2)
|
401
|
+
|
402
|
+
async def done():
|
403
|
+
await asyncio.sleep(3)
|
404
|
+
js.console.log("DONE")
|
405
|
+
</py-script>
|
406
|
+
|
407
|
+
<py-repl output="repl-target">
|
408
|
+
asyncio.ensure_future(print_it());
|
409
|
+
asyncio.ensure_future(display_it());
|
410
|
+
asyncio.ensure_future(done());
|
411
|
+
</py-repl>
|
412
|
+
"""
|
413
|
+
)
|
414
|
+
|
415
|
+
py_repl = self.page.locator("py-repl")
|
416
|
+
py_repl.locator("button").click()
|
417
|
+
|
418
|
+
self.wait_for_console("DONE")
|
419
|
+
|
420
|
+
assert self.page.locator("#repl-target").text_content() == ""
|
421
|
+
self.assert_no_banners()
|
422
|
+
|
423
|
+
@skip_worker("FIXME: js.document")
|
424
|
+
def test_repl_stdio_dynamic_tags(self):
|
425
|
+
self.pyscript_run(
|
426
|
+
"""
|
427
|
+
<div id="first"></div>
|
428
|
+
<div id="second"></div>
|
429
|
+
<py-repl output="first">
|
430
|
+
import js
|
431
|
+
|
432
|
+
print("first.")
|
433
|
+
|
434
|
+
# Using string, since no clean way to write to the
|
435
|
+
# code contents of the CodeMirror in a PyRepl
|
436
|
+
newTag = '<py-repl id="second-repl" output="second">print("second.")</py-repl>'
|
437
|
+
js.document.body.innerHTML += newTag
|
438
|
+
</py-repl>
|
439
|
+
"""
|
440
|
+
)
|
441
|
+
|
442
|
+
py_repl = self.page.locator("py-repl")
|
443
|
+
py_repl.locator("button").click()
|
444
|
+
|
445
|
+
assert self.page.wait_for_selector("#first").inner_text() == "first.\n"
|
446
|
+
|
447
|
+
second_repl = self.page.locator("py-repl#second-repl")
|
448
|
+
second_repl.locator("button").click()
|
449
|
+
assert self.page.wait_for_selector("#second").inner_text() == "second.\n"
|
450
|
+
|
451
|
+
def test_repl_output_id_errors(self):
|
452
|
+
self.pyscript_run(
|
453
|
+
"""
|
454
|
+
<py-repl output="not-on-page">
|
455
|
+
print("bad.")
|
456
|
+
print("bad.")
|
457
|
+
</py-repl>
|
458
|
+
|
459
|
+
<py-repl output="not-on-page">
|
460
|
+
print("bad.")
|
461
|
+
</py-repl>
|
462
|
+
"""
|
463
|
+
)
|
464
|
+
py_repls = self.page.query_selector_all("py-repl")
|
465
|
+
for repl in py_repls:
|
466
|
+
repl.query_selector_all("button")[0].click()
|
467
|
+
|
468
|
+
banner = self.page.wait_for_selector(".py-warning")
|
469
|
+
|
470
|
+
banner_content = banner.inner_text()
|
471
|
+
expected = (
|
472
|
+
'output = "not-on-page" does not match the id of any element on the page.'
|
473
|
+
)
|
474
|
+
|
475
|
+
assert banner_content == expected
|
476
|
+
|
477
|
+
def test_repl_stderr_id_errors(self):
|
478
|
+
self.pyscript_run(
|
479
|
+
"""
|
480
|
+
<py-repl stderr="not-on-page">
|
481
|
+
import sys
|
482
|
+
print("bad.", file=sys.stderr)
|
483
|
+
print("bad.", file=sys.stderr)
|
484
|
+
</py-repl>
|
485
|
+
|
486
|
+
<py-repl stderr="not-on-page">
|
487
|
+
print("bad.", file=sys.stderr)
|
488
|
+
</py-repl>
|
489
|
+
"""
|
490
|
+
)
|
491
|
+
py_repls = self.page.query_selector_all("py-repl")
|
492
|
+
for repl in py_repls:
|
493
|
+
repl.query_selector_all("button")[0].click()
|
494
|
+
|
495
|
+
banner = self.page.wait_for_selector(".py-warning")
|
496
|
+
|
497
|
+
banner_content = banner.inner_text()
|
498
|
+
expected = (
|
499
|
+
'stderr = "not-on-page" does not match the id of any element on the page.'
|
500
|
+
)
|
501
|
+
|
502
|
+
assert banner_content == expected
|
503
|
+
|
504
|
+
def test_repl_output_stderr(self):
|
505
|
+
# Test that stderr works, and routes to the same location as stdout
|
506
|
+
# Also, repls with the stderr attribute route to an additional location
|
507
|
+
self.pyscript_run(
|
508
|
+
"""
|
509
|
+
<div id="stdout-div"></div>
|
510
|
+
<div id="stderr-div"></div>
|
511
|
+
<py-repl output="stdout-div" stderr="stderr-div">
|
512
|
+
import sys
|
513
|
+
print("one.", file=sys.stderr)
|
514
|
+
print("two.")
|
515
|
+
</py-repl>
|
516
|
+
"""
|
517
|
+
)
|
518
|
+
|
519
|
+
py_repl = self.page.locator("py-repl")
|
520
|
+
py_repl.locator("button").click()
|
521
|
+
|
522
|
+
assert self.page.wait_for_selector("#stdout-div").inner_text() == "one.\ntwo.\n"
|
523
|
+
assert self.page.wait_for_selector("#stderr-div").inner_text() == "one.\n"
|
524
|
+
self.assert_no_banners()
|
525
|
+
|
526
|
+
@skip_worker("TIMEOUT")
|
527
|
+
def test_repl_output_attribute_change(self):
|
528
|
+
# If the user changes the 'output' attribute of a <py-repl> tag mid-execution,
|
529
|
+
# Output should no longer go to the selected div and a warning should appear
|
530
|
+
self.pyscript_run(
|
531
|
+
"""
|
532
|
+
<div id="first"></div>
|
533
|
+
<div id="second"></div>
|
534
|
+
<!-- There is no tag with id "third" -->
|
535
|
+
<py-repl id="repl-tag" output="first">
|
536
|
+
print("one.")
|
537
|
+
|
538
|
+
# Change the 'output' attribute of this tag
|
539
|
+
import js
|
540
|
+
this_tag = js.document.getElementById("repl-tag")
|
541
|
+
|
542
|
+
this_tag.setAttribute("output", "second")
|
543
|
+
print("two.")
|
544
|
+
|
545
|
+
this_tag.setAttribute("output", "third")
|
546
|
+
print("three.")
|
547
|
+
</py-script>
|
548
|
+
"""
|
549
|
+
)
|
550
|
+
|
551
|
+
py_repl = self.page.locator("py-repl")
|
552
|
+
py_repl.locator("button").click()
|
553
|
+
|
554
|
+
assert self.page.wait_for_selector("#first").inner_text() == "one.\n"
|
555
|
+
assert self.page.wait_for_selector("#second").inner_text() == "two.\n"
|
556
|
+
|
557
|
+
expected_alert_banner_msg = (
|
558
|
+
'output = "third" does not match the id of any element on the page.'
|
559
|
+
)
|
560
|
+
|
561
|
+
alert_banner = self.page.wait_for_selector(".alert-banner")
|
562
|
+
assert expected_alert_banner_msg in alert_banner.inner_text()
|
563
|
+
|
564
|
+
@skip_worker("TIMEOUT")
|
565
|
+
def test_repl_output_element_id_change(self):
|
566
|
+
# If the user changes the ID of the targeted DOM element mid-execution,
|
567
|
+
# Output should no longer go to the selected element and a warning should appear
|
568
|
+
self.pyscript_run(
|
569
|
+
"""
|
570
|
+
<div id="first"></div>
|
571
|
+
<div id="second"></div>
|
572
|
+
<!-- There is no tag with id "third" -->
|
573
|
+
<py-repl id="pyscript-tag" output="first">
|
574
|
+
print("one.")
|
575
|
+
|
576
|
+
# Change the ID of the targeted DIV to something else
|
577
|
+
import js
|
578
|
+
target_tag = js.document.getElementById("first")
|
579
|
+
|
580
|
+
# should fail and show banner
|
581
|
+
target_tag.setAttribute("id", "second")
|
582
|
+
print("two.")
|
583
|
+
|
584
|
+
# But changing both the 'output' attribute and the id of the target
|
585
|
+
# should work
|
586
|
+
target_tag.setAttribute("id", "third")
|
587
|
+
js.document.getElementById("pyscript-tag").setAttribute("output", "third")
|
588
|
+
print("three.")
|
589
|
+
</py-repl>
|
590
|
+
"""
|
591
|
+
)
|
592
|
+
|
593
|
+
py_repl = self.page.locator("py-repl")
|
594
|
+
py_repl.locator("button").click()
|
595
|
+
|
596
|
+
# Note the ID of the div has changed by the time of this assert
|
597
|
+
assert self.page.wait_for_selector("#third").inner_text() == "one.\nthree.\n"
|
598
|
+
|
599
|
+
expected_alert_banner_msg = (
|
600
|
+
'output = "first" does not match the id of any element on the page.'
|
601
|
+
)
|
602
|
+
alert_banner = self.page.wait_for_selector(".alert-banner")
|
603
|
+
assert expected_alert_banner_msg in alert_banner.inner_text()
|
604
|
+
|
605
|
+
def test_repl_load_content_from_src(self):
|
606
|
+
self.writefile("loadReplSrc1.py", "print('1')")
|
607
|
+
self.pyscript_run(
|
608
|
+
"""
|
609
|
+
<py-repl id="py-repl1" output="replOutput1" src="./loadReplSrc1.py"></py-repl>
|
610
|
+
<div id="replOutput1"></div>
|
611
|
+
"""
|
612
|
+
)
|
613
|
+
successMsg = "[py-repl] loading code from ./loadReplSrc1.py to repl...success"
|
614
|
+
assert self.console.info.lines[-1] == successMsg
|
615
|
+
|
616
|
+
py_repl = self.page.locator("py-repl")
|
617
|
+
code = py_repl.locator("div.cm-content").inner_text()
|
618
|
+
assert "print('1')" in code
|
619
|
+
|
620
|
+
@skip_worker("TIMEOUT")
|
621
|
+
def test_repl_src_change(self):
|
622
|
+
self.writefile("loadReplSrc2.py", "2")
|
623
|
+
self.writefile("loadReplSrc3.py", "print('3')")
|
624
|
+
self.pyscript_run(
|
625
|
+
"""
|
626
|
+
<py-repl id="py-repl2" output="replOutput2" src="./loadReplSrc2.py"></py-repl>
|
627
|
+
<div id="replOutput2"></div>
|
628
|
+
|
629
|
+
<py-repl id="py-repl3" output="replOutput3">
|
630
|
+
import js
|
631
|
+
target_tag = js.document.getElementById("py-repl2")
|
632
|
+
target_tag.setAttribute("src", "./loadReplSrc3.py")
|
633
|
+
</py-repl>
|
634
|
+
<div id="replOutput3"></div>
|
635
|
+
"""
|
636
|
+
)
|
637
|
+
|
638
|
+
successMsg1 = "[py-repl] loading code from ./loadReplSrc2.py to repl...success"
|
639
|
+
assert self.console.info.lines[-1] == successMsg1
|
640
|
+
|
641
|
+
py_repl3 = self.page.locator("py-repl#py-repl3")
|
642
|
+
py_repl3.locator("button").click()
|
643
|
+
py_repl2 = self.page.locator("py-repl#py-repl2")
|
644
|
+
py_repl2.locator("button").click()
|
645
|
+
self.page.wait_for_selector("py-terminal")
|
646
|
+
assert self.console.log.lines[-1] == "3"
|
647
|
+
|
648
|
+
successMsg2 = "[py-repl] loading code from ./loadReplSrc3.py to repl...success"
|
649
|
+
assert self.console.info.lines[-1] == successMsg2
|
650
|
+
|
651
|
+
def test_repl_src_path_that_do_not_exist(self):
|
652
|
+
self.pyscript_run(
|
653
|
+
"""
|
654
|
+
<py-repl id="py-repl4" output="replOutput4" src="./loadReplSrc4.py"></py-repl>
|
655
|
+
<div id="replOutput4"></div>
|
656
|
+
"""
|
657
|
+
)
|
658
|
+
errorMsg = (
|
659
|
+
"(PY0404): Fetching from URL ./loadReplSrc4.py "
|
660
|
+
"failed with error 404 (Not Found). "
|
661
|
+
"Are your filename and path correct?"
|
662
|
+
)
|
663
|
+
assert self.console.error.lines[-1] == errorMsg
|