Bug 1363982 - Clean up fixtures for session creation in wdspec, a=testonly
authorJames Graham <james@hoppipolla.co.uk>
Fri, 07 Apr 2017 15:56:33 +0100
changeset 357798 d68c202a67091f65e127e3b25fe2ca4db78b58ef
parent 357797 612b93488bf5e52e3698fe027411884343bf8dd7
child 357799 8c8ebe3b4621d3e0022ce70c59e54f48c8c7579f
push id90205
push userjames@hoppipolla.co.uk
push dateThu, 11 May 2017 17:02:12 +0000
treeherdermozilla-inbound@52aaad48467f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1363982
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1363982 - Clean up fixtures for session creation in wdspec, a=testonly [Cherry-picked from wpt] This introduces two fixtures for creating a multi-test session and a longlived session. For performance it is important that we don't ordinarily create a new session for each test, but it's also important that specific tests are able to create new sessions with arbitary capabilities. MozReview-Commit-ID: DNZCgmkrwvn
testing/web-platform/tests/webdriver/conftest.py
testing/web-platform/tests/webdriver/support/fixtures.py
--- a/testing/web-platform/tests/webdriver/conftest.py
+++ b/testing/web-platform/tests/webdriver/conftest.py
@@ -1,12 +1,13 @@
 import pytest
 from support.fixtures import (
-    create_frame, create_session, create_window, http, server_config, session,
+    configuration, create_frame, create_window, http, new_session, server_config, session,
     url)
 
+pytest.fixture(scope="session")(configuration)
 pytest.fixture()(create_frame)
-pytest.fixture()(create_session)
 pytest.fixture()(create_window)
 pytest.fixture()(http)
+pytest.fixture(scope="function")(new_session)
 pytest.fixture()(server_config)
 pytest.fixture(scope="function")(session)
 pytest.fixture()(url)
--- a/testing/web-platform/tests/webdriver/support/fixtures.py
+++ b/testing/web-platform/tests/webdriver/support/fixtures.py
@@ -26,116 +26,155 @@ def _dismiss_user_prompts(session):
         session.window_handle = window
         try:
             session.alert.dismiss()
         except webdriver.NoSuchAlertException:
             pass
 
     session.window_handle = current_window
 
+
 def _restore_windows(session):
     """Closes superfluous windows opened by the test without ending
     the session implicitly by closing the last window.
     """
     current_window = session.window_handle
 
     for window in _windows(session, exclude=[current_window]):
         session.window_handle = window
         if len(session.window_handles) > 1:
             session.close()
 
     session.window_handle = current_window
 
+
 def _switch_to_top_level_browsing_context(session):
     """If the current browsing context selected by WebDriver is a
     `<frame>` or an `<iframe>`, switch it back to the top-level
     browsing context.
     """
     session.switch_frame(None)
 
+
 def _windows(session, exclude=None):
     """Set of window handles, filtered by an `exclude` list if
     provided.
     """
     if exclude is None:
         exclude = []
     wins = [w for w in session.handles if w not in exclude]
     return set(wins)
 
+
 def create_frame(session):
     """Create an `iframe` element in the current browsing context and insert it
     into the document. Return an element reference."""
     def create_frame():
         append = """
             var frame = document.createElement('iframe');
             document.body.appendChild(frame);
             return frame;
         """
         response = session.execute_script(append)
 
     return create_frame
 
+
 def create_window(session):
     """Open new window and return the window handle."""
     def create_window():
         windows_before = session.handles
         name = session.execute_script("window.open()")
         assert len(session.handles) == len(windows_before) + 1
         new_windows = list(set(session.handles) - set(windows_before))
         return new_windows.pop()
     return create_window
 
+
 def http(session):
     return HTTPRequest(session.transport.host, session.transport.port)
 
+
 def server_config():
     return json.loads(os.environ.get("WD_SERVER_CONFIG"))
 
