from __future__ import absolute_import, unicode_literals from abc import ABCMeta from contextlib import contextmanager import warnings import robot.api from robot.libraries.BuiltIn import BuiltIn from selenium.webdriver.support.expected_conditions import staleness_of from selenium.webdriver.support.ui import WebDriverWait import six from .locatormap import LocatorMap class PageObject(six.with_metaclass(ABCMeta, object)): """Base class for page objects Classes that inherit from this class need to define the following class variables: PAGE_TITLE the title of the page; used by the default implementation of _is_current_page PAGE_URL this should be the URL of the page, minus the hostname and port (eg: /loginpage.html) By default, the PageObjectLibrary keyword 'the current page should be' calls the method _is_current_page. A default implementation is provided by this class. It compares the current page title to the class variable PAGE_TITLE. A class can override this method if the page title is not unique or is indeterminate. Classes that inherit from this class have access to the following properties: * selib a reference to an instance of SeleniumLibrary * browser a reference to the current webdriver instance * logger a reference to robot.api.logger * locator a wrapper around the page object's ``_locators`` dictionary This class implements the following context managers: * _wait_for_page_refresh This context manager is designed to be used in page objects when a keyword should wait to return until the html element has been refreshed. """ PAGE_URL = None PAGE_TITLE = None def __init__(self): self.logger = robot.api.logger self.locator = LocatorMap(getattr(self, "_locators", {})) self.builtin = BuiltIn() # N.B. selib, browser use @property so that a # subclass can be instantiated outside of the context of a running # test (eg: by libdoc, robotframework-hub, etc) @property def se2lib(self): warnings.warn("se2lib is deprecated. Use selib intead.", warnings.DeprecationWarning) return self.selib @property def selib(self): return self.builtin.get_library_instance("SeleniumLibrary") @property def browser(self): return self.selib._current_browser() def __str__(self): return self.__class__.__name__ def get_page_name(self): """Return the name of the current page """ return self.__class__.__name__ @contextmanager def _wait_for_page_refresh(self, timeout=10): """Context manager that waits for a page transition. This keyword works by waiting for two things to happen: 1) the <html> tag to go stale and get replaced, and 2) the javascript document.readyState variable to be set to "complete" """ old_page = self.browser.find_element_by_tag_name('html') yield WebDriverWait(self.browser, timeout).until( staleness_of(old_page), message="Old page did not go stale within %ss" % timeout ) self.selib.wait_for_condition("return (document.readyState == 'complete')", timeout=10) def _is_current_page(self): """Determine if this page object represents the current page. This works by comparing the current page title to the class variable PAGE_TITLE. Unless their page titles are unique, page objects should override this function. For example, a common solution is to look at the url of the current page, or to look for a specific heading or element on the page. """ actual_title = self.selib.get_title() expected_title = self.PAGE_TITLE if actual_title.lower() == expected_title.lower(): return True self.logger.info("expected title: '%s'" % expected_title) self.logger.info(" actual title: '%s'" % actual_title) raise Exception("expected title to be '%s' but it was '%s'" % (expected_title, actual_title)) return False