Skip to content

Commit a2a9255

Browse files
committed
Merge pull request #22 from codebendercc/seleniumbender-improvements
Seleniumbender improvements
2 parents edcadb6 + 2d7da32 commit a2a9255

File tree

10 files changed

+177
-67
lines changed

10 files changed

+177
-67
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
__pycache__/
22
.cache/
33
bin/env_vars.sh
4+
bin/config.cfg
45
.tox
56
*.pyc
67
*.swp

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ The most important class is `SeleniumTestCase`, which all of the unit test cases
114114
inherit from. This grants them access (via `self`) to a number of methods and
115115
attributes that are useful for performing codebender-specific actions.
116116

117-
**`codebender_testing/capabilities.yaml`** defines a list of `capabilities` to
117+
**`codebender_testing/capabilities_{firefox, chrome}.yaml`** defines a list of `capabilities` to
118118
be passed as arguments when instantiating remote webdrivers. In particular, it
119119
specifies the web browsers that we would like to use. Consult this file for more
120120
information.
@@ -141,4 +141,3 @@ compile.
141141
The `logs/` directory contains the results of running certain tests, e.g.
142142
whether certain sets of sketches have compiled successfully (see "Compilation
143143
Logs").
144-

bin/config.cfg.template

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[common]
2+
CAPABILITIES: capabilities_firefox.yaml
3+
SAUCELABS_USER: ~
4+
SAUCELABS_KEY: ~
5+
SAUCELABS_HUB_URL: http://SAUCELABS_USER:[email protected]:80/wd/hub
6+
LOCAL_HUB_URL: http://127.0.0.1:4444/wd/hub
7+
DISQUS_SSO_ID: ~
8+
DISQUS_SSO_USERNAME: ~
9+
DISQUS_SSO_EMAIL: ~
10+
CODEBENDER_TEST_USER: ~
11+
CODEBENDER_TEST_PASS: ~
12+
EMAIL: ~
13+
ROOTDIR: ~
14+
15+
[live]
16+
DISQUS_ACCESS_TOKEN: ~
17+
DISQUS_API_SECRET: ~
18+
DISQUS_API_PUBLIC: ~
19+
DISQUS_FORUM: ~
20+
AUTHOR_URL: ~
21+
22+
[staging]
23+
DISQUS_ACCESS_TOKEN: ~
24+
DISQUS_API_SECRET: ~
25+
DISQUS_API_PUBLIC: ~
26+
DISQUS_FORUM: ~
27+
AUTHOR_URL: ~
28+
29+
[local]
30+
CODEBENDER_TEST_USER: ~
31+
CODEBENDER_TEST_PASS: ~

bin/env_vars.sh.template

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/bin/bash
22

3-
export CAPABILITIES='capabilities_firefox.yaml'
3+
export CAPABILITIES="capabilities_firefox.yaml"
44

55
SAUCELABS_USER=""
66
SAUCELABS_KEY=""
7-
export CODEBENDER_SELENIUM_HUB_URL=http://${SAUCELABS_USER}:${SAUCELABS_KEY}@ondemand.saucelabs.com:80/wd/hub
7+
export SAUCELABS_HUB_URL="http://${SAUCELABS_USER}:${SAUCELABS_KEY}@ondemand.saucelabs.com:80/wd/hub"
8+
export LOCAL_HUB_URL="http://127.0.0.1:4444/wd/hub"
9+
CODEBENDER_SELENIUM_HUB_URL=$LOCAL_HUB_URL
810

911
export CODEBENDER_TEST_USER=""
1012
export CODEBENDER_TEST_PASS=""

bin/seleniumbender.py

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
#!/usr/bin/env python
22

33
from __future__ import print_function
4+
import ConfigParser
45
import argparse
56
import os
67
import re
78
import subprocess
89
import sys
910
import time
11+
import yaml
1012

1113
SHELL='/bin/bash'
12-
SOURCE = 'codebender_cc'
1314

1415
class Tests:
1516
def __init__(self, url, environment):
16-
self.source=SOURCE
1717
self.url = url
1818
self.environment = os.path.abspath(environment)
1919
self.email = os.getenv('EMAIL', '[email protected]')
@@ -39,19 +39,17 @@ def run(self, operation, libraries=None):
3939
elif operation == 'staging':
4040
self.staging()
4141

42-
def run_command(self, command, user_agent=None):
43-
if user_agent:
44-
os.environ['SELENIUM_USER_AGENT'] = user_agent
42+
def run_command(self, command):
4543
command = ' '.join(command)
4644
print('command:', command)
4745
return subprocess.call(command, shell=True, executable=SHELL)
4846

4947
def send_mail_no_logs(self, identifier):
5048
command = ['mail', '-s', '"Selenium Tests: {identifier} Failed To Run" {email} <<< "Something went wrong with {identifier} tests. Please check the logs."'.format(identifier=identifier, email=self.email)]
51-
run_command(command)
49+
self.run_command(command)
5250

5351
def send_mail_with_logs(self, identifier):
54-
default_tests_dir = os.path.normpath(os.path.join(os.getcwd(), '..'))
52+
default_tests_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
5553
root_dir = os.getenv('ROOTDIR', default_tests_dir)
5654

5755
logs = os.path.join(root_dir, 'logs')
@@ -74,12 +72,12 @@ def send_mail_with_logs(self, identifier):
7472
'uuencode "{reports}/{reportfile}" "{reportfile}")'.format(reports=reports, reportfile=reportfile),
7573
'| mail -s "Selenium Tests Report: {identifier} {email_date} Changes: {changes}" {email}'.format(identifier=identifier, email_date=email_date, changes=changes, email=self.email)
7674
]
77-
run_command(command)
75+
self.run_command(command)
7876
except:
7977
pass
8078