-def create_session(request):
-    """Provide a factory function that produces wdclient `Session` instances.
-    If the `WD_CAPABILITIES` environment variable is set, it will be parsed as
-    JSON and the resulting object will be included in the WebDriver "Create
-    Session" command. Additional capabilities may be specified as an optional
-    argument to this function, but the operation will fail if any values
-    conflict with those specified via the environment. If the session is still
-    active at the completion of the test, it will be destroyed
-    automatically."""
+
+def configuration():
+    host = os.environ.get("WD_HOST", default_host)
+    port = int(os.environ.get("WD_PORT", default_port))
+    capabilities = json.loads(os.environ.get("WD_CAPABILITIES", "{}"))
+
+    return {
+        "host": host,
+        "port": port,
+        "capabilities": capabilities
+    }
+
+
+_current_session = None
+
+
+def session(configuration, request):
+    """Create and start a session for a test that does not itself test session creation.
+
+    By default the session will stay open after each test, but we always try to start a
+    new one and assume that if that fails there is already a valid session. This makes it
+    possible to recover from some errors that might leave the session in a bad state, but
+    does not demand that we start a new session per test."""
+    global _current_session
+    if _current_session is None:
+        _current_session = webdriver.Session(configuration["host"],
+                                             configuration["port"],
+                                             capabilities=configuration["capabilities"])
+    try:
+        _current_session.start()
+    except webdriver.errors.SessionNotCreatedException:
+        if not _current_session.session_id:
+            raise
 
-    def create_session(test_capabilities=None):
-        host = os.environ.get("WD_HOST", default_host)
-        port = int(os.environ.get("WD_PORT", default_port))
-        if test_capabilities is None:
-            test_capabilities = {}
-        env_capabilities = json.loads(os.environ.get("WD_CAPABILITIES", "{}"))
+    # finalisers are popped off a stack,
+    # making their ordering reverse
+    request.addfinalizer(lambda: _switch_to_top_level_browsing_context(_current_session))
+    request.addfinalizer(lambda: _restore_windows(_current_session))
+    request.addfinalizer(lambda: _dismiss_user_prompts(_current_session))
+    request.addfinalizer(lambda: _ensure_valid_window(_current_session))
 
-        capabilities = merge_dictionaries(env_capabilities, test_capabilities)
-        session = webdriver.Session(host, port, capabilities=capabilities)
+    return _current_session
+
+
+def new_session(configuration, request):
+    """Return a factory function that will attempt to start a session with a given body.
 
-        def destroy():
-            if session.session_id is not None:
-                session.end()
+    This is intended for tests that are themselves testing new session creation, and the
+    session created is closed at the end of the test."""
+    def end():
+        global _current_session
+        if _current_session is not None and _current_session.session_id:
+            _current_session.end()
+            _current_session = None
 
-        # finalisers are popped off a stack, making their ordering reverse
-        request.addfinalizer(destroy)
-        request.addfinalizer(lambda: _switch_to_top_level_browsing_context(session))
-        request.addfinalizer(lambda: _restore_windows(session))
-        request.addfinalizer(lambda: _dismiss_user_prompts(session))
-        request.addfinalizer(lambda: _ensure_valid_window(session))
+    def create_session(body):
+        global _current_session
+        _session = webdriver.Session(configuration["host"],
+                                     configuration["port"],
+                                     capabilities=None)
+        # TODO: merge in some capabilities from the confguration capabilities
+        # since these might be needed to start the browser
+        value = _session.send_command("POST", "session", body=body)
+        # Don't set the global session until we are sure this succeeded
+        _current_session = _session
+        _session.session_id = value["sessionId"]
 
-        return session
+        return value, _current_session
+
+    end()
+    request.addfinalizer(end)
 
     return create_session
 
-# Create a wdclient `Session` object for each Pytest "session". If the
-# `WD_CAPABILITIES` environment variable is set, it will be parsed as JSON and
-# the resulting object will be included in the WebDriver "Create Session"
-# command. If the session is still active at the completion of the test, it
-# will be destroyed automatically.
-def session(create_session):
-    return create_session()
 
 def url(server_config):
     def inner(path, query="", fragment=""):
         rv = urlparse.urlunsplit(("http",
                                   "%s:%s" % (server_config["host"],
                                              server_config["ports"]["http"][0]),
                                   path,
                                   query,