From c90902c4269791e6c1c0bb77309822d2146aa428 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Mar 2016 17:59:12 -0700 Subject: [PATCH 1/5] removed NoneType import because it's not Python 3 compatible and removed every reference to NoneType with type(None), changed urllib2 import to urllib.error, basestring isn't Python 3 compatible so changed every reference to it to str, and iteritems() was changed to items() b/c that also isn't Python 3 compatible --- ngSe/browser.py | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/ngSe/browser.py b/ngSe/browser.py index 82c8b14..527ff19 100644 --- a/ngSe/browser.py +++ b/ngSe/browser.py @@ -1,9 +1,8 @@ from time import sleep from numbers import Number -from types import NoneType from atexit import register as register_exit -from urllib2 import URLError +from urllib.error import URLError from selenium.webdriver import Chrome import selenium.common.exceptions as selenium_exceptions from selenium.webdriver.remote.webelement import WebElement @@ -28,15 +27,15 @@ class Browser(Chrome): def __init__(self, scenario, download_directory=default_download_directory, app_host=None, app_port=None, executable_path=None, pages=None): # Contract - must_be(download_directory, "download_directory", (NoneType, basestring)) - must_be(app_host, "app_host", (NoneType, basestring)) - must_be(app_port, "app_port", (NoneType, Number)) - must_be(executable_path, "executable_path", (NoneType, basestring)) - must_be(pages, "pages", (dict, NoneType)) + must_be(download_directory, "download_directory", (type(None), str)) + must_be(app_host, "app_host", (type(None), str)) + must_be(app_port, "app_port", (type(None), Number)) + must_be(executable_path, "executable_path", (type(None), str)) + must_be(pages, "pages", (dict, type(None))) # TODO[TJ]: This should be implemented as part of the future contracts library if pages is not None: - for key, value in pages.iteritems(): - must_be(key, "pages key", basestring) + for key, value in pages.items(): + must_be(key, "pages key", str) must_be(value, "pages value", AppPage) # self.scenario = scenario @@ -72,7 +71,7 @@ def wait_for(self, value, by=By.ID, **kwargs): This is really just a wrapper around passing the browser to a ByClause, allowing for much cleaner syntax. """ # Contract - must_be(value, "value", basestring) + must_be(value, "value", str) must_be(by, "by", ByClause) # @@ -82,7 +81,7 @@ def goto(self, url): """Wrapper to check for navigation issues, like 404's """ # Contract - must_be(url, "url", basestring) + must_be(url, "url", str) # value = super(Browser, self).get(url) page_title = self.title @@ -94,9 +93,9 @@ def navigate(self, to): """Goes to a page in the app """ # Contract - must_be(to, "to", (AppPage, basestring)) + must_be(to, "to", (AppPage, str)) # - if isinstance(to, basestring): + if isinstance(to, str): to = self.pages[to.lower()] url = "http://{host}:{port}/{page}".format(host=self.app_host, port=self.app_port, page=to.page) return_value = self.goto(url) @@ -116,11 +115,11 @@ def click(self, what, by=By.LINK_TEXT, hover_time=0.1, wait_for=None, wait_for_b """Find, hover on, and click on the given element """ # Contract - must_be(what, "element", basestring) + must_be(what, "element", str) must_be(by, "by", ByClause) must_be(hover_time, "hover_time", Number) - must_be(wait_for, "wait_for", (NoneType, basestring)) - must_be(wait_for_by, "wait_for_by", (NoneType, ByClause)) + must_be(wait_for, "wait_for", (type(None), str)) + must_be(wait_for_by, "wait_for_by", (type(None), ByClause)) # element = by.find(what, self) self.hover_on(element, hover_time) @@ -174,11 +173,11 @@ def _fill(element, text, by=By.ID, check=True, check_against=None, check_attribu """ # Contract must_be(element, "element", WebElement) - must_be(text, "text", basestring) + must_be(text, "text", str) must_be(by, "by", ByClause) must_be(check, "check", bool) - must_be(check_against, "check_against", (NoneType, basestring)) - must_be(check_attribute, "check_attribute", basestring) + must_be(check_against, "check_against", (type(None), str)) + must_be(check_attribute, "check_attribute", str) must_be(empty, "empty", bool) # if empty: @@ -194,12 +193,12 @@ def fill(self, what, text, by=By.ID, check=True, check_against=None, check_attri """Finds and fills in an element with the given text. """ # Contract - must_be(what, "element", basestring) - must_be(text, "text", basestring) + must_be(what, "element", str) + must_be(text, "text", str) must_be(by, "by", ByClause) must_be(check, "check", bool) - must_be(check_against, "check_against", (NoneType, basestring)) - must_be(check_attribute, "check_attribute", basestring) + must_be(check_against, "check_against", (type(None), str)) + must_be(check_attribute, "check_attribute", str) must_be(empty, "empty", bool) # element = by.find(what, self) From 3b5c7fe78d65e4cfd6fd6eafb87c01c04c37123c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Mar 2016 18:11:14 -0700 Subject: [PATCH 2/5] removed NoneType from page.py and every reference of NoneType changed to type(None) and every reference to iteritems() changed to items() b/c iteritems() isn't Python 3 compatible --- ngSe/by.py | 26 +++++++++++++------------- ngSe/page.py | 8 +++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/ngSe/by.py b/ngSe/by.py index c3f1ba9..92aec2b 100644 --- a/ngSe/by.py +++ b/ngSe/by.py @@ -23,14 +23,14 @@ def __setattr__(self, key, value): def __getitem__(self, item): # Performs generation of NegativeByClause's - if isinstance(item, basestring): + if isinstance(item, str): if item.startswith(self.negative_prefix): return NegativeByClause(self[item[len(self.negative_prefix):]]) return super(ByDict, self).__getitem__(item) def __setitem__(self, key, value): # Prevent inaccessible keys from getting in - if isinstance(key, basestring): + if isinstance(key, str): if key.startswith(self.negative_prefix): raise ValueError("Keys in this dict cannot start with '{}'".format(self.negative_prefix)) super(ByDict, self).__setitem__(key, value) @@ -39,8 +39,8 @@ def __setitem__(self, key, value): By = ByDict() # Is this the best way to do this? Allow retrieving key: None = value: None (for simplifying step parsing) By[None] = None -for key, value in selenium_by.__dict__.iteritems(): - if not key.startswith('__') and isinstance(value, basestring): +for key, value in selenium_by.__dict__.items(): + if not key.startswith('__') and isinstance(value, str): By[key] = value @@ -57,7 +57,7 @@ class ByClause(object): def __init__(self, by, f): # Contract - must_be(by, "by", basestring) + must_be(by, "by", str) if not hasattr(f, "__call__"): raise ValueError("f must be a callable") # @@ -77,7 +77,7 @@ def wait(self, what, browser): This is put here to be override-able, so you can, say, wait for the element to 'leave' """ # Contract - must_be(what, "what", basestring) + must_be(what, "what", str) must_be(browser, "browser", Remote) # return self.find(what, browser) @@ -86,7 +86,7 @@ def find(self, what, browser): """Finds the desired element (what) in the provided browser """ # Contract - must_be(what, "what", basestring) + must_be(what, "what", str) must_be(browser, "browser", Remote) # what = self.convert(what) @@ -113,7 +113,7 @@ def wait(self, what, browser): """Waits for the desired element to 'leave'. Or tries to. """ # Contract - must_be(what, "what", basestring) + must_be(what, "what", str) must_be(browser, "browser", Remote) # try: @@ -126,7 +126,7 @@ def wait(self, what, browser): def _inner_text_convert(value): # Contract - must_be(value, "value", basestring) + must_be(value, "value", str) # parts = value.split('\\') text = parts.pop() @@ -138,7 +138,7 @@ def _inner_text_convert(value): def _table_path_convert(path): # Contract - must_be(path, "path", basestring) + must_be(path, "path", str) # parts = path.split('\\') if len(parts) < 3: @@ -174,7 +174,7 @@ def _table_path_convert(path): def _list_path_convert(path): # Contract - must_be(path, "path", basestring) + must_be(path, "path", str) # parts = path.split('\\') if len(parts) < 2: @@ -202,8 +202,8 @@ def _list_path_convert(path): By = ByDict() # Is this the best way to do this? Allow retrieving key: None = value: None (for simplifying step parsing) By[None] = None -for key, value in selenium_by.__dict__.iteritems(): - if not key.startswith('__') and isinstance(value, basestring): +for key, value in selenium_by.__dict__.items(): + if not key.startswith('__') and isinstance(value, str): By[key] = ByClause(value, lambda v: v) By.INNER_TEXT = ByClause(selenium_by.XPATH, _inner_text_convert) diff --git a/ngSe/page.py b/ngSe/page.py index 846fd03..203ebf9 100644 --- a/ngSe/page.py +++ b/ngSe/page.py @@ -1,5 +1,3 @@ -from types import NoneType - from .by import ByClause, By from .contract import must_be @@ -11,9 +9,9 @@ class AppPage(object): def __init__(self, page, wait_for=None, wait_for_by=By.ID): # Contract - if not isinstance(page, basestring) and not hasattr(page, "__call__"): + if not isinstance(page, str) and not hasattr(page, "__call__"): raise ValueError("page must be a string or callable") - must_be(wait_for, "wait_for", (NoneType, basestring)) + must_be(wait_for, "wait_for", (type(None), str)) must_be(wait_for_by, "wait_for_by", (ByClause)) # self._page = page @@ -22,6 +20,6 @@ def __init__(self, page, wait_for=None, wait_for_by=By.ID): @property def page(self): - if not isinstance(self._page, basestring) and hasattr(self._page, "__call__"): + if not isinstance(self._page, str) and hasattr(self._page, "__call__"): return self._page() return self._page From 71a52465ae79c4445d871ebb1ae24965d85e6fad Mon Sep 17 00:00:00 2001 From: Mark Schumacher Date: Fri, 11 Nov 2016 16:08:15 -0800 Subject: [PATCH 3/5] Break apart Browser class Browser class is currently hard coded to use the selenium chrome local plugin. Most of the logic has been removed to a Mixin class to be used re-used with the selenium remote browser plugin. Also bunch of pep8 stuff. --- ngSe/browser.py | 131 +++++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/ngSe/browser.py b/ngSe/browser.py index 527ff19..2dcbe16 100644 --- a/ngSe/browser.py +++ b/ngSe/browser.py @@ -13,54 +13,22 @@ from .page import AppPage from .by import By, ByClause from .contract import must_be -from .exceptions import NavigationError, WaitFailedError, DontRetryError, FrontEndError +from .exceptions import NavigationError, WaitFailedError, DontRetryError,\ + FrontEndError from .exceptions import element_exceptions, cant_see_exceptions default_download_directory = "./tmp" -class Browser(Chrome): - executable_path = '/usr/local/bin/chromedriver' - app_host = 'localhost' - app_port = 5000 - - def __init__(self, scenario, download_directory=default_download_directory, app_host=None, app_port=None, - executable_path=None, pages=None): - # Contract - must_be(download_directory, "download_directory", (type(None), str)) - must_be(app_host, "app_host", (type(None), str)) - must_be(app_port, "app_port", (type(None), Number)) - must_be(executable_path, "executable_path", (type(None), str)) - must_be(pages, "pages", (dict, type(None))) - # TODO[TJ]: This should be implemented as part of the future contracts library - if pages is not None: - for key, value in pages.items(): - must_be(key, "pages key", str) - must_be(value, "pages value", AppPage) - # - self.scenario = scenario - if download_directory is not None: - options = ChromeOptions() - prefs = {"download.default_directory": download_directory} - options.add_experimental_option('prefs', prefs) - else: - options = None - if app_host is not None: - self.app_host = app_host - if app_port is not None: - self.app_port = app_port - if executable_path is not None: - self.executable_path = executable_path - self.pages = pages - super(Browser, self).__init__(executable_path=self.executable_path, chrome_options=options) - register_exit(self.quit) +class BrowserMixin(object): def quit(self): try: super(Browser, self).quit() except URLError as e: if e.reason.errno == 61 or e.reason.errno == 111: - # 'Connection refused', this happens when the driver has already closed/quit + # 'Connection refused', this happens when the driver has + # already closed/quit pass else: raise @@ -68,7 +36,8 @@ def quit(self): def wait_for(self, value, by=By.ID, **kwargs): """Waits for an element according to the passed ByClause - This is really just a wrapper around passing the browser to a ByClause, allowing for much cleaner syntax. + This is really just a wrapper around passing the browser to a + ByClause, allowing for much cleaner syntax. """ # Contract must_be(value, "value", str) @@ -97,21 +66,23 @@ def navigate(self, to): # if isinstance(to, str): to = self.pages[to.lower()] - url = "http://{host}:{port}/{page}".format(host=self.app_host, port=self.app_port, page=to.page) + url = "http://{host}:{port}/{page}".format( + host=self.app_host, port=self.app_port, page=to.page) return_value = self.goto(url) if to.wait_for is not None: try: retry(to.wait_for_by.wait)(to.wait_for, self) except selenium_exceptions.NoSuchElementException: raise NavigationError( - "Expected element {}:{} didn't show when navigating to {}".format( + "Expected element {}:{} didn't show when navigating to {}".format( # nopep8 to.wait_for, to.wait_for_by, to.page)) return return_value @retry(timeout=15) - def click(self, what, by=By.LINK_TEXT, hover_time=0.1, wait_for=None, wait_for_by=By.ID): + def click(self, what, by=By.LINK_TEXT, hover_time=0.1, wait_for=None, + wait_for_by=By.ID): """Find, hover on, and click on the given element """ # Contract @@ -125,11 +96,13 @@ def click(self, what, by=By.LINK_TEXT, hover_time=0.1, wait_for=None, wait_for_b self.hover_on(element, hover_time) return_value = element.click() if wait_for is not None: - # If this fails, we need the whole function to fail (don't want to re-do a successful click) + # If this fails, we need the whole function to fail (don't want to + # re-do a successful click) try: wait_for_by.wait(wait_for, self) except cant_see_exceptions as e: - # TODO[TJ]: This custom exception feels clunky, only used for, and only outside of, the wait method + # TODO[TJ]: This custom exception feels clunky, only used for, + # and only outside of, the wait method raise WaitFailedError("Wait failed", e) except element_exceptions as e: # Some weird stuff, this shouldn't happen @@ -144,14 +117,16 @@ def _scroll_to(self, element, wait_after=0.25): must_be(element, "element", WebElement) must_be(wait_after, "wait_after", Number) # - """Currently a bug in the move_to_element on ActionChains, so we can't use it, must use JS instead. - This scrolls the element into the middle of the page, useful since we have the top and bottom fixed divs that + """Currently a bug in the move_to_element on ActionChains, so we can't + use it, must use JS instead. This scrolls the element into the middle + of the page, useful since we have the top and bottom fixed divs that will cover anything scrolled 'just to' the top or bottom. """ self.execute_script( "Element.prototype.documentOffsetTop = function () {return this.offsetTop + ( this.offsetParent ? this.offsetParent.documentOffsetTop() : 0 );};") # NOQA self.execute_script( - "window.scrollTo( 0, arguments[0].documentOffsetTop()-(window.innerHeight / 2 ));", element) + "window.scrollTo( 0, arguments[0].documentOffsetTop()-(window.innerHeight / 2 ));", # nopep8 + element) sleep(wait_after) def hover_on(self, element, hover_time=0.1): @@ -167,9 +142,11 @@ def hover_on(self, element, hover_time=0.1): return chain.perform() @staticmethod - def _fill(element, text, by=By.ID, check=True, check_against=None, check_attribute="value", empty=False): - """Fills in a given element with the given text, optionally checking emptying it first and/or checking the - contents after (optionally against a different value). + def _fill(element, text, by=By.ID, check=True, check_against=None, + check_attribute="value", empty=False): + """Fills in a given element with the given text, optionally checking + emptying it first and/or checking the contents after (optionally + against a different value). """ # Contract must_be(element, "element", WebElement) @@ -189,7 +166,8 @@ def _fill(element, text, by=By.ID, check=True, check_against=None, check_attribu assert check_against in element.get_attribute(check_attribute) return return_value - def fill(self, what, text, by=By.ID, check=True, check_against=None, check_attribute="value", empty=False): + def fill(self, what, text, by=By.ID, check=True, check_against=None, + check_attribute="value", empty=False): """Finds and fills in an element with the given text. """ # Contract @@ -202,7 +180,8 @@ def fill(self, what, text, by=By.ID, check=True, check_against=None, check_attri must_be(empty, "empty", bool) # element = by.find(what, self) - return self._fill(element, text, by, check, check_against, check_attribute, empty) + return self._fill(element, text, by, check, check_against, + check_attribute, empty) @retry def wait_for_success(self): @@ -241,3 +220,53 @@ def text_is_present(self, text, *args, **kwargs): @retry def _text_is_present(self, text): self.find_element_by_tag_name('body').text.index(text) + + +class Browser(object): + def __init__(*args, **kwargs): + return ChromeBrowser(*args, **kwargs) + + +class RemoteBrowser(BrowserMixin): + def __init__(self, scenario, app_host=None, app_port=None, pages=None): + pass + + +class ChromeBrowser(BrowserMixin, Chrome): + executable_path = '/usr/local/bin/chromedriver' + app_host = 'localhost' + app_port = 5000 + + def __init__(self, scenario, download_directory=default_download_directory, + app_host=None, app_port=None, executable_path=None, + pages=None): + # Contract + must_be(download_directory, "download_directory", (type(None), str)) + must_be(app_host, "app_host", (type(None), str)) + must_be(app_port, "app_port", (type(None), Number)) + must_be(executable_path, "executable_path", (type(None), str)) + must_be(pages, "pages", (dict, type(None))) + # TODO[TJ]: This should be implemented as part of the future contracts + # library + if pages is not None: + for key, value in pages.items(): + must_be(key, "pages key", str) + must_be(value, "pages value", AppPage) + # + self.scenario = scenario + if download_directory is not None: + options = ChromeOptions() + prefs = {"download.default_directory": download_directory} + options.add_experimental_option('prefs', prefs) + else: + options = None + if app_host is not None: + self.app_host = app_host + if app_port is not None: + self.app_port = app_port + if executable_path is not None: + self.executable_path = executable_path + self.pages = pages + super(Browser, self).__init__(executable_path=self.executable_path, + chrome_options=options) + register_exit(self.quit) From 7152818ce73ed54942c0617931b7537acd962201 Mon Sep 17 00:00:00 2001 From: Mark Schumacher Date: Wed, 16 Nov 2016 11:58:04 -0800 Subject: [PATCH 4/5] Add support for remote chrome browser Also some PEP8 changes. --- ngSe/__init__.py | 4 ++-- ngSe/browser.py | 44 +++++++++++++++++++++++++++++++++----------- ngSe/contract.py | 1 + 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/ngSe/__init__.py b/ngSe/__init__.py index deba38f..7cd2788 100644 --- a/ngSe/__init__.py +++ b/ngSe/__init__.py @@ -1,5 +1,5 @@ -from .browser import Browser +from .browser import RemoteBrowser, ChromeBrowser, Browser from .by import By from .page import AppPage -__author__ = 'Travis Johnson' \ No newline at end of file +__author__ = 'Travis Johnson' diff --git a/ngSe/browser.py b/ngSe/browser.py index 2dcbe16..c00a2ff 100644 --- a/ngSe/browser.py +++ b/ngSe/browser.py @@ -3,10 +3,11 @@ from atexit import register as register_exit from urllib.error import URLError -from selenium.webdriver import Chrome +from selenium.webdriver import Chrome, Remote, DesiredCapabilities import selenium.common.exceptions as selenium_exceptions from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.common.action_chains import ActionChains +from selenium.common.exceptions import WebDriverException from selenium.webdriver.chrome.options import Options as ChromeOptions from .utils import retry @@ -24,7 +25,7 @@ class BrowserMixin(object): def quit(self): try: - super(Browser, self).quit() + super(BrowserMixin, self).quit() except URLError as e: if e.reason.errno == 61 or e.reason.errno == 111: # 'Connection refused', this happens when the driver has @@ -32,6 +33,10 @@ def quit(self): pass else: raise + except WebDriverException: + # This happens when quit is called multiple times on the remote + # browser + pass def wait_for(self, value, by=By.ID, **kwargs): """Waits for an element according to the passed ByClause @@ -52,7 +57,7 @@ def goto(self, url): # Contract must_be(url, "url", str) # - value = super(Browser, self).get(url) + value = super(BrowserMixin, self).get(url) page_title = self.title if page_title in {'404 Not Found'}: raise NavigationError(page_title) @@ -222,14 +227,27 @@ def _text_is_present(self, text): self.find_element_by_tag_name('body').text.index(text) -class Browser(object): - def __init__(*args, **kwargs): - return ChromeBrowser(*args, **kwargs) +class RemoteBrowser(BrowserMixin, Remote): + def __init__(self, scenario, selenium_host, app_host=None, app_port=None, + pages=None): + + must_be(app_host, "app_host", (type(None), str)) + must_be(app_port, "app_port", (type(None), Number)) + must_be(pages, "pages", (dict, type(None))) + must_be(selenium_host, "selenium_host", str) + + if pages is not None: + for key, value in pages.items(): + must_be(key, "pages key", str) + must_be(value, "pages value", AppPage) + self.scenario = scenario + self.pages = pages -class RemoteBrowser(BrowserMixin): - def __init__(self, scenario, app_host=None, app_port=None, pages=None): - pass + super(RemoteBrowser, self).__init__( + desired_capabilities=DesiredCapabilities.CHROME, + command_executor='http://amg-selenium-test.stage.zefr.com:4444/wd/hub') # nopep8 + register_exit(self.quit) class ChromeBrowser(BrowserMixin, Chrome): @@ -267,6 +285,10 @@ def __init__(self, scenario, download_directory=default_download_directory, if executable_path is not None: self.executable_path = executable_path self.pages = pages - super(Browser, self).__init__(executable_path=self.executable_path, - chrome_options=options) + super(ChromeBrowser, self).__init__( + executable_path=self.executable_path, chrome_options=options) register_exit(self.quit) + + +# XXX This is here for backwards compatablity, should be removed later +Browser = ChromeBrowser diff --git a/ngSe/contract.py b/ngSe/contract.py index 2d413b5..52198cc 100644 --- a/ngSe/contract.py +++ b/ngSe/contract.py @@ -2,6 +2,7 @@ # Here is where we put things that help fulfill this need. Optimally this # will eventually be its own package. + # TODO[TJ]: This can, and should, be sliced off into its own library def must_be(what, name, types): if isinstance(what, types): From e7c24d9ca3ff09a69f01e377dd1b1f614763e95c Mon Sep 17 00:00:00 2001 From: Mark Schumacher Date: Wed, 7 Dec 2016 17:07:53 -0800 Subject: [PATCH 5/5] add RemoteBrowser parameters to varialbes --- ngSe/browser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngSe/browser.py b/ngSe/browser.py index c00a2ff..4c68eb8 100644 --- a/ngSe/browser.py +++ b/ngSe/browser.py @@ -243,6 +243,8 @@ def __init__(self, scenario, selenium_host, app_host=None, app_port=None, self.scenario = scenario self.pages = pages + self.app_host = app_host + self.app_port = app_port super(RemoteBrowser, self).__init__( desired_capabilities=DesiredCapabilities.CHROME,