Skip to content

Commit 1506af7

Browse files
committed
Merge remote-tracking branch 'origin/bduffany-docker-2' into development
2 parents c01c943 + ce6cba8 commit 1506af7

File tree

15 files changed

+327
-81
lines changed

15 files changed

+327
-81
lines changed

Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Dockerfile to build container with Codebender selenium tests.
2+
3+
FROM ubuntu:12.04
4+
# TODO: add MAINTAINER
5+
6+
# Install requirements and their dependencies
7+
RUN apt-get update && apt-get install -y \
8+
python \
9+
python-setuptools
10+
11+
RUN easy_install pip
12+
RUN pip install -U setuptools
13+
14+
# Add source code and install dependencies
15+
RUN mkdir -p /opt/codebender
16+
ADD . /opt/codebender/seleniumTests
17+
WORKDIR /opt/codebender/seleniumTests
18+
RUN pip install -r requirements-dev.txt
19+
20+
# Specify a default command for the container.
21+
# Right now we simply run bash. TODO: add ENTRYPOINT for running tests.
22+
WORKDIR /opt/codebender/seleniumTests
23+
CMD ["/bin/bash"]

README.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,50 @@
11
# Codebender Selenium Tests
22

33
This repo contains Selenium tests for the codebender website. The tests are
4-
written in Python 3, and utilize pytest as a testing framework and Selenium
4+
written in Python 2, and utilize pytest as a testing framework and Selenium
55
for browser automation.
66

77
## Running Tests
88

9-
### Dependencies
9+
Tests are run by invoking `tox`. Run `tox --help` to see all the
10+
Codebender-specific arguments that can be passed to `py.test`.
1011

11-
To run these tests, you'll need to have Python 3 installed. In addition, it is
12-
advantageous to have pip, a package manager for Python, in order to install
13-
dependencies.
12+
In addition to these arguments, there are certain environment variables that
13+
should be set when running tests:
1414