8179
def create_command(self, test_directory, *extra_arguments):
82-
return ['tox', 'tests/' + test_directory, '--', '--url={}'.format(TARGETS[self.url]), '--source={}'.format(SOURCE)] + list(extra_arguments)
80+
return ['tox', 'tests/' + test_directory, '--', '--url={}'.format(TARGETS[self.url])] + list(extra_arguments)
8381

8482
def common(self, identifier='common'):
8583
command = self.create_command('common', '--plugin')
@@ -115,14 +113,13 @@ def noplugin(self, identifier = 'noplugin'):
115113
def walkthrough(self, identifier='walkthrough'):
116114
command = self.create_command('walkthrough', '--plugin')
117115
retvals = []
118-
user_agents = [
119-
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0 codebender-selenium',
120-
'Mozilla/5.0 (Windows NT 6.1; rv:43.0) Gecko/20100101 Firefox/43.0 codebender-selenium',
121-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1; rv:43.0) Gecko/20100101 Firefox/43.0 codebender-selenium'
122-
]
123-
for user_agent in user_agents:
124-
retval = self.run_command(command, user_agent=user_agent)
116+
for platform in USER_AGENTS.keys():
117+
for browser in USER_AGENTS[platform].keys():
118+
os.environ['SELENIUM_USER_AGENT_' + browser.upper()] = USER_AGENTS[platform][browser]
119+
os.environ['SELENIUM_PLATFORM'] = platform
120+
retval = self.run_command(command)
125121
retvals.append(retval)
122+
126123
retval = max(retvals)
127124
if retval != 0:
128125
self.send_mail_no_logs(identifier)
@@ -148,6 +145,35 @@ def staging(self):
148145
'local': 'http://dev.codebender.cc'
149146
}
150147

148+
PLATFORMS = {
149+
'Linux': 'X11; Ubuntu; Linux x86_64',
150+
'Windows 7': 'Windows NT 6.1',
151+
'OS X 10.11': 'Macintosh; Intel Mac OS X 10_11_1'
152+
}
153+
USER_AGENTS = {}
154+
USER_AGENT_IDENTIFIER = 'codebender-selenium'
155+
156+
def generate_user_agents(file_path):
157+
with open(file_path, 'rb') as fp:
158+
capabilities_file = yaml.load(fp)
159+
for section in capabilities_file:
160+
browser = section['browserName']
161+
for platform in PLATFORMS.keys():
162+
if platform == 'Linux':
163+
os.environ['SELENIUM_PLATFORM'] = platform
164+
version = section['version']
165+
if browser == 'chrome':
166+
user_agent = 'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.2564.109 Safari/537.36 {identifier}'.format(platform=PLATFORMS[platform], version=version, identifier=USER_AGENT_IDENTIFIER)
167+
if platform == 'Linux':
168+
os.environ['SELENIUM_USER_AGENT_CHROME'] = user_agent
169+
elif browser == 'firefox':
170+
user_agent = 'Mozilla/5.0 ({platform}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0 {identifier}'.format(platform=PLATFORMS[platform], version=version, identifier=USER_AGENT_IDENTIFIER)
171+
if platform == 'Linux':
172+
os.environ['SELENIUM_USER_AGENT'] = user_agent
173+
174+
USER_AGENTS.setdefault(platform, {})
175+
USER_AGENTS[platform][browser] = user_agent
176+
151177
def main():
152178
available_operations = ['{operation}\t{description}'.format(operation=x, description=OPERATIONS[x]) for x in sorted(OPERATIONS.keys())]
153179
available_targets = ['{target}\t{url}'.format(target=x, url=TARGETS[x]) for x in sorted(TARGETS.keys())]
@@ -161,7 +187,7 @@ def main():
161187
default='live',
162188
help='Target site for the tests.\nAvailable targets (default: live):\n\t{targets}'.format(targets='\n\t'.join(available_targets)))
163189
parser.add_argument('--config',
164-
default='env_vars.sh',
190+
default='config.cfg',
165191
help='Configuration file to load (default: config.cfg).')
166192
parser.add_argument('--libraries',
167193
default=None,
@@ -170,6 +196,9 @@ def main():
170196
action='store_true',
171197
default=False,
172198
help='Use saucelabs as the Selenium server')
199+
parser.add_argument('--capabilities',
200+
default='capabilities_firefox.yaml',
201+
help='Selenium capabilities file (default: capabilities_firefox.yaml).'.format())
173202

