Bug 738825 - mirror Marionette Python client into gecko, a=testonly (npotb)

This commit is contained in:
Jonathan Griffin 2012-03-26 12:40:25 -07:00
parent 711b8fd781
commit b84967dd54
47 changed files with 4578 additions and 0 deletions

View File

@ -0,0 +1,7 @@
[DEFAULT]
b2g = true
browser = false
qemu = true
[test_battery.py]

View File

@ -0,0 +1,99 @@
import unittest
from marionette_test import MarionetteTestCase
class BatteryTest(MarionetteTestCase):
@unittest.expectedFailure
def test_chargingchange(self):
marionette = self.marionette
self.assertTrue(marionette.emulator.is_running)
marionette.set_script_timeout(10000)
moz_charging = marionette.execute_script("return navigator.mozBattery.charging;")
emulator_charging = marionette.emulator.battery.charging
self.assertEquals(moz_charging, emulator_charging)
# setup event listeners to be notified when the level or charging status
# changes
self.assertTrue(marionette.execute_script("""
window.wrappedJSObject._chargingchanged = false;
navigator.mozBattery.addEventListener("chargingchange", function() {
window.wrappedJSObject._chargingchanged = true;
});
return true;
"""))
# set the battery charging state, and verify
marionette.emulator.battery.charging = not emulator_charging
new_emulator_charging_state = marionette.emulator.battery.charging
self.assertEquals(new_emulator_charging_state, (not emulator_charging))
# verify that the 'chargingchange' listener was hit
charging_changed = marionette.execute_async_script("""
var callback = arguments[arguments.length - 1];
function check_charging_change() {
if (window.wrappedJSObject._chargingchanged) {
callback(window.wrappedJSObject._chargingchanged);
}
else {
setTimeout(check_charging_change, 500);
}
}
setTimeout(check_charging_change, 0);
""")
self.assertTrue(charging_changed)
# if we have set the charging state to 'off', set it back to 'on' to prevent
# the emulator from sleeping
if not new_emulator_charging_state:
marionette.emulator.battery.charging = True
def test_levelchange(self):
marionette = self.marionette
self.assertTrue(marionette.emulator.is_running)
marionette.set_script_timeout(10000)
# verify the emulator's battery status as reported by Gecko is the same as
# reported by the device
moz_level = marionette.execute_script("return navigator.mozBattery.level;")
self.assertEquals(moz_level, marionette.emulator.battery.level)
# setup event listeners to be notified when the level or charging status
# changes
self.assertTrue(marionette.execute_script("""
window.wrappedJSObject._levelchanged = false;
navigator.mozBattery.addEventListener("levelchange", function() {
window.wrappedJSObject._levelchanged = true;
});
return true;
"""))
# set the battery to a new level, and verify
if moz_level > 0.2:
new_level = moz_level - 0.1
else:
new_level = moz_level + 0.1
marionette.emulator.battery.level = new_level
# XXX: do we need to wait here a bit? this WFM...
moz_level = marionette.emulator.battery.level
self.assertEquals(int(new_level * 100), int(moz_level * 100))
# verify that the 'levelchange' listener was hit
level_changed = marionette.execute_async_script("""
var callback = arguments[arguments.length - 1];
function check_level_change() {
if (window.wrappedJSObject._levelchanged) {
callback(window.wrappedJSObject._levelchanged);
}
else {
setTimeout(check_level_change, 500);
}
}
setTimeout(check_level_change, 0);
""")
self.assertTrue(level_changed)

View File

@ -0,0 +1,9 @@
[DEFAULT]
b2g = true
browser = false
qemu = true
[test_dial_answer.py]
[test_dial_between_emulators.py]
[test_dial_listeners.py]

View File

@ -0,0 +1,89 @@
from marionette_test import *
class MultiEmulatorDialTest(MarionetteTestCase):
"""A simple test which verifies the ability of one emulator to dial
another and to detect an incoming call.
"""
def test_dial_answer(self):
# Tests always have one emulator available as self.marionette; we'll
# use this for the receiving emulator. We'll also launch a second
# emulator to use as the sender.
sender = self.get_new_emulator()
receiver = self.marionette
# Setup the event listsener on the receiver, which should store
# a global variable when an incoming call is received.
receiver.set_context("chrome")
self.assertTrue(receiver.execute_script("""
return navigator.mozTelephony != undefined && navigator.mozTelephony != null;
"""))
receiver.execute_script("""
window.wrappedJSObject.incoming = null;
navigator.mozTelephony.addEventListener("incoming", function test_incoming(e) {
navigator.mozTelephony.removeEventListener("incoming", test_incoming);
window.wrappedJSObject.incoming = e.call;
});
""")
# Dial the receiver from the sender.
toPhoneNumber = "1555521%d" % receiver.emulator.port
fromPhoneNumber = "1555521%d" % sender.emulator.port
sender.set_context("chrome")
sender.execute_script("""
window.wrappedJSObject.call = navigator.mozTelephony.dial("%s");
""" % toPhoneNumber)
# On the receiver, wait up to 30s for an incoming call to be
# detected, by checking the value of the global var that the
# listener will change.
receiver.set_script_timeout(30000)
received = receiver.execute_async_script("""
window.wrappedJSObject.callstate = null;
waitFor(function() {
let call = window.wrappedJSObject.incoming;
call.addEventListener("connected", function test_connected(e) {
call.removeEventListener("connected", test_connected);
window.wrappedJSObject.callstate = e.call.state;
});
marionetteScriptFinished(call.number);
},
function() {
return window.wrappedJSObject.incoming != null;
});
""")
# Verify the phone number of the incoming call.
self.assertEqual(received, fromPhoneNumber)
# On the sender, add a listener to verify that the call changes
# state to connected when it's answered.
sender.execute_script("""
let call = window.wrappedJSObject.call;
window.wrappedJSObject.callstate = null;
call.addEventListener("connected", function test_connected(e) {
call.removeEventListener("connected", test_connected);
window.wrappedJSObject.callstate = e.call.state;
});
""")
# Answer the call and verify that the callstate changes to
# connected.
receiver.execute_async_script("""
window.wrappedJSObject.incoming.answer();
waitFor(function() {
marionetteScriptFinished(true);
}, function() {
return window.wrappedJSObject.callstate == "connected";
});
""")
# Verify that the callstate changes to connected on the caller as well.
self.assertTrue(receiver.execute_async_script("""
waitFor(function() {
marionetteScriptFinished(true);
}, function() {
return window.wrappedJSObject.callstate == "connected";
});
"""))

View File

@ -0,0 +1,55 @@
from marionette_test import *
class MultiEmulatorDialTest(MarionetteTestCase):
"""A simple test which verifies the ability of one emulator to dial
another and to detect an incoming call.
"""
def test_dial_between_emulators(self):
# Tests always have one emulator available as self.marionette; we'll
# use this for the receiving emulator. We'll also launch a second
# emulator to use as the sender.
sender = self.get_new_emulator()
receiver = self.marionette
# Setup the event listsener on the receiver, which should store
# a global variable when an incoming call is received.
receiver.set_context("chrome")
self.assertTrue(receiver.execute_script("""
return navigator.mozTelephony != undefined && navigator.mozTelephony != null;
"""))
receiver.execute_script("""
window.wrappedJSObject.incoming = "none";
navigator.mozTelephony.addEventListener("incoming", function(e) {
window.wrappedJSObject.incoming = e.call.number;
});
""")
# Dial the receiver from the sender.
toPhoneNumber = "1555521%d" % receiver.emulator.port
fromPhoneNumber = "1555521%d" % sender.emulator.port
sender.set_context("chrome")
sender.execute_script("""
navigator.mozTelephony.dial("%s");
""" % toPhoneNumber)
# On the receiver, wait up to 30s for an incoming call to be
# detected, by checking the value of the global var that the
# listener will change.
receiver.set_script_timeout(30000)
received = receiver.execute_async_script("""
function check_incoming() {
if (window.wrappedJSObject.incoming != "none") {
marionetteScriptFinished(window.wrappedJSObject.incoming);
}
else {
setTimeout(check_incoming, 500);
}
}
setTimeout(check_incoming, 0);
""")
# Verify the phone number of the incoming call.
self.assertEqual(received, fromPhoneNumber)

View File

@ -0,0 +1,149 @@
from marionette_test import *
class DialListenerTest(MarionetteTestCase):
"""A test of some of the different listeners for nsIDOMTelephonyCall.
"""
def test_dial_listeners(self):
# Tests always have one emulator available as self.marionette; we'll
# use this for the receiving emulator. We'll also launch a second
# emulator to use as the sender.
sender = self.get_new_emulator()
receiver = self.marionette
receiver.set_script_timeout(30000)
sender.set_script_timeout(30000)
receiver.set_context("chrome")
sender.set_context("chrome")
toPhoneNumber = "1555521%d" % receiver.emulator.port
fromPhoneNumber = "1555521%d" % sender.emulator.port
# Setup the event listsener on the receiver, which should store
# a global variable when an incoming call is received.
self.assertTrue(receiver.execute_script("""
return navigator.mozTelephony != undefined && navigator.mozTelephony != null;
"""))
receiver.execute_script("""
window.wrappedJSObject.incoming = null;
navigator.mozTelephony.addEventListener("incoming", function test_incoming(e) {
navigator.mozTelephony.removeEventListener("incoming", test_incoming);
window.wrappedJSObject.incoming = e.call;
});
""")
# dial the receiver from the sender
sender.execute_script("""
window.wrappedJSObject.sender_state = [];
window.wrappedJSObject.sender_call = navigator.mozTelephony.dial("%s");
window.wrappedJSObject.sender_call.addEventListener("statechange", function test_sender_statechange(e) {
if (e.call.state == 'disconnected')
window.wrappedJSObject.sender_call.removeEventListener("statechange", test_sender_statechange);
window.wrappedJSObject.sender_state.push(e.call.state);
});
window.wrappedJSObject.sender_alerting = false;
window.wrappedJSObject.sender_call.addEventListener("alerting", function test_sender_alerting(e) {
window.wrappedJSObject.sender_call.removeEventListener("alerting", test_sender_alerting);
window.wrappedJSObject.sender_alerting = e.call.state == 'alerting';
});
""" % toPhoneNumber)
# On the receiver, wait up to 30s for an incoming call to be
# detected, by checking the value of the global var that the
# listener will change.
received = receiver.execute_async_script("""
window.wrappedJSObject.receiver_state = [];
waitFor(function() {
let call = window.wrappedJSObject.incoming;
call.addEventListener("statechange", function test_statechange(e) {
if (e.call.state == 'disconnected')
call.removeEventListener("statechange", test_statechange);
window.wrappedJSObject.receiver_state.push(e.call.state);
});
call.addEventListener("connected", function test_connected(e) {
call.removeEventListener("connected", test_connected);
window.wrappedJSObject.receiver_connected = e.call.state == 'connected';
});
marionetteScriptFinished(call.number);
},
function() {
return window.wrappedJSObject.incoming != null;
});
""")
# Verify the phone number of the incoming call.
self.assertEqual(received, fromPhoneNumber)
# At this point, the sender's call should be in a 'alerting' state,
# as reflected by both 'statechange' and 'alerting' listeners.
self.assertTrue('alerting' in sender.execute_script("return window.wrappedJSObject.sender_state;"))
self.assertTrue(sender.execute_script("return window.wrappedJSObject.sender_alerting;"))
# Answer the call and verify that the callstate changes to
# connected.
receiver.execute_async_script("""
window.wrappedJSObject.incoming.answer();
waitFor(function() {
marionetteScriptFinished(true);
}, function() {
return window.wrappedJSObject.receiver_connected;
});
""")
state = receiver.execute_script("return window.wrappedJSObject.receiver_state;")
self.assertTrue('connecting' in state)
self.assertTrue('connected' in state)
# verify that the callstate changes to connected on the caller as well
self.assertTrue('connected' in sender.execute_async_script("""
waitFor(function() {
marionetteScriptFinished(window.wrappedJSObject.sender_state);
}, function() {
return window.wrappedJSObject.sender_call.state == "connected";
});
"""))
# setup listeners to detect the 'disconnected event'
sender.execute_script("""
window.wrappedJSObject.sender_disconnected = null;
window.wrappedJSObject.sender_call.addEventListener("disconnected", function test_disconnected(e) {
window.wrappedJSObject.sender_call.removeEventListener("disconnected", test_disconnected);
window.wrappedJSObject.sender_disconnected = e.call.state == 'disconnected';
});
""")
receiver.execute_script("""
window.wrappedJSObject.receiver_disconnected = null;
window.wrappedJSObject.incoming.addEventListener("disconnected", function test_disconnected(e) {
window.wrappedJSObject.incoming.removeEventListener("disconnected", test_disconnected);
window.wrappedJSObject.receiver_disconnected = e.call.state == 'disconnected';
});
""")
# hang up from the receiver's side
receiver.execute_script("""
window.wrappedJSObject.incoming.hangUp();
""")
# Verify that the call state on the sender is 'disconnected', as
# notified by both the 'statechange' and 'disconnected' listeners.
sender_state = sender.execute_async_script("""
waitFor(function() {
marionetteScriptFinished(window.wrappedJSObject.sender_state);
}, function () {
return window.wrappedJSObject.sender_call.state == 'disconnected';
});
""")
self.assertTrue('disconnected' in sender_state)
self.assertTrue(sender.execute_script("return window.wrappedJSObject.sender_disconnected;"))
# Verify that the call state on the receiver is 'disconnected', as
# notified by both the 'statechange' and 'disconnected' listeners.
state = receiver.execute_async_script("""
waitFor(function() {
marionetteScriptFinished(window.wrappedJSObject.receiver_state);
}, function () {
return window.wrappedJSObject.incoming.state == 'disconnected';
});
""")
self.assertTrue('disconnected' in state)
self.assertTrue('disconnecting' in state)
self.assertTrue(receiver.execute_script("return window.wrappedJSObject.receiver_disconnected;"))

View File

