@pyscript/core 0.1.18 → 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.
Files changed (52) hide show
  1. package/README.md +19 -2
  2. package/dist/core.js +3 -3
  3. package/dist/core.js.map +1 -1
  4. package/dist/error-87e0706c.js +2 -0
  5. package/dist/error-87e0706c.js.map +1 -0
  6. package/docs/README.md +3 -12
  7. package/package.json +4 -3
  8. package/src/config.js +110 -0
  9. package/src/core.js +84 -106
  10. package/src/fetch.js +3 -0
  11. package/src/plugins/error.js +1 -1
  12. package/src/plugins.js +1 -1
  13. package/src/stdlib/pyscript/__init__.py +34 -0
  14. package/src/stdlib/pyscript/display.py +154 -0
  15. package/src/stdlib/pyscript/event_handling.py +45 -0
  16. package/src/stdlib/pyscript/magic_js.py +32 -0
  17. package/src/stdlib/pyscript/util.py +22 -0
  18. package/src/stdlib/pyscript.js +9 -5
  19. package/src/stdlib/pyweb/pydom.py +314 -0
  20. package/tests/integration/__init__.py +0 -0
  21. package/tests/integration/conftest.py +184 -0
  22. package/tests/integration/support.py +1038 -0
  23. package/tests/integration/test_00_support.py +495 -0
  24. package/tests/integration/test_01_basic.py +353 -0
  25. package/tests/integration/test_02_display.py +452 -0
  26. package/tests/integration/test_03_element.py +303 -0
  27. package/tests/integration/test_assets/line_plot.png +0 -0
  28. package/tests/integration/test_assets/tripcolor.png +0 -0
  29. package/tests/integration/test_async.py +197 -0
  30. package/tests/integration/test_event_handling.py +193 -0
  31. package/tests/integration/test_importmap.py +66 -0
  32. package/tests/integration/test_interpreter.py +98 -0
  33. package/tests/integration/test_plugins.py +419 -0
  34. package/tests/integration/test_py_config.py +294 -0
  35. package/tests/integration/test_py_repl.py +663 -0
  36. package/tests/integration/test_py_terminal.py +270 -0
  37. package/tests/integration/test_runtime_attributes.py +64 -0
  38. package/tests/integration/test_script_type.py +121 -0
  39. package/tests/integration/test_shadow_root.py +33 -0
  40. package/tests/integration/test_splashscreen.py +124 -0
  41. package/tests/integration/test_stdio_handling.py +370 -0
  42. package/tests/integration/test_style.py +47 -0
  43. package/tests/integration/test_warnings_and_banners.py +32 -0
  44. package/tests/integration/test_zz_examples.py +419 -0
  45. package/tests/integration/test_zzz_docs_snippets.py +305 -0
  46. package/types/config.d.ts +3 -0
  47. package/types/core.d.ts +1 -2
  48. package/types/fetch.d.ts +1 -0
  49. package/types/plugins/error.d.ts +1 -1
  50. package/types/stdlib/pyscript.d.ts +8 -4
  51. package/dist/error-91f1c2f6.js +0 -2
  52. package/dist/error-91f1c2f6.js.map +0 -1
@@ -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