1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
import threading
import SimpleHTTPServer
import SocketServer
import BaseHTTPServer
import urllib
import urlparse
import os
import posixpath
from marionette_driver.errors import NoSuchElementException
from marionette_harness import MarionetteTestCase
DEBUG = True
# Example taken from mozilla-central/browser/components/loop/
# XXX Once we're on a branch with bug 993478 landed, we may want to get
# rid of this HTTP server and just use the built-in one from Marionette,
# since there will less code to maintain, and it will be faster. We'll
# need to consider whether this code wants to be shared with WebDriver tests
# for other browsers, though.
class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
BaseHTTPServer.HTTPServer):
pass
class HttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler, object):
def __init__(self, *args):
# set root to toolkit/components/microformats/
self.root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.normpath(__file__))))
super(HttpRequestHandler, self).__init__(*args)
def log_message(self, format, *args, **kwargs):
if DEBUG:
super(HttpRequestHandler, self).log_message(format, *args, **kwargs)
def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.
Components that mean special things to the local file system
(e.g. drive or directory names) are ignored. (XXX They should
probably be diagnosed.)
"""
# abandon query parameters
path = path.split('?',1)[0]
path = path.split('#',1)[0]
# Don't forget explicit trailing slash when normalizing. Issue17324
trailing_slash = path.rstrip().endswith('/')
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = self.root
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir): continue
path = os.path.join(path, word)
if trailing_slash:
path += '/'
return path
class BaseTestFrontendUnits(MarionetteTestCase):
@classmethod
def setUpClass(cls):
super(BaseTestFrontendUnits, cls).setUpClass()
# Port 0 means to select an arbitrary unused port
cls.server = ThreadingSimpleServer(('', 0), HttpRequestHandler)
cls.ip, cls.port = cls.server.server_address
cls.server_thread = threading.Thread(target=cls.server.serve_forever)
cls.server_thread.daemon = False
cls.server_thread.start()
@classmethod
def tearDownClass(cls):
cls.server.shutdown()
cls.server_thread.join()
# make sure everything gets GCed so it doesn't interfere with the next
# test class. Even though this is class-static, each subclass gets
# its own instance of this stuff.
cls.server_thread = None
cls.server = None
def setUp(self):
super(BaseTestFrontendUnits, self).setUp()
# Unfortunately, enforcing preferences currently comes with the side
# effect of launching and restarting the browser before running the
# real functional tests. Bug 1048554 has been filed to track this.
#
# Note: when e10s is enabled by default, this pref can go away. The automatic
# restart will also go away if this is still the only pref set here.
self.marionette.enforce_gecko_prefs({
"browser.tabs.remote.autostart": True
})
# This extends the timeout for find_element. We need this as the tests
# take an amount of time to run after loading, which we have to wait for.
self.marionette.timeout.implicit = 120
self.marionette.timeout.page_load = 120
# srcdir_path should be the directory relative to this file.
def set_server_prefix(self, srcdir_path):
self.server_prefix = urlparse.urljoin("http://localhost:" + str(self.port),
srcdir_path)
def check_page(self, page):
self.marionette.navigate(urlparse.urljoin(self.server_prefix, page))
try:
self.marionette.find_element("id", 'complete')
except NoSuchElementException:
fullPageUrl = urlparse.urljoin(self.relPath, page)
details = "%s: 1 failure encountered\n%s" % \
(fullPageUrl,
self.get_failure_summary(
fullPageUrl, "Waiting for Completion",
"Could not find the test complete indicator"))
raise AssertionError(details)
fail_node = self.marionette.find_element("css selector",
'.failures > em')
if fail_node.text == "0":
return
# This may want to be in a more general place triggerable by an env
# var some day if it ends up being something we need often:
#
# If you have browser-based unit tests which work when loaded manually
# but not from marionette, uncomment the two lines below to break
# on failing tests, so that the browsers won't be torn down, and you
# can use the browser debugging facilities to see what's going on.
#from ipdb import set_trace
#set_trace()
raise AssertionError(self.get_failure_details(page))
def get_failure_summary(self, fullPageUrl, testName, testError):
return "TEST-UNEXPECTED-FAIL | %s | %s - %s" % (fullPageUrl, testName, testError)
def get_failure_details(self, page):
fail_nodes = self.marionette.find_elements("css selector",
'.test.fail')
fullPageUrl = urlparse.urljoin(self.relPath, page)
details = ["%s: %d failure(s) encountered:" % (fullPageUrl, len(fail_nodes))]
for node in fail_nodes:
errorText = node.find_element("css selector", '.error').text
# We have to work our own failure message here, as we could be reporting multiple failures.
# XXX Ideally we'd also give the full test tree for <test name> - that requires walking
# up the DOM tree.
# Format: TEST-UNEXPECTED-FAIL | <filename> | <test name> - <test error>
details.append(
self.get_failure_summary(page,
node.find_element("tag name", 'h2').text.split("\n")[0],
errorText.split("\n")[0]))
details.append(
errorText)
return "\n".join(details)
|