Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement plugin system #841

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions splinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@

from splinter.browser import Browser # NOQA

from splinter.plugin_manager import hookimpl, plugins

__version__ = "0.14.0"

__all__ = ['hookimpl', 'plugins']
55 changes: 32 additions & 23 deletions splinter/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)

Expand Down
7 changes: 6 additions & 1 deletion splinter/driver/webdriver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -290,6 +290,8 @@ def __init__(self, wait_time=2):

self.links = FindLinks(self)

self.hook = plugins.hook

def __enter__(self):
return self

Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
35 changes: 35 additions & 0 deletions splinter/hookspecs.py
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions splinter/plugin_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pluggy

from splinter import hookspecs


hookimpl = pluggy.HookimplMarker('splinter')

plugins = pluggy.PluginManager('splinter')
plugins.add_hookspecs(hookspecs)
75 changes: 20 additions & 55 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand All @@ -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:
Expand All @@ -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"]
74 changes: 74 additions & 0 deletions tests/test_hooks.py
Original file line number Diff line number Diff line change
@@ -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'