Testing with Selenium & Sauce labs (#3321)
* Initial selenium test * Try configuring Travis to run selenium tests on Sauce * Encryption key needs to be for my account, not jupyter * Install selenium on Travis * Get more data from server info file * Set cwd when launching notebook server Will this help on Travis? * Use JUPYTER_TEST_BROWSER=chrome to test with Chrome * Debugging test * Separate fixtures into conftest.py * Try with --Cls.a=b option syntax * Try using sauce labs directly, not through Travis proxy * Back to using proxy, with http instead of https Idea from https://stackoverflow.com/questions/48236104/ssl-errors-using- sauce-labs-in-travis-ci-with-selenium-webriver-tests-django-pr * Specify browserName in desired_capabilities for Sauce * Try connecting to Sauce for only some jobs in matrix * Exclude selenium tests from regular test run * Remove redundant JS test for dashboard navigation (converted to Selenium) * Re-enable other tests * Exclude selenium tests on Appveyor * Later browser versions are available on Windows * Try running tests with Firefox 57 instead of 58 * Try running with local Firefox on Travis * Install geckodriver for Selenium tests * Untar the right version of geckodriver * Try stepping back one version of Firefox againpull/3334/head
parent
4285574b96
commit
aa9c977880
@ -0,0 +1,96 @@
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import requests
|
||||
from subprocess import Popen
|
||||
import sys
|
||||
from testpath.tempdir import TemporaryDirectory
|
||||
import time
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from selenium.webdriver import Firefox, Remote, Chrome
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
def _wait_for_server(proc, info_file_path):
|
||||
"""Wait 30 seconds for the notebook server to start"""
|
||||
for i in range(300):
|
||||
if proc.poll() is not None:
|
||||
raise RuntimeError("Notebook server failed to start")
|
||||
if os.path.exists(info_file_path):
|
||||
try:
|
||||
with open(info_file_path) as f:
|
||||
return json.load(f)
|
||||
except ValueError:
|
||||
# If the server is halfway through writing the file, we may
|
||||
# get invalid JSON; it should be ready next iteration.
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError("Didn't find %s in 30 seconds", info_file_path)
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def notebook_server():
|
||||
info = {}
|
||||
with TemporaryDirectory() as td:
|
||||
nbdir = info['nbdir'] = pjoin(td, 'notebooks')
|
||||
os.makedirs(pjoin(nbdir, u'sub ∂ir1', u'sub ∂ir 1a'))
|
||||
os.makedirs(pjoin(nbdir, u'sub ∂ir2', u'sub ∂ir 1b'))
|
||||
|
||||
info['extra_env'] = {
|
||||
'JUPYTER_CONFIG_DIR': pjoin(td, 'jupyter_config'),
|
||||
'JUPYTER_RUNTIME_DIR': pjoin(td, 'jupyter_runtime'),
|
||||
'IPYTHONDIR': pjoin(td, 'ipython'),
|
||||
}
|
||||
env = os.environ.copy()
|
||||
env.update(info['extra_env'])
|
||||
|
||||
command = [sys.executable, '-m', 'notebook',
|
||||
'--no-browser',
|
||||
'--notebook-dir', nbdir,
|
||||
# run with a base URL that would be escaped,
|
||||
# to test that we don't double-escape URLs
|
||||
'--NotebookApp.base_url=/a@b/',
|
||||
]
|
||||
print("command=", command)
|
||||
proc = info['popen'] = Popen(command, cwd=nbdir, env=env)
|
||||
info_file_path = pjoin(td, 'jupyter_runtime', 'nbserver-%i.json' % proc.pid)
|
||||
info.update(_wait_for_server(proc, info_file_path))
|
||||
|
||||
print("Notebook server info:", info)
|
||||
yield info
|
||||
|
||||
# Shut the server down
|
||||
requests.post(urljoin(info['url'], 'api/shutdown'),
|
||||
headers={'Authorization': 'token '+info['token']})
|
||||
|
||||
|
||||
def _get_selenium_driver():
|
||||
if os.environ.get('SAUCE_USERNAME'):
|
||||
username = os.environ["SAUCE_USERNAME"]
|
||||
access_key = os.environ["SAUCE_ACCESS_KEY"]
|
||||
capabilities = {
|
||||
"tunnel-identifier": os.environ["TRAVIS_JOB_NUMBER"],
|
||||
"build": os.environ["TRAVIS_BUILD_NUMBER"],
|
||||
"tags": [os.environ['TRAVIS_PYTHON_VERSION'], 'CI'],
|
||||
"platform": "Windows 10",
|
||||
"browserName": os.environ['JUPYTER_TEST_BROWSER'],
|
||||
"version": "latest",
|
||||
}
|
||||
if capabilities['browserName'] == 'firefox':
|
||||
# Attempt to work around issue where browser loses authentication
|
||||
capabilities['version'] = '57.0'
|
||||
hub_url = "%s:%s@localhost:4445" % (username, access_key)
|
||||
print("Connecting remote driver on Sauce Labs")
|
||||
return Remote(desired_capabilities=capabilities,
|
||||
command_executor="http://%s/wd/hub" % hub_url)
|
||||
elif os.environ.get('JUPYTER_TEST_BROWSER') == 'chrome':
|
||||
return Chrome()
|
||||
else:
|
||||
return Firefox()
|
||||
|
||||
@pytest.fixture
|
||||
def browser(notebook_server):
|
||||
b = _get_selenium_driver()
|
||||
b.get("{url}?token={token}".format(**notebook_server))
|
||||
yield b
|
||||
b.quit()
|
||||
@ -0,0 +1,45 @@
|
||||
import os
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
def get_list_items(browser):
|
||||
return [{
|
||||
'link': a.get_attribute('href'),
|
||||
'label': a.find_element_by_class_name('item_name').text,
|
||||
} for a in browser.find_elements_by_class_name('item_link')]
|
||||
|
||||
|
||||
def wait_for_selector(browser, selector, timeout=10):
|
||||
wait = WebDriverWait(browser, timeout)
|
||||
return wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
|
||||
|
||||
|
||||
|
||||
def test_items(browser, visited=None):
|
||||
tree_root_url = browser.current_url
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
wait_for_selector(browser, '.item_link')
|
||||
items = get_list_items(browser)
|
||||
print(browser.current_url, len(items))
|
||||
for item in items:
|
||||
print(item)
|
||||
url = item['link']
|
||||
if url.startswith(tree_root_url):
|
||||
print("Going to", url)
|
||||
if url in visited:
|
||||
continue
|
||||
visited.add(url)
|
||||
browser.get(url)
|
||||
wait_for_selector(browser, '.item_link')
|
||||
assert browser.current_url == url
|
||||
|
||||
test_items(browser, visited)
|
||||
#browser.back()
|
||||
|
||||
print()
|
||||
@ -1,47 +0,0 @@
|
||||
|
||||
|
||||
casper.get_list_items = function () {
|
||||
return this.evaluate(function () {
|
||||
return $.makeArray($('.item_link').map(function () {
|
||||
return {
|
||||
link: $(this).attr('href'),
|
||||
label: $(this).find('.item_name').text()
|
||||
};
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
casper.test_items = function (origin, prefix, visited) {
|
||||
visited = visited || {};
|
||||
casper.then(function () {
|
||||
var items = casper.get_list_items();
|
||||
var tree_link = RegExp('^' + (prefix + 'tree/').replace(/\//g, '\\/'));
|
||||
casper.each(items, function (self, item) {
|
||||
if (item.link.match(tree_link)) {
|
||||
var followed_url = item.link;
|
||||
if (!visited[followed_url]) {
|
||||
visited[followed_url] = true;
|
||||
casper.thenOpen(origin + followed_url, function () {
|
||||
this.waitFor(this.page_loaded);
|
||||
casper.wait_for_dashboard();
|
||||
// getCurrentUrl is with host, and url-decoded,
|
||||
// but item.link is without host, and url-encoded
|
||||
var expected = origin + decodeURIComponent(item.link);
|
||||
this.test.assertEquals(this.getCurrentUrl(), expected, 'Testing dashboard link: ' + expected);
|
||||
casper.test_items(origin, prefix, visited);
|
||||
this.back();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
casper.dashboard_test(function () {
|
||||
var baseUrl = this.get_notebook_server();
|
||||
m = /(https?:\/\/[^\/]+)(.*)/.exec(baseUrl);
|
||||
origin = m[1];
|
||||
prefix = m[2];
|
||||
casper.test_items(origin, prefix);
|
||||
});
|
||||
|
||||
Loading…
Reference in new issue