@pyscript/core 0.1.22 → 0.2.0

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 (37) hide show
  1. package/dist/core.js +2 -2
  2. package/dist/core.js.map +1 -1
  3. package/dist/error-e4fe78fd.js +2 -0
  4. package/dist/error-e4fe78fd.js.map +1 -0
  5. package/package.json +2 -2
  6. package/src/core.js +19 -33
  7. package/src/plugins/error.js +2 -2
  8. package/src/stdlib/pyscript/display.py +7 -0
  9. package/src/stdlib/pyscript.js +1 -1
  10. package/dist/error-87e0706c.js +0 -2
  11. package/dist/error-87e0706c.js.map +0 -1
  12. package/tests/integration/__init__.py +0 -0
  13. package/tests/integration/conftest.py +0 -184
  14. package/tests/integration/support.py +0 -1038
  15. package/tests/integration/test_00_support.py +0 -495
  16. package/tests/integration/test_01_basic.py +0 -353
  17. package/tests/integration/test_02_display.py +0 -452
  18. package/tests/integration/test_03_element.py +0 -303
  19. package/tests/integration/test_assets/line_plot.png +0 -0
  20. package/tests/integration/test_assets/tripcolor.png +0 -0
  21. package/tests/integration/test_async.py +0 -197
  22. package/tests/integration/test_event_handling.py +0 -193
  23. package/tests/integration/test_importmap.py +0 -66
  24. package/tests/integration/test_interpreter.py +0 -98
  25. package/tests/integration/test_plugins.py +0 -419
  26. package/tests/integration/test_py_config.py +0 -294
  27. package/tests/integration/test_py_repl.py +0 -663
  28. package/tests/integration/test_py_terminal.py +0 -270
  29. package/tests/integration/test_runtime_attributes.py +0 -64
  30. package/tests/integration/test_script_type.py +0 -121
  31. package/tests/integration/test_shadow_root.py +0 -33
  32. package/tests/integration/test_splashscreen.py +0 -124
  33. package/tests/integration/test_stdio_handling.py +0 -370
  34. package/tests/integration/test_style.py +0 -47
  35. package/tests/integration/test_warnings_and_banners.py +0 -32
  36. package/tests/integration/test_zz_examples.py +0 -419
  37. package/tests/integration/test_zzz_docs_snippets.py +0 -305