@ -0,0 +1,47 @@
# Marionette Client
[Marionette](https://developer.mozilla.org/en/Marionette) is a
Mozilla project to enable remote automation in Gecko-based projects,
including desktop Firefox, mobile Firefox, and Boot-to-Gecko.
It utilizes the [remote-debugger](https://wiki.mozilla.org/Platform/JSDebugv2)
inside Gecko for the transport layer of the Marionette server. The commands
the Marionette server will eventually implement are based on
Selenium's [JSON Wire Protocol](http://code.google.com/p/selenium/wiki/JsonWireProtocol),
although not all commands are presently implemented, and additional commands
will likely be added.
## Package Files
- client.py: This is the Marionette socket client; it speaks the same
socket protocol as the Gecko remote debugger.
- marionette.py: The Marionette client. This uses client.py to communicate
with a server that speaks the Gecko remote debugger protocol.
- selenium_proxy.py: Acts as a remote driver for Selenium test runners.
This code translates the Selenium
[JSON Wire Protocol](http://code.google.com/p/selenium/wiki/JsonWireProtocol)
to the [Marionette JSON Protocol](https://wiki.mozilla.org/Auto-tools/Projects/Marionette/JSON_Protocol).
This allows Selenium tests to utilize Marionette.
- testserver.py: A socket server which mimics the remote debugger in
Gecko, and can be used to test pieces of the Marionette client.
- test_protocol.py: Tests the Marionette JSON Protocol by using testserver.py.
- test_selenium.py: Tests the Selenium proxy by using testserver.py.
## Installation
You'll need the ManifestDestiny and MozHttpd packages from Mozbase:
git clone git://github.com/mozilla/mozbase.git
cd mozbase
python setup_development.py
Other than that, there are no special requirements, unless you're using the Selenium proxy, in which
case you'll need to install the Selenium Python bindings using:
pip install selenium
## Writing and Running Tests Using Marionette
See [Writing Marionette tests](https://developer.mozilla.org/en/Marionette/Tests),
and [Running Marionette tests](https://developer.mozilla.org/en/Marionette/Running_Tests).

View File

@ -0,0 +1,3 @@
from marionette import Marionette, HTMLElement
from marionette_test import MarionetteTestCase

View File

@ -0,0 +1,8 @@
[marionette]
es_server = buildbot-es.metrics.sjc1.mozilla.com:9200
rest_server = http://brasstacks.mozilla.com/autologserver/
[tests]
marionette = tests/unit-tests.ini
marionette-gaia = $homedir$/gaia/tests

View File

@ -0,0 +1,218 @@
# This function will run the pulse build watcher,
# then on detecting a build, it will run the tests
# using that build.
import ConfigParser
import json
import logging
import os
import sys
import traceback
import urllib
import mozlog
import shutil
from optparse import OptionParser
from threading import Thread, RLock
from manifestparser import TestManifest
from runtests import MarionetteTestRunner
from marionette import Marionette
from mozinstall import install
from mozillapulse.config import PulseConfiguration
from mozillapulse.consumers import GenericConsumer
class B2GPulseConsumer(GenericConsumer):
def __init__(self, **kwargs):
super(B2GPulseConsumer, self).__init__(PulseConfiguration(**kwargs),
'org.mozilla.exchange.b2g',
**kwargs)
class B2GAutomation:
def __init__(self, tests, testfile=None,
es_server=None, rest_server=None, testgroup='marionette'):
self.logger = mozlog.getLogger('B2G_AUTOMATION')
self.tests = tests
self.testfile = testfile
self.es_server = es_server
self.rest_server = rest_server
self.testgroup = testgroup
self.lock = RLock()
self.logger.info("Testlist: %s" % self.tests)
pulse = B2GPulseConsumer(applabel='b2g_build_listener')
pulse.configure(topic='#', callback=self.on_build)
if not self.testfile:
self.logger.info('waiting for pulse messages...')
pulse.listen()
else:
t = Thread(target=pulse.listen)
t.daemon = True
t.start()
f = open(self.testfile, 'r')
data = json.loads(f.read())
self.on_build(data, None)
def get_test_list(self, manifest):
self.logger.info("Reading test manifest: %s" % manifest)
mft = TestManifest()
mft.read(manifest)
# In the future if we want to add in more processing to the manifest
# here is where you'd do that. Right now, we just return a list of
# tests
testlist = []
for i in mft.get():
testlist.append(i["path"])
return testlist
def on_build(self, data, msg):
# Found marionette build! Install it
if msg is not None:
msg.ack()
self.lock.acquire()
try:
self.logger.info("got pulse message! %s" % repr(data))
if "buildurl" in data["payload"]:
directory = self.install_build(data['payload']['buildurl'])
rev = data["payload"]["commit"]
if directory == None:
self.logger.info("Failed to return build directory")
else:
self.run_marionette(directory, rev)
self.cleanup(directory)
else:
self.logger.error("Failed to find buildurl in msg, not running test")
except:
self.logger.exception("error while processing build")
self.lock.release()
# Download the build and untar it, return the directory it untared to
def install_build(self, url):
try:
self.logger.info("Installing build from url: %s" % url)
buildfile = os.path.abspath("b2gtarball.tar.gz")
urllib.urlretrieve(url, buildfile)
except:
self.logger.exception("Failed to download build")
try:
self.logger.info("Untarring build")
# Extract to the same local directory where we downloaded the build
# to. This defaults to the local directory where our script runs
dest = os.path.join(os.path.dirname(buildfile), 'downloadedbuild')
if (os.access(dest, os.F_OK)):
shutil.rmtree(dest)
install(buildfile, dest)
# This should extract into a qemu directory
qemu = os.path.join(dest, 'qemu')
if os.path.exists(qemu):
return qemu
else:
return None
except:
self.logger.exception("Failed to untar file")
return None
def run_marionette(self, dir, rev):
self.logger.info("Starting test run for revision: %s" % rev)
runner = MarionetteTestRunner(emulator=True,
homedir=dir,
autolog=True,
revision=rev,
logger=self.logger,
es_server=self.es_server,
rest_server=self.rest_server,
testgroup=self.testgroup)
for test in self.tests:
manifest = test[1].replace('$homedir$', os.path.dirname(dir))
testgroup = test[0]
runner.testgroup = testgroup
runner.run_tests([manifest], 'b2g')
def cleanup(self, dir):
self.logger.info("Cleaning up")
if os.path.exists("b2gtarball.tar.gz"):
os.remove("b2gtarball.tar.gz")
if os.path.exists(dir):
shutil.rmtree(dir)
def main():
parser = OptionParser(usage="%prog <options>")
parser.add_option("--config", action="store", dest="config_file",
default="automation.conf",
help="Specify the configuration file")
parser.add_option("--testfile", action="store", dest="testfile",
help = "Start in test mode without using pulse, "
"utilizing the pulse message defined in the specified file")
parser.add_option("--test-manifest", action="store", dest="testmanifest",
default = os.path.join("tests","unit-tests.ini"),
help="Specify the test manifest, defaults to tests/all-tests.ini")
parser.add_option("--log-file", action="store", dest="logfile",
default="b2gautomation.log",
help="Log file to store results, defaults to b2gautomation.log")
LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR")
LEVEL_STRING = ", ".join(LOG_LEVELS)
parser.add_option("--log-level", action="store", type="choice",
dest="loglevel", default="DEBUG", choices=LOG_LEVELS,
help = "One of %s for logging level, defaults to debug" % LEVEL_STRING)
options, args = parser.parse_args()
cfg = ConfigParser.ConfigParser()
cfg.read(options.config_file)
try:
es_server = cfg.get('marionette', 'es_server')
except:
# let mozautolog provide the default
es_server = None
try:
rest_server = cfg.get('marionette', 'rest_server')
except:
# let mozautolog provide the default
rest_server = None
try:
tests = cfg.items('tests')
except:
tests = [('marionette', options.testmanifest)]
if not options.testmanifest:
parser.print_usage()
parser.exit()
if not os.path.exists(options.testmanifest):
print "Could not find manifest file: %s" % options.testmanifest
parser.print_usage()
parser.exit()
# Set up the logger
if os.path.exists(options.logfile):
os.remove(options.logfile)
logger = mozlog.getLogger("B2G_AUTOMATION", options.logfile)
if options.loglevel:
logger.setLevel(getattr(mozlog, options.loglevel, "DEBUG"))
logger.addHandler(logging.StreamHandler())
try:
b2gauto = B2GAutomation(tests,
testfile=options.testfile,
es_server=es_server,
rest_server=rest_server)
except:
s = traceback.format_exc()
logger.error(s)
return 1
return 0
if __name__ == "__main__":
main()

View File

@ -0,0 +1,118 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import json
import socket
class MarionetteClient(object):
""" The Marionette socket client. This speaks the same protocol
as the remote debugger inside Gecko, in which messages are
always preceded by the message length and a colon, e.g.,
20:{'command': 'test'}
"""
def __init__(self, addr, port):
self.addr = addr
self.port = port
self.sock = None
self.traits = None
self.applicationType = None
self.actor = 'root'
def _recv_n_bytes(self, n):
""" Convenience method for receiving exactly n bytes from
self.sock (assuming it's open and connected).
"""
data = ''
while len(data) < n:
chunk = self.sock.recv(n - len(data))
if chunk == '':
break
data += chunk
return data
def receive(self):
""" Receive the next complete response from the server, and return
it as a dict. Each response from the server is prepended by
len(message) + ':'.
"""
assert(self.sock)
response = self.sock.recv(10)
sep = response.find(':')
length = response[0:sep]
response = response[sep + 1:]
response += self._recv_n_bytes(int(length) + 1 + len(length) - 10)
return json.loads(response)
def connect(self):
""" Connect to the server and process the hello message we expect
to receive in response.
"""
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(180.0)
try:
self.sock.connect((self.addr, self.port))
except:
# Unset self.sock so that the next attempt to send will cause
# another connection attempt.
self.sock = None
raise
hello = self.receive()
self.traits = hello.get('traits')
self.applicationType = hello.get('applicationType')
# get the marionette actor id
response = self.send({'to':'root', 'type': 'getMarionetteID'})
self.actor = response['id']
def send(self, msg):
""" Send a message on the socket, prepending it with len(msg) + ':'.
"""
if not self.sock:
self.connect()
if 'to' not in msg:
msg['to'] = self.actor
data = json.dumps(msg)
self.sock.send('%s:%s' % (len(data), data))
response = self.receive()
return response
def close(self):
""" Close the socket.
"""
self.sock.close()
self.sock = None

View File

@ -0,0 +1,262 @@
import datetime
import os
import re
import shutil
import socket
import subprocess
from telnetlib import Telnet
import tempfile
import time
from emulator_battery import EmulatorBattery
class Emulator(object):
deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$")
def __init__(self, homedir=None, noWindow=False):
self.port = None
self._emulator_launched = False
self.proc = None
self.marionette_port = None
self.telnet = None
self._tmp_userdata = None
self._adb_started = False
self.battery = EmulatorBattery(self)
self.homedir = homedir
self.noWindow = noWindow
if self.homedir is not None:
self.homedir = os.path.expanduser(homedir)
def _check_for_b2g(self):
if self.homedir is None:
self.homedir = os.getenv('B2G_HOME')
if self.homedir is None:
raise Exception('Must define B2G_HOME or pass the homedir parameter')
self.adb = os.path.join(self.homedir,
'glue/gonk/out/host/linux-x86/bin/adb')
if not os.access(self.adb, os.F_OK):
self.adb = os.path.join(self.homedir, 'bin/adb')
self.binary = os.path.join(self.homedir,
'glue/gonk/out/host/linux-x86/bin/emulator')
if not os.access(self.binary, os.F_OK):
self.binary = os.path.join(self.homedir, 'bin/emulator')
self._check_file(self.binary)
self.kernelImg = os.path.join(self.homedir,
'boot/kernel-android-qemu/arch/arm/boot/zImage')
if not os.access(self.kernelImg, os.F_OK):
self.kernelImg = os.path.join(self.homedir, 'zImage')
self._check_file(self.kernelImg)
self.sysDir = os.path.join(self.homedir,
'glue/gonk/out/target/product/generic/')
if not os.access(self.sysDir, os.F_OK):
self.sysDir = os.path.join(self.homedir, 'generic/')
self._check_file(self.sysDir)
self.dataImg = os.path.join(self.sysDir, 'userdata.img')
self._check_file(self.dataImg)
def __del__(self):
if self.telnet:
self.telnet.write('exit\n')
self.telnet.read_all()
def _check_file(self, filePath):
if not os.access(filePath, os.F_OK):
raise Exception(('File not found: %s; did you pass the B2G home '
'directory as the homedir parameter, or set '
'B2G_HOME correctly?') % filePath)
@property
def args(self):
qemuArgs = [ self.binary,
'-kernel', self.kernelImg,
'-sysdir', self.sysDir,
'-data', self.dataImg ]
if self.noWindow:
qemuArgs.append('-no-window')
qemuArgs.extend(['-memory', '512',
'-partition-size', '512',
'-verbose',
'-skin', '480x800',
'-qemu', '-cpu', 'cortex-a8'])
return qemuArgs
@property
def is_running(self):
if self._emulator_launched:
return self.proc is not None and self.proc.poll() is None
else:
return self.port is not None
def _check_for_adb(self):
self.adb = os.path.join(self.homedir,
'glue/gonk/out/host/linux-x86/bin/adb')
if not os.access(self.adb, os.F_OK):
self.adb = os.path.join(self.homedir, 'bin/adb')
if not os.access(self.adb, os.F_OK):
adb = subprocess.Popen(['which', 'adb'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
retcode = adb.wait()
if retcode:
raise Exception('adb not found!')
out = adb.stdout.read().strip()
if len(out) and out.find('/') > -1:
self.adb = out
def _run_adb(self, args):
args.insert(0, self.adb)
adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
retcode = adb.wait()
if retcode:
raise Exception('adb terminated with exit code %d: %s'
% (retcode, adb.stdout.read()))
return adb.stdout.read()
def _get_telnet_response(self, command=None):
output = []
assert(self.telnet)
if command is not None:
self.telnet.write('%s\n' % command)
while True:
line = self.telnet.read_until('\n')
output.append(line.rstrip())
if line.startswith('OK'):
return output
elif line.startswith('KO:'):
raise Exception ('bad telnet response: %s' % line)
def _run_telnet(self, command):
if not self.telnet:
self.telnet = Telnet('localhost', self.port)
self._get_telnet_response()
return self._get_telnet_response(command)
def close(self):
if self.is_running and self._emulator_launched:
self.proc.terminate()
self.proc.wait()
if self._adb_started:
self._run_adb(['kill-server'])
self._adb_started = False
if self.proc:
retcode = self.proc.poll()
self.proc = None
if self._tmp_userdata:
os.remove(self._tmp_userdata)
self._tmp_userdata = None
return retcode
return 0
def _get_adb_devices(self):
offline = set()
online = set()
output = self._run_adb(['devices'])
for line in output.split('\n'):
m = self.deviceRe.match(line)
if m:
if m.group(3) == 'offline':
offline.add(m.group(1))
else:
online.add(m.group(1))
return (online, offline)
def restart(self, port):
if not self._emulator_launched:
return
self.close()
self.start()
return self.setup_port_forwarding(port)
def start_adb(self):
result = self._run_adb(['start-server'])
# We keep track of whether we've started adb or not, so we know
# if we need to kill it.
if 'daemon started successfully' in result:
self._adb_started = True
else:
self._adb_started = False
def connect(self):
self._check_for_adb()
self.start_adb()
online, offline = self._get_adb_devices()
now = datetime.datetime.now()
while online == set([]):
time.sleep(1)
if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
raise Exception('timed out waiting for emulator to be available')
online, offline = self._get_adb_devices()
self.port = int(list(online)[0])
def start(self):
self._check_for_b2g()
self.start_adb()
# Make a copy of the userdata.img for this instance of the emulator
# to use.
self._tmp_userdata = tempfile.mktemp(prefix='marionette')
shutil.copyfile(self.dataImg, self._tmp_userdata)
qemu_args = self.args[:]
qemu_args[qemu_args.index('-data') + 1] = self._tmp_userdata
original_online, original_offline = self._get_adb_devices()
self.proc = subprocess.Popen(qemu_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
online, offline = self._get_adb_devices()
now = datetime.datetime.now()
while online - original_online == set([]):
time.sleep(1)
if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
raise Exception('timed out waiting for emulator to start')
online, offline = self._get_adb_devices()
self.port = int(list(online - original_online)[0])
self._emulator_launched = True
def setup_port_forwarding(self, remote_port):
""" Setup TCP port forwarding to the specified port on the device,
using any availble local port, and return the local port.
"""
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("",0))
local_port = s.getsockname()[1]
s.close()
output = self._run_adb(['-s', 'emulator-%d' % self.port,
'forward',
'tcp:%d' % local_port,
'tcp:%d' % remote_port])
self.marionette_port = local_port
return local_port
def wait_for_port(self, timeout=300):
assert(self.marionette_port)
starttime = datetime.datetime.now()
while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', self.marionette_port))
data = sock.recv(16)
sock.close()
if '"from"' in data:
return True
except:
import traceback
print traceback.format_exc()
time.sleep(1)
return False

View File

@ -0,0 +1,49 @@
class EmulatorBattery(object):
def __init__(self, emulator):
self.emulator = emulator
def get_state(self):
status = {}
state = {}
response = self.emulator._run_telnet('power display')
for line in response:
if ':' in line:
field, value = line.split(':')
value = value.strip()
if value == 'true':
value = True
elif value == 'false':
value = False
elif field == 'capacity':
value = float(value)
status[field] = value
state['level'] = status.get('capacity', 0.0) / 100
if status.get('AC') == 'online':
state['charging'] = True
else:
state['charging'] = False
return state
def get_charging(self):
return self.get_state()['charging']
def get_level(self):
return self.get_state()['level']
def set_level(self, level):
self.emulator._run_telnet('power capacity %d' % (level * 100))
def set_charging(self, charging):
if charging:
cmd = 'power ac on'
else:
cmd = 'power ac off'
self.emulator._run_telnet(cmd)
charging = property(get_charging, set_charging)
level = property(get_level, set_level)

View File

@ -0,0 +1,78 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
class MarionetteException(Exception):
def __init__(self, message=None, status=500, stacktrace=None):
self.message = message
self.status = status
self.stacktrace = stacktrace
def __str__(self):
if self.stacktrace:
return '%s\nstacktrace:\n%s' % (self.message,
''.join(['\t%s\n' % x for x in self.stacktrace.split('\n')]))
else:
return self.message
class TimeoutException(MarionetteException):
pass
class JavascriptException(MarionetteException):
pass
class NoSuchElementException(MarionetteException):
pass
class XPathLookupException(MarionetteException):
pass
class NoSuchWindowException(MarionetteException):
pass
class StaleElementException(MarionetteException):
pass
class ScriptTimeoutException(MarionetteException):
pass
class ElementNotVisibleException(MarionetteException):
pass
class NoSuchFrameException(MarionetteException):
pass

View File

@ -0,0 +1,68 @@
from git import *
from optparse import OptionParser
import os
import sys
def updategaia(repopath):
b2g = Repo(repopath)
gaia = Repo(os.path.join(repopath, 'gaia'))
gaia_submodule = None
for submodule in b2g.submodules:
if 'gaia' in submodule.name:
gaia_submodule = submodule
assert(gaia_submodule)
gaia_submodule_commit = gaia_submodule.hexsha
print 'gaia_submodule_commit', gaia_submodule_commit
gaia.heads.master.checkout()
print 'pulling from gaia origin/master'
gaia.remotes.origin.pull('master')
gaia_new_head = gaia.heads.master.commit.hexsha
print 'gaia_new_head', gaia_new_head
if gaia_submodule_commit == gaia_new_head:
print 'no change, exiting with code 10'
sys.exit(10)
def commitgaia(repopath):
b2g = Repo(repopath)
gaia = Repo(os.path.join(repopath, 'gaia'))
gaia_submodule = None
for submodule in b2g.submodules:
if 'gaia' in submodule.name:
gaia_submodule = submodule
assert(gaia_submodule)
gaia_submodule.binsha = gaia_submodule.module().head.commit.binsha
b2g.index.add([gaia_submodule])
commit = b2g.index.commit('Update gaia')
print 'pushing to B2G origin/master'
b2g.remotes.origin.push(b2g.head.reference)
print 'done!'
if __name__ == '__main__':
parser = OptionParser(usage='%prog [options]')
parser.add_option("--repo",
action = "store", dest = "repo",
help = "path to B2G repo")
parser.add_option("--updategaia",
action = "store_true", dest = "updategaia",
help = "update the Gaia submodule to HEAD")
parser.add_option("--commitgaia",
action = "store_true", dest = "commitgaia",
help = "commit current Gaia submodule HEAD")
options, tests = parser.parse_args()
if not options.repo:
raise 'must specify --repo /path/to/B2G'
if options.updategaia:
updategaia(options.repo)
elif options.commitgaia:
commitgaia(options.repo)
else:
raise 'No command specified'

View File

@ -0,0 +1,353 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import socket
from client import MarionetteClient
from errors import *
from emulator import Emulator
class HTMLElement(object):
CLASS = "class name"
SELECTOR = "css selector"
ID = "id"
NAME = "name"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
TAG = "tag name"
XPATH = "xpath"
def __init__(self, marionette, id):
self.marionette = marionette
assert(id is not None)
self.id = id
def __str__(self):
return self.id
def equals(self, other_element):
return self.marionette._send_message('elementsEqual', 'value', elements=[self.id, other_element.id])
def find_element(self, method, target):
return self.marionette.find_element(method, target, self.id)
def find_elements(self, method, target):
return self.marionette.find_elements(method, target, self.id)
def get_attribute(self, attribute):
return self.marionette._send_message('getElementAttribute', 'value', element=self.id, name=attribute)
def click(self):
return self.marionette._send_message('clickElement', 'ok', element=self.id)
def text(self):
return self.marionette._send_message('getElementText', 'value', element=self.id)
def send_keys(self, string):
return self.marionette._send_message('sendKeysToElement', 'ok', element=self.id, value=string)
def value(self):
return self.marionette._send_message('getElementValue', 'value', element=self.id)
def clear(self):
return self.marionette._send_message('clearElement', 'ok', element=self.id)
def selected(self):
return self.marionette._send_message('isElementSelected', 'value', element=self.id)
def enabled(self):
return self.marionette._send_message('isElementEnabled', 'value', element=self.id)
def displayed(self):
return self.marionette._send_message('isElementDisplayed', 'value', element=self.id)
class Marionette(object):
CONTEXT_CHROME = 'chrome'
CONTEXT_CONTENT = 'content'
def __init__(self, host='localhost', port=2828, emulator=False,
connectToRunningEmulator=False, homedir=None,
baseurl=None, noWindow=False):
self.host = host
self.port = self.local_port = port
self.session = None
self.window = None
self.emulator = None
self.homedir = homedir
self.baseurl = baseurl
self.noWindow = noWindow
if emulator:
self.emulator = Emulator(homedir=homedir, noWindow=self.noWindow)
self.emulator.start()
self.port = self.emulator.setup_port_forwarding(self.port)
assert(self.emulator.wait_for_port())
if connectToRunningEmulator:
self.emulator = Emulator(homedir=homedir)
self.emulator.connect()
self.port = self.emulator.setup_port_forwarding(self.port)
assert(self.emulator.wait_for_port())
self.client = MarionetteClient(self.host, self.port)
def __del__(self):
if self.emulator:
self.emulator.close()
def _send_message(self, command, response_key, **kwargs):
if not self.session and command not in ('newSession', 'getStatus'):
raise MarionetteException(message="Please start a session")
message = { 'type': command }
if self.session:
message['session'] = self.session
if kwargs:
message.update(kwargs)
try:
response = self.client.send(message)
except socket.timeout:
self.session = None
self.window = None
self.client.close()
if self.emulator:
port = self.emulator.restart(self.local_port)
if port is not None:
self.port = self.client.port = port
raise TimeoutException(message='socket.timeout', status=21, stacktrace=None)
if (response_key == 'ok' and response.get('ok') == True) or response_key in response:
return response[response_key]
else:
self._handle_error(response)
def _handle_error(self, response):
if 'error' in response and isinstance(response['error'], dict):
status = response['error'].get('status', 500)
message = response['error'].get('message')
stacktrace = response['error'].get('stacktrace')
# status numbers come from
# http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
if status == 7:
raise NoSuchElementException(message=message, status=status, stacktrace=stacktrace)
elif status == 8:
raise NoSuchFrameException(message=message, status=status, stacktrace=stacktrace)
elif status == 10:
raise StaleElementException(message=message, status=status, stacktrace=stacktrace)
elif status == 11:
raise ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace)
elif status == 17:
raise JavascriptException(message=message, status=status, stacktrace=stacktrace)
elif status == 19:
raise XPathLookupException(message=message, status=status, stacktrace=stacktrace)
elif status == 21:
raise TimeoutException(message=message, status=status, stacktrace=stacktrace)
elif status == 23:
raise NoSuchWindowException(message=message, status=status, stacktrace=stacktrace)
elif status == 28:
raise ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace)
else:
raise MarionetteException(message=message, status=status, stacktrace=stacktrace)
raise MarionetteException(message=response, status=500)
def absolute_url(self, relative_url):
return "%s%s" % (self.baseurl, relative_url)
def status(self):
return self._send_message('getStatus', 'value')
def start_session(self, desired_capabilities=None):
# We are ignoring desired_capabilities, at least for now.
self.session = self._send_message('newSession', 'value')
self.b2g = 'b2g' in self.session
return self.session
def delete_session(self):
response = self._send_message('deleteSession', 'ok')
self.session = None
self.window = None
self.client.close()
return response
def get_session_capabilities(self):
response = self._send_message('getSessionCapabilities', 'value')
return response
def set_script_timeout(self, timeout):
response = self._send_message('setScriptTimeout', 'ok', value=timeout)
return response
def set_search_timeout(self, timeout):
response = self._send_message('setSearchTimeout', 'ok', value=timeout)
return response
def get_window(self):
self.window = self._send_message('getWindow', 'value')
return self.window
def get_windows(self):
response = self._send_message('getWindows', 'value')
return response
def close_window(self, window_id=None):
if not window_id:
window_id = self.get_window()
response = self._send_message('closeWindow', 'ok', value=window_id)
return response
def set_context(self, context):
assert(context == self.CONTEXT_CHROME or context == self.CONTEXT_CONTENT)
return self._send_message('setContext', 'ok', value=context)
def switch_to_window(self, window_id):
response = self._send_message('switchToWindow', 'ok', value=window_id)
self.window = window_id
return response
def switch_to_frame(self, frame=None):
if isinstance(frame, HTMLElement):
response = self._send_message('switchToFrame', 'ok', element=frame.id)
else:
response = self._send_message('switchToFrame', 'ok', value=frame)
return response
def get_url(self):
response = self._send_message('getUrl', 'value')
return response
def navigate(self, url):
response = self._send_message('goUrl', 'ok', value=url)
return response
def go_back(self):
response = self._send_message('goBack', 'ok')
return response
def go_forward(self):
response = self._send_message('goForward', 'ok')
return response
def refresh(self):
response = self._send_message('refresh', 'ok')
return response
def wrapArguments(self, args):
if isinstance(args, list):
wrapped = []
for arg in args:
wrapped.append(self.wrapArguments(arg))
elif isinstance(args, dict):
wrapped = {}
for arg in args:
wrapped[arg] = self.wrapArguments(args[arg])
elif type(args) == HTMLElement:
wrapped = {'ELEMENT': args.id }
elif (isinstance(args, bool) or isinstance(args, basestring) or
isinstance(args, int) or args is None):
wrapped = args
return wrapped
def unwrapValue(self, value):
if isinstance(value, list):
unwrapped = []
for item in value:
unwrapped.append(self.unwrapValue(item))
elif isinstance(value, dict):
unwrapped = {}
for key in value:
if key == 'ELEMENT':
unwrapped = HTMLElement(self, value[key])
else:
unwrapped[key] = self.unwrapValue(value[key])
else:
unwrapped = value
return unwrapped
def execute_js_script(self, script, script_args=None, timeout=True):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
response = self._send_message('executeJSScript',
'value',
value=script,
args=args,
timeout=timeout)
return self.unwrapValue(response)
def execute_script(self, script, script_args=None):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
response = self._send_message('executeScript', 'value', value=script, args=args)
return self.unwrapValue(response)
def execute_async_script(self, script, script_args=None):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
response = self._send_message('executeAsyncScript', 'value', value=script, args=args)
return self.unwrapValue(response)
def find_element(self, method, target, id=None):
kwargs = { 'value': target, 'using': method }
if id:
kwargs['element'] = id
response = self._send_message('findElement', 'value', **kwargs)
element = HTMLElement(self, response)
return element
def find_elements(self, method, target, id=None):
kwargs = { 'value': target, 'using': method }
if id:
kwargs['element'] = id
response = self._send_message('findElements', 'value', **kwargs)
assert(isinstance(response, list))
elements = []
for x in response:
elements.append(HTMLElement(self, x))
return elements
def log(self, msg, level=None):
return self._send_message('log', 'ok', value=msg, level=level)
def get_logs(self):
return self._send_message('getLogs', 'value')

View File

@ -0,0 +1,167 @@
import os
import re
import sys
import types
import unittest
from errors import *
from marionette import HTMLElement, Marionette
def skip_if_b2g(target):
def wrapper(self, *args, **kwargs):
if not hasattr(self.marionette, 'b2g') or not self.marionette.b2g:
return target(self, *args, **kwargs)
else:
sys.stderr.write('skipping ... ')
return wrapper
class CommonTestCase(unittest.TestCase):
def __init__(self, methodName):
self._qemu = []
unittest.TestCase.__init__(self, methodName)
def kill_gaia_app(self, url):
self.marionette.execute_script("""
window.wrappedJSObject.getApplicationManager().kill("%s");
return(true);
""" % url)
def kill_gaia_apps(self):
# shut down any running Gaia apps
# XXX there's no API to do this currently
pass
def launch_gaia_app(self, url):
# launch the app using Gaia's AppManager
self.marionette.set_script_timeout(30000)
frame = self.marionette.execute_async_script("""
var frame = window.wrappedJSObject.getApplicationManager().launch("%s").element;
window.addEventListener('message', function frameload(e) {
if (e.data == 'appready') {
window.removeEventListener('message', frameload);
marionetteScriptFinished(frame);
}
});
""" % url)
self.assertTrue(isinstance(frame, HTMLElement))
return frame
def setUp(self):
if self.marionette.session is None:
self.marionette.start_session()
self.loglines = None
def tearDown(self):
if self.marionette.session is not None:
self.marionette.delete_session()
for _qemu in self._qemu:
_qemu.emulator.close()
_qemu = None
self._qemu = []
class MarionetteTestCase(CommonTestCase):
def __init__(self, marionette, methodName='runTest'):
self.marionette = marionette
CommonTestCase.__init__(self, methodName)
def get_new_emulator(self):
_qemu = Marionette(emulator=True,
homedir=self.marionette.homedir,
baseurl=self.marionette.baseurl,
noWindow=self.marionette.noWindow)
_qemu.start_session()
self._qemu.append(_qemu)
return _qemu
class MarionetteJSTestCase(CommonTestCase):
context_re = re.compile(r"MARIONETTE_CONTEXT(\s*)=(\s*)['|\"](.*?)['|\"];")
timeout_re = re.compile(r"MARIONETTE_TIMEOUT(\s*)=(\s*)(\d+);")
launch_re = re.compile(r"MARIONETTE_LAUNCH_APP(\s*)=(\s*)['|\"](.*?)['|\"];")
def __init__(self, marionette, methodName='runTest', jsFile=None):
assert(jsFile)
self.jsFile = jsFile
self.marionette = marionette
CommonTestCase.__init__(self, methodName)
def runTest(self):
if self.marionette.session is None:
self.marionette.start_session()
f = open(self.jsFile, 'r')
js = f.read()
args = []
# if this is a browser_ test, prepend head.js to it
if os.path.basename(self.jsFile).startswith('browser_'):
local_head = open(os.path.join(os.path.dirname(__file__), 'tests', 'head.js'), 'r')
js = local_head.read() + js
head = open(os.path.join(os.path.dirname(self.jsFile), 'head.js'), 'r')
for line in head:
# we need a bigger timeout than the default specified by the
# 'real' head.js
if 'const kDefaultWait' in line:
js += 'const kDefaultWait = 45000;\n'
else:
js += line
context = self.context_re.search(js)
if context:
context = context.group(3)
self.marionette.set_context(context)
timeout = self.timeout_re.search(js)
if timeout:
timeout = timeout.group(3)
self.marionette.set_script_timeout(timeout)
launch_app = self.launch_re.search(js)
if launch_app:
launch_app = launch_app.group(3)
frame = self.launch_gaia_app(launch_app)
args.append({'__marionetteArgs': {'appframe': frame}})
try:
results = self.marionette.execute_js_script(js, args)
self.loglines = self.marionette.get_logs()
if launch_app:
self.kill_gaia_app(launch_app)
self.assertTrue(not 'timeout' in self.jsFile,
'expected timeout not triggered')
if 'fail' in self.jsFile:
self.assertTrue(results['failed'] > 0,
"expected test failures didn't occur")
else:
fails = []
for failure in results['failures']:
diag = "" if failure.get('diag') is None else "| %s " % failure['diag']
name = "got false, expected true" if failure.get('name') is None else failure['name']
fails.append('TEST-UNEXPECTED-FAIL %s| %s' % (diag, name))
self.assertEqual(0, results['failed'],
'%d tests failed:\n%s' % (results['failed'], '\n'.join(fails)))
self.assertTrue(results['passed'] + results['failed'] > 0,
'no tests run')
if self.marionette.session is not None:
self.marionette.delete_session()
except ScriptTimeoutException:
if 'timeout' in self.jsFile:
# expected exception
pass
else:
self.loglines = self.marionette.get_logs()
raise

View File

@ -0,0 +1,391 @@
from datetime import datetime
import imp
import inspect
import logging
from optparse import OptionParser
import os
import types
import unittest
import socket
import sys
import time
try:
from manifestparser import TestManifest
from mozhttpd import iface, MozHttpd
except ImportError:
print "manifestparser or mozhttpd not found! Please install mozbase:\n"
print "\tgit clone git clone git://github.com/mozilla/mozbase.git"
print "\tpython setup_development.py\n"
import sys
sys.exit(1)
from marionette import Marionette
from marionette_test import MarionetteJSTestCase
class MarionetteTestResult(unittest._TextTestResult):
def __init__(self, *args):
super(MarionetteTestResult, self).__init__(*args)
self.passed = 0
def addSuccess(self, test):
super(MarionetteTestResult, self).addSuccess(test)
self.passed += 1
def getInfo(self, test):
if hasattr(test, 'jsFile'):
return os.path.basename(test.jsFile)
else:
return '%s.py:%s.%s' % (test.__class__.__module__,
test.__class__.__name__,
test._testMethodName)
def getDescription(self, test):
doc_first_line = test.shortDescription()
if self.descriptions and doc_first_line:
return '\n'.join((str(test), doc_first_line))
else:
desc = str(test)
if hasattr(test, 'jsFile'):
desc = "%s, %s" % (test.jsFile, desc)
return desc
def printLogs(self, test):
for testcase in test._tests:
if hasattr(testcase, 'loglines') and testcase.loglines:
print 'START LOG:'
for line in testcase.loglines:
print ' '.join(line)
print 'END LOG:'
class MarionetteTextTestRunner(unittest.TextTestRunner):
resultclass = MarionetteTestResult
def _makeResult(self):
return self.resultclass(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
result = self._makeResult()
result.failfast = self.failfast
result.buffer = self.buffer
startTime = time.time()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
try:
test(result)
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
stopTime = time.time()
timeTaken = stopTime - startTime
result.printErrors()
result.printLogs(test)
if hasattr(result, 'separator2'):
self.stream.writeln(result.separator2)
run = result.testsRun
self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", timeTaken))
self.stream.writeln()
expectedFails = unexpectedSuccesses = skipped = 0
try:
results = map(len, (result.expectedFailures,
result.unexpectedSuccesses,
result.skipped))
except AttributeError:
pass
else:
expectedFails, unexpectedSuccesses, skipped = results
infos = []
if not result.wasSuccessful():
self.stream.write("FAILED")
failed, errored = map(len, (result.failures, result.errors))
if failed:
infos.append("failures=%d" % failed)
if errored:
infos.append("errors=%d" % errored)
else:
self.stream.write("OK")
if skipped:
infos.append("skipped=%d" % skipped)
if expectedFails:
infos.append("expected failures=%d" % expectedFails)
if unexpectedSuccesses:
infos.append("unexpected successes=%d" % unexpectedSuccesses)
if infos:
self.stream.writeln(" (%s)" % (", ".join(infos),))
else:
self.stream.write("\n")
return result
class MarionetteTestRunner(object):
def __init__(self, address=None, emulator=False, homedir=None,
autolog=False, revision=None, es_server=None,
rest_server=None, logger=None, testgroup="marionette",
noWindow=False):
self.address = address
self.emulator = emulator
self.homedir = homedir
self.autolog = autolog
self.testgroup = testgroup
self.revision = revision
self.es_server = es_server
self.rest_server = rest_server
self.logger = logger
self.noWindow = noWindow
self.httpd = None
self.baseurl = None
self.marionette = None
self.reset_test_stats()
if self.logger is None:
self.logger = logging.getLogger('Marionette')
self.logger.setLevel(logging.INFO)
self.logger.addHandler(logging.StreamHandler())
def reset_test_stats(self):
self.passed = 0
self.failed = 0
self.todo = 0
self.failures = []
def start_httpd(self):
host = iface.get_lan_ip()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("",0))
port = s.getsockname()[1]
s.close()
self.baseurl = 'http://%s:%d/' % (host, port)
self.logger.info('running webserver on %s' % self.baseurl)
self.httpd = MozHttpd(host=host,
port=port,
docroot=os.path.join(os.path.dirname(__file__), 'www'))
self.httpd.start()
def start_marionette(self):
assert(self.baseurl is not None)
if self.address:
host, port = self.address.split(':')
if self.emulator:
self.marionette = Marionette(host=host, port=int(port),
connectToRunningEmulator=True,
homedir=self.homedir,
baseurl=self.baseurl)
else:
self.marionette = Marionette(host=host, port=int(port), baseurl=self.baseurl)
elif self.emulator:
self.marionette = Marionette(emulator=True,
homedir=self.homedir,
baseurl=self.baseurl,
noWindow=self.noWindow)
else:
raise Exception("must specify address or emulator")
def post_to_autolog(self, elapsedtime):
self.logger.info('posting results to autolog')
# This is all autolog stuff.
# See: https://wiki.mozilla.org/Auto-tools/Projects/Autolog
from mozautolog import RESTfulAutologTestGroup
testgroup = RESTfulAutologTestGroup(
testgroup = self.testgroup,
os = 'android',
platform = 'emulator',
harness = 'marionette',
server = self.es_server,
restserver = self.rest_server,
machine = socket.gethostname())
testgroup.set_primary_product(
tree = 'b2g',
buildtype = 'opt',
revision = self.revision)
testgroup.add_test_suite(
testsuite = 'b2g emulator testsuite',
elapsedtime = elapsedtime.seconds,
cmdline = '',
passed = self.passed,
failed = self.failed,
todo = self.todo)
# Add in the test failures.
for f in self.failures:
testgroup.add_test_failure(test=f[0], text=f[1], status=f[2])
testgroup.submit()
def run_tests(self, tests, testtype=None):
self.reset_test_stats()
starttime = datetime.utcnow()
for test in tests:
self.run_test(test, testtype)
self.logger.info('\nSUMMARY\n-------')
self.logger.info('passed: %d' % self.passed)
self.logger.info('failed: %d' % self.failed)
self.logger.info('todo: %d' % self.todo)
elapsedtime = datetime.utcnow() - starttime
if self.autolog:
self.post_to_autolog(elapsedtime)
if self.marionette.emulator:
self.marionette.emulator.close()
self.marionette.emulator = None
self.marionette = None
def run_test(self, test, testtype):
if not self.httpd:
self.start_httpd()
if not self.marionette:
self.start_marionette()
if not os.path.isabs(test):
filepath = os.path.join(os.path.dirname(__file__), test)
else:
filepath = test
if os.path.isdir(filepath):
for root, dirs, files in os.walk(filepath):
for filename in files:
if ((filename.startswith('test_') or filename.startswith('browser_')) and
(filename.endswith('.py') or filename.endswith('.js'))):
filepath = os.path.join(root, filename)
self.run_test(filepath, testtype)
return
mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])
testloader = unittest.TestLoader()
suite = unittest.TestSuite()
if file_ext == '.ini':
if testtype is not None:
testargs = {}
testtypes = testtype.replace('+', ' +').replace('-', ' -').split()
for atype in testtypes:
if atype.startswith('+'):
testargs.update({ atype[1:]: 'true' })
elif atype.startswith('-'):
testargs.update({ atype[1:]: 'false' })
else:
testargs.update({ atype: 'true' })
manifest = TestManifest()
manifest.read(filepath)
if testtype is None:
manifest_tests = manifest.get()
else:
manifest_tests = manifest.get(**testargs)
for i in manifest_tests:
self.run_test(i["path"], testtype)
return
self.logger.info('TEST-START %s' % os.path.basename(test))
if file_ext == '.py':
test_mod = imp.load_source(mod_name, filepath)
for name in dir(test_mod):
obj = getattr(test_mod, name)
if (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, unittest.TestCase)):
testnames = testloader.getTestCaseNames(obj)
for testname in testnames:
suite.addTest(obj(self.marionette, methodName=testname))
elif file_ext == '.js':
suite.addTest(MarionetteJSTestCase(self.marionette, jsFile=filepath))
if suite.countTestCases():
results = MarionetteTextTestRunner(verbosity=3).run(suite)
self.failed += len(results.failures) + len(results.errors)
self.todo = 0
if hasattr(results, 'skipped'):
self.todo += len(results.skipped) + len(results.expectedFailures)
self.passed += results.passed
for failure in results.failures + results.errors:
self.failures.append((results.getInfo(failure[0]), failure[1], 'TEST-UNEXPECTED-FAIL'))
if hasattr(results, 'unexpectedSuccess'):
self.failed += len(results.unexpectedSuccesses)
for failure in results.unexpectedSuccesses:
self.failures.append((results.getInfo(failure[0]), failure[1], 'TEST-UNEXPECTED-PASS'))
def cleanup(self):
if self.httpd:
self.httpd.stop()
__del__ = cleanup
if __name__ == "__main__":
parser = OptionParser(usage='%prog [options] test_file_or_dir <test_file_or_dir> ...')
parser.add_option("--autolog",
action = "store_true", dest = "autolog",
default = False,
help = "send test results to autolog")
parser.add_option("--revision",
action = "store", dest = "revision",
help = "git revision for autolog submissions")
parser.add_option("--testgroup",
action = "store", dest = "testgroup",
help = "testgroup names for autolog submissions")
parser.add_option("--emulator",
action = "store_true", dest = "emulator",
default = False,
help = "launch a B2G emulator on which to run tests")
parser.add_option("--no-window",
action = "store_true", dest = "noWindow",
default = False,
help = "when Marionette launches an emulator, start it "
"with the -no-window argument")
parser.add_option('--address', dest='address', action='store',
help='host:port of running Gecko instance to connect to')
parser.add_option('--type', dest='type', action='store',
default='browser+b2g',
help = "The type of test to run, can be a combination "
"of values defined in unit-tests.ini; individual values "
"are combined with '+' or '-' chars. Ex: 'browser+b2g' "
"means the set of tests which are compatible with both "
"browser and b2g; 'b2g-qemu' means the set of tests "
"which are compatible with b2g but do not require an "
"emulator. This argument is only used when loading "
"tests from .ini files.")
parser.add_option('--homedir', dest='homedir', action='store',
help='home directory of emulator files')
options, tests = parser.parse_args()
if not tests:
parser.print_usage()
parser.exit()
if not options.emulator and not options.address:
parser.print_usage()
print "must specify --emulator or --address"
parser.exit()
runner = MarionetteTestRunner(address=options.address,
emulator=options.emulator,
homedir=options.homedir,
noWindow=options.noWindow,
revision=options.revision,
testgroup=options.testgroup,
autolog=options.autolog)
runner.run_tests(tests, testtype=options.type)
if runner.failed > 0:
sys.exit(10)

View File

@ -0,0 +1,297 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import BaseHTTPServer
import json
import re
import traceback
from errors import *
from marionette import Marionette, HTMLElement
class SeleniumRequestServer(BaseHTTPServer.HTTPServer):
def __init__(self, marionette, *args, **kwargs):
self.marionette = marionette
BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
def __del__(self):
if self.marionette.server:
self.marionette.delete_session()
class SeleniumRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
pathRe = re.compile(r'/session/(.*?)/((element/(.*?)/)?(.*))')
def server_error(self, error):
self.send_response(500)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({'status': 500, 'value': {'message': error}}))
def file_not_found(self):
self.send_response(404)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({'status': 404, 'value': {'message': '%s not found' % self.path}}))
def send_JSON(self, data=None, session=None, value=None):
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
if data is None:
data = {}
if not 'status' in data:
data['status'] = 0
if session is not None:
data['sessionId'] = session
if value is None:
data['value'] = {}
else:
data['value'] = value
self.wfile.write(json.dumps(data))
def process_request(self):
session = body = None
path = self.path
element = None
m = self.pathRe.match(self.path)
if m:
session = m.group(1)
element = m.group(4)
path = '/%s' % m.group(5)
content_len = self.headers.getheader('content-length')
if content_len:
body = json.loads(self.rfile.read(int(content_len)))
return path, body, session, element
def do_DELETE(self):
try:
path, body, session, element = self.process_request()
if path == '/session':
assert(session)
assert(self.server.marionette.delete_session())
self.send_JSON(session=session)
elif path == '/window':
assert(session)
assert(self.server.marionette.close_window())
self.send_JSON(session=session)
else:
self.file_not_found()
except MarionetteException, e:
self.send_JSON(data={'status': e.status}, value={'message': e.message})
except:
self.server_error(traceback.format_exc())
def do_GET(self):
try:
path, body, session, element = self.process_request()
if path.startswith('/attribute/'):
assert(session)
name = path[len('/attribute/'):]
marionette_element = HTMLElement(self.server.marionette, element)
self.send_JSON(session=session,
value=marionette_element.get_attribute(name))
elif path == '/displayed':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
self.send_JSON(session=session,
value=marionette_element.displayed())
elif path == '/enabled':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
self.send_JSON(session=session,
value=marionette_element.enabled())
elif path.startswith('/equals/'):
assert(session)
other = path[len('/equals'):]
marionette_element = HTMLElement(self.server.marionette, element)
other_element = HTMLElement(self.server.marionette, other)
self.send_JSON(session=session,
value=marionette_element.equals(other))
elif path == '/selected':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
self.send_JSON(session=session,
value=marionette_element.selected())
elif path == '/status':
self.send_JSON(data=self.server.marionette.status())
elif path == '/text':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
self.send_JSON(session=session,
value=marionette_element.text())
elif path == '/url':
assert(session)
self.send_JSON(value=self.server.marionette.get_url(),
session=session)
elif path == '/value':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
send.send_JSON(session=session,
value=marionette_element.value())
elif path == '/window_handle':
assert(session)
self.send_JSON(session=session,
value=self.server.marionette.get_window())
elif path == '/window_handles':
assert(session)
self.send_JSON(session=session,
value=self.server.marionette.get_windows())
else:
self.file_not_found()
except MarionetteException, e:
self.send_JSON(data={'status': e.status}, value={'message': e.message})
except:
self.server_error(traceback.format_exc())
def do_POST(self):
try:
path, body, session, element = self.process_request()
if path == '/back':
assert(session)
assert(self.server.marionette.go_back())
self.send_JSON(session=session)
elif path == '/clear':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
marionette_element.clear()
self.send_JSON(session=session)
elif path == '/click':
assert(session)
marionette_element = HTMLElement(self.server.marionette, element)
marionette_element.click()
self.send_JSON(session=session)
elif path == '/element':
# find element variants
assert(session)
self.send_JSON(session=session,
value={'ELEMENT': str(self.server.marionette.find_element(body['using'], body['value'], id=element))})
elif path == '/elements':
# find elements variants
assert(session)
self.send_JSON(session=session,
value=[{'ELEMENT': str(x)} for x in self.server.marionette.find_elements(body['using'], body['value'])])
elif path == '/execute':
assert(session)
if body['args']:
result = self.server.marionette.execute_script(body['script'], script_args=body['args'])
else:
result = self.server.marionette.execute_script(body['script'])
self.send_JSON(session=session, value=result)
elif path == '/execute_async':
assert(session)
if body['args']:
result = self.server.marionette.execute_async_script(body['script'], script_args=body['args'])
else:
result = self.server.marionette.execute_async_script(body['script'])
self.send_JSON(session=session, value=result)
elif path == '/forward':
assert(session)
assert(self.server.marionette.go_forward())
self.send_JSON(session=session)
elif path == '/frame':
assert(session)
frame = body['id']
if isinstance(frame, dict) and 'ELEMENT' in frame:
frame = HTMLElement(self.server.marionette, frame['ELEMENT'])
assert(self.server.marionette.switch_to_frame(frame))
self.send_JSON(session=session)
elif path == '/refresh':
assert(session)
assert(self.server.marionette.refresh())
self.send_JSON(session=session)
elif path == '/session':
session = self.server.marionette.start_session()
# 'value' is the browser capabilities, which we're ignoring for now
self.send_JSON(session=session, value={})
elif path == '/timeouts/async_script':
assert(session)
assert(self.server.marionette.set_script_timeout(body['ms']))
self.send_JSON(session=session)
elif path == '/timeouts/implicit_wait':
assert(session)
assert(self.server.marionette.set_search_timeout(body['ms']))
self.send_JSON(session=session)
elif path == '/url':
assert(session)
assert(self.server.marionette.navigate(body['url']))
self.send_JSON(session=session)
elif path == '/value':
assert(session)
keys = ''.join(body['value'])
marionette_element = HTMLElement(self.server.marionette, element)
assert(marionette_element.send_keys(keys))
self.send_JSON(session=session)
elif path == '/window':
assert(session)
assert(self.server.marionette.switch_to_window(body['name']))
self.send_JSON(session=session)
else:
self.file_not_found()
except MarionetteException, e:
self.send_JSON(data={'status': e.status}, value={'message': e.message})
except:
self.server_error(traceback.format_exc())
class SeleniumProxy(object):
def __init__(self, remote_host, remote_port, proxy_port=4444):
self.remote_host = remote_host
self.remote_port = remote_port
self.proxy_port = proxy_port
def start(self):
marionette = Marionette(self.remote_host, self.remote_port)
httpd = SeleniumRequestServer(marionette,
('127.0.0.1', self.proxy_port),
SeleniumRequestHandler)
httpd.serve_forever()
if __name__ == "__main__":
proxy = SeleniumProxy('localhost', 2626)
proxy.start()

View File

@ -0,0 +1,45 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
from marionette import Marionette, HTMLElement
if __name__ == '__main__':
# launch Fennec with Marionette before starting this test!
m = Marionette(host='localhost', port=2828)
assert(m.start_session())
assert(10 == m.execute_script('return 10;'))

View File

@ -0,0 +1,40 @@
import time
from marionette import Marionette, HTMLElement
#
# Test that Marionette can manage multiple emulators.
# Before running this code, you should have built B2G with the config-qemu
# configuration, see
# https://wiki.mozilla.org/Auto-tools/Projects/Marionette/DevNotes#Running_B2G_on_an_emulator
#
# You should also set your B2G_HOME environment variable to point to the
# directory where the B2G code lives.
#
if __name__ == '__main__':
# launch two instance of Marionette, each with their own emulator
driver1 = Marionette(emulator=True, port=2828)
assert(driver1.emulator.is_running)
assert(driver1.emulator.port)
print 'emulator1 is running on port', driver1.emulator.port
assert(driver1.port != 2828)
print 'emulator1 port forwarding configured from port', driver1.port
print 'on localhost to port 2828 on the device'
assert(driver1.start_session())
driver2 = Marionette(emulator=True, port=2828)
assert(driver2.emulator.is_running)
assert(driver2.emulator.port)
print 'emulator2 is running on port', driver2.emulator.port
assert(driver2.port != 2828)
print 'emulator1 port forwarding configured from port', driver2.port
print 'on localhost to port 2828 on the device'
assert(driver2.start_session())
# shutdown both emulators
assert(driver2.emulator.close() == 0)
assert(not driver2.emulator.is_running)
assert(driver1.emulator.close() == 0)
assert(not driver1.emulator.is_running)

View File

@ -0,0 +1,97 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import threading
from testserver import TestServer
from marionette import Marionette, HTMLElement
if __name__ == '__main__':
# start the test server
server = TestServer(2626)
thread = threading.Thread(target=server.run)
thread.daemon = True
thread.start()
# run some trivial unit tests which just verify the protocol
m = Marionette(host='localhost', port=2626)
assert(m.status()['os']['arch'] == 'x86')
assert(m.start_session())
assert(m.get_session_capabilities()['javascriptEnabled'] == True)
assert(m.get_window() == server.TEST_CURRENT_WINDOW)
assert(m.window == server.TEST_CURRENT_WINDOW)
assert(m.get_windows() == server.TEST_WINDOW_LIST)
assert(m.switch_to_window('window2'))
assert(m.window == 'window2')
assert(m.close_window('window2'))
assert(m.set_script_timeout(1000))
assert(m.set_search_timeout(500))
assert(m.get_url() == server.TEST_URL)
assert(m.navigate(server.TEST_URL))
assert(m.go_back())
assert(m.go_forward())
assert(m.refresh())
assert(m.execute_script(server.TEST_EXECUTE_SCRIPT))
assert(m.execute_js_script(server.TEST_EXECUTE_SCRIPT))
assert(m.execute_js_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS))
assert(m.execute_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS))
assert(m.execute_async_script(server.TEST_EXECUTE_SCRIPT))
assert(m.execute_async_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS))
assert(str(m.find_element(HTMLElement.CLASS, 'heading')) == server.TEST_FIND_ELEMENT)
assert([str(x) for x in m.find_elements(HTMLElement.TAG, 'p')] == server.TEST_FIND_ELEMENTS)
assert(str(m.find_element(HTMLElement.CLASS, 'heading').find_element(HTMLElement.TAG, 'h1')) == server.TEST_FIND_ELEMENT)
assert([str(x) for x in m.find_element(HTMLElement.ID, 'div1').find_elements(HTMLElement.SELECTOR, '.main')] == \
server.TEST_FIND_ELEMENTS)
assert(m.find_element(HTMLElement.ID, 'id1').click())
assert(m.find_element(HTMLElement.ID, 'id2').text() == server.TEST_GET_TEXT)
assert(m.find_element(HTMLElement.ID, 'id3').send_keys('Mozilla Firefox'))
assert(m.find_element(HTMLElement.ID, 'id3').value() == server.TEST_GET_VALUE)
assert(m.find_element(HTMLElement.ID, 'id3').clear())
assert(m.find_element(HTMLElement.ID, 'id3').selected())
assert(m.find_element(HTMLElement.ID, 'id1').equals(m.find_element(HTMLElement.TAG, 'p')))
assert(m.find_element(HTMLElement.ID, 'id3').enabled())
assert(m.find_element(HTMLElement.ID, 'id3').displayed())
assert(m.find_element(HTMLElement.ID, 'id3').get_attribute('value') == server.TEST_GET_VALUE)
assert(m.delete_session())
# verify a session is started automatically for us if needed
assert(m.switch_to_frame('frame1'))
assert(m.switch_to_frame(1))
assert(m.switch_to_frame(m.find_element(HTMLElement.ID, 'frameid')))
assert(m.switch_to_frame())
assert(m.get_window() == server.TEST_CURRENT_WINDOW)
assert(m.set_context(m.CONTEXT_CHROME))
assert(m.delete_session())

