"""Basis for Selenium test framework.""" from __future__ import absolute_import from __future__ import print_function import datetime import json import os import traceback import unittest from functools import partial, wraps import requests try: from pyvirtualdisplay import Display except ImportError: Display = None from six.moves.urllib.parse import urljoin from base import populators # noqa: I100,I202 from base.api import UsesApiTestCaseMixin # noqa: I100 from base.driver_util import classproperty, DEFAULT_WEB_HOST, get_ip_address # noqa: I100 from base.testcase import FunctionalTestCase # noqa: I100 from base.workflows_format_2 import ( # noqa: I100 convert_and_import_workflow, ImporterGalaxyInterface, ) from galaxy_selenium import ( # noqa: I100,I201 driver_factory, ) from galaxy_selenium.navigates_galaxy import ( # noqa: I100 NavigatesGalaxy, retry_during_transitions ) from galaxy.util import asbool # noqa: I201 DEFAULT_TIMEOUT_MULTIPLIER = 1 DEFAULT_TEST_ERRORS_DIRECTORY = os.path.abspath("database/test_errors") DEFAULT_SELENIUM_BROWSER = "auto" DEFAULT_SELENIUM_REMOTE = False DEFAULT_SELENIUM_REMOTE_PORT = "4444" DEFAULT_SELENIUM_REMOTE_HOST = "127.0.0.1" DEFAULT_SELENIUM_HEADLESS = "auto" DEFAULT_ADMIN_USER = "test@bx.psu.edu" DEFAULT_ADMIN_PASSWORD = "testpass" TIMEOUT_MULTIPLIER = float(os.environ.get("GALAXY_TEST_TIMEOUT_MULTIPLIER", DEFAULT_TIMEOUT_MULTIPLIER)) GALAXY_TEST_ERRORS_DIRECTORY = os.environ.get("GALAXY_TEST_ERRORS_DIRECTORY", DEFAULT_TEST_ERRORS_DIRECTORY) GALAXY_TEST_SCREENSHOTS_DIRECTORY = os.environ.get("GALAXY_TEST_SCREENSHOTS_DIRECTORY", None) # Test browser can be ["CHROME", "FIREFOX", "OPERA", "PHANTOMJS"] GALAXY_TEST_SELENIUM_BROWSER = os.environ.get("GALAXY_TEST_SELENIUM_BROWSER", DEFAULT_SELENIUM_BROWSER) GALAXY_TEST_SELENIUM_REMOTE = os.environ.get("GALAXY_TEST_SELENIUM_REMOTE", DEFAULT_SELENIUM_REMOTE) GALAXY_TEST_SELENIUM_REMOTE_PORT = os.environ.get("GALAXY_TEST_SELENIUM_REMOTE_PORT", DEFAULT_SELENIUM_REMOTE_PORT) GALAXY_TEST_SELENIUM_REMOTE_HOST = os.environ.get("GALAXY_TEST_SELENIUM_REMOTE_HOST", DEFAULT_SELENIUM_REMOTE_HOST) GALAXY_TEST_SELENIUM_HEADLESS = os.environ.get("GALAXY_TEST_SELENIUM_HEADLESS", DEFAULT_SELENIUM_HEADLESS) GALAXY_TEST_EXTERNAL_FROM_SELENIUM = os.environ.get("GALAXY_TEST_EXTERNAL_FROM_SELENIUM", None) # Auto-retry selenium tests this many times. GALAXY_TEST_SELENIUM_RETRIES = int(os.environ.get("GALAXY_TEST_SELENIUM_RETRIES", "0")) GALAXY_TEST_SELENIUM_USER_EMAIL = os.environ.get("GALAXY_TEST_SELENIUM_USER_EMAIL", None) GALAXY_TEST_SELENIUM_USER_PASSWORD = os.environ.get("GALAXY_TEST_SELENIUM_USER_PASSWORD", None) GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL = os.environ.get("GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL", DEFAULT_ADMIN_USER) GALAXY_TEST_SELENIUM_ADMIN_USER_PASSWORD = os.environ.get("GALAXY_TEST_SELENIUM_ADMIN_USER_PASSWORD", DEFAULT_ADMIN_PASSWORD) # JS code to execute in Galaxy JS console to setup localStorage of session for logging and # logging "flatten" messages because it seems Selenium (with Chrome at least) only grabs # the first argument to console.XXX when recovering the browser log. SETUP_LOGGING_JS = ''' window.localStorage && window.localStorage.setItem("galaxy:debug", true); window.localStorage && window.localStorage.setItem("galaxy:debug:flatten", true); ''' try: from nose.tools import nottest except ImportError: def nottest(x): return x def managed_history(f): """Ensure a Selenium test has a distinct, named history. Cleanup the history after the job is complete as well unless GALAXY_TEST_NO_CLEANUP is set in the environment. """ @wraps(f) def func_wrapper(self, *args, **kwds): self.home() history_name = f.__name__ + datetime.datetime.now().strftime("%Y%m%d%H%M%s") self.history_panel_create_new_with_name(history_name) try: f(self, *args, **kwds) finally: if "GALAXY_TEST_NO_CLEANUP" not in os.environ: current_history_id = self.current_history_id() self.api_delete("histories/%s" % current_history_id) return func_wrapper def dump_test_information(self, name_prefix): if GALAXY_TEST_ERRORS_DIRECTORY and GALAXY_TEST_ERRORS_DIRECTORY != "0": if not os.path.exists(GALAXY_TEST_ERRORS_DIRECTORY): os.makedirs(GALAXY_TEST_ERRORS_DIRECTORY) result_name = name_prefix + datetime.datetime.now().strftime("%Y%m%d%H%M%s") target_directory = os.path.join(GALAXY_TEST_ERRORS_DIRECTORY, result_name) def write_file(name, content, raw=False): with open(os.path.join(target_directory, name), "wb") as buf: buf.write(content.encode("utf-8") if not raw else content) os.makedirs(target_directory) self.driver.save_screenshot(os.path.join(target_directory, "last.png")) write_file("page_source.txt", self.driver.page_source) write_file("DOM.txt", self.driver.execute_script("return document.documentElement.outerHTML")) write_file("stacktrace.txt", traceback.format_exc()) for snapshot in getattr(self, "snapshots", []): snapshot.write_to_error_directory(write_file) for log_type in ["browser", "driver"]: full_log = self.driver.get_log(log_type) trimmed_log = [l for l in full_log if l["level"] not in ["DEBUG", "INFO"]] try: write_file("%s.log.json" % log_type, json.dumps(trimmed_log, indent=True)) write_file("%s.log.verbose.json" % log_type, json.dumps(full_log, indent=True)) except Exception: continue iframes = self.driver.find_elements_by_css_selector("iframe") for iframe in iframes: pass # TODO: Dump content out for debugging in the future. # iframe_id = iframe.get_attribute("id") # if iframe_id: # write_file("iframe_%s" % iframe_id, "My content") @nottest def selenium_test(f): @wraps(f) def func_wrapper(self, *args, **kwds): retry_attempts = 0 while True: if retry_attempts > 0: self.reset_driver_and_session() try: return f(self, *args, **kwds) except Exception: dump_test_information(self, f.__name__) if retry_attempts < GALAXY_TEST_SELENIUM_RETRIES: retry_attempts += 1 else: raise return func_wrapper retry_assertion_during_transitions = partial(retry_during_transitions, exception_check=lambda e: isinstance(e, AssertionError)) class TestSnapshot(object): def __init__(self, driver, index, description): self.screenshot_binary = driver.get_screenshot_as_png() self.description = description self.index = index self.exc = traceback.format_exc() self.stack = traceback.format_stack() def write_to_error_directory(self, write_file_func): prefix = "%d-%s" % (self.index, self.description) write_file_func("%s-screenshot.png" % prefix, self.screenshot_binary, raw=True) write_file_func("%s-traceback.txt" % prefix, self.exc) write_file_func("%s-stack.txt" % prefix, str(self.stack)) class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy, UsesApiTestCaseMixin): # If run one-off via nosetests, the next line ensures test # tools and datatypes are used instead of configured tools. framework_tool_and_types = True # Override this in subclasses to ensure a user is logged in # before each test. If GALAXY_TEST_SELENIUM_USER_EMAIL and # GALAXY_TEST_SELENIUM_USER_PASSWORD are set these values # will be used to login. ensure_registered = False requires_admin = False def setUp(self): super(SeleniumTestCase, self).setUp() # Deal with the case when Galaxy has a different URL when being accessed by Selenium # then when being accessed by local API calls. if GALAXY_TEST_EXTERNAL_FROM_SELENIUM is not None: self.target_url_from_selenium = GALAXY_TEST_EXTERNAL_FROM_SELENIUM else: self.target_url_from_selenium = self.url self.snapshots = [] self.setup_driver_and_session() if self.requires_admin and GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL == DEFAULT_ADMIN_USER: self._setup_interactor() self._setup_user(GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL) try: self.setup_with_driver() except Exception: dump_test_information(self, self.__class__.__name__ + "_setup") raise def setup_with_driver(self): """Override point that allows setting up data using self.driver and Selenium connection. Using this instead of overriding will ensure debug data such as screenshots and stack traces are dumped if there are problems with the setup. """ def tearDown(self): exception = None try: super(SeleniumTestCase, self).tearDown() except Exception as e: exception = e try: self.tear_down_driver() except Exception as e: exception = e if exception is not None: raise exception def snapshot(self, description): """Create a debug snapshot (DOM, screenshot, etc...) that is written out on tool failure. This information will be automatically written to a per-test directory created for all failed tests. """ self.snapshots.append(TestSnapshot(self.driver, len(self.snapshots), description)) def screenshot(self, label): """If GALAXY_TEST_SCREENSHOTS_DIRECTORY is set create a screenshot there named