@@ -1,303 +0,0 @@
1
- import pytest
2
-
3
- from .support import PyScriptTest
4
-
5
-
6
- class TestElement(PyScriptTest):
7
- """Test the Element api"""
8
-
9
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
10
- def test_element_id(self):
11
- """Test the element id"""
12
- self.pyscript_run(
13
- """
14
- <div id="foo"></div>
15
- <py-script>
16
- from pyscript import Element
17
- el = Element("foo")
18
- print(el.id)
19
- </py-script>
20
- """
21
- )
22
- assert self.console.log.lines[-1] == "foo"
23
-
24
- py_terminal = self.page.wait_for_selector("py-terminal")
25
- assert "foo" in py_terminal.inner_text()
26
-
27
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
28
- def test_element_value(self):
29
- """Test the element value"""
30
- self.pyscript_run(
31
- """
32
- <input id="foo" value="bar">
33
- <py-script>
34
- from pyscript import Element
35
- el = Element("foo")
36
- print(el.value)
37
- </py-script>
38
- """
39
- )
40
- assert self.console.log.lines[-1] == "bar"
41
-
42
- py_terminal = self.page.wait_for_selector("py-terminal")
43
- assert "bar" in py_terminal.inner_text()
44
-
45
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
46
- def test_element_innerHtml(self):
47
- """Test the element innerHtml"""
48
- self.pyscript_run(
49
- """
50
- <div id="foo"><b>bar</b></div>
51
- <py-script>
52
- from pyscript import Element
53
- el = Element("foo")
54
- print(el.innerHtml)
55
- </py-script>
56
- """
57
- )
58
- assert self.console.log.lines[-1] == "<b>bar</b>"
59
-
60
- py_terminal = self.page.wait_for_selector("py-terminal")
61
- assert "bar" in py_terminal.inner_text()
62
-
63
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
64
- def test_element_write_no_append(self):
65
- """Test the element write"""
66
- self.pyscript_run(
67
- """
68
- <div id="foo"></div>
69
- <py-script>
70
- from pyscript import Element
71
- el = Element("foo")
72
- el.write("Hello!")
73
- el.write("World!")
74
- </py-script>
75
- """
76
- )
77
- div = self.page.wait_for_selector("#foo")
78
- assert "World!" in div.inner_text()
79
-
80
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
81
- def test_element_write_append(self):
82
- """Test the element write"""
83
- self.pyscript_run(
84
- """
85
- <div id="foo"></div>
86
- <py-script>
87
- from pyscript import Element
88
- el = Element("foo")
89
- el.write("Hello!")
90
- el.write("World!", append=True)
91
- </py-script>
92
- """
93
- )
94
- parent_div = self.page.wait_for_selector("#foo")
95
-
96
- assert "Hello!" in parent_div.inner_text()
97
- # confirm that the second write was appended
98
- assert "Hello!<div>World!</div>" in parent_div.inner_html()
99
-
100
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
101
- def test_element_clear_div(self):
102
- """Test the element clear"""
103
- self.pyscript_run(
104
- """
105
- <div id="foo">Hello!</div>
106
- <py-script>
107
- from pyscript import Element
108
- el = Element("foo")
109
- el.clear()
110
- </py-script>
111
- """
112
- )
113
- div = self.page.locator("#foo")
114
- assert div.inner_text() == ""
115
-
116
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
117
- def test_element_clear_input(self):
118
- """Test the element clear"""
119
- self.pyscript_run(
120
- """
121
- <input id="foo" value="bar">
122
- <py-script>
123
- from pyscript import Element
124
- el = Element("foo")
125
- el.clear()
126
- </py-script>
127
- """
128
- )
129
- input = self.page.wait_for_selector("#foo")
130
- assert input.input_value() == ""
131
-
132
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
133
- def test_element_select(self):
134
- """Test the element select"""
135
- self.pyscript_run(
136
- """
137
- <select id="foo">
138
- <option value="bar">Bar</option>
139
- </select>
140
- <py-script>
141
- from pyscript import Element
142
- el = Element("foo")
143
- js.console.log(el.select("option").value)
144
- </py-script>
145
- """
146
- )
147
- assert self.console.log.lines[-1] == "bar"
148
-
149
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
150
- def test_element_select_content(self):
151
- """Test the element select"""
152
- self.pyscript_run(
153
- """
154
- <template id="foo">
155
- <div>Bar</div>
156
- </template>
157
- <py-script>
158
- from pyscript import Element
159
- el = Element("foo")
160
- js.console.log(el.select("div", from_content=True).innerHtml)
161
- </py-script>
162
- """
163
- )
164
- assert self.console.log.lines[-1] == "Bar"
165
-
166
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
167
- def test_element_clone_no_id(self):
168
- """Test the element clone"""
169
- self.pyscript_run(
170
- """
171
- <div id="foo">Hello!</div>
172
- <py-script>
173
- from pyscript import Element
174
- el = Element("foo")
175
- el.clone()
176
- </py-script>
177
- """
178
- )
179
- divs = self.page.locator("#foo")
180
- assert divs.count() == 2
181
- assert divs.first.inner_text() == "Hello!"
182
- assert divs.last.inner_text() == "Hello!"
183
-
184
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
185
- def test_element_clone_with_id(self):
186
- """Test the element clone"""
187
- self.pyscript_run(
188
- """
189
- <div id="foo">Hello!</div>
190
- <py-script>
191
- from pyscript import Element
192
- el = Element("foo")
193
- el.clone(new_id="bar")
194
- </py-script>
195
- """
196
- )
197
- divs = self.page.locator("#foo")
198
- assert divs.count() == 1
199
- assert divs.inner_text() == "Hello!"
200
-
201
- clone = self.page.locator("#bar")
202
- assert clone.inner_text() == "Hello!"
203
-
204
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
205
- def test_element_clone_to_other_element(self):
206
- """Test the element clone"""
207
- self.pyscript_run(
208
- """
209
- <div id="container">
210
- <div id="bond">
211
- Bond
212
- </div>
213
- <div id="james">
214
- James
215
- </div>
216
- </div>
217
- <py-script>
218
- from pyscript import Element
219
-
220
- bond_div = Element("bond")
221
- james_div = Element("james")
222
-
223
- bond_div.clone(new_id="bond-2", to=james_div)
224
- </py-script>
225
- """
226
- )
227
- bond_divs = self.page.locator("#bond")
228
- james_divs = self.page.locator("#james")
229
- bond_2_divs = self.page.locator("#bond-2")
230
-
231
- assert bond_divs.count() == 1
232
- assert james_divs.count() == 1
233
- assert bond_2_divs.count() == 1
234
-
235
- container_div = self.page.locator("#container")
236
- # Make sure that the clones are rendered in the right order
237
- assert container_div.inner_text() == "Bond\nJames\nBond"
238
-
239
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
240
- def test_element_remove_single_class(self):
241
- """Test the element remove_class"""
242
- self.pyscript_run(
243
- """
244
- <div id="foo" class="bar baz"></div>
245
- <py-script>
246
- from pyscript import Element
247
- el = Element("foo")
248
- el.remove_class("bar")
249
- </py-script>
250
- """
251
- )
252
- div = self.page.locator("#foo")
253
- assert div.get_attribute("class") == "baz"
254
-
255
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
256
- def test_element_remove_multiple_classes(self):
257
- """Test the element remove_class"""
258
- self.pyscript_run(
259
- """
260
- <div id="foo" class="foo bar baz"></div>
261
- <py-script>
262
- from pyscript import Element
263
- el = Element("foo")
264
- el.remove_class(["foo", "baz", "bar"])
265
- </py-script>
266
- """
267
- )
268
- div = self.page.locator("#foo")
269
- assert div.get_attribute("class") == ""
270
-
271
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
272
- def test_element_add_single_class(self):
273
- """Test the element add_class"""
274
- self.pyscript_run(
275
- """
276
- <style> .red { color: red; } </style>
277
- <div id="foo">Hi!</div>
278
- <py-script>
279
- from pyscript import Element
280
- el = Element("foo")
281
- el.add_class("red")
282
- </py-script>
283
- """
284
- )
285
- div = self.page.locator("#foo")
286
- assert div.get_attribute("class") == "red"
287
-
288
- @pytest.mark.skip("FIXME: Element interface is gone. Replace with PyDom")
289
- def test_element_add_multiple_class(self):
290
- """Test the element add_class"""
291
- self.pyscript_run(
292
- """
293
- <style> .red { color: red; } .bold { font-weight: bold; } </style>
294
- <div id="foo">Hi!</div>
295
- <py-script>
296
- from pyscript import Element
297
- el = Element("foo")
298
- el.add_class(["red", "bold"])
299
- </py-script>
300
- """
301
- )
302
- div = self.page.locator("#foo")
303
- assert div.get_attribute("class") == "red bold"
@@ -1,197 +0,0 @@
1
- from .support import PyScriptTest, filter_inner_text
2
-
3
-
4
- class TestAsync(PyScriptTest):
5
- # ensure_future() and create_task() should behave similarly;
6
- # we'll use the same source code to test both
7
- coroutine_script = """
8
- <py-script>
9
- import js
10
- import asyncio
11
- js.console.log("first")
12
- async def main():
13
- await asyncio.sleep(1)
14
- js.console.log("third")
15
- asyncio.{func}(main())
16
- js.console.log("second")
17
- </py-script>
18
- """
19
-
20
- def test_asyncio_ensure_future(self):
21
- self.pyscript_run(self.coroutine_script.format(func="ensure_future"))
22
- self.wait_for_console("third")
23
- assert self.console.log.lines[-3:] == ["first", "second", "third"]
24
-
25
- def test_asyncio_create_task(self):
26
- self.pyscript_run(self.coroutine_script.format(func="create_task"))
27
- self.wait_for_console("third")
28
- assert self.console.log.lines[-3:] == ["first", "second", "third"]
29
-
30
- def test_asyncio_gather(self):
31
- self.pyscript_run(
32
- """
33
- <py-script id="pys">
34
- import asyncio
35
- import js
36
- from pyodide.ffi import to_js
37
-
38
- async def coro(delay):
39
- await asyncio.sleep(delay)
40
- return(delay)
41
-
42
- async def get_results():
43
- results = await asyncio.gather(*[coro(d) for d in range(3,0,-1)])
44
- js.console.log(str(results)) #Compare to string representation, not Proxy
45
- js.console.log("DONE")
46
-
47
- asyncio.ensure_future(get_results())
48
- </py-script>
49
- """
50
- )
51
- self.wait_for_console("DONE")
52
- assert self.console.log.lines[-2:] == ["[3, 2, 1]", "DONE"]
53
-
54
- def test_multiple_async(self):
55
- self.pyscript_run(
56
- """
57
- <py-script>
58
- import js
59
- import asyncio
60
- async def a_func():
61
- for i in range(3):
62
- js.console.log('A', i)
63
- await asyncio.sleep(0.1)
64
- asyncio.ensure_future(a_func())
65
- </py-script>
66
-
67
- <py-script>
68
- import js
69
- import asyncio
70
- async def b_func():
71
- for i in range(3):
72
- js.console.log('B', i)
73
- await asyncio.sleep(0.1)
74
- js.console.log('b func done')
75
- asyncio.ensure_future(b_func())
76
- </py-script>
77
- """
78
- )
79
- self.wait_for_console("b func done")
80
- assert self.console.log.lines == [
81
- "A 0",
82
- "B 0",
83
- "A 1",
84
- "B 1",
85
- "A 2",
86
- "B 2",
87
- "b func done",
88
- ]
89
-
90
- def test_multiple_async_multiple_display_targeted(self):
91
- self.pyscript_run(
92
- """
93
- <py-script id='pyA'>
94
- from pyscript import display
95
- import js
96
- import asyncio
97
-
98
- async def a_func():
99
- for i in range(2):
100
- display(f'A{i}', target='pyA', append=True)
101
- await asyncio.sleep(0.1)
102
- asyncio.ensure_future(a_func())
103
-
104
- </py-script>
105
- <py-script id='pyB'>
106
- from pyscript import display
107
- import js
108
- import asyncio
109
-
110
- async def a_func():
111
- for i in range(2):
112
- display(f'B{i}', target='pyB', append=True)
113
- await asyncio.sleep(0.1)
114
- js.console.log("B DONE")
115
-
116
- asyncio.ensure_future(a_func())
117
- </py-script>
118
- """
119
- )
120
- self.wait_for_console("B DONE")
121
- inner_text = self.page.inner_text("html")
122
- assert "A0\nA1\nB0\nB1" in filter_inner_text(inner_text)
123
-
124
- def test_async_display_untargeted(self):
125
- self.pyscript_run(
126
- """
127
- <py-script>
128
- from pyscript import display
129
- import asyncio
130
- import js
131
-
132
- async def a_func():
133
- display('A')
134
- await asyncio.sleep(1)
135
- js.console.log("DONE")
136
-
137
- asyncio.ensure_future(a_func())
138
- </py-script>
139
- """
140
- )
141
- self.wait_for_console("DONE")
142
- assert self.page.locator("py-script").inner_text() == "A"
143
-
144
- def test_sync_and_async_order(self):
145
- """
146
- The order of execution is defined as follows:
147
- 1. first, we execute all the py-script tag in order
148
- 2. then, we start all the tasks which were scheduled with create_task
149
-
150
- Note that tasks are started *AFTER* all py-script tags have been
151
- executed. That's why the console.log() inside mytask1 and mytask2 are
152
- executed after e.g. js.console.log("6").
153
- """
154
- src = """
155
- <py-script>
156
- import js
157
- js.console.log("1")
158
- </py-script>
159
-
160
- <py-script>
161
- import asyncio
162
- import js
163
-
164
- async def mytask1():
165
- js.console.log("7")
166
- await asyncio.sleep(0)
167
- js.console.log("9")
168
-
169
- js.console.log("2")
170
- asyncio.create_task(mytask1())
171
- js.console.log("3")
172
- </py-script>
173
-
174
- <py-script>
175
- import js
176
- js.console.log("4")
177
- </py-script>
178
-
179
- <py-script>
180
- import asyncio
181
- import js
182
-
183
- async def mytask2():
184
- js.console.log("8")
185
- await asyncio.sleep(0)
186
- js.console.log("10")
187
- js.console.log("DONE")
188
-
189
- js.console.log("5")
190
- asyncio.create_task(mytask2())
191
- js.console.log("6")
192
- </py-script>
193
- """
194
- self.pyscript_run(src, wait_for_pyscript=False)
195
- self.wait_for_console("DONE")
196
- lines = self.console.log.lines[-11:]
197
- assert lines == ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "DONE"]
@@ -1,193 +0,0 @@
1
- import pytest
2
-
3
- from .support import PyScriptTest
4
-
5
- pytest.skip(
6
- reason="FIXME: @when decorator missing from pyscript", allow_module_level=True
7
- )
8
-
9
-
10
- class TestEventHandler(PyScriptTest):
11
- def test_when_decorator_with_event(self):
12
- """When the decorated function takes a single parameter,
13
- it should be passed the event object
14
- """
15
- self.pyscript_run(
16
- """
17
- <button id="foo_id">foo_button</button>
18
- <py-script>
19
- from pyscript import when
20
- @when("click", selector="#foo_id")
21
- def foo(evt):
22
- print(f"I've clicked {evt.target} with id {evt.target.id}")
23
- </py-script>
24
- """
25
- )
26
- self.page.locator("text=foo_button").click()
27
- console_text = self.console.all.lines
28
- self.wait_for_console("I've clicked [object HTMLButtonElement] with id foo_id")
29
- assert "I've clicked [object HTMLButtonElement] with id foo_id" in console_text
30
- self.assert_no_banners()
31
-
32
- def test_when_decorator_without_event(self):
33
- """When the decorated function takes no parameters (not including 'self'),
34
- it should be called without the event object
35
- """
36
- self.pyscript_run(
37
- """
38
- <button id="foo_id">foo_button</button>
39
- <py-script>
40
- from pyscript import when
41
- @when("click", selector="#foo_id")
42
- def foo():
43
- print("The button was clicked")
44
- </py-script>
45
- """
46
- )
47
- self.page.locator("text=foo_button").click()
48
- self.wait_for_console("The button was clicked")
49
- assert "The button was clicked" in self.console.log.lines
50
- self.assert_no_banners()
51
-
52
- def test_multiple_when_decorators_with_event(self):
53
- self.pyscript_run(
54
- """
55
- <button id="foo_id">foo_button</button>
56
- <button id="bar_id">bar_button</button>
57
- <py-script>
58
- from pyscript import when
59
- @when("click", selector="#foo_id")
60
- def foo(evt):
61
- print(f"I've clicked {evt.target} with id {evt.target.id}")
62
- @when("click", selector="#bar_id")
63
- def foo(evt):
64
- print(f"I've clicked {evt.target} with id {evt.target.id}")
65
- </py-script>
66
- """
67
- )
68
- self.page.locator("text=foo_button").click()
69
- console_text = self.console.all.lines
70
- self.wait_for_console("I've clicked [object HTMLButtonElement] with id foo_id")
71
- assert "I've clicked [object HTMLButtonElement] with id foo_id" in console_text
72
-
73
- self.page.locator("text=bar_button").click()
74
- console_text = self.console.all.lines
75
- self.wait_for_console("I've clicked [object HTMLButtonElement] with id bar_id")
76
- assert "I've clicked [object HTMLButtonElement] with id bar_id" in console_text
77
- self.assert_no_banners()
78
-
79
- def test_two_when_decorators(self):
80
- """When decorating a function twice, both should function"""
81
- self.pyscript_run(
82
- """
83
- <button id="foo_id">foo_button</button>
84
- <button class="bar_class">bar_button</button>
85
- <py-script>
86
- from pyscript import when
87
- @when("click", selector="#foo_id")
88
- @when("mouseover", selector=".bar_class")
89
- def foo(evt):
90
- print(f"An event of type {evt.type} happened")
91
- </py-script>
92
- """
93
- )
94
- self.page.locator("text=bar_button").hover()
95
- self.page.locator("text=foo_button").click()
96
- self.wait_for_console("An event of type click happened")
97
- assert "An event of type mouseover happened" in self.console.log.lines
98
- assert "An event of type click happened" in self.console.log.lines
99
- self.assert_no_banners()
100
-
101
- def test_two_when_decorators_same_element(self):
102
- """When decorating a function twice *on the same DOM element*, both should function"""
103
- self.pyscript_run(
104
- """
105
- <button id="foo_id">foo_button</button>
106
- <py-script>
107
- from pyscript import when
108
- @when("click", selector="#foo_id")
109
- @when("mouseover", selector="#foo_id")
110
- def foo(evt):
111
- print(f"An event of type {evt.type} happened")
112
- </py-script>
113
- """
114
- )
115
- self.page.locator("text=foo_button").hover()
116
- self.page.locator("text=foo_button").click()
117
- self.wait_for_console("An event of type click happened")
118
- assert "An event of type mouseover happened" in self.console.log.lines
119
- assert "An event of type click happened" in self.console.log.lines
120
- self.assert_no_banners()
121
-
122
- def test_when_decorator_multiple_elements(self):
123
- """The @when decorator's selector should successfully select multiple
124
- DOM elements
125
- """
126
- self.pyscript_run(
127
- """
128
- <button class="bar_class">button1</button>
129
- <button class="bar_class">button2</button>
130
- <py-script>
131
- from pyscript import when
132
- @when("click", selector=".bar_class")
133
- def foo(evt):
134
- print(f"{evt.target.innerText} was clicked")
135
- </py-script>
136
- """
137
- )
138
- self.page.locator("text=button1").click()
139
- self.page.locator("text=button2").click()
140
- self.wait_for_console("button2 was clicked")
141
- assert "button1 was clicked" in self.console.log.lines
142
- assert "button2 was clicked" in self.console.log.lines
143
- self.assert_no_banners()
144
-
145
- def test_when_decorator_duplicate_selectors(self):
146
- """ """
147
- self.pyscript_run(
148
- """
149
- <button id="foo_id">foo_button</button>
150
- <py-script>
151
- from pyscript import when
152
- @when("click", selector="#foo_id")
153
- @when("click", selector="#foo_id")
154
- def foo(evt):
155
- print(f"I've clicked {evt.target} with id {evt.target.id}")
156
- </py-script>
157
- """
158
- )
159
- self.page.locator("text=foo_button").click()
160
- console_text = self.console.all.lines
161
- self.wait_for_console("I've clicked [object HTMLButtonElement] with id foo_id")
162
- assert (
163
- console_text.count("I've clicked [object HTMLButtonElement] with id foo_id")
164
- == 2
165
- )
166
- self.assert_no_banners()
167
-
168
- def test_when_decorator_invalid_selector(self):
169
- """When the selector parameter of @when is invalid, it should show an error"""
170
- self.pyscript_run(
171
- """
172
- <button id="foo_id">foo_button</button>
173
- <py-script>
174
- from pyscript import when
175
- @when("click", selector="#.bad")
176
- def foo(evt):
177
- ...
178
- </py-script>
179
- """
180
- )
181
- self.page.locator("text=foo_button").click()
182
- msg = "Failed to execute 'querySelectorAll' on 'Document': '#.bad' is not a valid selector."
183
- error = self.page.wait_for_selector(".py-error")
184
- banner_text = error.inner_text()
185
-
186
- if msg not in banner_text:
187
- raise AssertionError(
188
- f"Expected message '{msg}' does not "
189
- f"match banner text '{banner_text}'"
190
- )
191
-
192
- assert msg in self.console.error.lines[-1]
193
- self.check_py_errors(msg)