Files
galaxy/lib/tool_shed/test/base/playwrightbrowser.py
T
mvdbeek 2b4b7ed37f Maybe fix flaky navigation in v2 ts tests
The stack trace is this:
```
__ TestSimplePriorInstallation.test_0005_create_convert_repository[chromium] ___

self = <tool_shed.test.functional.test_1170_prior_installation_required.TestSimplePriorInstallation object at 0x7f87c33834c0>

    def test_0005_create_convert_repository(self):
        """Create and populate convert_chars_0150."""
        global running_standalone
        category = self.create_category(name=category_name, description=category_description)
>       self.login(email=common.test_user_1_email, username=common.test_user_1_name)

lib/tool_shed/test/functional/test_1170_prior_installation_required.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
lib/tool_shed/test/base/twilltestcase.py:776: in login
    previously_created, username_taken, invalid_username = self.create(
lib/tool_shed/test/base/twilltestcase.py:714: in create
    self.visit_url("/user/create", params)
lib/tool_shed/test/base/twilltestcase.py:850: in visit_url
    return self._browser.visit_url(url, allowed_codes=allowed_codes)
lib/tool_shed/test/base/playwrightbrowser.py:31: in visit_url
    response = self._page.goto(url)
.venv/lib/python3.8/site-packages/playwright/sync_api/_generated.py:8851: in goto
    self._sync(
.venv/lib/python3.8/site-packages/playwright/_impl/_page.py:524: in goto
    return await self._main_frame.goto(**locals_to_params(locals()))
.venv/lib/python3.8/site-packages/playwright/_impl/_frame.py:145: in goto
    await self._channel.send("goto", locals_to_params(locals()))
.venv/lib/python3.8/site-packages/playwright/_impl/_connection.py:59: in send
    return await self._connection.wrap_api_call(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <playwright._impl._connection.Connection object at 0x7f873c8bebe0>
cb = <function Channel.send.<locals>.<lambda> at 0x7f87af067550>
is_internal = False

    async def wrap_api_call(
        self, cb: Callable[[], Any], is_internal: bool = False
    ) -> Any:
        if self._api_zone.get():
            return await cb()
        task = asyncio.current_task(self._loop)
        st: List[inspect.FrameInfo] = getattr(task, "__pw_stack__", inspect.stack())
        parsed_st = _extract_stack_trace_information_from_stack(st, is_internal)
        self._api_zone.set(parsed_st)
        try:
            return await cb()
        except Exception as error:
>           raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
E           playwright._impl._errors.Error: Page.goto: Navigation to "http://127.0.0.1:8723/user/create?cntrller=user&use_panels=False" is interrupted by another navigation to "http://127.0.0.1:8723/logout_success"
E           Call log:
E           navigating to "http://127.0.0.1:8723/user/create?cntrller=user&use_panels=False", waiting until "load"
```
2024-09-17 17:17:04 +02:00

192 lines
7.5 KiB
Python