15-
Notably, the pip2 (for Python 2) and pip3 (for Python 3) packages both attempt
16-
to link `/usr/local/bin/pip` to the `pip2` or `pip3` executable, respectively.
17-
To deal with this, you could explicitly type out `pip3` or `pip2` instead of
18-
`pip` whenever you use pip via the command line. (It may be best to just remove
19-
`/usr/local/bin/pip` entirely).
15+
- `CODEBENDER_SELENIUM_HUB_URL`: the URL of the Selenium Hub. If you are using
16+
SauceLabs, the URL has the following format:
17+
`http://{USERNAME}:{ACCESS_KEY}@ondemand.saucelabs.com:80/wd/hub`. You can
18+
also use a [docker-selenium](https://github.com/SeleniumHQ/docker-selenium)
19+
hub. In that case, it is necessary to link the docker-selenium instance to the
20+
Docker instance from which tests are running.
21+
- `CODEBENDER_TEST_USER`: username that the webdriver will use to log into the
22+
site in order to perform tests.
23+
- `CODEBENDER_TEST_PASS`: password for `CODEBENDER_TEST_USER`.
2024

21-
To install `pip` in Ubuntu, run `$ sudo apt-get install python3
22-
python3-setuptools`, then `$ sudo easy_install3 pip`.
25+
Rather than invoking `tox` directly, the easiest way to run tests is with
26+
Docker. If you are not familiar with Docker, please consult the
27+
[documentation](http://docs.docker.com/) for an introduction.
2328

24-
After getting set up with pip and cloning the seleniumTests repo, you should
25-
make sure to install all the seleniumTests dependencies by `cd`ing to your local
26-
clone of the repo and running `$ sudo pip3 install -r requirements-dev.txt`.
29+
First, build the image with `$ docker build . -t codebender/selenium`.
2730

28-
### Invoking Tests via `tox`
31+
Then invoke `tox` via `docker run`. Here is a sample command to run all tests,
32+
where the Codebender server is running at `http://192.168.1.2:8080`:
2933

30-
After installing dependencies, you should have the `tox` command available. To
31-
run all of the tests, you can simply run `$ tox` from within the cloned repo.
34+
```
35+
$ docker run -e CODEBENDER_SELENIUM_HUB_URL=http://johndoe:[email protected]:80/wd/hub \
36+
-e CODEBENDER_TEST_USER=tester \
37+
-e CODEBENDER_TEST_PASS=1234 \
38+
-it codebender/selenium \
39+
tox -- --url http://192.168.1.2:8080 --source bachelor
40+
```
3241

33-
You can also run individual tests by providing the appropriate directory or
34-
filename as an argument, for example: `$ tox tests/sketch`.
42+
### Running Tests Manually
3543

36-
Invoking tox will also run `flake8`, which is essentially a lint checker for
37-
Python. It is best to fix any issues reported by `flake8` before committing
38-
to the repo. It can be run on its own via the command `$ flake8`.
44+
The recommended way of running tests is with Docker. If you would like to
45+
manually provision your machine to be able to run tests, you can use the
46+
Dockerfile as a step-by-step guide for provisioning. Then invoke `tox` to run
47+
tests.
3948

4049
#### Specifying a URL for Tests
4150

@@ -105,6 +114,11 @@ The most important class is `SeleniumTestCase`, which all of the unit test cases
105114
inherit from. This grants them access (via `self`) to a number of methods and
106115
attributes that are useful for performing codebender-specific actions.
107116

117+
**`codebender_testing/capabilities.yaml`** defines a list of `capabilities` to
118+
be passed as arguments when instantiating remote webdrivers. In particular, it
119+
specifies the web browsers that we would like to use. Consult this file for more
120+
information.
121+
108122
#### `batch/`
109123

110124
The `batch/` directory contains any executable scripts not directly used to

batch/requirements-batch.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Requirements for running batch scripts.
2+
3+
# If lxml fails to install in Ubuntu, install the following dependencies with
4+
# apt-get: libxml2-dev libxslt1-dev python3-dev
5+
6+
lxml

codebender_testing/capabilities.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This file contains a list of capabilities which will be used to instantiate
2+
# the remote selenium webdrivers.
3+
# Each list entry will cause the entire test suite to be run for a remote
4+
# webdriver with the capabilities specified in the entry.
5+
6+
# See here for more on test configuration:
7+
# https://docs.saucelabs.com/reference/test-configuration
8+
9+
- browserName: "firefox"
10+
- browserName: "chrome"
11+
version: 41

codebender_testing/config.py

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
22

33
from selenium import webdriver
4+
from selenium.webdriver import chrome
5+
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
6+
import yaml
47

58

69
def _rel_path(*args):
@@ -12,6 +15,10 @@ def _rel_path(*args):
1215
# URL of the actual Codebender website
1316
LIVE_SITE_URL = "http://codebender.cc"
1417

18+
# Names of sources (i.e. repositories) used to generate the codebender site.
19+
SOURCE_BACHELOR = 'bachelor'
20+
SOURCE_CODEBENDER_CC = 'codebender_cc'
21+
1522
# User whose projects we'd like to compile in our compile_tester
1623
# test case(s).
1724
COMPILE_TESTER_URL = "/user/cb_compile_tester"
@@ -29,6 +36,14 @@ def _rel_path(*args):
2936

3037
_EXTENSIONS_DIR = _rel_path('..', 'extensions')
3138
_FIREFOX_EXTENSION_FNAME = 'codebender.xpi'
39+
_CHROME_EXTENSION_FNAME = 'codebendercc-extension.crx'
40+
41+
# Maximum version number that we can use the Chrome extension with.
42+
# For versions higher than this, we need to use the newer Codebender app
43+
CHROME_EXT_MAX_CHROME_VERSION = 41
44+
45+
# Path to YAML file specifying capability list
46+
DEFAULT_CAPABILITIES_FILE_PATH = _rel_path('capabilities.yaml')
3247

3348
# Files used for testing
3449
TEST_DATA_DIR = _rel_path('..', 'test_data')
@@ -51,18 +66,72 @@ def _get_firefox_profile():
5166
)
5267
return firefox_profile
5368

54-
# Webdrivers to be used for testing. Specifying additional webdrivers here
55-
# will cause every test to be re-run using that webdriver.
56-
# These webdrivers are specified as lambdas to allow for "lazy" evaluation.
57-
# The lambda invocation will return the actual webdriver and open up a
58-
# browser window, and we don't want a browser window to open whenever this
59-
# module is imported (hence the need for lazy evaluation).
60-
WEBDRIVERS = {
61-
"firefox": lambda: webdriver.Firefox(firefox_profile=_get_firefox_profile()),
62-
# "chrome": lambda: webdriver.Chrome()
63-
}
69+
def get_browsers(capabilities_file_path=None):
70+
"""Returns a list of capabilities. Each item in the list will cause
71+
the entire suite of tests to be re-run for a browser with those
72+
particular capabilities.
73+
74+
`capabilities_file_path` is a path to a YAML file specifying a list of
75+
capabilities for each browser. "Capabilities" are the dictionaries
76+
passed as the `desired_capabilities` argument to the webdriver constructor.
77+
"""
78+
if capabilities_file_path is None:
79+
capabilities_file_path = DEFAULT_CAPABILITIES_FILE_PATH
80+
stream = file(capabilities_file_path, 'rb')
81+
return yaml.load(stream)
82+
83+
84+
def create_webdriver(command_executor, desired_capabilities):
85+
"""Creates a new remote webdriver with the following properties:
86+
- The remote URL of the webdriver is defined by `command_executor`.
87+
- desired_capabilities is a dict with the same interpretation as
88+
it is used elsewhere in selenium. If no browserName key is present,
89+
we default to firefox.
90+
"""
91+
if 'browserName' not in desired_capabilities:
92+
desired_capabilities['browserName'] = 'firefox'
93+
browser_name = desired_capabilities['browserName']
94+
# Fill in defaults from DesiredCapabilities.{CHROME,FIREFOX} if they are
95+
# missing from the desired_capabilities dict above.
96+
_capabilities = desired_capabilities
97+
browser_profile = None
98+
99+
if browser_name == "chrome":
100+
desired_capabilities = DesiredCapabilities.CHROME.copy()
101+
desired_capabilities.update(_capabilities)
102+
103+
# NOTE: the following logic is disabled since the remote webdriver is
104+
# not properly installing the codebender extension. It is kept for
105+
# reference until we can figure out how to properly add the Chrome
106+
# extension.
107+
108+
# # Add chrome extension to capabilities
109+
# options = chrome.options.Options()
110+
# options.add_extension(os.path.join(_EXTENSIONS_DIR, _CHROME_EXTENSION_FNAME))
111+
# desired_capabilities.update(options.to_capabilities())
112+
# # Right now we only support up to v41 for this testing suite.
113+
# if "version" in desired_capabilities:
114+
# if desired_capabilities["version"] > CHROME_EXT_MAX_CHROME_VERSION:
115+
# raise ValueError("The testing suite only supports Chrome versions up to v%d, "
116+
# "but v%d was specified. Please specify a lower version "
117+
# "number." % (CHROME_EXT_MAX_CHROME_VERSION, desired_capabilities["version"]))
118+
# else:
119+
# desired_capabilities["version"] = CHROME_EXT_MAX_CHROME_VERSION
120+
121+
elif browser_name == "firefox":
122+
desired_capabilities = DesiredCapabilities.FIREFOX.copy()
123+
desired_capabilities.update(_capabilities)
124+
browser_profile = _get_firefox_profile()
125+
else:
126+
raise ValueError("Invalid webdriver %s (only chrome and firefox are supported)" % browser_name)
127+
return webdriver.Remote(
128+
command_executor=command_executor,
129+
desired_capabilities=desired_capabilities,
130+
browser_profile=browser_profile,
131+
)
132+
64133

65-
# Credentials to use when logging into the site via selenium
134+
# Credentials to use when logging into the bachelor site
66135
TEST_CREDENTIALS = {
67136
"username": "tester",
68137
"password": "testerPASS"
@@ -71,4 +140,4 @@ def _get_firefox_profile():
71140
TEST_PROJECT_NAME = "test_project"
72141

73142
# How long we wait until giving up on trying to locate an element
74-
ELEMENT_FIND_TIMEOUT = 5
143+
ELEMENT_FIND_TIMEOUT = 10

codebender_testing/utils.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from codebender_testing.config import ELEMENT_FIND_TIMEOUT
2121
from codebender_testing.config import TEST_CREDENTIALS
2222
from codebender_testing.config import TEST_PROJECT_NAME
23-
from codebender_testing.config import WEBDRIVERS
2423

2524

2625
# Time to wait until we give up on a DOM property becoming available.
@@ -92,16 +91,13 @@ class CodebenderSeleniumBot(object):
9291
# It is set via command line option in _testcase_attrs (below)
9392
site_url = None
9493

95-
def start(url=None, webdriver=None):
96-
"""Create the selenium webdriver, operating on `url`. We can't do this
97-
in an __init__ method, otherwise py.test complains about
98-
SeleniumTestCase having an init method.
99-
The webdriver that is created is specified as a key into the WEBDRIVERS
100-
dict (in codebender_testing.config)
94+
def init(self, url=None, webdriver=None):
95+
"""Create a bot with the given selenium webdriver, operating on `url`.
96+
We can't do this in an __init__ method, otherwise py.test complains,
97+
presumably because it does something special with __init__ for test
98+
cases.
10199
"""
102-
if webdriver is None:
103-
webdriver = WEBDRIVERS.keys()[0]
104-
self.driver = WEBDRIVERS[webdriver]()
100+
self.driver = webdriver
105101

106102
if url is None:
107103
url = BASE_URL
@@ -186,17 +182,22 @@ def upload_project(self, test_fname, project_name=None):
186182

187183
return last_project.text, last_project.get_attribute('href')
188184

189-
def login(self):
190-
"""Performs a login."""
185+
def login(self, credentials=None):
186+
"""Performs a login. Note that the current URL may change to an
187+
unspecified location when calling this function.
188+
`credentials` should be a dict with keys 'username' and 'password',
189+
mapped to the appropriate values."""
190+
if credentials is None:
191+
credentials = TEST_CREDENTIALS
191192
try:
192193
self.open()
193194
login_button = self.driver.find_element_by_id('login_btn')
194195
login_button.send_keys(Keys.ENTER)
195196
# Enter credentials and log in
196197
user_field = self.driver.find_element_by_id('username')
197-
user_field.send_keys(TEST_CREDENTIALS['username'])
198+
user_field.send_keys(credentials['username'])
198199
pass_field = self.driver.find_element_by_id('password')
199-
pass_field.send_keys(TEST_CREDENTIALS['password'])
200+
pass_field.send_keys(credentials['password'])
200201
do_login = self.driver.find_element_by_id('_submit')
201202
do_login.send_keys(Keys.ENTER)
202203
except NoSuchElementException:
@@ -238,6 +239,8 @@ def delete_project(self, project_name):
238239
"""Deletes the project specified by `project_name`. Note that this will
239240
navigate to the user's homepage."""
240241
self.open('/')
242+
# Scroll to the bottom so that the footer doesn't obscure anything
243+
self.execute_script("window.scrollTo(0, document.body.scrollHeight);")
241244
created_project = self.get_element(By.LINK_TEXT, project_name)
242245
delete_button_li = created_project.find_element_by_xpath('..')
243246
delete_button = delete_button_li.find_element_by_css_selector('button:last-child')
@@ -344,11 +347,15 @@ def _testcase_attrs(cls, webdriver, testing_url, testing_full):
344347
cls.run_full_compile_tests = testing_full
345348

346349
@pytest.fixture(scope="class")
347-
def tester_login(self):
348-
self.login()
350+
def tester_login(self, testing_credentials):
351+
"""A fixture to perform a login with the credentials provided by the
352+
`testing_credentials` fixture.
353+
"""
354+
self.login(credentials=testing_credentials)
349355

350356
@pytest.fixture(scope="class")
351357
def tester_logout(self):
358+
"""A fixture to guarantee that we are logged out before running a test."""
352359
self.logout()
353360

354361

extensions/codebender-app.crx

49 KB
Binary file not shown.

extensions/codebendercc-extension.crx

4.41 MB
Binary file not shown.

requirements-dev.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
flake8
2-
lxml
32
pytest
43
setuptools>=12.1
54
tox

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
flake8
22
pytest==2.6.4
3+
pyyaml
34
selenium==2.44.0

0 commit comments

Comments
 (0)