View File

@ -0,0 +1,222 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import threading
try:
from selenium import webdriver
from selenium.common.exceptions import *
from selenium.webdriver.remote.webelement import WebElement
except:
print 'requires selenium Python bindings; pip install selenium'
raise
from selenium_proxy import SeleniumProxy
from testserver import TestServer
def test_find_element(driver, fn):
""" Test a find_element_by_FOO method of both the webdriver
and the WebElement class. The former will return a WebElement which
should have a method of the same name, which should also return
a WebElement.
"""
element = getattr(driver, fn)('foo')
assert(isinstance(element, WebElement))
assert(element.id == TestServer.TEST_FIND_ELEMENT)
child = getattr(element, fn)('foo')
assert(isinstance(child, WebElement))
assert(child.id == TestServer.TEST_FIND_ELEMENT)
def test_find_elements(driver, fn):
""" Test a find_elements_by_FOO method of both the webdriver
and the WebElement class. The former will return a list of
WebElements, each of which should have a method of the same name,
and which in should turn should also return a list of WebElements.
"""
elements = getattr(driver, fn)('foo')
# elements should be a list
assert(isinstance(elements, list))
# elements should match the TEST_FIND_ELEMENTS list
assert(map(lambda x: x.id, elements) == TestServer.TEST_FIND_ELEMENTS)
# Each member of elements should be a WebElement that has the same
# method, which should in turn return a list of WebElements when called.
for element in elements:
assert(isinstance(element, WebElement))
children = getattr(element, fn)('foo')
assert(isinstance(children, list))
assert(map(lambda x: x.id, children) == TestServer.TEST_FIND_ELEMENTS)
assert(len(filter(lambda x: not isinstance(x, WebElement), children)) == 0)
if __name__ == '__main__':
# start the test server on port 2626
server = TestServer(2626)
thread = threading.Thread(target=server.run)
thread.daemon = True
thread.start()
# Start the selenium proxy on port 4444, connecting to the test server
# on port 2626.
proxy = SeleniumProxy('127.0.0.1', 2626, proxy_port=4444)
proxy_thread = threading.Thread(target=proxy.start)
proxy_thread.daemon = True
proxy_thread.start()
# invoke selenium commands as tests
driver = webdriver.Remote(command_executor='http://127.0.0.1:4444',
desired_capabilities=webdriver.DesiredCapabilities.FIREFOX)
assert(driver)
# test navigation methods
driver.get(TestServer.TEST_URL)
assert(driver.current_url == TestServer.TEST_URL)
driver.back()
driver.forward()
driver.refresh()
# test script methods
driver.set_script_timeout(10) # in selenium the number is in seconds
driver.implicitly_wait(10) # ditto
assert(TestServer.TEST_EXECUTE_RETURN_VALUE == driver.execute_script(TestServer.TEST_EXECUTE_SCRIPT))
assert(TestServer.TEST_EXECUTE_RETURN_VALUE == driver.execute_script(TestServer.TEST_EXECUTE_SCRIPT,
TestServer.TEST_EXECUTE_SCRIPT_ARGS))
assert(TestServer.TEST_EXECUTE_RETURN_VALUE == driver.execute_async_script(TestServer.TEST_EXECUTE_SCRIPT))
assert(TestServer.TEST_EXECUTE_RETURN_VALUE == driver.execute_async_script(TestServer.TEST_EXECUTE_SCRIPT,
TestServer.TEST_EXECUTE_SCRIPT_ARGS))
# test all the find_element_by_FOO methods
test_find_element(driver, 'find_element_by_name')
test_find_element(driver, 'find_element_by_id')
test_find_element(driver, 'find_element_by_xpath')
test_find_element(driver, 'find_element_by_link_text')
test_find_element(driver, 'find_element_by_partial_link_text')
test_find_element(driver, 'find_element_by_tag_name')
test_find_element(driver, 'find_element_by_class_name')
test_find_element(driver, 'find_element_by_css_selector')
# test all the find_elements_by_FOO methods
test_find_elements(driver, 'find_elements_by_name')
test_find_elements(driver, 'find_elements_by_id')
test_find_elements(driver, 'find_elements_by_xpath')
test_find_elements(driver, 'find_elements_by_link_text')
test_find_elements(driver, 'find_elements_by_partial_link_text')
test_find_elements(driver, 'find_elements_by_tag_name')
test_find_elements(driver, 'find_elements_by_class_name')
test_find_elements(driver, 'find_elements_by_css_selector')
# test WebElement methods
element = driver.find_element_by_name('foo')
element.click()
assert(element.text == TestServer.TEST_GET_TEXT)
element.send_keys('Mozilla Firefox')
element.clear()
assert(element.is_selected())
assert(element.is_enabled())
assert(element.is_displayed())
assert(element.get_attribute('id') == TestServer.TEST_GET_VALUE)
# make the server return error responses so we can test them
server.responses = server.error_responses
# test exception handling
try:
driver.execute_async_script(TestServer.TEST_EXECUTE_SCRIPT)
assert(False)
except TimeoutException:
# the Selenium Python driver maps SCRIPT_TIMEOUT to TIMEOUT
pass
try:
driver.execute_script(TestServer.TEST_EXECUTE_SCRIPT)
assert(False)
except WebDriverException:
# the Selenium Python driver doesn't specifically support JAVASCRIPT_ERROR
pass
try:
driver.find_element_by_name('foo')
assert(False)
except NoSuchElementException:
pass
try:
driver.find_elements_by_name('foo')
assert(False)
except WebDriverException:
# the Selenium Python driver doesn't specifically support XPATH_LOOKUP_ERROR
pass
try:
driver.close()
assert(False)
except NoSuchWindowException:
pass
try:
element.click()
assert(False)
except StaleElementReferenceException:
pass
try:
element.send_keys('Mozilla Firefox')
assert(False)
except ElementNotVisibleException:
pass
try:
driver.switch_to_frame('aframe')
assert(False)
except NoSuchFrameException:
pass
# restore normal test responses
server.responses = server.test_responses
# test window methods
assert(driver.current_window_handle == TestServer.TEST_CURRENT_WINDOW)
assert(driver.window_handles == TestServer.TEST_WINDOW_LIST)
driver.switch_to_window(TestServer.TEST_CURRENT_WINDOW)
# test frame methods
driver.switch_to_frame('aframe') # by name or id
driver.switch_to_frame(1) # by index
driver.switch_to_frame(element) # by element reference
driver.switch_to_frame(None) # null; switch to default frame
driver.close() # this is close_window
print 'Tests complete!'

