diff --git a/.gitignore b/.gitignore index 76bc995a70..6e5338c066 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,126 @@ node_modules/ *.swp docker-selenium.iml + +# Created by https://www.gitignore.io/api/virtualenv + +### VirtualEnv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +.Python +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +.venv +pip-selfcheck.json + +# End of https://www.gitignore.io/api/virtualenv +tests/tests/* + +# Created by https://www.gitignore.io/api/python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# End of https://www.gitignore.io/api/python diff --git a/.travis.yml b/.travis.yml index cf8ddc1de5..a35ced3cff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,16 @@ ---- +language: python + sudo: required services: - docker -language: bash - notifications: slack: seleniumhq:Kx5MB7T51SPMpbBMYfvxNW4q before_install: - docker info - VERSION="$TRAVIS_BRANCH" make build - - docker build -t test:local ./Test script: - VERSION="$TRAVIS_BRANCH" make test diff --git a/Makefile b/Makefile index 91811bf347..0996ef92b5 100644 --- a/Makefile +++ b/Makefile @@ -228,11 +228,43 @@ release: tag_major_minor docker push $(NAME)/standalone-chrome-debug:$(MAJOR_MINOR_PATCH) docker push $(NAME)/standalone-firefox-debug:$(MAJOR_MINOR_PATCH) -test: - VERSION=$(VERSION) ./test.sh - VERSION=$(VERSION) ./sa-test.sh - VERSION=$(VERSION) ./test.sh debug - VERSION=$(VERSION) ./sa-test.sh debug +test: test_chrome \ + test_firefox \ + test_chrome_debug \ + test_firefox_debug \ + test_chrome_standalone \ + test_firefox_standalone \ + test_chrome_standalone_debug \ + test_firefox_standalone_debug + + +test_chrome: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeChrome + +test_chrome_debug: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeChromeDebug + +test_chrome_standalone: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneChrome + +test_chrome_standalone_debug: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneChromeDebug + +test_firefox: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeFirefox + +test_firefox_debug: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeFirefoxDebug + +test_firefox_standalone: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneFirefox + +test_firefox_standalone_debug: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneFirefoxDebug + +test_phantomjs: + VERSION=$(VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodePhantomJS + .PHONY: \ all \ diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py new file mode 100644 index 0000000000..5e5f854192 --- /dev/null +++ b/tests/SeleniumTests/__init__.py @@ -0,0 +1,51 @@ +import unittest +from selenium import webdriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + + +class SeleniumGenericTests(unittest.TestCase): + def test_title(self): + self.driver.get('https://the-internet.herokuapp.com') + self.assertTrue(self.driver.title == 'The Internet') + + # https://github.com/tourdedave/elemental-selenium-tips/blob/master/03-work-with-frames/python/frames.py + def test_with_frames(self): + driver = self.driver + driver.get('http://the-internet.herokuapp.com/nested_frames') + driver.switch_to.frame('frame-top') + driver.switch_to.frame('frame-middle') + self.assertTrue(driver.find_element_by_id('content').text == "MIDDLE", "content should be MIDDLE") + + # https://github.com/tourdedave/elemental-selenium-tips/blob/master/04-work-with-multiple-windows/python/windows.py + def test_with_windows(self): + driver = self.driver + driver.get('http://the-internet.herokuapp.com/windows') + driver.find_element_by_css_selector('.example a').click() + driver.switch_to_window(driver.window_handles[0]) + self.assertTrue(driver.title != "New Window", "title should not be New Window") + driver.switch_to_window(driver.window_handles[-1]) + self.assertTrue(driver.title == "New Window", "title should be New Window") + + # https://github.com/tourdedave/elemental-selenium-tips/blob/master/13-work-with-basic-auth/python/basic_auth_1.py + def test_visit_basic_auth_secured_page(self): + driver = self.driver + driver.get('http://admin:admin@the-internet.herokuapp.com/basic_auth') + page_message = driver.find_element_by_css_selector('.example p').text + self.assertTrue(page_message == 'Congratulations! You must have the proper credentials.') + + def tearDown(self): + self.driver.quit() + + +class ChromeTests(SeleniumGenericTests): + def setUp(self): + self.driver = webdriver.Remote( + desired_capabilities=DesiredCapabilities.CHROME + ) + + +class FirefoxTests(SeleniumGenericTests): + def setUp(self): + self.driver = webdriver.Remote( + desired_capabilities=DesiredCapabilities.FIREFOX + ) diff --git a/tests/SmokeTests/__init__.py b/tests/SmokeTests/__init__.py new file mode 100644 index 0000000000..e5585cddb4 --- /dev/null +++ b/tests/SmokeTests/__init__.py @@ -0,0 +1,38 @@ +import unittest +import urllib2 +import time +import json + + +class SmokeTests(unittest.TestCase): + def smoke_test_container(self, port): + current_attempts = 0 + max_attempts = 3 + sleep_interval = 3 + status_fetched = False + status_json = None + + while current_attempts < max_attempts: + current_attempts = current_attempts + 1 + try: + response = urllib2.urlopen('http://localhost:%s/wd/hub/status' % port) + status_json = json.loads(response.read()) + self.assertTrue(status_json['value']['ready'], "Container is not ready on port %s" % port) + status_fetched = True + except Exception as e: + time.sleep(sleep_interval) + + self.assertTrue(status_fetched, "Container status was not fetched on port %s" % port) + self.assertTrue(status_json['status'] == 0, "Wrong status value for container on port %s" % port) + self.assertTrue(status_json['value']['ready'], "Container is not ready on port %s" % port) + + +class NodeTest(SmokeTests): + def test_hub_and_node_up(self): + self.smoke_test_container(4444) + self.smoke_test_container(5555) + + +class StandaloneTest(SmokeTests): + def test_standalone_up(self): + self.smoke_test_container(4444) diff --git a/tests/bootstrap.sh b/tests/bootstrap.sh new file mode 100755 index 0000000000..895567ef57 --- /dev/null +++ b/tests/bootstrap.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +cd tests +pip install selenium===3.5.0 \ + docker===2.5.1 \ + | grep -v 'Requirement already satisfied' + +python test.py $1 $2 diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000000..b967cce25c --- /dev/null +++ b/tests/test.py @@ -0,0 +1,172 @@ +import os +import docker +import unittest +import logging +import sys + +from docker.errors import NotFound + +# LOGGING # +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +# Docker Client +client = docker.from_env() + +NAMESPACE = os.environ.get('NAMESPACE') +VERSION = os.environ.get('VERSION') + +IMAGE_NAME_MAP = { + # Hub + 'Hub': 'hub', + + # Chrome Images + 'NodeChrome': 'node-chrome', + 'NodeChromeDebug': 'node-chrome-debug', + 'StandaloneChrome': 'standalone-chrome', + 'StandaloneChromeDebug': 'standalone-chrome-debug', + + # Firefox Images + 'NodeFirefox': 'node-firefox', + 'NodeFirefoxDebug': 'node-firefox-debug', + 'StandaloneFirefox': 'standalone-firefox', + 'StandaloneFirefoxDebug': 'standalone-firefox-debug', + + # PhantomJS Images + 'NodePhantomJS': 'node-phantomjs', +} + +TEST_NAME_MAP = { + # Chrome Images + 'NodeChrome': 'ChromeTests', + 'NodeChromeDebug': 'ChromeTests', + 'StandaloneChrome': 'ChromeTests', + 'StandaloneChromeDebug': 'ChromeTests', + + # Firefox Images + 'NodeFirefox': 'FirefoxTests', + 'NodeFirefoxDebug': 'FirefoxTests', + 'StandaloneFirefox': 'FirefoxTests', + 'StandaloneFirefoxDebug': 'FirefoxTests', + + # PhantomJS Images + 'NodePhantomJS': 'PhantomJSTests', +} + + +def launch_hub(): + """ + Launch the hub + :return: the hub ID + """ + logger.info("Launching Hub...") + + existing_hub = None + + try: + existing_hub = client.containers.get('selenium-hub') + except NotFound: + pass + + if existing_hub: + logger.debug("hub already exists. removing.") + if existing_hub.status == 'running': + logger.debug("hub is running. Killing") + existing_hub.kill() + logger.debug("hub killed") + existing_hub.remove() + logger.debug("hub removed") + hub_container_id = launch_container('Hub', ports={'4444': 4444}) + logger.info("Hub Launched") + return hub_container_id + + +def launch_container(container, **kwargs): + """ + Launch a specific container + :param container: + :return: the container ID + """ + # Build the container if it doesn't exist + logger.info("Building %s container..." % container) + client.images.build(path='../%s' % container, + tag="%s/%s:%s" % (NAMESPACE, IMAGE_NAME_MAP[container], VERSION), + rm=True) + logger.info("Done building %s" % container) + + # Run the container + logger.info("Running %s container..." % container) + container_id = client.containers.run("%s/%s:%s" % (NAMESPACE, IMAGE_NAME_MAP[container], VERSION), + detach=True, + **kwargs).short_id + logger.info("%s up and running" % container) + return container_id + + +if __name__ == '__main__': + # The container to test against + image = sys.argv[1] + + standalone = 'standalone' in image.lower() + + # Flag for failure (for posterity) + failed = False + + logger.info('========== Starting %s Container ==========' % image) + + if standalone: + """ + Standalone Configuration + """ + smoke_test_class = 'StandaloneTest' + test_container_id = launch_container(image, ports={'4444': 4444}) + else: + """ + Hub / Node Configuration + """ + smoke_test_class = 'NodeTest' + hub_id = launch_hub() + test_container_id = launch_container(image, links={hub_id: 'hub'}, ports={'5555': 5555}) + + logger.info('========== / Containers ready to go ==========') + + try: + # Smoke tests + logger.info('*********** Running smoke tests %s Tests **********' % image) + image_class = "%sTest" % image + test_class = getattr(__import__('SmokeTests', fromlist=[smoke_test_class]), smoke_test_class) + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + test_runner = unittest.TextTestRunner(verbosity=3) + failed = not test_runner.run(suite).wasSuccessful() + except Exception as e: + logger.fatal(e.message) + failed = True + + try: + # Run Selenium tests + logger.info('*********** Running Selenium tests %s Tests **********' % image) + test_class = getattr(__import__('SeleniumTests', fromlist=[TEST_NAME_MAP[image]]), TEST_NAME_MAP[image]) + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + test_runner = unittest.TextTestRunner(verbosity=3) + failed = not test_runner.run(suite).wasSuccessful() + except Exception as e: + logger.fatal(e.message) + failed = True + + logger.info("Cleaning up...") + + test_container = client.containers.get(test_container_id) + test_container.kill() + test_container.remove() + + if standalone: + logger.info("Standalone Cleaned up") + else: + #Kill the launched hub + hub = client.containers.get(hub_id) + hub.kill() + hub.remove() + logger.info("Hub / Node Cleaned up") + + if failed: + exit(1)