def test_locator_protocol_and_dict_kwargs(): class Class(object): def __init__(self, loc): self.loc = loc def __locator__(self): return self.loc assert Locator(Class('#foo')) == (By.CSS_SELECTOR, '#foo') assert Locator(Class({'xpath': '//h1'})) == (By.XPATH, '//h1')
def test_locator_find_elements(): selenium = Mock(spec=['find_elements']) locator = Locator(xpath='//h1') locator.find_elements(selenium) selenium.find_elements.assert_called_once_with(By.XPATH, '//h1')
def delete_source(view, source_name): """Delete a source through the UI.""" clear_toasts(view=view) dash = DashboardView(view) dash.nav.select("Sources") wait_for_animation() view.wait_for_element(locator=Locator(xpath=(delete_xpath(source_name)))) GenericLocatorWidget( view, locator=Locator(xpath=delete_xpath(source_name))).click() # mitigate database lock issue quipucords/quipucords/issues/1275 wait_for_animation() DeleteModalView(view).delete_button.click() wait_for_animation() clear_toasts(view=view) with pytest.raises(NoSuchElementException): view.wait_for_element(locator=Locator(xpath=delete_xpath(source_name)), timeout=2)
def check_auth_type(credential_name, auth_type): """Verify the authentication type of a credential. Example types include 'SSH Key' and 'Username and Password'. If the Locator cannot find a match, an exception is raised. """ Locator(xpath=(f'//span[text()="{auth_type}" and ancestor::node()[2]' f'//*[text()="{credential_name}"]]'))
def set_checkbox(view, name, fill): """Fill or clear a checkbox next to a credential.""" checkbox = Checkbox(view, locator=Locator(xpath=checkbox_xpath(name))) try: checkbox.fill(fill) except WidgetOperationFailed: clear_toasts(view=view) checkbox.fill(fill)
def create_source(view, credential_name, source_type, source_name, addresses): """Create a source through the UI.""" clear_toasts(view=view) dash = DashboardView(view) dash.nav.select("Sources") # Display varies depending on whether or not sources already exist. wait_for_animation(1) try: Button(view, "Add Source").click() except NoSuchElementException: Button(view, "Add").click() # Source creation wizard modal = SourceModalView(view, locator=Locator(css=".modal-content")) radio_label = SOURCE_TYPE_RADIO_LABELS[source_type] # Wait for radio button to become responsive before clicking a source type. wait_for_animation() GenericLocatorWidget( modal, locator=Locator(xpath=radio_xpath(radio_label))).click() wait_for_animation(1) modal.next_button.click() # Fill in required source information. fill(modal, field_xpath("Name"), source_name) if source_type == "Network": fill(modal, field_xpath("Search Addresses", textarea=True), addresses) fill(modal, field_xpath("Port"), "") # default port of 22 cred_dropdown = Dropdown(modal, "Select one or more credentials") cred_dropdown.item_select(credential_name) else: fill(modal, field_xpath("IP Address or Hostname"), addresses) cred_dropdown = Dropdown(modal, "Select a credential") cred_dropdown.item_select(credential_name) Button(modal, "Save").click() wait_for_animation(2) view.wait_for_element(locator=Locator('//button[text()="Close"]')) Button(modal, "Close", classes=[Button.PRIMARY]).click() wait_for_animation(1) # mitigate database lock issue quipucords/quipucords/issues/1275 clear_toasts(view=view) # Verify that the new row source has been created. view.wait_for_element(locator=Locator(xpath=row_xpath(source_name))) view.element(locator=Locator(xpath=row_xpath(source_name)))
def clear_toasts(view, count=20): """Attempt to flush any confirmation dialogs that may have appeared. Use this function to clear out dialogs (toasts) that may be preventing buttons from being clicked properly. Sometimes it might need to be used in succession. By default, this tries to flush a maximum of 20 toasts, but will quit early if it cannot find more. """ for i in range(count): try: view.wait_for_element(locator=Locator(css=".close"), timeout=0.6) GenericLocatorWidget(view, locator=Locator(css=".close")).click() except ( MoveTargetOutOfBoundsException, NoSuchElementException, StaleElementReferenceException, ): break
def delete_credential(view, names): """Delete a credential through the UI.""" view.refresh() dash = DashboardView(view) dash.nav.select("Credentials") # Select all the checkboxes next to the credentials to be deleted. for name in names: set_checkbox(view, name, True) Button(view, "Delete").click() DeleteModalView( view, locator=Locator(css=".modal-content")).delete_button.click() # Wait for the deletion animations to complete, # and verify that the rows are gone. wait_for_animation(1) for name in names: with pytest.raises(NoSuchElementException): view.wait_for_element(locator=Locator(xpath=row_xpath(name)), timeout=1)
def __new__(cls, name, bases, attrs): new_attrs = {} desc_name_mapping = {} included_widgets = [] for base in bases: for key, value in six.iteritems( getattr(base, '_desc_name_mapping', {})): desc_name_mapping[key] = value for widget_includer in getattr(base, '_included_widgets', ()): included_widgets.append(widget_includer) for widget_name in widget_includer.widget_class.cls_widget_names( ): new_attrs[widget_name] = IncludedWidget( widget_includer._seq_id, widget_name, widget_includer.use_parent) for key, value in six.iteritems(attrs): if inspect.isclass(value) and issubclass(value, View): new_attrs[key] = WidgetDescriptor(value) desc_name_mapping[new_attrs[key]] = key elif isinstance(value, WidgetIncluder): included_widgets.append(value) # Now generate accessors for each included widget for widget_name in value.widget_class.cls_widget_names(): new_attrs[widget_name] = IncludedWidget( value._seq_id, widget_name, value.use_parent) elif isinstance(value, Widgetable): new_attrs[key] = value desc_name_mapping[value] = key for widget in value.child_items: if not isinstance(widget, (Widgetable, Widget)): continue desc_name_mapping[widget] = key elif key == 'fill': # handle fill() specifics new_attrs[key] = logged(log_args=True, log_result=True)( wrap_fill_method(value)) elif key == 'read': # handle read() specifics new_attrs[key] = logged(log_result=True)(value) elif isinstance(value, types.FunctionType): # VP resolution wrapper, allows to resolve VersionPicks in all widget methods new_attrs[key] = resolve_verpicks_in_method(value) else: # Do nothing new_attrs[key] = value if 'ROOT' in new_attrs and '__locator__' not in new_attrs: # For handling the root locator of the View root = new_attrs['ROOT'] if isinstance(root, ParametrizedLocator): new_attrs['__locator__'] = _gen_locator_root() else: new_attrs['__locator__'] = _gen_locator_meth(Locator(root)) new_attrs['_included_widgets'] = tuple( sorted(included_widgets, key=lambda w: w._seq_id)) new_attrs['_desc_name_mapping'] = desc_name_mapping return super(WidgetMetaclass, cls).__new__(cls, name, bases, new_attrs)
def create_credential(view, options): """Create a credential through the UI.""" clear_toasts(view=view) dash = DashboardView(view) dash.nav.select("Credentials") # Display differs depending on whether or not credentials already exist. try: add_credential_dropdown = Dropdown(view, "Add Credential") add_credential_dropdown.item_select(options["source_type"] + " Credential") except NoSuchElementException: add_credential_dropdown = Dropdown(view, "Add") add_credential_dropdown.item_select(options["source_type"] + " Credential") modal = CredentialModalView(view, locator=Locator(css=".modal-content")) # Workaround, should be `assert modal.save_button.disabled` # https://github.com/RedHatQE/widgetastic.patternfly/pull/66 # https://github.com/quipucords/camayoc/issues/279 assert modal.save_button.browser.get_attribute("disabled", modal.save_button) fill_credential_info(view, options) assert not modal.save_button.browser.get_attribute("disabled", modal.save_button) # Hack to deal with the fact that the GET refresh isn't # implemented when the save button is clicked. # https://github.com/quipucords/quipucords/issues/1399 # https://github.com/quipucords/camayoc/issues/280 wait_for_animation() modal.save_button.click() wait_for_animation() view.refresh() dash.nav.select("Credentials") # Assert the row with the credential name exists. view.wait_for_element(locator=Locator(xpath=row_xpath(options["name"])), delay=0.5, timeout=10) assert isinstance( view.element(locator=Locator(xpath=row_xpath(options["name"]))), WebElement)
def test_simple_css(): assert Locator('foo#bar.baz.bat') == (By.CSS_SELECTOR, 'foo#bar.baz.bat') assert Locator('#bar.baz.bat') == (By.CSS_SELECTOR, '#bar.baz.bat') assert Locator('#bar-bar.baz.bat') == (By.CSS_SELECTOR, '#bar-bar.baz.bat') assert Locator('#bar') == (By.CSS_SELECTOR, '#bar') assert Locator('.bat') == (By.CSS_SELECTOR, '.bat') assert Locator('.99-luftballons') == (By.CSS_SELECTOR, '.99-luftballons')
def _process_locator(locator): """Processes the locator so the :py:meth:`elements` gets exactly what it needs.""" if isinstance(locator, WebElement): return locator try: return Locator(locator) except TypeError: if hasattr(locator, '__locator__'): # Deal with the case when __locator__ returns a webelement. loc = locator.__locator__() if isinstance(loc, WebElement): return loc raise LocatorNotImplemented( 'You have to implement __locator__ on {!r}'.format(type(locator)))
def _process_locator(locator: LocatorAlias) -> Union[WebElement, Locator]: """Processes the locator so the :py:meth:`elements` gets exactly what it needs.""" if isinstance(locator, WebElement): return locator if hasattr(locator, "__element__"): # https://github.com/python/mypy/issues/1424 return cast("Widget", locator).__element__() try: return Locator(locator) except TypeError: if hasattr(locator, "__locator__"): # Deal with the case when __locator__ returns a webelement. loc = cast(LocatorProtocol, locator).__locator__() if isinstance(loc, WebElement): return loc raise LocatorNotImplemented( f"You have to implement __locator__ on {type(locator)!r}" ) from None
def edit_credential(view, original_name, options): """Edit a credential through the UI and verify it was edited. :param view: The view context (should be the browser view) :param original_name: The original name of the credential. :param options: The options to be edited within the credential. """ view.refresh() dash = DashboardView(view) dash.nav.select("Credentials") view.wait_for_element(locator=Locator(xpath=(edit_xpath(original_name)))) GenericLocatorWidget( view, locator=Locator(xpath=edit_xpath(original_name))).click() modal = CredentialModalView(view, locator=Locator(css=".modal-content")) wait_for_animation(1) fill_credential_info(view, options) # Hack to deal with the fact that the GET refresh isn't # implemented when the save button is clicked. # https://github.com/quipucords/quipucords/issues/1399 # https://github.com/quipucords/camayoc/issues/280 wait_for_animation() modal.save_button.click() wait_for_animation() view.refresh() dash.nav.select("Credentials") # Assert the row with the credential name exists. # If the name was updated, use the new name. current_name = original_name if "name" in options: current_name = options["name"] view.wait_for_element(locator=Locator(xpath=row_xpath(current_name)), delay=0.5, timeout=10) GenericLocatorWidget( view, locator=Locator(xpath=edit_xpath(current_name))).click() modal = CredentialModalView(view, locator=Locator(css=".modal-content")) # Assert that the changed variables were in fact changed. # Passwords are skipped because they aren't accessible. for option, data in options.items(): if ((option == "password") or (option == "become_pass") or (option == "source_type")): continue browser_data = get_field_value(view, CREDENTIAL_FIELD_LABELS[option]) if option == "sshkeyfile": # tmp files are resolved with alias prefixes in some cases. # the characters afer the final '/' remain consistent. assert browser_data.rpartition("/")[2] == data.rpartition("/")[2] else: assert browser_data == data
def __get__(self, o, t=None): result = super().__get__(o, t) if isinstance(result, ParametrizedString): return result else: return Locator(result)
class DashboardView(View): """Dashboard view.""" user_dropdown = NavDropdown(locator=Locator(css="li.dropdown:nth-child(2)")) logout = Button("Log out") nav = VerticalNavigation(locator=(Locator(css=".list-group")))
def test_bad_param(): with pytest.raises(TypeError): Locator(1)
def test_many_params(): with pytest.raises(TypeError): Locator('foo', 'bar', 'baz')
def test_bad_strategy_tuple(): with pytest.raises(ValueError): Locator('foo', 'bar')
def test_bad_strategy_kwarg(): with pytest.raises(ValueError): Locator(by='foo', locator='bar')
def test_bad_kwarg(): with pytest.raises(ValueError): Locator(foo='bar')
def test_kwargs_no_by(): with pytest.raises(ValueError): Locator(locator='bar')
def test_kwargs_no_locator(): with pytest.raises(ValueError): Locator(by='xpath')
def test_implicit_xpath(): assert Locator('//h1') == (By.XPATH, '//h1')
def test_no_params(): with pytest.raises(TypeError): Locator()
def test_selenium_like(): assert Locator(By.XPATH, '//h1') == (By.XPATH, '//h1')
def fill(view, xpath_locator, text): """Fill in a textbox using a xpath locator.""" TextInput(view, locator=Locator(xpath=xpath_locator)).fill(text)
def test_nested_locator(): loc = Locator(By.XPATH, '//h1') loc2 = Locator(loc) assert loc2 == (By.XPATH, '//h1') assert loc2 is loc