View File

@ -0,0 +1,287 @@
MARIONETTE_CONTEXT="chrome";
MARIONETTE_TIMEOUT=120000;
// Must be synchronized with nsIDOMWindowUtils.
const COMPOSITION_ATTR_RAWINPUT = 0x02;
const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
var EventUtils = {
/**
* see http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/EventUtils.js
*/
sendMouseEvent: function EventUtils__sendMouseEvent(aEvent, aTarget, aWindow) {
if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
}
if (!aWindow) {
aWindow = window;
}
if (typeof(aTarget) == "string") {
aTarget = aWindow.document.getElementById(aTarget);
}
var event = aWindow.document.createEvent('MouseEvent');
var typeArg = aEvent.type;
var canBubbleArg = true;
var cancelableArg = true;
var viewArg = aWindow;
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
aEvent.type == 'mousedown' ||
aEvent.type == 'mouseup' ? 1 :
aEvent.type == 'dblclick'? 2 : 0);
var screenXArg = aEvent.screenX || 0;
var screenYArg = aEvent.screenY || 0;
var clientXArg = aEvent.clientX || 0;
var clientYArg = aEvent.clientY || 0;
var ctrlKeyArg = aEvent.ctrlKey || false;
var altKeyArg = aEvent.altKey || false;
var shiftKeyArg = aEvent.shiftKey || false;
var metaKeyArg = aEvent.metaKey || false;
var buttonArg = aEvent.button || 0;
var relatedTargetArg = aEvent.relatedTarget || null;
event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
screenXArg, screenYArg, clientXArg, clientYArg,
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, relatedTargetArg);
aTarget.dispatchEvent(event);
},
sendChar: function EventUtils_sendChar(aChar, aWindow) {
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
var hasShift = (aChar == aChar.toUpperCase());
this.synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
},
sendString: function EventUtils_sendString(aStr, aWindow) {
for (var i = 0; i < aStr.length; ++i) {
this.sendChar(aStr.charAt(i), aWindow);
}
},
sendKey: function EventUtils_sendKey(aKey, aWindow) {
var keyName = "VK_" + aKey.toUpperCase();
this.synthesizeKey(keyName, { shiftKey: false }, aWindow);
},
_getDOMWindowUtils: function EventUtils__getDOMWindowUtils(aWindow) {
if (!aWindow) {
aWindow = window;
}
// we need parent.SpecialPowers for:
// layout/base/tests/test_reftests_with_caret.html
// chrome: toolkit/content/tests/chrome/test_findbar.xul
// chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
/*if ("SpecialPowers" in window && window.SpecialPowers != undefined) {
return SpecialPowers.getDOMWindowUtils(aWindow);
}
if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) {
return parent.SpecialPowers.getDOMWindowUtils(aWindow);
}*/
//TODO: this is assuming we are in chrome space
return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
},
_computeKeyCodeFromChar: function EventUtils__computeKeyCodeFromChar(aChar) {
if (aChar.length != 1) {
return 0;
}
const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
if (aChar >= 'a' && aChar <= 'z') {
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
}
if (aChar >= 'A' && aChar <= 'Z') {
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
}
if (aChar >= '0' && aChar <= '9') {
return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
}
// returns US keyboard layout's keycode
switch (aChar) {
case '~':
case '`':
return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
case '!':
return nsIDOMKeyEvent.DOM_VK_1;
case '@':
return nsIDOMKeyEvent.DOM_VK_2;
case '#':
return nsIDOMKeyEvent.DOM_VK_3;
case '$':
return nsIDOMKeyEvent.DOM_VK_4;
case '%':
return nsIDOMKeyEvent.DOM_VK_5;
case '^':
return nsIDOMKeyEvent.DOM_VK_6;
case '&':
return nsIDOMKeyEvent.DOM_VK_7;
case '*':
return nsIDOMKeyEvent.DOM_VK_8;
case '(':
return nsIDOMKeyEvent.DOM_VK_9;
case ')':
return nsIDOMKeyEvent.DOM_VK_0;
case '-':
case '_':
return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
case '+':
case '=':
return nsIDOMKeyEvent.DOM_VK_EQUALS;
case '{':
case '[':
return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
case '}':
case ']':
return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
case '|':
case '\\':
return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
case ':':
case ';':
return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
case '\'':
case '"':
return nsIDOMKeyEvent.DOM_VK_QUOTE;
case '<':
case ',':
return nsIDOMKeyEvent.DOM_VK_COMMA;
case '>':
case '.':
return nsIDOMKeyEvent.DOM_VK_PERIOD;
case '?':
case '/':
return nsIDOMKeyEvent.DOM_VK_SLASH;
default:
return 0;
}
},
_parseModifiers: function EventUtils__parseModifiers(aEvent) {
const masks = Components.interfaces.nsIDOMNSEvent;
var mval = 0;
if (aEvent.shiftKey)
mval |= masks.SHIFT_MASK;
if (aEvent.ctrlKey)
mval |= masks.CONTROL_MASK;
if (aEvent.altKey)
mval |= masks.ALT_MASK;
if (aEvent.metaKey)
mval |= masks.META_MASK;
if (aEvent.accelKey)
mval |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
masks.CONTROL_MASK;
return mval;
},
isKeypressFiredKey: function EventUtils_isKeypressFiredKey(aDOMKeyCode) {
if (typeof(aDOMKeyCode) == "string") {
if (aDOMKeyCode.indexOf("VK_") == 0) {
aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
if (!aDOMKeyCode) {
throw "Unknown key: " + aDOMKeyCode;
}
} else {
// If the key generates a character, it must cause a keypress event.
return true;
}
}
switch (aDOMKeyCode) {
case KeyEvent.DOM_VK_SHIFT:
case KeyEvent.DOM_VK_CONTROL:
case KeyEvent.DOM_VK_ALT:
case KeyEvent.DOM_VK_CAPS_LOCK:
case KeyEvent.DOM_VK_NUM_LOCK:
case KeyEvent.DOM_VK_SCROLL_LOCK:
case KeyEvent.DOM_VK_META:
return false;
default:
return true;
}
},
synthesizeKey: function EventUtils_synthesizeKey(aKey, aEvent, aWindow) {
var utils = this._getDOMWindowUtils(aWindow);
if (utils) {
var keyCode = 0, charCode = 0;
if (aKey.indexOf("VK_") == 0) {
keyCode = KeyEvent["DOM_" + aKey];
if (!keyCode) {
throw "Unknown key: " + aKey;
}
} else {
charCode = aKey.charCodeAt(0);
keyCode = this._computeKeyCodeFromChar(aKey.charAt(0));
}
var modifiers = this._parseModifiers(aEvent);
if (!("type" in aEvent) || !aEvent.type) {
// Send keydown + (optional) keypress + keyup events.
var keyDownDefaultHappened =
utils.sendKeyEvent("keydown", keyCode, 0, modifiers);
if (this.isKeypressFiredKey(keyCode)) {
utils.sendKeyEvent("keypress", charCode ? 0 : keyCode, charCode,
modifiers, !keyDownDefaultHappened);
}
utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
} else if (aEvent.type == "keypress") {
// Send standalone keypress event.
utils.sendKeyEvent(aEvent.type, charCode ? 0 : keyCode,
charCode, modifiers);
} else {
// Send other standalone event than keypress.
utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers);
}
}
},
};
function waitForExplicitFinish() {}
var SpecialPowers = {
_prefService: Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch),
setBoolPref: function SpecialPowers__setBoolPref(pref, value) {
this._prefService.setBoolPref(pref, value);
},
};
var readyAndUnlocked;
// see http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/browser-test.js#478
function nextStep(arg) {
try {
__generator.send(arg);
} catch(ex if ex instanceof StopIteration) {
finish();
} catch(ex) {
ok(false, "Unhandled exception: " + ex);
finish();
}
}
// see http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/browser-test.js#523
function requestLongerTimeout() {
/* no-op! */
}
// The browser-chrome tests either start with test() or generatorTest().
var __generator = null;
if (typeof(test) != 'undefined')
test();
else if (typeof(generatorTest) != 'undefined') {
__generator = generatorTest();
__generator.next();
}

