Skip to content

Commit 1d3626f

Browse files
author
Brandon Duffany
committed
Factor core functionality out of SeleniumTestCase
1 parent 7ed3277 commit 1d3626f

File tree

2 files changed

+84
-19
lines changed

2 files changed

+84
-19
lines changed

codebender_testing/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ def _rel_path(*args):
99

1010
# URL of the default site to be used for testing
1111
BASE_URL = "http://localhost"
12+
# URL of the actual Codebender website
13+
LIVE_SITE_URL = "http://codebender.cc"
14+
1215

1316
# User whose projects we'd like to compile in our compile_tester
1417
# test case(s).
@@ -25,9 +28,6 @@ def _rel_path(*args):
2528
# Logfile for /libraries compilation results
2629
LIBRARIES_TEST_LOGFILE = LOGFILE_PREFIX.format(log_name="libraries_test")
2730

28-
# URL of the actual Codebender website
29-
LIVE_SITE_URL = "http://codebender.cc"
30-
3131
_EXTENSIONS_DIR = _rel_path('..', 'extensions')
3232
_FIREFOX_EXTENSION_FNAME = 'codebender.xpi'
3333

codebender_testing/utils.py

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
from selenium.webdriver.support.ui import WebDriverWait
1515
import pytest
1616

17+
from codebender_testing.config import BASE_URL
1718
from codebender_testing.config import ELEMENT_FIND_TIMEOUT
1819
from codebender_testing.config import TEST_CREDENTIALS
1920
from codebender_testing.config import TEST_PROJECT_NAME
21+
from codebender_testing.config import WEBDRIVERS
2022

2123

2224
# Time to wait until we give up on a DOM property becoming available.
@@ -81,28 +83,58 @@ def temp_copy(fname):
8183
yield copy
8284

8385

84-
class SeleniumTestCase(object):
85-
"""Base class for all Selenium tests."""
86+
class CodebenderSeleniumBot(object):
87+
"""Contains various utilities for navigating the Codebender website."""
8688

8789
# This can be configured on a per-test case basis to use a different
8890
# URL for testing; e.g., http://localhost, or http://codebender.cc.
8991
# It is set via command line option in _testcase_attrs (below)
9092
site_url = None
9193

92-
@classmethod
93-
@pytest.fixture(scope="class", autouse=True)
94-
def _testcase_attrs(cls, webdriver, testing_url):
95-
"""Sets up any class attributes to be used by any SeleniumTestCase.
96-
Here, we just store fixtures as class attributes. This allows us to avoid
97-
the pytest boilerplate of getting a fixture value, and instead just
98-
refer to the fixture as `self.<fixture>`.
94+
def start(url=None, webdriver=None):
95+
"""Create the selenium webdriver, operating on `url`. We can't do this
96+
in an __init__ method, otherwise py.test complains about
97+
SeleniumTestCase having an init method.
98+
The webdriver that is created is specified as a key into the WEBDRIVERS
99+
dict (in codebender_testing.config)
99100
"""
100-
cls.driver = webdriver
101-
cls.site_url = testing_url
101+
if webdriver is None:
102+
webdriver = WEBDRIVERS.keys()[0]
103+
self.driver = WEBDRIVERS[webdriver]
102104

103-
@pytest.fixture(scope="class")
104-
def tester_login(self):
105-
self.login()
105+
if url is None:
106+
url = BASE_URL
107+
self.site_url = url
108+
109+
@classmethod
110+
@contextmanager
111+
def session(cls, **kwargs):
112+
"""Start a new session with a new webdriver. Regardless of whether an
113+
exception is raised, the webdriver is guaranteed to quit.
114+
The keyword arguments should be interpreted as in `start`.
115+
116+
Sample usage:
117+
118+
```
119+
with CodebenderSeleniumBot.session(url="localhost",
120+
webdriver="firefox") as bot:
121+
# The browser is now open
122+
bot.open("/")
123+
assert "Codebender" in bot.driver.title
124+
# The browser is now closed
125+
```
126+
127+
Test cases shouldn't need to use this method; it's mostly useful for
128+
scripts, automation, etc.
129+
"""
130+
try:
131+
bot = cls()
132+
bot.start(**kwargs)
133+
yield bot
134+
bot.driver.quit()
135+
except:
136+
bot.driver.quit()
137+
raise
106138

107139
def open(self, url=None):
108140
"""Open the resource specified by `url`.
@@ -153,6 +185,21 @@ def get_element(self, *locator):
153185
expected_conditions.visibility_of_element_located(locator))
154186
return self.driver.find_element(*locator)
155187

188+
def get_elements(self, *locator):
189+
"""Like `get_element`, but returns a list of all elements matching
190+
the selector."""
191+
WebDriverWait(self.driver, ELEMENT_FIND_TIMEOUT).until(
192+
expected_conditions.visibility_of_all_elements_located_by(locator))
193+
return self.driver.find_elements(*locator)
194+
195+
def get(self, selector):
196+
"""Alias for `self.get_element(By.CSS_SELECTOR, selector)`."""
197+
return self.get_element(By.CSS_SELECTOR, selector)
198+
199+
def get_all(self, selector):
200+
"""Alias for `self.get_elements(By.CSS_SELECTOR, selector)`."""
201+
return self.get_elements(By.CSS_SELECTOR, selector)
202+
156203
def delete_project(self, project_name):
157204
"""Deletes the project specified by `project_name`. Note that this will
158205
navigate to the user's homepage."""
@@ -234,14 +281,32 @@ def compile_all_sketches(self, url, selector, iframe=False, logfile=None):
234281

235282
def execute_script(self, script, *deps):
236283
"""Waits for all JavaScript variables in `deps` to be defined, then
237-
executes the given script. Especially useful for waiting for things like
238-
jQuery to become available for use."""
284+
executes the given script."""
239285
if len(deps) > 0:
240286
WebDriverWait(self.driver, DOM_PROPERTY_DEFINED_TIMEOUT).until(
241287
dom_properties_defined(*deps))
242288
return self.driver.execute_script(script)
243289

244290

291+
class SeleniumTestCase(CodebenderSeleniumBot):
292+
"""Base class for all Selenium tests."""
293+
294+
@classmethod
295+
@pytest.fixture(scope="class", autouse=True)
296+
def _testcase_attrs(cls, webdriver, testing_url):
297+
"""Sets up any class attributes to be used by any SeleniumTestCase.
298+
Here, we just store fixtures as class attributes. This allows us to avoid
299+
the pytest boilerplate of getting a fixture value, and instead just
300+
refer to the fixture as `self.<fixture>`.
301+
"""
302+
cls.driver = webdriver
303+
cls.site_url = testing_url
304+
305+
@pytest.fixture(scope="class")
306+
def tester_login(self):
307+
self.login()
308+
309+
245310
class VerificationError(Exception):
246311
"""An exception representing a failed verification of a sketch."""
247312
pass

0 commit comments

Comments
 (0)