174203
# Parse arguments
175204
args = parser.parse_args()
@@ -185,21 +214,47 @@ def main():
185214
parser.print_help()
186215
sys.exit()
187216

217+
libraries = args.libraries
218+
if operation == 'target' and not libraries:
219+
print('No target libraries specified!\n')
220+
parser.print_help()
221+
sys.exit()
222+
188223
config = args.config
189224
if not os.path.exists(config):
190225
print('Config file:', config, 'does not exist')
191226
sys.exit()
192227

193-
libraries = args.libraries
194-
if operation == 'target' and not libraries:
195-
print('No target libraries specified!\n')
196-
parser.print_help()
228+
# Read config file
229+
config_parser = ConfigParser.RawConfigParser()
230+
config_parser.optionxform = str
231+
sections = ['common', target]
232+
try:
233+
config_parser.read(config)
234+
for section in sections:
235+
for option, value in config_parser.items(section):
236+
if option == 'SAUCELABS_HUB_URL':
237+
saucelabs_user = os.environ['SAUCELABS_USER']
238+
saucelabs_key = os.environ['SAUCELABS_KEY']
239+
value = value.replace('SAUCELABS_USER', saucelabs_user)
240+
value = value.replace('SAUCELABS_KEY', saucelabs_key)
241+
os.environ[option] = value
242+
except:
243+
print('Error parsing config file:', config)
244+
print('Please check the config.cfg.template for the required format')
197245
sys.exit()
198246

199-
# Read environment variables file
200-
output = subprocess.check_output('source {}; env'.format(config), shell=True, executable=SHELL)
201-
env_vars = dict((line.split('=', 1) for line in output.splitlines()))
202-
os.environ.update(env_vars)
247+
os.environ['CODEBENDER_SELENIUM_HUB_URL'] = os.environ['LOCAL_HUB_URL']
248+
if args.saucelabs:
249+
os.environ['CODEBENDER_SELENIUM_HUB_URL'] = os.environ['SAUCELABS_HUB_URL']
250+
251+
capabilities = args.capabilities
252+
if capabilities:
253+
os.environ['CAPABILITIES'] = capabilities
254+
255+
# Generate User agents
256+
file_path = os.path.join(os.path.dirname(__file__), '..', 'codebender_testing', capabilities)
257+
generate_user_agents(file_path)
203258

204259
# Run tests
205260
tests = Tests(target, config)

codebender_testing/capabilities.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@
77
# https://docs.saucelabs.com/reference/test-configuration
88

99
- browserName: "firefox"
10-
version: 42
10+
version: 43
1111
public: "public restricted"
12-
seleniumVersion: "2.48.0"
1312
maxDuration: 10800
1413
- browserName: "chrome"
1514
version: 47
1615
public: "public restricted"
17-
seleniumVersion: "2.50.1"
1816
maxDuration: 10800
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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: "chrome"
10+
version: 47
11+
public: "public restricted"
12+
maxDuration: 10800

codebender_testing/capabilities_firefox.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
# https://docs.saucelabs.com/reference/test-configuration
88

99
- browserName: "firefox"
10-
version: 42
10+
version: 43
1111
public: "public restricted"
12-
seleniumVersion: "2.48.0"
1312
maxDuration: 10800

codebender_testing/config.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import yaml
77
import simplejson
88
import pytest
9-
9+
import string
10+
import random
1011

1112
def _rel_path(*args):
1213
"""Returns a path relative to config.py file's directory."""
@@ -65,7 +66,7 @@ def jsondump(data):
6566
CHROME_EXT_MAX_CHROME_VERSION = 41
6667

6768
# Path to YAML file specifying capability list.
68-
DEFAULT_CAPABILITIES_FILE = os.getenv('CAPABILITIES', 'capabilities.yaml')
69+
DEFAULT_CAPABILITIES_FILE = os.getenv('CAPABILITIES', 'capabilities_firefox.yaml')
6970
DEFAULT_CAPABILITIES_FILE_PATH = _rel_path(DEFAULT_CAPABILITIES_FILE)
7071