View File

@ -0,0 +1,19 @@
[DEFAULT]
; true if the test requires an emulator, otherwise false
qemu = false
; true if the test is compatible with the browser, otherwise false
browser = true
; true if the test is compatible with b2g, otherwise false
b2g = true
; webapi tests
[include:../../../../../dom/telephony/test/marionette/manifest.ini]
[include:../../../../../dom/battery/test/marionette/manifest.ini]
; marionette unit tests
[include:unit/unit-tests.ini]

View File

@ -0,0 +1,46 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
class TestClick(MarionetteTestCase):
def test_click(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
link = self.marionette.find_element("id", "mozLink")
link.click()
self.assertEqual("Clicked", self.marionette.execute_script("return document.getElementById('mozLink').innerHTML;"))

View File

@ -0,0 +1,121 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
from marionette_test import MarionetteTestCase, skip_if_b2g
from errors import JavascriptException, MarionetteException, ScriptTimeoutException
class TestExecuteAsyncContent(MarionetteTestCase):
def setUp(self):
super(TestExecuteAsyncContent, self).setUp()
self.marionette.set_script_timeout(1000)
def test_execute_async_simple(self):
self.assertEqual(1, self.marionette.execute_async_script("arguments[arguments.length-1](1);"))
def test_execute_async_ours(self):
self.assertEqual(1, self.marionette.execute_async_script("marionetteScriptFinished(1);"))
def test_execute_async_timeout(self):
self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;")
def test_no_timeout(self):
self.marionette.set_script_timeout(2000)
self.assertTrue(self.marionette.execute_async_script("""
var callback = arguments[arguments.length - 1];
setTimeout(function() { callback(true); }, 500);
"""))
@skip_if_b2g
def test_execute_async_unload(self):
self.marionette.set_script_timeout(5000)
unload = """
window.location.href = "about:blank";
"""
self.assertRaises(JavascriptException, self.marionette.execute_async_script, unload)
def test_check_window(self):
self.assertTrue(self.marionette.execute_async_script("marionetteScriptFinished(window !=null && window != undefined);"))
def test_same_context(self):
var1 = 'testing'
self.assertEqual(self.marionette.execute_script("""
window.wrappedJSObject._testvar = '%s';
return window.wrappedJSObject._testvar;
""" % var1), var1)
self.assertEqual(self.marionette.execute_async_script(
"marionetteScriptFinished(window.wrappedJSObject._testvar);"), var1)
def test_execute_no_return(self):
self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None)
def test_execute_js_exception(self):
self.assertRaises(JavascriptException,
self.marionette.execute_async_script, "foo(bar);")
def test_execute_async_js_exception(self):
self.assertRaises(JavascriptException,
self.marionette.execute_async_script, """
var callback = arguments[arguments.length - 1];
callback(foo());
""")
def test_script_finished(self):
self.assertTrue(self.marionette.execute_async_script("""
marionetteScriptFinished(true);
"""))
def test_execute_permission(self):
self.assertRaises(JavascriptException, self.marionette.execute_async_script, """
var c = Components.classes;
marionetteScriptFinished(1);
""")
class TestExecuteAsyncChrome(TestExecuteAsyncContent):
def setUp(self):
super(TestExecuteAsyncChrome, self).setUp()
self.marionette.set_context("chrome")
def test_execute_async_unload(self):
pass
def test_same_context(self):
pass
def test_execute_permission(self):
self.assertEqual(1, self.marionette.execute_async_script("""
var c = Components.classes;
marionetteScriptFinished(1);
"""))

