Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add Browser.have_test_api() #21349

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions test/common/testlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,22 @@ def __init__(
except FileNotFoundError:
self.layouts = default_layouts
self.current_layout = None
self.valid = True

def _is_running(self) -> bool:
"""True initially, false after calling .kill()"""

return self.driver is not None and self.driver.bidi_session is not None

def have_test_api(self) -> bool:
"""Check if the browser is running and has a Cockpit page

I.e. are our test-functions.js available? This is only true after
opening cockpit, not for the initial blank page (before login_and_go)
or other URLs like Grafana.
"""
if not self._is_running():
return False
return self.eval_js("!!window.ph_find")

def run_async(self, coro: Coroutine[Any, Any, Any]) -> JsonObject:
"""Run coro in main loop in our BiDi thread
Expand All @@ -309,12 +324,11 @@ def asyncio_loop_thread(loop: asyncio.AbstractEventLoop) -> None:
loop.run_forever()

def kill(self) -> None:
if not self.valid:
if not self._is_running():
return
self.run_async(self.driver.close())
self.loop.call_soon_threadsafe(self.loop.stop)
self.bidi_thread.join()
self.valid = False

def bidi(self, method: str, **params: Any) -> webdriver_bidi.JsonObject:
"""Send a Webdriver BiDi command and return the JSON response"""
Expand Down Expand Up @@ -417,7 +431,8 @@ def allow_download(self) -> None:
"""Allow browser downloads"""
# this is only necessary for headless chromium
if self.browser == "chromium":
self.cdp_command("Browser.setDownloadBehavior", behavior="allow", downloadPath=str(self.driver.download_dir))
self.cdp_command("Browser.setDownloadBehavior", behavior="allow",
downloadPath=str(self.driver.download_dir))

def upload_files(self, selector: str, files: Sequence[str]) -> None:
"""Upload a local file to the browser
Expand Down Expand Up @@ -774,7 +789,8 @@ def wait_js_cond(self, cond: str, error_description: str = "null") -> None:
duration = time.time() - start
percent = int(duration / timeout * 100)
if percent >= 50:
print(f"WARNING: Waiting for {cond} took {duration:.1f} seconds, which is {percent}% of the timeout.")
print(f"WARNING: Waiting for {cond} took {duration:.1f} seconds, "
f"which is {percent}% of the timeout.")
return
except Error as e:
last_error = e
Expand Down Expand Up @@ -1029,7 +1045,7 @@ def logout(self) -> None:
# we don't need it here, if the session menu is visible then so is the dropdown
self.mouse('#logout', "click", scrollVisible=False)
except RuntimeError as e:
# logging out does destroy the current frame context, it races with the CDP driver finishing the command
# logging out does destroy the current frame context, it races with the driver finishing the command
if "Execution context was destroyed" not in str(e):
raise
self.wait_visible('#login')
Expand Down Expand Up @@ -1203,7 +1219,7 @@ def snapshot(self, title: str, label: str | None = None) -> None:
Arguments:
title: Used for the filename.
"""
if self.valid:
if self._is_running():
filename = unique_filename(f"{label or self.label}-{title}", "png")
try:
ret = self.bidi("browsingContext.captureScreenshot", quiet=True,
Expand Down Expand Up @@ -1498,7 +1514,7 @@ def assert_no_unused_pixel_test_references(self) -> None:
def get_js_log(self) -> Sequence[str]:
"""Return the current javascript log"""

if self.valid:
if self._is_running():
return [str(log) for log in self.driver.logs]
return []

Expand All @@ -1514,15 +1530,15 @@ def copy_js_log(self, title: str, label: str | None = None) -> None:
print("Wrote JS log to " + filename)

def write_coverage_data(self) -> None:
if self.coverage_label and self.valid:
if self.coverage_label and self._is_running():
coverage = self.cdp_command("Profiler.takePreciseCoverage")["result"]
write_lcov(coverage['result'], self.coverage_label)

def assert_no_oops(self) -> None:
if self.allow_oops:
return

if self.valid:
if self.have_test_api():
self.switch_to_top()
if self.eval_js("!!document.getElementById('navbar-oops')"):
assert not self.is_visible("#navbar-oops"), "Cockpit shows an Oops"
Expand Down Expand Up @@ -1580,7 +1596,8 @@ def new_machine(
if opts.address:
if forward:
raise unittest.SkipTest("Cannot run this test when specific machine address is specified")
machine = testvm.Machine(address=opts.address, image=image or self.image, verbose=opts.trace, browser=opts.browser)
machine = testvm.Machine(address=opts.address, image=image or self.image,
verbose=opts.trace, browser=opts.browser)
if cleanup:
self.addCleanup(machine.disconnect)
else:
Expand Down