7172
# Files used for testing.
@@ -79,16 +80,17 @@ def jsondump(data):
7980
TEST_PROJECT_NAME = "test_project"
8081

8182
TIMEOUT = {
82-
'LOCATE_ELEMENT': 30
83+
'LOCATE_ELEMENT': 30,
84+
'FLASH_FAIL': 30
8385
}
8486

85-
DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0 codebender-selenium'
86-
TESTS_USER_AGENT = os.getenv('SELENIUM_USER_AGENT', DEFAULT_USER_AGENT)
87+
DEFAULT_USER_AGENT_FIREFOX = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0 codebender-selenium'
88+
TESTS_USER_AGENT_FIREFOX = os.getenv('SELENIUM_USER_AGENT', DEFAULT_USER_AGENT_FIREFOX)
8789

8890
DEFAULT_USER_AGENT_CHROME = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36'
8991
TESTS_USER_AGENT_CHROME = os.getenv('SELENIUM_USER_AGENT_CHROME', DEFAULT_USER_AGENT_CHROME)
9092

91-
BROWSER = "firefox"
93+
SELENIUM_BROWSER = 'firefox'
9294

9395
# Set up Selenium Webdrivers to be used for selenium tests.
9496
def _get_firefox_profile():
@@ -103,6 +105,12 @@ def _get_firefox_profile():
103105
)
104106
return firefox_profile
105107

108+
def _get_chrome_profile():
109+
"""Returns the Chrome profile directory to be used for the Chrome webdriver."""
110+
seed = string.ascii_letters + string.digits
111+
profile_hash = ''.join(random.choice(seed) for x in range(6))
112+
return '.org.chromium.Chromium.' + profile_hash
113+
106114
def get_browsers(capabilities_file_path=None):
107115
"""Returns a list of capabilities. Each item in the list will cause
108116
the entire suite of tests to be re-run for a browser with those
@@ -131,34 +139,40 @@ def create_webdriver(command_executor, desired_capabilities):
131139
# Fill in defaults from DesiredCapabilities.{CHROME,FIREFOX} if they are
132140
# missing from the desired_capabilities dict above.
133141
_capabilities = desired_capabilities
142+
platform = os.getenv('SELENIUM_PLATFORM', None)
143+
if platform:
144+
_capabilities['platform'] = platform
134145
browser_profile = None
135146
browser_profile_path = None
136147

148+
SELENIUM_BROWSER = browser_name
149+
137150
if browser_name == "chrome":
138-
BROWSER = "chrome"
139151
desired_capabilities = DesiredCapabilities.CHROME.copy()
140-
desired_capabilities.update(_capabilities)
152+
options = chrome.options.Options()
153+
browser_profile_path = os.path.join('/tmp', _get_chrome_profile())
154+
options.add_argument('--user-data-dir=' + browser_profile_path)
141155
if desired_capabilities["version"] > CHROME_EXT_MAX_CHROME_VERSION:
142-
# Add new chrome extension to capabilities.
143-
options = chrome.options.Options()
156+
# Add the chrome app to capabilities.
144157
options.add_extension(os.path.join(_EXTENSIONS_DIR, _CHROME_APP_FNAME))
145158
options.add_argument("--user-agent=" + TESTS_USER_AGENT_CHROME)
146-
desired_capabilities.update(options.to_capabilities())
147-
desired_capabilities.update(_capabilities)
148159
else:
149160
raise ValueError("The testing suite only supports Chrome versions greater than v%d, "
150161
"but v%d was specified. Please specify a higher version number."
151162
% (CHROME_EXT_MAX_CHROME_VERSION, desired_capabilities["version"]))
163+
desired_capabilities.update(options.to_capabilities())
164+
desired_capabilities.update(_capabilities)
152165

153166
elif browser_name == "firefox":
154167
desired_capabilities = DesiredCapabilities.FIREFOX.copy()
155168
desired_capabilities.update(_capabilities)
156169
browser_profile = _get_firefox_profile()
157170
browser_profile_path = browser_profile.path
158-
browser_profile.set_preference("general.useragent.override", TESTS_USER_AGENT)
171+
browser_profile.set_preference("general.useragent.override", TESTS_USER_AGENT_FIREFOX)
159172
desired_capabilities["firefox_profile"] = browser_profile.update_preferences()
160173
else:
161174
raise ValueError("Invalid webdriver %s (only chrome and firefox are supported)" % browser_name)
175+
162176
return {
163177
'driver': webdriver.Remote(
164178
command_executor=command_executor,

0 commit comments

Comments
 (0)