Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing with Selenium & Sauce labs #3321

Merged
merged 24 commits into from Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aa9272a
Initial selenium test
takluyver Feb 8, 2018
a8c4258
Try configuring Travis to run selenium tests on Sauce
takluyver Feb 8, 2018
9759074
Encryption key needs to be for my account, not jupyter
takluyver Feb 8, 2018
c417727
Install selenium on Travis
takluyver Feb 8, 2018
162e7aa
Get more data from server info file
takluyver Feb 8, 2018
f578d4e
Set cwd when launching notebook server
takluyver Feb 8, 2018
7bfb7a8
Use JUPYTER_TEST_BROWSER=chrome to test with Chrome
takluyver Feb 8, 2018
b2b449f
Debugging test
takluyver Feb 8, 2018
0c65390
Separate fixtures into conftest.py
takluyver Feb 8, 2018
739b14a
Try with --Cls.a=b option syntax
takluyver Feb 8, 2018
dca1255
Try using sauce labs directly, not through Travis proxy
takluyver Feb 8, 2018
5391f32
Back to using proxy, with http instead of https
takluyver Feb 8, 2018
93d9fa6
Specify browserName in desired_capabilities for Sauce
takluyver Feb 8, 2018
1eee66e
Try connecting to Sauce for only some jobs in matrix
takluyver Feb 9, 2018
daeea41
Exclude selenium tests from regular test run
takluyver Feb 9, 2018
2e8bf3b
Remove redundant JS test for dashboard navigation (converted to Selen…
takluyver Feb 9, 2018
a1189fc
Re-enable other tests
takluyver Feb 9, 2018
c0361cb
Exclude selenium tests on Appveyor
takluyver Feb 9, 2018
cdee97a
Later browser versions are available on Windows
takluyver Feb 9, 2018
abc38a7
Try running tests with Firefox 57 instead of 58
takluyver Feb 9, 2018
b1f9cb2
Try running with local Firefox on Travis
takluyver Feb 9, 2018
3be1943
Install geckodriver for Selenium tests
takluyver Feb 9, 2018
58e7e48
Untar the right version of geckodriver
takluyver Feb 9, 2018
9390b14
Try stepping back one version of Firefox again
takluyver Feb 9, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 21 additions & 5 deletions .travis.yml
Expand Up @@ -11,6 +11,7 @@ python:

sudo: required


env:
global:
- PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
Expand All @@ -19,8 +20,6 @@ env:
- GROUP=python
- GROUP=js/base
- GROUP=js/services
- GROUP=js/tree
- GROUP=docs

before_install:
- pip install --upgrade pip
Expand All @@ -40,6 +39,15 @@ before_install:
if [[ $GROUP == docs ]]; then
pip install -r docs/doc-requirements.txt
fi
- |
if [[ $GROUP == selenium ]]; then
pip install selenium
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes it nice n' easy, this is great.

# Install Webdriver backend for Firefox:
wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz
mkdir geckodriver
tar -xzf geckodriver-v0.19.1-linux64.tar.gz -C geckodriver
export PATH=$PATH:$PWD/geckodriver
fi

install:
- pip install -f travis-wheels/wheelhouse file://$PWD#egg=notebook[test]
Expand All @@ -59,7 +67,8 @@ script:
true
fi
- 'if [[ $GROUP == js* ]]; then travis_retry python -m notebook.jstest ${GROUP:3}; fi'
- 'if [[ $GROUP == python ]]; then nosetests -v --with-coverage --cover-package=notebook notebook; fi'
- 'if [[ $GROUP == python ]]; then nosetests -v --exclude-dir notebook/tests/selenium --with-coverage --cover-package=notebook notebook; fi'
- 'if [[ $GROUP == selenium ]]; then py.test -sv notebook/tests/selenium; fi'
- |
if [[ $GROUP == docs ]]; then
EXIT_STATUS=0
Expand All @@ -72,12 +81,19 @@ script:

matrix:
include:
- python: 3.6
env:
- GROUP=selenium
- JUPYTER_TEST_BROWSER=firefox
- MOZ_HEADLESS=1
addons:
firefox: 57.0

- python: 3.4
env: GROUP=python
- python: 3.5
env: GROUP=python
exclude:
- python: 2.7
- python: 3.6
env: GROUP=docs

after_success:
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Expand Up @@ -23,4 +23,4 @@ install:
- cmd: pip install .[test]

test_script:
- nosetests -v notebook
- nosetests -v notebook --exclude-dir notebook\tests\selenium
Empty file.
96 changes: 96 additions & 0 deletions notebook/tests/selenium/conftest.py
@@ -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()
45 changes: 45 additions & 0 deletions notebook/tests/selenium/test_dashboard_nav.py
@@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to translate the JS test I removed directly to Python to illustrate how the machinery works, but I don't think it's actually a very meaningful test. The next step will be to write some new tests using this; I'm planning to leave that for other PRs, because I think there's already enough to look at in this PR.


test_items(browser, visited)
#browser.back()

print()
47 changes: 0 additions & 47 deletions notebook/tests/tree/dashboard_nav.js

This file was deleted.

3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -91,7 +91,8 @@
],
extras_require = {
'test:python_version == "2.7"': ['mock'],
'test': ['nose', 'coverage', 'requests', 'nose_warnings_filters', 'nbval'],
'test': ['nose', 'coverage', 'requests', 'nose_warnings_filters',
'nbval', 'nose-exclude'],
'test:sys_platform == "win32"': ['nose-exclude'],
},
entry_points = {
Expand Down