mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-11-02 19:52:14 +08:00
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"
```
192 lines
7.5 KiB
Python
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()
|