View File

@ -0,0 +1,66 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
from marionette_test import MarionetteTestCase, skip_if_b2g
from errors import JavascriptException, MarionetteException, ScriptTimeoutException
class TestExecuteIsolationContent(MarionetteTestCase):
def setUp(self):
super(TestExecuteIsolationContent, self).setUp()
self.content = True
def test_execute_async_isolate(self):
# Results from one execute call that has timed out should not
# contaminate a future call.
multiplier = "*3" if self.content else "*1"
self.marionette.set_script_timeout(500)
self.assertRaises(ScriptTimeoutException,
self.marionette.execute_async_script,
("setTimeout(function() { marionetteScriptFinished(5%s); }, 3000);"
% multiplier))
self.marionette.set_script_timeout(6000)
result = self.marionette.execute_async_script("""
setTimeout(function() { marionetteScriptFinished(10%s); }, 5000);
""" % multiplier)
self.assertEqual(result, 30 if self.content else 10)
class TestExecuteIsolationChrome(TestExecuteIsolationContent):
def setUp(self):
super(TestExecuteIsolationChrome, self).setUp()
self.marionette.set_context("chrome")
self.content = False

View File

@ -0,0 +1,75 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
from marionette_test import MarionetteTestCase
from errors import JavascriptException, MarionetteException
class TestExecuteContent(MarionetteTestCase):
def test_execute_simple(self):
self.assertEqual(1, self.marionette.execute_script("return 1;"))
def test_check_window(self):
self.assertTrue(self.marionette.execute_script("return (window !=null && window != undefined);"))
def test_execute_no_return(self):
self.assertEqual(self.marionette.execute_script("1;"), None)
def test_execute_js_exception(self):
self.assertRaises(JavascriptException,
self.marionette.execute_script, "return foo(bar);")
def test_execute_permission(self):
self.assertRaises(JavascriptException,
self.marionette.execute_script,
"return Components.classes;")
def test_complex_return_values(self):
self.assertEqual(self.marionette.execute_script("return [1, 2];"), [1, 2])
self.assertEqual(self.marionette.execute_script("return {'foo': 'bar', 'fizz': 'fazz'};"),
{'foo': 'bar', 'fizz': 'fazz'})
self.assertEqual(self.marionette.execute_script("return [1, {'foo': 'bar'}, 2];"),
[1, {'foo': 'bar'}, 2])
self.assertEqual(self.marionette.execute_script("return {'foo': [1, 'a', 2]};"),
{'foo': [1, 'a', 2]})
class TestExecuteChrome(TestExecuteContent):
def setUp(self):
super(TestExecuteChrome, self).setUp()
self.marionette.set_context("chrome")
def test_execute_permission(self):
self.assertEqual(1, self.marionette.execute_script("var c = Components.classes;return 1;"))

