From 2eea0911307584e0d1bb227816c46749ec0e6eb7 Mon Sep 17 00:00:00 2001 From: Joshua Fehler Date: Wed, 28 Oct 2020 14:44:07 -0400 Subject: [PATCH] Implement plugin system to register drivers and add hooks into the visit and quit methods. --- splinter/__init__.py | 3 ++ splinter/browser.py | 55 ++++++++++++-------- splinter/driver/webdriver/__init__.py | 7 ++- splinter/hookspecs.py | 35 +++++++++++++ splinter/plugin_manager.py | 9 ++++ tests/test_browser.py | 75 +++++++-------------------- tests/test_hooks.py | 74 ++++++++++++++++++++++++++ 7 files changed, 179 insertions(+), 79 deletions(-) create mode 100644 splinter/hookspecs.py create mode 100644 splinter/plugin_manager.py create mode 100644 tests/test_hooks.py diff --git a/splinter/__init__.py b/splinter/__init__.py index 79898e55a..9d2085a0d 100644 --- a/splinter/__init__.py +++ b/splinter/__init__.py @@ -4,5 +4,8 @@ from splinter.browser import Browser # NOQA +from splinter.plugin_manager import hookimpl, plugins __version__ = "0.14.0" + +__all__ = ['hookimpl', 'plugins'] diff --git a/splinter/browser.py b/splinter/browser.py index e93d2931a..1e35d9090 100644 --- a/splinter/browser.py +++ b/splinter/browser.py @@ -4,6 +4,8 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +import sys + try: from httplib import HTTPException except ImportError: @@ -17,37 +19,42 @@ from splinter.driver.webdriver.remote import WebDriver as RemoteWebDriver from splinter.driver.webdriver.chrome import WebDriver as ChromeWebDriver from splinter.exceptions import DriverNotFoundError +from splinter.plugin_manager import hookimpl, plugins -_DRIVERS = { - "firefox": FirefoxWebDriver, - "remote": RemoteWebDriver, - "chrome": ChromeWebDriver, -} +@hookimpl +def splinter_prepare_drivers(drivers): + """Called before drivers are initialized.""" + drivers["firefox"] = FirefoxWebDriver + drivers["remote"] = RemoteWebDriver + drivers["chrome"] = ChromeWebDriver + try: + from splinter.driver.zopetestbrowser import ZopeTestBrowser -try: - from splinter.driver.zopetestbrowser import ZopeTestBrowser + drivers["zope.testbrowser"] = ZopeTestBrowser + except ImportError: + pass - _DRIVERS["zope.testbrowser"] = ZopeTestBrowser -except ImportError: - pass + try: + import django # noqa + from splinter.driver.djangoclient import DjangoClient -try: - import django # noqa - from splinter.driver.djangoclient import DjangoClient + drivers["django"] = DjangoClient + except ImportError: + pass - _DRIVERS["django"] = DjangoClient -except ImportError: - pass + try: + import flask # noqa + from splinter.driver.flaskclient import FlaskClient + drivers['flask'] = FlaskClient + except ImportError: + pass -try: - import flask # noqa - from splinter.driver.flaskclient import FlaskClient + return drivers - _DRIVERS["flask"] = FlaskClient -except ImportError: - pass + +plugins.register(sys.modules[__name__]) def get_driver(driver, retry_count=3, *args, **kwargs): @@ -81,9 +88,11 @@ def Browser(driver_name="firefox", retry_count=3, *args, **kwargs): # NOQA: N80 function will raise a :class:`splinter.exceptions.DriverNotFoundError` exception. """ + drivers = {} + plugins.hook.splinter_prepare_drivers(drivers=drivers) try: - driver = _DRIVERS[driver_name] + driver = drivers[driver_name] except KeyError: raise DriverNotFoundError("No driver for %s" % driver_name) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index d58704c07..96c8cac3e 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -32,8 +32,8 @@ from splinter.driver.find_links import FindLinks from splinter.driver.xpath_utils import _concat_xpath_from_str from splinter.element_list import ElementList - from splinter.exceptions import ElementDoesNotExist +from splinter.plugin_manager import plugins if sys.version_info[0] > 2: @@ -290,6 +290,8 @@ def __init__(self, wait_time=2): self.links = FindLinks(self) + self.hook = plugins.hook + def __enter__(self): return self @@ -314,6 +316,7 @@ def status_code(self): def visit(self, url): self.driver.get(url) + self.hook.splinter_after_visit(browser=self) def back(self): self.driver.back() @@ -684,6 +687,8 @@ def select_by_text(self, name, text): ).first._element.click() def quit(self): + self.hook.splinter_before_quit(browser=self) + try: self.driver.quit() except WebDriverException: diff --git a/splinter/hookspecs.py b/splinter/hookspecs.py new file mode 100644 index 000000000..903750f18 --- /dev/null +++ b/splinter/hookspecs.py @@ -0,0 +1,35 @@ +import pluggy + + +hookspec = pluggy.HookspecMarker('splinter') + + +@hookspec +def splinter_after_visit(browser): + """Is run after browser.visit() + + Arguments: + browser (Browser): Current browser instance. + """ + pass + + +@hookspec +def splinter_before_quit(browser): + """Is run before browser.quit(). + + Arguments: + browser (Browser): Current browser instance. + """ + pass + + +@hookspec +def splinter_prepare_drivers(drivers): + # type(Dict[str, Callable]) -> Dict[str, Callable] + """Called before a driver is initialized. + + Arguments: + drivers: Dict containing name: driver mappings + """ + pass diff --git a/splinter/plugin_manager.py b/splinter/plugin_manager.py new file mode 100644 index 000000000..cffa8ce38 --- /dev/null +++ b/splinter/plugin_manager.py @@ -0,0 +1,9 @@ +import pluggy + +from splinter import hookspecs + + +hookimpl = pluggy.HookimplMarker('splinter') + +plugins = pluggy.PluginManager('splinter') +plugins.add_hookspecs(hookspecs) diff --git a/tests/test_browser.py b/tests/test_browser.py index f07004fde..52ed45e7a 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -4,63 +4,24 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -try: - import __builtin__ as builtins -except ImportError: - import builtins -import unittest -from imp import reload - from splinter.exceptions import DriverNotFoundError from selenium.common.exceptions import WebDriverException import pytest -from .fake_webapp import EXAMPLE_APP - +import splinter -class BrowserTest(unittest.TestCase): - def patch_driver(self, pattern): - self.old_import = builtins.__import__ - def custom_import(name, *args, **kwargs): - if pattern in name: - return None - return self.old_import(name, *args, **kwargs) +def test_browser_can_still_be_imported_from_splinters_browser_module(): + from splinter.browser import Browser # NOQA - builtins.__import__ = custom_import - def unpatch_driver(self, module): - builtins.__import__ = self.old_import - reload(module) - - def browser_can_change_user_agent(self, webdriver): +def test_should_raise_an_exception_when_browser_driver_is_not_found(): + with pytest.raises(DriverNotFoundError): from splinter import Browser - browser = Browser(driver_name=webdriver, user_agent="iphone") - browser.visit(EXAMPLE_APP + "useragent") - result = "iphone" in browser.html - browser.quit() - - return result - - def test_brower_can_still_be_imported_from_splinters_browser_module(self): - from splinter.browser import Browser # NOQA - - def test_should_work_even_without_zope_testbrowser(self): - self.patch_driver("zope") - from splinter import browser - - reload(browser) - self.assertNotIn("zope.testbrowser", browser._DRIVERS) - self.unpatch_driver(browser) - - def test_should_raise_an_exception_when_browser_driver_is_not_found(self): - with self.assertRaises(DriverNotFoundError): - from splinter import Browser - - Browser("unknown-driver") + Browser("unknown-driver") @pytest.mark.parametrize('browser_name', ['chrome', 'firefox']) @@ -75,16 +36,23 @@ def test_local_driver_not_present(browser_name): def test_driver_retry_count(): - """Checks that the retry count is being used""" - from splinter.browser import _DRIVERS from splinter import Browser + global test_retry_count - def test_driver(*args, **kwargs): - global test_retry_count - test_retry_count += 1 - raise IOError("test_retry_count: " + str(test_retry_count)) - _DRIVERS["test_driver"] = test_driver + class TestDriverPlugin: + """Add a test driver that can return how many times it retried.""" + @splinter.hookimpl + def splinter_prepare_drivers(self, drivers): + + def test_driver(*args, **kwargs): + global test_retry_count + test_retry_count += 1 + raise IOError("test_retry_count: {}".format(str(test_retry_count))) + + drivers['test_driver'] = test_driver + + splinter.plugins.register(TestDriverPlugin()) test_retry_count = 0 with pytest.raises(IOError) as e: @@ -95,6 +63,3 @@ def test_driver(*args, **kwargs): with pytest.raises(IOError) as e: Browser("test_driver", retry_count=10) assert "test_retry_count: 10" == str(e.value) - - del test_retry_count - del _DRIVERS["test_driver"] diff --git a/tests/test_hooks.py b/tests/test_hooks.py new file mode 100644 index 000000000..758e95a6f --- /dev/null +++ b/tests/test_hooks.py @@ -0,0 +1,74 @@ +from .base import get_browser +from .fake_webapp import EXAMPLE_APP + +import splinter + + +class DummyPlugin: + """Bare bones dummy pluggin.""" + @splinter.hookimpl + def splinter_after_visit(self, browser): + return 'after visit' + + @splinter.hookimpl + def splinter_before_quit(self, browser): + return 'before quit' + + +class DummyPlugin2: + """Dummy plugin with effects that can be seen.""" + @splinter.hookimpl + def splinter_after_visit(self, browser): + browser.execute_script('document.foo = "after visit"') + + @splinter.hookimpl + def splinter_before_quit(self, browser): + splinter.foo = 'bar' + + +splinter.plugins.register(DummyPlugin()) +splinter.plugins.register(DummyPlugin2()) + + +def test_splinter_after_visit_register(request): + """Hook should be registered.""" + + browser = get_browser('chrome') + request.addfinalizer(browser.quit) + + results = browser.hook.splinter_after_visit(browser=browser) + + assert ['after visit'] == results + + +def test_splinter_before_quit_register(request): + """Hook should be registered.""" + + browser = get_browser('chrome') + request.addfinalizer(browser.quit) + + results = browser.hook.splinter_before_quit(browser=browser) + + assert ['before quit'] == results + + +def test_splinter_after_visit(request): + """Hooks should run correctly.""" + + browser = get_browser('chrome') + request.addfinalizer(browser.quit) + + browser.visit(EXAMPLE_APP) + + result = browser.evaluate_script("document.foo") + + assert 'after visit' == result + + +def test_splinter_before_quit(request): + """Hooks should run correctly.""" + + browser = get_browser('chrome') + browser.quit() + + assert splinter.foo == 'bar'