Bug 906712 - Tab modal dialog support for marionette.;r=automatedtester

This commit is contained in:
Chris Manchester 2015-02-10 17:44:52 -08:00
Родитель 12e8adaf54
Коммит d0425a1871
6 изменённых файлов: 449 добавлений и 18 удалений

Просмотреть файл

@ -99,18 +99,8 @@ class HTMLElement(object):
'''
Sends the string via synthesized keypresses to the element.
'''
typing = []
for val in string:
if isinstance(val, Keys):
typing.append(val)
elif isinstance(val, int):
val = str(val)
for i in range(len(val)):
typing.append(val[i])
else:
for i in range(len(val)):
typing.append(val[i])
return self.marionette._send_message('sendKeysToElement', 'ok', id=self.id, value=typing)
keys = Marionette.convert_keys(*string)
return self.marionette._send_message('sendKeysToElement', 'ok', id=self.id, value=keys)
def clear(self):
'''
@ -473,6 +463,42 @@ class MultiActions(object):
'''
return self.marionette._send_message('multiAction', 'ok', value=self.multi_actions, max_length=self.max_length)
class Alert(object):
'''
A class for interacting with alerts.
::
Alert(marionette).accept()
Alert(merionette).dismiss()
'''
def __init__(self, marionette):
self.marionette = marionette
def accept(self):
"""Accept a currently displayed modal dialog.
"""
self.marionette._send_message('acceptDialog', 'ok')
def dismiss(self):
"""Dismiss a currently displayed modal dialog.
"""
self.marionette._send_message('dismissDialog', 'ok')
@property
def text(self):
"""Return the currently displayed text in a tab modal.
"""
return self.marionette._send_message('getTextFromDialog', 'value')
def send_keys(self, *string):
"""Send keys to the currently displayed text input area in an open
tab modal dialog.
"""
keys = Marionette.convert_keys(*string)
self.marionette._send_message('sendKeysToDialog', 'ok', value=keys)
class Marionette(object):
"""
@ -763,6 +789,21 @@ class Marionette(object):
(name, returncode))
return crashed
@staticmethod
def convert_keys(*string):
typing = []
for val in string:
if isinstance(val, Keys):
typing.append(val)
elif isinstance(val, int):
val = str(val)
for i in range(len(val)):
typing.append(val[i])
else:
for i in range(len(val)):
typing.append(val[i])
return typing
def enforce_gecko_prefs(self, prefs):
"""
Checks if the running instance has the given prefs. If not, it will kill the
@ -999,7 +1040,6 @@ class Marionette(object):
'''
response = self._send_message('getPageSource', 'value')
return response
def close(self):
"""Close the current window, ending the session if it's the last
window currently open.

Просмотреть файл

@ -0,0 +1,178 @@
# 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, skip_if_e10s
from errors import NoAlertPresentException, ElementNotVisibleException
from marionette import Alert
from wait import Wait
class TestTabModals(MarionetteTestCase):
def setUp(self):
MarionetteTestCase.setUp(self)
self.marionette.enforce_gecko_prefs({"prompts.tab_modal.enabled": True})
self.marionette.navigate(self.marionette.absolute_url('modal_dialogs.html'))
def alert_present(self):
try:
Alert(self.marionette).text
return True
except NoAlertPresentException:
return False
def tearDown(self):
# Ensure an alert is absent before proceeding past this test.
Wait(self.marionette).until(lambda _: not self.alert_present())
self.marionette.execute_script("window.onbeforeunload = null;")
def wait_for_alert(self):
Wait(self.marionette).until(lambda _: self.alert_present())
def test_no_alert_raises(self):
self.assertRaises(NoAlertPresentException, Alert(self.marionette).accept)
self.assertRaises(NoAlertPresentException, Alert(self.marionette).dismiss)
def test_alert_accept(self):
self.marionette.find_element('id', 'modal-alert').click()
self.wait_for_alert()
Alert(self.marionette).accept()
def test_alert_dismiss(self):
self.marionette.find_element('id', 'modal-alert').click()
self.wait_for_alert()
Alert(self.marionette).dismiss()
def test_confirm_accept(self):
self.marionette.find_element('id', 'modal-confirm').click()
self.wait_for_alert()
Alert(self.marionette).accept()
self.wait_for_condition(lambda mn: mn.find_element('id', 'confirm-result').text == 'true')
def test_confirm_dismiss(self):
self.marionette.find_element('id', 'modal-confirm').click()
self.wait_for_alert()
Alert(self.marionette).dismiss()
self.wait_for_condition(lambda mn: mn.find_element('id', 'confirm-result').text == 'false')
def test_prompt_accept(self):
self.marionette.find_element('id', 'modal-prompt').click()
self.wait_for_alert()
Alert(self.marionette).accept()
self.wait_for_condition(lambda mn: mn.find_element('id', 'prompt-result').text == '')
def test_prompt_dismiss(self):
self.marionette.find_element('id', 'modal-prompt').click()
self.wait_for_alert()
Alert(self.marionette).dismiss()
self.wait_for_condition(lambda mn: mn.find_element('id', 'prompt-result').text == 'null')
def test_alert_text(self):
with self.assertRaises(NoAlertPresentException):
Alert(self.marionette).text
self.marionette.find_element('id', 'modal-alert').click()
self.wait_for_alert()
self.assertEqual(Alert(self.marionette).text, 'Marionette alert')
Alert(self.marionette).accept()
def test_prompt_text(self):
with self.assertRaises(NoAlertPresentException):
Alert(self.marionette).text
self.marionette.find_element('id', 'modal-prompt').click()
self.wait_for_alert()
self.assertEqual(Alert(self.marionette).text, 'Marionette prompt')
Alert(self.marionette).accept()
def test_confirm_text(self):
with self.assertRaises(NoAlertPresentException):
Alert(self.marionette).text
self.marionette.find_element('id', 'modal-confirm').click()
self.wait_for_alert()
self.assertEqual(Alert(self.marionette).text, 'Marionette confirm')
Alert(self.marionette).accept()
def test_set_text_throws(self):
self.assertRaises(NoAlertPresentException, Alert(self.marionette).send_keys, "Foo")
self.marionette.find_element('id', 'modal-alert').click()
self.wait_for_alert()
self.assertRaises(ElementNotVisibleException, Alert(self.marionette).send_keys, "Foo")
Alert(self.marionette).accept()
def test_set_text_accept(self):
self.marionette.find_element('id', 'modal-prompt').click()
self.wait_for_alert()
Alert(self.marionette).send_keys("Some text!");
Alert(self.marionette).accept()
self.wait_for_condition(lambda mn: mn.find_element('id', 'prompt-result').text == 'Some text!')
def test_set_text_dismiss(self):
self.marionette.find_element('id', 'modal-prompt').click()
self.wait_for_alert()
Alert(self.marionette).send_keys("Some text!");
Alert(self.marionette).dismiss()
self.wait_for_condition(lambda mn: mn.find_element('id', 'prompt-result').text == 'null')
def test_onbeforeunload_dismiss(self):
start_url = self.marionette.get_url()
self.marionette.find_element('id', 'onbeforeunload-handler').click()
self.wait_for_condition(
lambda mn: mn.execute_script("""
return window.onbeforeunload !== null;
"""))
self.marionette.navigate("about:blank")
self.wait_for_alert()
alert_text = Alert(self.marionette).text
self.assertTrue(alert_text.startswith("This page is asking you to confirm"))
Alert(self.marionette).dismiss()
self.assertTrue(self.marionette.get_url().startswith(start_url))
def test_onbeforeunload_accept(self):
self.marionette.find_element('id', 'onbeforeunload-handler').click()
self.wait_for_condition(
lambda mn: mn.execute_script("""
return window.onbeforeunload !== null;
"""))
self.marionette.navigate("about:blank")
self.wait_for_alert()
alert_text = Alert(self.marionette).text
self.assertTrue(alert_text.startswith("This page is asking you to confirm"))
Alert(self.marionette).accept()
self.assertEqual(self.marionette.get_url(), "about:blank")
@skip_if_e10s
def test_unrelated_command_when_alert_present(self):
click_handler = self.marionette.find_element('id', 'click-handler')
text = self.marionette.find_element('id', 'click-result').text
self.assertEqual(text, '')
self.marionette.find_element('id', 'modal-alert').click()
self.wait_for_alert()
# Commands succeed, but because the dialog blocks the event loop,
# our actions aren't reflected on the page.
text = self.marionette.find_element('id', 'click-result').text
self.assertEqual(text, '')
click_handler.click()
text = self.marionette.find_element('id', 'click-result').text
self.assertEqual(text, '')
Alert(self.marionette).accept()
Wait(self.marionette).until(lambda _: not self.alert_present())
click_handler.click()
text = self.marionette.find_element('id', 'click-result').text
self.assertEqual(text, 'result')
class TestGlobalModals(TestTabModals):
def setUp(self):
MarionetteTestCase.setUp(self)
self.marionette.enforce_gecko_prefs({"prompts.tab_modal.enabled": False})
self.marionette.navigate(self.marionette.absolute_url('modal_dialogs.html'))
def test_unrelated_command_when_alert_present(self):
# The assumptions in this test do not hold on certain platforms, and not when
# e10s is enabled.
pass

Просмотреть файл

@ -139,4 +139,7 @@ b2g = false
b2g = false
skip-if = os == "linux" # Bug 1085717
[test_with_using_context.py]
[test_modal_dialogs.py]
b2g = false
[test_key_actions.py]

Просмотреть файл

@ -0,0 +1,39 @@
<!-- 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/. -->
<!DOCTYPE html>
<html>
<head>
<title>Marionette Test</title>
<script type="text/javascript">
function handleAlert () {
window.alert('Marionette alert');
}
function handleConfirm () {
var alertAccepted = window.confirm('Marionette confirm');
document.getElementById('confirm-result').innerHTML = alertAccepted;
}
function handlePrompt () {
var promptText = window.prompt('Marionette prompt');
document.getElementById('prompt-result').innerHTML = promptText === null ? 'null' : promptText;
}
function onBeforeUnload () {
window.onbeforeunload = function () { return "Are you sure?"; }
}
</script>
</head>
<body>
<a href="#" id="modal-alert" onclick="handleAlert()">Open an alert dialog.</a>
<a href="#" id="modal-confirm" onclick="handleConfirm()">Open a confirm dialog.</a>
<a href="#" id="modal-prompt" onclick="handlePrompt()">Open a prompt dialog.</a>
<a href="#" id="onbeforeunload-handler" onclick="onBeforeUnload()">Add an onbeforeunload handler.</a>
<a href="#" id="click-handler" onclick="document.getElementById('click-result').innerHTML='result';">Make text appear.</a>
<div id="confirm-result"></div>
<div id="prompt-result"></div>
<div id="click-result"></div>
</body>
</html>

Просмотреть файл

@ -169,7 +169,8 @@ MarionetteComponent.prototype = {
this.logger.info("Marionette server ready");
}
catch(e) {
this.logger.error('exception: ' + e.name + ', ' + e.message);
this.logger.error('exception: ' + e.name + ', ' + e.message + ': ' +
e.fileName + " :: " + e.lineNumber);
}
}
},

Просмотреть файл

@ -6,6 +6,7 @@
const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
const BROWSER_STARTUP_FINISHED = "browser-delayed-startup-finished";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// import logger
Cu.import("resource://gre/modules/Log.jsm");
@ -190,6 +191,8 @@ function MarionetteServerConnection(aPrefix, aTransport, aServer)
"device": qemu == "1" ? "qemu" : (!device ? "desktop" : device),
"version": Services.appinfo.version
};
this.observing = null;
}
MarionetteServerConnection.prototype = {
@ -598,6 +601,18 @@ MarionetteServerConnection.prototype = {
}
}
if (appName == "Firefox") {
this._dialogWindowRef = null;
let modalHandler = this.handleDialogLoad.bind(this);
this.observing = {
"tabmodal-dialog-loaded": modalHandler,
"common-dialog-loaded": modalHandler
}
for (let topic in this.observing) {
Services.obs.addObserver(this.observing[topic], topic, false);
}
}
function waitForWindow() {
let win = this.getCurrentWindow();
if (!win) {
@ -623,7 +638,6 @@ MarionetteServerConnection.prototype = {
let clickToStart;
try {
clickToStart = Services.prefs.getBoolPref('marionette.debugging.clicktostart');
Services.prefs.setBoolPref('marionette.debugging.clicktostart', false);
} catch (e) { }
if (clickToStart && (appName != "B2G")) {
let pService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
@ -2511,8 +2525,9 @@ MarionetteServerConnection.prototype = {
}
//delete session in each frame in each browser
for (let win in this.browsers) {
for (let i in this.browsers[win].knownFrames) {
this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
let browser = this.browsers[win];
for (let i in browser.knownFrames) {
this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + browser.knownFrames[i], {});
}
}
let winEnum = this.getWinEnumerator();
@ -2531,6 +2546,13 @@ MarionetteServerConnection.prototype = {
this.sessionId = null;
this.deleteFile('marionetteChromeScripts');
this.deleteFile('marionetteContentScripts');
if (this.observing !== null) {
for (let topic in this.observing) {
Services.obs.removeObserver(this.observing[topic], topic);
}
this.observing = null;
}
},
/**
@ -2854,6 +2876,104 @@ MarionetteServerConnection.prototype = {
this.sendOk(this.command_id);
},
/**
* Returns the ChromeWindow associated with an open dialog window if it is
* currently attached to the dom.
*/
get activeDialogWindow () {
if (this._dialogWindowRef !== null) {
let dialogWin = this._dialogWindowRef.get();
if (dialogWin && dialogWin.parent) {
return dialogWin;
}
}
return null;
},
get activeDialogUI () {
let dialogWin = this.activeDialogWindow;
if (dialogWin) {
return dialogWin.Dialog.ui;
}
return this.curBrowser.getTabModalUI();
},
/**
* Dismisses a currently displayed tab modal, or returns no such alert if
* no modal is displayed.
*/
dismissDialog: function MDA_dismissDialog() {
this.command_id = this.getCommandId();
if (this.activeDialogUI === null) {
this.sendError("No tab modal was open when attempting to dismiss the dialog",
27, null, this.command_id);
return;
}
let {button0, button1} = this.activeDialogUI;
(button1 ? button1 : button0).click();
this.sendOk(this.command_id);
},
/**
* Accepts a currently displayed tab modal, or returns no such alert if
* no modal is displayed.
*/
acceptDialog: function MDA_acceptDialog() {
this.command_id = this.getCommandId();
if (this.activeDialogUI === null) {
this.sendError("No tab modal was open when attempting to accept the dialog",
27, null, this.command_id);
return;
}
let {button0} = this.activeDialogUI;
button0.click();
this.sendOk(this.command_id);
},
/**
* Returns the message shown in a currently displayed modal, or returns a no such
* alert error if no modal is currently displayed.
*/
getTextFromDialog: function MDA_getTextFromDialog() {
this.command_id = this.getCommandId();
if (this.activeDialogUI === null) {
this.sendError("No tab modal was open when attempting to get the dialog text",
27, null, this.command_id);
return;
}
let {infoBody} = this.activeDialogUI;
this.sendResponse(infoBody.textContent, this.command_id);
},
/**
* Sends keys to the input field of a currently displayed modal, or returns a
* no such alert error if no modal is currently displayed. If a tab modal is currently
* displayed but has no means for text input, an element not visible error is returned.
*/
sendKeysToDialog: function MDA_sendKeysToDialog(aRequest) {
this.command_id = this.getCommandId();
if (this.activeDialogUI === null) {
this.sendError("No tab modal was open when attempting to send keys to a dialog",
27, null, this.command_id);
return;
}
// See toolkit/components/prompts/contentb/commonDialog.js
let {loginContainer, loginTextbox} = this.activeDialogUI;
if (loginContainer.hidden) {
this.sendError("This prompt does not accept text input",
11, null, this.command_id);
}
let win = this.activeDialogWindow ? this.activeDialogWindow : this.getCurrentWindow();
utils.sendKeysToElement(win, loginTextbox, aRequest.parameters.value,
this.sendOk.bind(this), this.sendError.bind(this),
this.command_id, "chrome");
},
/**
* Helper function to convert an outerWindowID into a UID that Marionette
* tracks.
@ -2863,6 +2983,36 @@ MarionetteServerConnection.prototype = {
return uid;
},
/**
* Handle a dialog opening by shortcutting the current request to prevent the client
* from hanging entirely. This is inspired by selenium's mode of dealing with this,
* but is significantly lighter weight, and may necessitate a different framework
* for handling this as more features are required.
*/
handleDialogLoad: function MDA_handleModalLoad(subject, topic) {
// We shouldn't return to the client due to the modal associated with the
// jsdebugger.
let clickToStart;
try {
clickToStart = Services.prefs.getBoolPref('marionette.debugging.clicktostart');
} catch (e) { }
if (clickToStart) {
Services.prefs.setBoolPref('marionette.debugging.clicktostart', false);
return;
}
if (topic == "common-dialog-loaded") {
this._dialogWindowRef = Cu.getWeakReference(subject);
}
if (this.command_id) {
// This is a shortcut to get the client to accept our response whether
// the expected key is 'ok' (in case a click or similar got us here)
// or 'value' (in case an execute script or similar got us here).
this.sendToClient({from:this.actorID, ok: true, value: null}, this.command_id);
}
},
/**
* Receives all messages from content messageManager
*/
@ -3124,7 +3274,11 @@ MarionetteServerConnection.prototype.requestTypes = {
"setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation,
"getWindowSize": MarionetteServerConnection.prototype.getWindowSize,
"setWindowSize": MarionetteServerConnection.prototype.setWindowSize,
"maximizeWindow": MarionetteServerConnection.prototype.maximizeWindow
"maximizeWindow": MarionetteServerConnection.prototype.maximizeWindow,
"dismissDialog": MarionetteServerConnection.prototype.dismissDialog,
"acceptDialog": MarionetteServerConnection.prototype.acceptDialog,
"getTextFromDialog": MarionetteServerConnection.prototype.getTextFromDialog,
"sendKeysToDialog": MarionetteServerConnection.prototype.sendKeysToDialog
};
/**
@ -3159,6 +3313,22 @@ BrowserObj.prototype = {
return this.browser ? this.browser.selectedTab : null;
},
/**
* Retrieves the current tabmodal ui object. According to the browser associated
* with the currently selected tab.
*/
getTabModalUI: function MDA__getTabModaUI () {
let browserForTab = this.browser.getBrowserForTab(this.tab);
if (!browserForTab.hasAttribute('tabmodalPromptShowing')) {
return null;
}
// The modal is a direct sibling of the browser element. See tabbrowser.xml's
// getTabModalPromptBox.
let modals = browserForTab.parentNode
.getElementsByTagNameNS(XUL_NS, 'tabmodalprompt');
return modals[0].ui;
},
/**
* Set the browser if the application is not B2G
*