import time
from typing import List
from playwright.sync_api import (
expect,
Locator,
Page,
)
from .browser import (
FormValueType,
ShedBrowser,
)
class Locators:
toolbar_login = ".toolbar-login"
toolbar_logout = ".toolbar-logout"
login_submit_button = '[name="login_button"]'
register_link = ".register-link"
class PlaywrightShedBrowser(ShedBrowser):
_page: Page
def __init__(self, page: Page):
self._page = page
def visit_url(self, url: str, allowed_codes: List[int]) -> str:
try:
response = self._page.goto(url)
except Exception as e:
if "Navigation interrupted by another one" in str(e):
# I believe redirect on the target page interfering with
# this thread's test.
time.sleep(0.25)
response = self._page.goto(url)
else:
raise
assert response is not None
return_code = response.status
assert return_code in allowed_codes, "Invalid HTTP return code {}, allowed codes: {}".format(
return_code,
", ".join(str(code) for code in allowed_codes),
)
return response.url
def page_content(self) -> str:
self._page.wait_for_load_state("networkidle")
return self._page.content()
def check_page_for_string(self, patt: str) -> None:
"""Looks for 'patt' in the current browser page"""
patt = patt.replace("<b>", "").replace("</b>", "")
expect(self._page.locator("body")).to_contain_text(patt)
def check_string_not_in_page(self, patt: str) -> None:
patt = patt.replace("<b>", "").replace("</b>", "")
expect(self._page.locator("body")).not_to_contain_text(patt)
def xcheck_page_for_string(self, patt: str) -> None:
page = self.page_content()
if page.find(patt) == -1:
fname = self.write_temp_file(page)
errmsg = f"no match to '{patt}'\npage content written to '{fname}'\npage: [[{page}]]"
raise AssertionError(errmsg)
def xcheck_string_not_in_page(self, patt: str) -> None:
page = self.page_content()
if page.find(patt) != -1:
fname = self.write_temp_file(page)
errmsg = f"string ({patt}) incorrectly displayed in page.\npage content written to '{fname}'"
raise AssertionError(errmsg)
def write_temp_file(self, content, suffix=".html"):
import tempfile
from galaxy.util import smart_str
with tempfile.NamedTemporaryFile(suffix=suffix, prefix="twilltestcase-", delete=False) as fh:
fh.write(smart_str(content))
return fh.name
def show_forms(self) -> Locator:
"""Shows form, helpful for debugging new tests"""
return self._page.locator("form")
def submit_form_with_name(self, form_name: str, button="runtool_btn", **kwd):
form = self._form_with_name(form_name)
self._submit_form(form, button, **kwd)
def submit_form(self, form_no=-1, button="runtool_btn", form=None, **kwd):
"""Populates and submits a form from the keyword arguments."""
# An HTMLForm contains a sequence of Controls. Supported control classes are:
# TextControl, FileControl, ListControl, RadioControl, CheckboxControl, SelectControl,
# SubmitControl, ImageControl
if form is None:
try:
form = self.show_forms().nth(form_no)
except IndexError:
raise ValueError("No form to submit found")
self._submit_form(form, button, **kwd)
def _submit_form(self, form: Locator, button="runtool_btn", **kwd):
for control_name, control_value in kwd.items():
self._fill_form_value(form, control_name, control_value)
input = self._page.locator(f"[name='{button}']")
if input.count():
input.click()
else:
submit_input = form.locator("input[type=submit]")
submit_input.click()
time.sleep(0.25)
# tc.submit(button)
def _form_with_name(self, name: str) -> Locator:
forms = self.show_forms()
count = forms.count()
for i in range(count):
nth_form = self.show_forms().nth(i)
if nth_form.get_attribute("name") == name:
return nth_form
raise KeyError(f"No form with name [{name}]")
def fill_form_value(self, form_name: str, control_name: str, value: FormValueType):
form: Locator = self._form_with_name(form_name)
self._fill_form_value(form, control_name, value)
def _fill_form_value(self, form: Locator, control_name: str, value: FormValueType):
input_i = form.locator(f"input[name='{control_name}']")
input_t = form.locator(f"textarea[name='{control_name}']")
input_s = form.locator(f"select[name='{control_name}']")
if input_i.count():
if control_name in ["redirect"]:
input_i.input_value = value # type:ignore[assignment, unused-ignore]
else:
if isinstance(value, bool):
if value and not input_i.is_checked():
input_i.check()
elif not value and input_i.is_checked():
input_i.uncheck()
else:
input_i.fill(value)
if input_t.count():
input_t.fill(value) # type:ignore[arg-type, unused-ignore]
if input_s.count():
input_s.select_option(value) # type:ignore[arg-type, unused-ignore]
def edit_repository_categories(self, categories_to_add: List[str], categories_to_remove: List[str]) -> None:
multi_select = "form[name='categories'] select[name='category_id']"
select_locator = self._page.locator(multi_select)
select_locator.evaluate("node => node.selectedOptions = []")
select_locator.select_option(label=categories_to_add)
self.submit_form_with_name("categories", "manage_categories_button")
select_locator.evaluate("node => node.selectedOptions = []")
select_locator.select_option(label=categories_to_remove)
self.submit_form_with_name("categories", "manage_categories_button")
def grant_users_access(self, usernames: List[str]):
multi_select = "form[name='user_access'] select[name='allow_push']"
select_locator = self._page.locator(multi_select)
select_locator.evaluate("node => node.selectedOptions = []")
select_locator.select_option(label=usernames)
self.submit_form_with_name("user_access", "user_access_button")
@property
def is_twill(self) -> bool:
return False
def logout_if_logged_in(self, assert_logged_out=True):
self._page.wait_for_selector(f"{Locators.toolbar_login}, {Locators.toolbar_logout}")
logout_locator = self._page.locator(Locators.toolbar_logout)
if logout_locator.is_visible():
logout_locator.click()
self._page.wait_for_load_state("networkidle")
if assert_logged_out:
self.expect_not_logged_in()
def explicit_logout(self):
self._page.wait_for_selector(Locators.toolbar_logout)
logout_locator = self._page.locator(Locators.toolbar_logout)
logout_locator.click()
self._page.wait_for_load_state("networkidle")
expect(self._page.locator(Locators.toolbar_logout)).not_to_be_visible()
def expect_not_logged_in(self):
expect(self._page.locator(Locators.toolbar_logout)).not_to_be_visible()
def expect_logged_in(self):
expect(self._page.locator(Locators.toolbar_logout)).to_be_visible()