mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Bug 738825 - mirror Marionette Python client into gecko, a=testonly (npotb)
This commit is contained in:
parent
711b8fd781
commit
b84967dd54
7
dom/battery/test/marionette/manifest.ini
Normal file
7
dom/battery/test/marionette/manifest.ini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
b2g = true
|
||||||
|
browser = false
|
||||||
|
qemu = true
|
||||||
|
|
||||||
|
[test_battery.py]
|
||||||
|
|
99
dom/battery/test/marionette/test_battery.py
Normal file
99
dom/battery/test/marionette/test_battery.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
9
dom/telephony/test/marionette/manifest.ini
Normal file
9
dom/telephony/test/marionette/manifest.ini
Normal 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]
|
||||||
|
|
89
dom/telephony/test/marionette/test_dial_answer.py
Normal file
89
dom/telephony/test/marionette/test_dial_answer.py
Normal 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";
|
||||||
|
});
|
||||||
|
"""))
|
||||||
|
|
55
dom/telephony/test/marionette/test_dial_between_emulators.py
Normal file
55
dom/telephony/test/marionette/test_dial_between_emulators.py
Normal 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)
|
||||||
|
|
||||||
|
|
149
dom/telephony/test/marionette/test_dial_listeners.py
Normal file
149
dom/telephony/test/marionette/test_dial_listeners.py
Normal 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;"))
|
||||||
|
|
47
testing/marionette/client/README.md
Normal file
47
testing/marionette/client/README.md
Normal 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).
|
||||||
|
|
3
testing/marionette/client/marionette/__init__.py
Normal file
3
testing/marionette/client/marionette/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from marionette import Marionette, HTMLElement
|
||||||
|
from marionette_test import MarionetteTestCase
|
||||||
|
|
8
testing/marionette/client/marionette/automation.conf
Normal file
8
testing/marionette/client/marionette/automation.conf
Normal 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
|
||||||
|
|
218
testing/marionette/client/marionette/automator.py
Normal file
218
testing/marionette/client/marionette/automator.py
Normal 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()
|
||||||
|
|
118
testing/marionette/client/marionette/client.py
Normal file
118
testing/marionette/client/marionette/client.py
Normal 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
|
262
testing/marionette/client/marionette/emulator.py
Normal file
262
testing/marionette/client/marionette/emulator.py
Normal 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
|
||||||
|
|
49
testing/marionette/client/marionette/emulator_battery.py
Normal file
49
testing/marionette/client/marionette/emulator_battery.py
Normal 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)
|
||||||
|
|
78
testing/marionette/client/marionette/errors.py
Normal file
78
testing/marionette/client/marionette/errors.py
Normal 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
|
||||||
|
|
68
testing/marionette/client/marionette/gitutils.py
Normal file
68
testing/marionette/client/marionette/gitutils.py
Normal 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'
|
||||||
|
|
353
testing/marionette/client/marionette/marionette.py
Normal file
353
testing/marionette/client/marionette/marionette.py
Normal 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')
|
167
testing/marionette/client/marionette/marionette_test.py
Normal file
167
testing/marionette/client/marionette/marionette_test.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
391
testing/marionette/client/marionette/runtests.py
Normal file
391
testing/marionette/client/marionette/runtests.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
297
testing/marionette/client/marionette/selenium_proxy.py
Normal file
297
testing/marionette/client/marionette/selenium_proxy.py
Normal 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()
|
45
testing/marionette/client/marionette/test_debugger.py
Normal file
45
testing/marionette/client/marionette/test_debugger.py
Normal 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;'))
|
||||||
|
|
40
testing/marionette/client/marionette/test_emulator.py
Normal file
40
testing/marionette/client/marionette/test_emulator.py
Normal 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)
|
||||||
|
|
||||||
|
|
97
testing/marionette/client/marionette/test_protocol.py
Normal file
97
testing/marionette/client/marionette/test_protocol.py
Normal 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())
|
222
testing/marionette/client/marionette/test_selenium.py
Normal file
222
testing/marionette/client/marionette/test_selenium.py
Normal 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!'
|
||||||
|
|
287
testing/marionette/client/marionette/tests/head.js
Normal file
287
testing/marionette/client/marionette/tests/head.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
19
testing/marionette/client/marionette/tests/unit-tests.ini
Normal file
19
testing/marionette/client/marionette/tests/unit-tests.ini
Normal 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]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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;"))
|
||||||
|
|
@ -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);
|
||||||
|
"""))
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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;"))
|
||||||
|
|
@ -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'));")
|
60
testing/marionette/client/marionette/tests/unit/test_log.py
Normal file
60
testing/marionette/client/marionette/tests/unit/test_log.py
Normal 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")
|
@ -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;"))
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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);
|
@ -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())
|
@ -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;"))
|
||||||
|
|
@ -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)
|
@ -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
|
236
testing/marionette/client/marionette/testserver.py
Normal file
236
testing/marionette/client/marionette/testserver.py
Normal 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()
|
81
testing/marionette/client/marionette/venv_automation.sh
Normal file
81
testing/marionette/client/marionette/venv_automation.sh
Normal 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 $@
|
71
testing/marionette/client/marionette/venv_test.sh
Normal file
71
testing/marionette/client/marionette/venv_test.sh
Normal 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 $@
|
27
testing/marionette/client/marionette/www/test.html
Normal file
27
testing/marionette/client/marionette/www/test.html
Normal 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>
|
@ -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>
|
31
testing/marionette/client/setup.py
Normal file
31
testing/marionette/client/setup.py
Normal 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,
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user