mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Improve self.wait_for_console() (#1363)
- Previously, if the message appeared on the console immediately before the call to self.wait_for_console(), the call would hang forever until the timeout. Now, it returns immediately. - `wait_for_pyscript` now logs the time actually taken for waiting - `wait_for_pyscript` takes a `timeout` argument so that we can tweak it on a test-by-test basis
This commit is contained in:
@@ -221,25 +221,36 @@ class PyScriptTest:
|
|||||||
|
|
||||||
def wait_for_console(self, text, *, timeout=None, check_js_errors=True):
|
def wait_for_console(self, text, *, timeout=None, check_js_errors=True):
|
||||||
"""
|
"""
|
||||||
Wait until the given message appear in the console.
|
Wait until the given message appear in the console. If the message was
|
||||||
|
already printed in the console, return immediately.
|
||||||
|
|
||||||
Note: it must be the *exact* string as printed by e.g. console.log.
|
Note: it must be the *exact* string as printed by e.g. console.log.
|
||||||
If you need more control on the predicate (e.g. if you want to match a
|
|
||||||
substring), use self.page.expect_console_message directly.
|
|
||||||
|
|
||||||
timeout is expressed in milliseconds. If it's None, it will use
|
timeout is expressed in milliseconds. If it's None, it will use
|
||||||
playwright's own default value, which is 30 seconds).
|
the same default as playwright, which is 30 seconds.
|
||||||
|
|
||||||
If check_js_errors is True (the default), it also checks that no JS
|
If check_js_errors is True (the default), it also checks that no JS
|
||||||
errors were raised during the waiting.
|
errors were raised during the waiting.
|
||||||
|
|
||||||
|
Return the elapsed time in ms.
|
||||||
"""
|
"""
|
||||||
|
if timeout is None:
|
||||||
def pred(msg):
|
timeout = 30 * 1000
|
||||||
return msg.text == text
|
# NOTE: we cannot use playwright's own page.expect_console_message(),
|
||||||
|
# because if you call it AFTER the text has already been emitted, it
|
||||||
|
# waits forever. Instead, we have to use our own custom logic.
|
||||||
try:
|
try:
|
||||||
with self.page.expect_console_message(pred, timeout=timeout):
|
t0 = time.time()
|
||||||
pass
|
while True:
|
||||||
|
elapsed_ms = (time.time() - t0) * 1000
|
||||||
|
if elapsed_ms > timeout:
|
||||||
|
raise TimeoutError(f"{elapsed_ms:.2f} ms")
|
||||||
|
#
|
||||||
|
if text in self.console.all.lines:
|
||||||
|
# found it!
|
||||||
|
return elapsed_ms
|
||||||
|
#
|
||||||
|
self.page.wait_for_timeout(50)
|
||||||
finally:
|
finally:
|
||||||
# raise JsError if there were any javascript exception. Note that
|
# raise JsError if there were any javascript exception. Note that
|
||||||
# this might happen also in case of a TimeoutError. In that case,
|
# this might happen also in case of a TimeoutError. In that case,
|
||||||
@@ -260,16 +271,21 @@ class PyScriptTest:
|
|||||||
errors were raised during the waiting.
|
errors were raised during the waiting.
|
||||||
"""
|
"""
|
||||||
# this is printed by interpreter.ts:Interpreter.initialize
|
# this is printed by interpreter.ts:Interpreter.initialize
|
||||||
self.wait_for_console(
|
elapsed_ms = self.wait_for_console(
|
||||||
"[pyscript/main] PyScript page fully initialized",
|
"[pyscript/main] PyScript page fully initialized",
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
check_js_errors=check_js_errors,
|
check_js_errors=check_js_errors,
|
||||||
)
|
)
|
||||||
|
self.logger.log(
|
||||||
|
"wait_for_pyscript", f"Waited for {elapsed_ms/1000:.2f} s", color="yellow"
|
||||||
|
)
|
||||||
# We still don't know why this wait is necessary, but without it
|
# We still don't know why this wait is necessary, but without it
|
||||||
# events aren't being triggered in the tests.
|
# events aren't being triggered in the tests.
|
||||||
self.page.wait_for_timeout(100)
|
self.page.wait_for_timeout(100)
|
||||||
|
|
||||||
def pyscript_run(self, snippet, *, extra_head="", wait_for_pyscript=True):
|
def pyscript_run(
|
||||||
|
self, snippet, *, extra_head="", wait_for_pyscript=True, timeout=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Main entry point for pyscript tests.
|
Main entry point for pyscript tests.
|
||||||
|
|
||||||
@@ -295,11 +311,13 @@ class PyScriptTest:
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
if not wait_for_pyscript and timeout is not None:
|
||||||
|
raise ValueError("Cannot set a timeout if wait_for_pyscript=False")
|
||||||
filename = f"{self.testname}.html"
|
filename = f"{self.testname}.html"
|
||||||
self.writefile(filename, doc)
|
self.writefile(filename, doc)
|
||||||
self.goto(filename)
|
self.goto(filename)
|
||||||
if wait_for_pyscript:
|
if wait_for_pyscript:
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=timeout)
|
||||||
|
|
||||||
def iter_locator(self, loc):
|
def iter_locator(self, loc):
|
||||||
"""
|
"""
|
||||||
@@ -594,7 +612,7 @@ class Logger:
|
|||||||
def log(self, category, text, *, color=None):
|
def log(self, category, text, *, color=None):
|
||||||
delta = time.time() - self.start_time
|
delta = time.time() - self.start_time
|
||||||
text = self.colorize_prefix(text, color="teal")
|
text = self.colorize_prefix(text, color="teal")
|
||||||
line = f"[{delta:6.2f} {category:16}] {text}"
|
line = f"[{delta:6.2f} {category:17}] {text}"
|
||||||
if color:
|
if color:
|
||||||
line = Color.set(color, line)
|
line = Color.set(color, line)
|
||||||
print(line)
|
print(line)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import re
|
|||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from playwright import sync_api
|
|
||||||
|
|
||||||
from .support import JsErrors, JsErrorsDidNotRaise, PyScriptTest
|
from .support import JsErrors, JsErrorsDidNotRaise, PyScriptTest
|
||||||
|
|
||||||
@@ -266,7 +265,7 @@ class TestSupport(PyScriptTest):
|
|||||||
# cleared
|
# cleared
|
||||||
self.check_js_errors()
|
self.check_js_errors()
|
||||||
|
|
||||||
def test_wait_for_console(self):
|
def test_wait_for_console_simple(self):
|
||||||
"""
|
"""
|
||||||
Test that self.wait_for_console actually waits.
|
Test that self.wait_for_console actually waits.
|
||||||
If it's buggy, the test will try to read self.console.log BEFORE the
|
If it's buggy, the test will try to read self.console.log BEFORE the
|
||||||
@@ -285,11 +284,46 @@ class TestSupport(PyScriptTest):
|
|||||||
"""
|
"""
|
||||||
self.writefile("mytest.html", doc)
|
self.writefile("mytest.html", doc)
|
||||||
self.goto("mytest.html")
|
self.goto("mytest.html")
|
||||||
# we use a timeout of 500ms to give plenty of time to the page to
|
# we use a timeout of 200ms to give plenty of time to the page to
|
||||||
# actually run the setTimeout callback
|
# actually run the setTimeout callback
|
||||||
self.wait_for_console("Page loaded!", timeout=200)
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
assert self.console.log.lines[-1] == "Page loaded!"
|
assert self.console.log.lines[-1] == "Page loaded!"
|
||||||
|
|
||||||
|
def test_wait_for_console_timeout(self):
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
self.wait_for_console("This text will never be printed", timeout=200)
|
||||||
|
|
||||||
|
def test_wait_for_console_dont_wait_if_already_emitted(self):
|
||||||
|
"""
|
||||||
|
If the text is already on the console, wait_for_console() should return
|
||||||
|
immediately without waiting.
|
||||||
|
"""
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
console.log('Hello world')
|
||||||
|
console.log('Page loaded!');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
|
assert self.console.log.lines[-2] == "Hello world"
|
||||||
|
assert self.console.log.lines[-1] == "Page loaded!"
|
||||||
|
# the following call should return immediately without waiting
|
||||||
|
self.wait_for_console("Hello world", timeout=1)
|
||||||
|
|
||||||
def test_wait_for_console_exception_1(self):
|
def test_wait_for_console_exception_1(self):
|
||||||
"""
|
"""
|
||||||
Test that if a JS exception is raised while waiting for the console, we
|
Test that if a JS exception is raised while waiting for the console, we
|
||||||
@@ -316,12 +350,12 @@ class TestSupport(PyScriptTest):
|
|||||||
with pytest.raises(JsErrors) as exc:
|
with pytest.raises(JsErrors) as exc:
|
||||||
self.wait_for_console("Page loaded!", timeout=200)
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
assert "this is an error" in str(exc.value)
|
assert "this is an error" in str(exc.value)
|
||||||
assert isinstance(exc.value.__context__, sync_api.TimeoutError)
|
assert isinstance(exc.value.__context__, TimeoutError)
|
||||||
#
|
#
|
||||||
# if we use check_js_errors=False, the error are ignored, but we get the
|
# if we use check_js_errors=False, the error are ignored, but we get the
|
||||||
# Timeout anyway
|
# Timeout anyway
|
||||||
self.goto("mytest.html")
|
self.goto("mytest.html")
|
||||||
with pytest.raises(sync_api.TimeoutError):
|
with pytest.raises(TimeoutError):
|
||||||
self.wait_for_console("Page loaded!", timeout=200, check_js_errors=False)
|
self.wait_for_console("Page loaded!", timeout=200, check_js_errors=False)
|
||||||
# we still got a JsErrors, so we need to manually clear it, else the
|
# we still got a JsErrors, so we need to manually clear it, else the
|
||||||
# test fails at teardown
|
# test fails at teardown
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class TestExamples(PyScriptTest):
|
|||||||
def test_bokeh(self):
|
def test_bokeh(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
self.goto("examples/bokeh.html")
|
self.goto("examples/bokeh.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "Bokeh Example"
|
assert self.page.title() == "Bokeh Example"
|
||||||
wait_for_render(self.page, "*", '<div.*?class="bk.*".*?>')
|
wait_for_render(self.page, "*", '<div.*?class="bk.*".*?>')
|
||||||
self.assert_no_banners()
|
self.assert_no_banners()
|
||||||
@@ -111,7 +111,7 @@ class TestExamples(PyScriptTest):
|
|||||||
def test_bokeh_interactive(self):
|
def test_bokeh_interactive(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
self.goto("examples/bokeh_interactive.html")
|
self.goto("examples/bokeh_interactive.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "Bokeh Example"
|
assert self.page.title() == "Bokeh Example"
|
||||||
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
|
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
|
||||||
self.assert_no_banners()
|
self.assert_no_banners()
|
||||||
@@ -248,7 +248,7 @@ class TestExamples(PyScriptTest):
|
|||||||
|
|
||||||
def test_panel(self):
|
def test_panel(self):
|
||||||
self.goto("examples/panel.html")
|
self.goto("examples/panel.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "Panel Example"
|
assert self.page.title() == "Panel Example"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
slider_title = self.page.wait_for_selector(".bk-slider-title")
|
slider_title = self.page.wait_for_selector(".bk-slider-title")
|
||||||
@@ -268,7 +268,7 @@ class TestExamples(PyScriptTest):
|
|||||||
def test_panel_deckgl(self):
|
def test_panel_deckgl(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
self.goto("examples/panel_deckgl.html")
|
self.goto("examples/panel_deckgl.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "PyScript/Panel DeckGL Demo"
|
assert self.page.title() == "PyScript/Panel DeckGL Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
self.assert_no_banners()
|
self.assert_no_banners()
|
||||||
@@ -277,7 +277,7 @@ class TestExamples(PyScriptTest):
|
|||||||
def test_panel_kmeans(self):
|
def test_panel_kmeans(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
self.goto("examples/panel_kmeans.html")
|
self.goto("examples/panel_kmeans.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "Pyscript/Panel KMeans Demo"
|
assert self.page.title() == "Pyscript/Panel KMeans Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
self.assert_no_banners()
|
self.assert_no_banners()
|
||||||
@@ -286,7 +286,7 @@ class TestExamples(PyScriptTest):
|
|||||||
def test_panel_stream(self):
|
def test_panel_stream(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
self.goto("examples/panel_stream.html")
|
self.goto("examples/panel_stream.html")
|
||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript(timeout=90 * 1000)
|
||||||
assert self.page.title() == "PyScript/Panel Streaming Demo"
|
assert self.page.title() == "PyScript/Panel Streaming Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
self.assert_no_banners()
|
self.assert_no_banners()
|
||||||
|
|||||||
Reference in New Issue
Block a user