|
14 | 14 | from selenium.webdriver.support.ui import WebDriverWait
|
15 | 15 | import pytest
|
16 | 16 |
|
| 17 | +from codebender_testing.config import BASE_URL |
17 | 18 | from codebender_testing.config import ELEMENT_FIND_TIMEOUT
|
18 | 19 | from codebender_testing.config import TEST_CREDENTIALS
|
19 | 20 | from codebender_testing.config import TEST_PROJECT_NAME
|
| 21 | +from codebender_testing.config import WEBDRIVERS |
20 | 22 |
|
21 | 23 |
|
22 | 24 | # Time to wait until we give up on a DOM property becoming available.
|
@@ -81,28 +83,58 @@ def temp_copy(fname):
|
81 | 83 | yield copy
|
82 | 84 |
|
83 | 85 |
|
84 |
| -class SeleniumTestCase(object): |
85 |
| - """Base class for all Selenium tests.""" |
| 86 | +class CodebenderSeleniumBot(object): |
| 87 | + """Contains various utilities for navigating the Codebender website.""" |
86 | 88 |
|
87 | 89 | # This can be configured on a per-test case basis to use a different
|
88 | 90 | # URL for testing; e.g., http://localhost, or http://codebender.cc.
|
89 | 91 | # It is set via command line option in _testcase_attrs (below)
|
90 | 92 | site_url = None
|
91 | 93 |
|
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) |
99 | 100 | """
|
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] |
102 | 104 |
|
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 |
106 | 138 |
|
107 | 139 | def open(self, url=None):
|
108 | 140 | """Open the resource specified by `url`.
|
@@ -153,6 +185,21 @@ def get_element(self, *locator):
|
153 | 185 | expected_conditions.visibility_of_element_located(locator))
|
154 | 186 | return self.driver.find_element(*locator)
|
155 | 187 |
|
| 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 | + |
156 | 203 | def delete_project(self, project_name):
|
157 | 204 | """Deletes the project specified by `project_name`. Note that this will
|
158 | 205 | navigate to the user's homepage."""
|
@@ -234,14 +281,32 @@ def compile_all_sketches(self, url, selector, iframe=False, logfile=None):
|
234 | 281 |
|
235 | 282 | def execute_script(self, script, *deps):
|
236 | 283 | """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.""" |
239 | 285 | if len(deps) > 0:
|
240 | 286 | WebDriverWait(self.driver, DOM_PROPERTY_DEFINED_TIMEOUT).until(
|
241 | 287 | dom_properties_defined(*deps))
|
242 | 288 | return self.driver.execute_script(script)
|
243 | 289 |
|
244 | 290 |
|
| 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 | + |
245 | 310 | class VerificationError(Exception):
|
246 | 311 | """An exception representing a failed verification of a sketch."""
|
247 | 312 | pass
|
|
0 commit comments