import time 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("", "").replace("", "") expect(self._page.locator("body")).to_contain_text(patt) def check_string_not_in_page(self, patt: str) -> None: patt = patt.replace("", "").replace("", "") 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="testcase-", 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") 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()