View File

@ -0,0 +1,157 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
from marionette import HTMLElement
from errors import NoSuchElementException
class TestElements(MarionetteTestCase):
def test_id(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
found_el = self.marionette.find_element("id", "mozLink")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_tag_name(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementsByTagName('body')[0];")
found_el = self.marionette.find_element("tag name", "body")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_class_name(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementsByClassName('linkClass')[0];")
found_el = self.marionette.find_element("class name", "linkClass")
self.assertEqual(HTMLElement, type(found_el));
self.assertTrue(el.id, found_el.id)
def test_name(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementsByName('myInput')[0];")
found_el = self.marionette.find_element("name", "myInput")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_selector(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementById('testh1');")
found_el = self.marionette.find_element("css selector", "h1")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_link_text(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
found_el = self.marionette.find_element("link text", "Click me!")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_partial_link_text(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
found_el = self.marionette.find_element("partial link text", "Click m")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_xpath(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
found_el = self.marionette.find_element("xpath", "id('mozLink')")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_not_found(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertRaises(NoSuchElementException, self.marionette.find_element, "id", "I'm not on the page")
def test_timeout(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertRaises(NoSuchElementException, self.marionette.find_element, "id", "newDiv")
self.assertTrue(True, self.marionette.set_search_timeout(4000))
self.marionette.navigate(test_html)
self.assertEqual(HTMLElement, type(self.marionette.find_element("id", "newDiv")))
class TestElementsChrome(MarionetteTestCase):
def setUp(self):
MarionetteTestCase.setUp(self)
self.marionette.set_context("chrome")
def test_id(self):
el = self.marionette.execute_script("return window.document.getElementById('main-window');")
found_el = self.marionette.find_element("id", "main-window")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_tag_name(self):
el = self.marionette.execute_script("return window.document.getElementsByTagName('window')[0];")
found_el = self.marionette.find_element("tag name", "window")
self.assertEqual(HTMLElement, type(found_el))
self.assertTrue(el.id, found_el.id)
def test_class_name(self):
el = self.marionette.execute_script("return window.document.getElementsByClassName('editBookmarkPanelHeaderButton')[0];")
found_el = self.marionette.find_element("class name", "editBookmarkPanelHeaderButton")
self.assertEqual(HTMLElement, type(found_el));
self.assertTrue(el.id, found_el.id)
def test_xpath(self):
el = self.marionette.execute_script("return window.document.getElementById('main-window');")
found_el = self.marionette.find_element("xpath", "id('main-window')")
self.assertEqual(HTMLElement, type(found_el));
self.assertTrue(el.id, found_el.id)
def test_not_found(self):
self.assertRaises(NoSuchElementException, self.marionette.find_element, "id", "I'm not on the page")
def test_timeout(self):
self.assertRaises(NoSuchElementException, self.marionette.find_element, "id", "myid")
self.assertTrue(True, self.marionette.set_search_timeout(4000))
self.marionette.execute_script("window.setTimeout(function() {var b = window.document.createElement('button'); b.id = 'myid'; document.getElementById('main-window').appendChild(b);}, 1000)")
self.assertEqual(HTMLElement, type(self.marionette.find_element("id", "myid")))
self.marionette.execute_script("window.document.getElementById('main-window').removeChild(window.document.getElementById('myid'));")

View File

@ -0,0 +1,60 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
class TestLog(MarionetteTestCase):
def test_log_basic(self):
self.marionette.log("I am info")
self.assertTrue("I am info" in self.marionette.get_logs()[0])
self.marionette.log("I AM ERROR", "ERROR")
self.assertTrue("I AM ERROR" in self.marionette.get_logs()[1])
def test_log_script(self):
self.marionette.execute_script("log('some log');")
self.assertTrue("some log" in self.marionette.get_logs()[0])
self.marionette.execute_script("log('some error', 'ERROR');")
self.assertTrue("some error" in self.marionette.get_logs()[1])
self.marionette.set_script_timeout(2000)
self.marionette.execute_async_script("log('some more logs'); finish();")
self.assertTrue("some more logs" in self.marionette.get_logs()[2])
self.marionette.execute_async_script("log('some more errors', 'ERROR'); finish();")
self.assertTrue("some more errors" in self.marionette.get_logs()[3])
class TestLogChrome(TestLog):
def setUp(self):
MarionetteTestCase.setUp(self)
self.marionette.set_context("chrome")

View File

@ -0,0 +1,93 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
class TestNavigate(MarionetteTestCase):
def test_navigate(self):
self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
def test_getUrl(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertTrue(test_html in self.marionette.get_url())
self.marionette.navigate("about:blank")
self.assertEqual("about:blank", self.marionette.get_url())
def test_goBack(self):
self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
self.marionette.navigate("about:blank")
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.marionette.go_back()
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
def test_goForward(self):
self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
self.marionette.navigate("about:blank")
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.marionette.go_back()
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
self.marionette.go_forward()
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
def test_refresh(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
self.assertTrue(self.marionette.execute_script("var elem = window.document.createElement('div'); elem.id = 'someDiv';" +
"window.document.body.appendChild(elem); return true;"))
self.assertFalse(self.marionette.execute_script("return window.document.getElementById('someDiv') == undefined;"))
self.marionette.refresh()
self.assertEqual("Marionette Test", self.marionette.execute_script("return window.document.title;"))
self.assertTrue(self.marionette.execute_script("return window.document.getElementById('someDiv') == undefined;"))

View File

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
MARIONETTE_TIMEOUT = 1000;
MARIONETTE_CONTEXT = 'chrome';
is(2, 2, "test for is()");
isnot(2, 3, "test for isnot()");
ok(2 == 2, "test for ok()");
finish();

View File

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
MARIONETTE_TIMEOUT = 1000;
/* this test will fail */
setTimeout(function() {
is(1, 2);
finish();
}, 100);
isnot(1, 1);
ok(1 == 2);

View File

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
MARIONETTE_TIMEOUT = 1000;
is(2, 2, "test for is()");
isnot(2, 3, "test for isnot()");
ok(2 == 2, "test for ok()");
setTimeout(finish, 100);

View File

@ -0,0 +1,80 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_test import MarionetteTestCase
from errors import JavascriptException, MarionetteException, ScriptTimeoutException
class SimpletestSanityTest(MarionetteTestCase):
callFinish = "return finish();"
def test_is(self):
def runtests():
sentFail1 = "is(true, false, 'isTest1');" + self.callFinish
sentFail2 = "is(true, false, 'isTest2');" + self.callFinish
sentPass1 = "is(true, true, 'isTest3');" + self.callFinish
sentPass2 = "is(true, true, 'isTest4');" + self.callFinish
self.assertEqual(1, self.marionette.execute_script(sentFail1)["failed"])
self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"])
self.assertEqual(1, self.marionette.execute_script(sentPass1)["passed"])
self.assertEqual(0, self.marionette.execute_script(sentPass2)["failed"])
self.marionette.set_script_timeout(1000)
self.assertEqual(1, self.marionette.execute_async_script(sentFail1)["failed"])
self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"])
self.assertEqual(1, self.marionette.execute_async_script(sentPass1)["passed"])
self.assertEqual(0, self.marionette.execute_async_script(sentPass2)["failed"])
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
def test_isnot(self):
def runtests():
sentFail1 = "isnot(true, true, 'isTest3');" + self.callFinish
sentFail2 = "isnot(true, true, 'isTest4');" + self.callFinish
sentPass1 = "isnot(true, false, 'isTest1');" + self.callFinish
sentPass2 = "isnot(true, false, 'isTest2');" + self.callFinish
self.assertEqual(1, self.marionette.execute_script(sentFail1)["failed"]);
self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"]);
self.assertEqual(0, self.marionette.execute_script(sentPass1)["failed"]);
self.assertEqual(1, self.marionette.execute_script(sentPass2)["passed"]);
self.marionette.set_script_timeout(1000)
self.assertEqual(1, self.marionette.execute_async_script(sentFail1)["failed"]);
self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"]);
self.assertEqual(0, self.marionette.execute_async_script(sentPass1)["failed"]);
self.assertEqual(1, self.marionette.execute_async_script(sentPass2)["passed"]);
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
def test_ok(self):
def runtests():
sentFail1 = "ok(1==2, 'testOk');" + self.callFinish
sentFail2 = "ok(1==2, 'testOk');" + self.callFinish
sentPass1 = "ok(1==1, 'testOk');" + self.callFinish
sentPass2 = "ok(1==1, 'testOk');" + self.callFinish
self.assertEqual(1, self.marionette.execute_script(sentFail1)["failed"]);
self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"]);
self.assertEqual(0, self.marionette.execute_script(sentPass1)["failed"]);
self.assertEqual(1, self.marionette.execute_script(sentPass2)["passed"]);
self.marionette.set_script_timeout(1000)
self.assertEqual(1, self.marionette.execute_async_script(sentFail1)["failed"]);
self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"]);
self.assertEqual(0, self.marionette.execute_async_script(sentPass1)["failed"]);
self.assertEqual(1, self.marionette.execute_async_script(sentPass2)["passed"]);
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()

View File

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
MARIONETTE_TIMEOUT = 100;
/* this test will timeout */
function do_test() {
is(1, 1);
isnot(1, 2);
ok(1 == 1);
finish();
}
setTimeout(do_test, 1000);

View File

@ -0,0 +1,48 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
class TestSwitchFrame(MarionetteTestCase):
def test_switch_simple(self):
self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
test_html = self.marionette.absolute_url("test_iframe.html")
self.marionette.navigate(test_html)
self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
self.assertEqual("Marionette IFrame Test", self.marionette.execute_script("return window.document.title;"))
self.marionette.switch_to_frame("test_iframe")
self.assertTrue("test.html" in self.marionette.get_url())

View File

@ -0,0 +1,31 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_test import MarionetteTestCase
from errors import *
class TestGaiaLaunch(MarionetteTestCase):
"""Trivial example of launching a Gaia app, entering its context and performing some test on it.
"""
def test_launch_app(self):
# Launch a Gaia app; see CommonTestCase.launch_gaia_app in
# marionette.py for implementation. This returns an HTMLElement
# object representing the iframe the app was loaded in.
app_frame = self.launch_gaia_app('../sms/sms.html')
# Verify that the <title> element of the content loaded in the
# iframe contains the text 'Messages'.
page_title = self.marionette.execute_script("""
var frame = arguments[0];
return frame.contentWindow.document.getElementsByTagName('title')[0].innerHTML;
""", [app_frame])
self.assertEqual(page_title, 'Messages')
self.marionette.switch_to_frame(0)
self.assertEqual(self.marionette.execute_script("return window.document.getElementsByTagName('title')[0].innerHTML;"), 'Messages')
self.assertTrue("sms" in self.marionette.execute_script("return document.location.href;"))
self.marionette.switch_to_frame()
self.assertTrue("homescreen" in self.marionette.execute_script("return document.location.href;"))

View File

@ -0,0 +1,92 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import os
from marionette_test import MarionetteTestCase
class TestSwitchWindow(MarionetteTestCase):
def open_new_window(self):
self.marionette.set_context("chrome")
self.marionette.set_script_timeout(5000)
self.marionette.execute_async_script("""
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
var win = ww.openWindow(null, "chrome://browser/content/browser.xul", "testWin", null, null);
win.addEventListener("load", function() {
win.removeEventListener("load", arguments.callee, true);
marionetteScriptFinished();
}, null);
""")
self.marionette.set_context("content")
def close_new_window(self):
self.marionette.set_context("chrome")
self.marionette.execute_script("""
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
var win = ww.getWindowByName("testWin", null);
if (win != null)
win.close();
""")
self.marionette.set_context("content")
def test_windows(self):
orig_win = self.marionette.get_window()
orig_available = self.marionette.get_windows()
self.open_new_window()
#assert we're still in the original window
self.assertEqual(self.marionette.get_window(), orig_win)
now_available = self.marionette.get_windows()
#assert we can find the new window
self.assertEqual(len(now_available), len(orig_available) + 1)
#assert that our window is there
self.assertTrue(orig_win in now_available)
new_win = None
for win in now_available:
if win != orig_win:
new_win = orig_win
#switch to another window
self.marionette.switch_to_window(new_win)
self.assertEqual(self.marionette.get_window(), new_win)
#switch back
self.marionette.switch_to_window(orig_win)
self.close_new_window()
self.assertEqual(self.marionette.get_window(), orig_win)
self.assertEqual(len(self.marionette.get_windows()), len(orig_available))
def tearDown(self):
#ensure that we close the window, regardless of pass/failure
self.close_new_window()
MarionetteTestCase.tearDown(self)

View File

@ -0,0 +1,22 @@
[test_click.py]
b2g = false
[test_log.py]
[test_execute_async_script.py]
[test_execute_script.py]
[test_simpletest_fail.js]
[test_findelement.py]
b2g = false
[test_navigation.py]
b2g = false
[test_simpletest_pass.js]
[test_simpletest_sanity.py]
[test_simpletest_chrome.js]
[test_simpletest_timeout.js]
[test_switch_frame.py]
b2g = false
[test_window_management.py]
b2g = false

View File

@ -0,0 +1,236 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/ #
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Marionette Client.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Jonathan Griffin <jgriffin@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import json
import select
import socket
class TestServer(object):
""" A test Marionette server which can be used to test the Marionette
protocol. Each request will trigger a canned response; see
process_command().
"""
TEST_URL = 'http://www.mozilla.org'
TEST_CURRENT_WINDOW = 'window1'
TEST_WINDOW_LIST = ['window1', 'window2', 'window3']
TEST_EXECUTE_RETURN_VALUE = 10
TEST_EXECUTE_SCRIPT = 'return 2 * 5;'
TEST_EXECUTE_SCRIPT_ARGS = 'testing'
TEST_FIND_ELEMENT = 'element1'
TEST_FIND_ELEMENTS = ['element1', 'element2', 'element3']
TEST_GET_TEXT = 'first name'
TEST_GET_VALUE = 'Mozilla Firefox'
# canned responses for test messages
test_responses = {
'newSession': { 'value': 'a65bef90b145' },
'getMarionetteID': { 'id': 'conn0.marionette' },
'deleteSession': { 'ok': True },
'setScriptTimeout': { 'ok': True },
'setSearchTimeout': { 'ok': True },
'getWindow': { 'value': TEST_CURRENT_WINDOW },
'getWindows': { 'values': TEST_WINDOW_LIST },
'closeWindow': { 'ok': True },
'switchToWindow': { 'ok': True },
'switchToFrame': { 'ok': True },
'setContext': { 'ok': True },
'getUrl' : { 'value': TEST_URL },
'goUrl': { 'ok': True },
'goBack': { 'ok': True },
'goForward': { 'ok': True },
'refresh': { 'ok': True },
'executeScript': { 'value': TEST_EXECUTE_RETURN_VALUE },
'executeAsyncScript': { 'value': TEST_EXECUTE_RETURN_VALUE },
'executeJSScript': { 'value': TEST_EXECUTE_RETURN_VALUE },
'findElement': { 'value': TEST_FIND_ELEMENT },
'findElements': { 'values': TEST_FIND_ELEMENTS },
'clickElement': { 'ok': True },
'getElementText': { 'value': TEST_GET_TEXT },
'sendKeysToElement': { 'ok': True },
'getElementValue': { 'value': TEST_GET_VALUE },
'clearElement': { 'ok': True },
'isElementSelected': { 'value': True },
'elementsEqual': { 'value': True },
'isElementEnabled': { 'value': True },
'isElementDisplayed': { 'value': True },
'getElementAttribute': { 'value': TEST_GET_VALUE },
'getSessionCapabilities': { 'value': {
"cssSelectorsEnabled": True,
"browserName": "firefox",
"handlesAlerts": True,
"javascriptEnabled": True,
"nativeEvents": True,
"platform": 'linux',
"takeScreenshot": False,
"version": "10.1"
}
},
'getStatus': { 'value': {
"os": {
"arch": "x86",
"name": "linux",
"version": "unknown"
},
"build": {
"revision": "unknown",
"time": "unknown",
"version": "unknown"
}
}
}
}
# canned error responses for test messages
error_responses = {
'executeScript': { 'error': { 'message': 'JavaScript error', 'status': 17 } },
'executeAsyncScript': { 'error': { 'message': 'Script timed out', 'status': 28 } },
'findElement': { 'error': { 'message': 'Element not found', 'status': 7 } },
'findElements': { 'error': { 'message': 'XPath is invalid', 'status': 19 } },
'closeWindow': { 'error': { 'message': 'No such window', 'status': 23 } },
'getWindow': { 'error': { 'message': 'No such window', 'status': 23 } },
'clickElement': { 'error': { 'message': 'Element no longer exists', 'status': 10 } },
'sendKeysToElement': { 'error': { 'message': 'Element is not visible on the page', 'status': 11 } },
'switchToFrame': { 'error': { 'message': 'No such frame', 'status': 8 } }
}
def __init__(self, port):
self.port = port
self.srvsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.srvsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.srvsock.bind(("", port))
self.srvsock.listen(5)
self.descriptors = [self.srvsock]
self.responses = self.test_responses
print 'TestServer started on port %s' % port
def _recv_n_bytes(self, sock, n):
""" Convenience method for receiving exactly n bytes from
self.sock (assuming it's open and connected).
"""
data = ''
while len(data) < n:
chunk = sock.recv(n - len(data))
if chunk == '':
break
data += chunk
return data
def receive(self, sock):
""" Receive the next complete response from the server, and return
it as a dict. Each response from the server is prepended by
len(message) + ':'.
"""
assert(sock)
response = sock.recv(10)
sep = response.find(':')
if sep == -1:
return None
length = response[0:sep]
response = response[sep + 1:]
response += self._recv_n_bytes(sock, int(length) + 1 + len(length) - 10)
print 'received', response
return json.loads(response)
def send(self, sock, msg):
print 'msg', msg
data = json.dumps(msg)
print 'sending %s' % data
sock.send('%s:%s' % (len(data), data))
def accept_new_connection(self):
newsock, (remhost, remport) = self.srvsock.accept()
self.descriptors.append( newsock )
str = 'Client connected %s:%s\r\n' % (remhost, remport)
print str
self.send(newsock, {'from': 'root',
'applicationType': 'gecko',
'traits': []})
def process_command(self, data):
command = data['type']
if command == 'use_test_responses':
self.responses = self.test_responses
return { 'ok': True }
elif command == 'use_error_responses':
self.responses = self.error_responses
return { 'ok': True }
if command in self.responses:
response = self.responses[command]
else:
response = { 'error': { 'message': 'unknown command: %s' % command, 'status': 500} }
if command not in ('newSession', 'getStatus', 'getMarionetteID') and 'session' not in data:
response = { 'error': { 'message': 'no session specified', 'status': 500 } }
return response
def run(self):
while 1:
# Await an event on a readable socket descriptor
(sread, swrite, sexc) = select.select( self.descriptors, [], [] )
# Iterate through the tagged read descriptors
for sock in sread:
# Received a connect to the server (listening) socket
if sock == self.srvsock:
self.accept_new_connection()
else:
# Received something on a client socket
try:
data = self.receive(sock)
except:
data = None
# Check to see if the peer socket closed
if data is None:
host,port = sock.getpeername()
str = 'Client disconnected %s:%s\r\n' % (host, port)
print str
sock.close
self.descriptors.remove(sock)
else:
if 'type' in data:
msg = self.process_command(data)
else:
msg = 'command: %s' % json.dumps(data)
self.send(sock, msg)
if __name__ == "__main__":
server = TestServer(2626)
server.run()

View File

@ -0,0 +1,81 @@
#!/bin/bash
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# the Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2012
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Malini Das (mdas@mozilla.com)
# Jonathan Griffin (jgriffin@mozilla.com)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
PYTHON=$1
if [ -z "${PYTHON}" ]
then
echo "No python found"
exit 1
fi
# Check if environment exists, if not, create a virtualenv:
if [ -d "marionette_auto_venv" ]
then
cd marionette_auto_venv
. bin/activate
else
curl https://raw.github.com/pypa/virtualenv/develop/virtualenv.py | ${PYTHON} - marionette_auto_venv
cd marionette_auto_venv
. bin/activate
# set up mozbase
git clone git://github.com/mozilla/mozbase.git
cd mozbase
python setup_development.py
cd ..
# set up mozautolog
hg clone http://hg.mozilla.org/users/jgriffin_mozilla.com/mozautolog/
cd mozautolog
python setup.py develop
cd ..
# set up gitpython
easy_install http://pypi.python.org/packages/source/G/GitPython/GitPython-0.3.2.RC1.tar.gz
fi
cd ../..
# update the marionette_client
python setup.py develop
cd marionette
#pop off the python parameter
shift
python runtests.py $@

View File

@ -0,0 +1,71 @@
#!/bin/bash
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# the Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2012
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Malini Das (mdas@mozilla.com)
# Jonathan Griffin (jgriffin@mozilla.com)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
PYTHON=$1
if [ -z "${PYTHON}" ]
then
echo "No python found"
exit 1
fi
# Check if environment exists, if not, create a virtualenv:
if [ -d "marionette_venv" ]
then
cd marionette_venv
. bin/activate
else
curl https://raw.github.com/pypa/virtualenv/develop/virtualenv.py | ${PYTHON} - marionette_venv
cd marionette_venv
. bin/activate
# set up mozbase
git clone git://github.com/mozilla/mozbase.git
cd mozbase
python setup_development.py
cd ..
fi
cd ../..
# update the marionette_client
python setup.py develop
cd marionette
#pop off the python parameter
shift
python runtests.py $@

View File

@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<title>Marionette Test</title>
</head>
<body>
<h1 id="testh1">Test Page</h1>
<script type="text/javascript">
window.ready = true;
setTimeout(addDelayedElement, 1000);
function addDelayedElement() {
var newDiv = document.createElement("div");
newDiv.id = "newDiv";
var newContent = document.createTextNode("I am a newly created div!");
newDiv.appendChild(newContent);
document.body.appendChild(newDiv);
}
function clicked() {
var link = document.getElementById("mozLink");
link.innerHTML = "Clicked";
}
</script>
<a href="#" id="mozLink" class="linkClass" onclick="clicked()">Click me!</a>
<a href="#" id="mozLink" class="linkClass" onclick="clicked()">Click me!</a>
<input name="myInput" type="text" />
</body>
</html>

View File

@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<title>Marionette IFrame Test</title>
</head>
<body>
<iframe src="test.html" id="test_iframe"></iframe>
</body>
</html>

View File

@ -0,0 +1,31 @@
import os
from setuptools import setup, find_packages
version = '0.2'
# get documentation from the README
try:
here = os.path.dirname(os.path.abspath(__file__))
description = file(os.path.join(here, 'README.md')).read()
except (OSError, IOError):
description = ''
# dependencies
deps = []
setup(name='marionette',
version=version,
description="Marionette test automation client",
long_description=description,
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Jonathan Griffin',
author_email='jgriffin@mozilla.com',
url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette',
license='MPL',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=deps,
)