--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -43,17 +43,17 @@ Cu.import("chrome://marionette/content/e
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/interaction.js");
Cu.import("chrome://marionette/content/l10n.js");
Cu.import("chrome://marionette/content/legacyaction.js");
Cu.import("chrome://marionette/content/modal.js");
Cu.import("chrome://marionette/content/proxy.js");
Cu.import("chrome://marionette/content/reftest.js");
Cu.import("chrome://marionette/content/session.js");
-Cu.import("chrome://marionette/content/wait.js");
+const {wait, TimedPromise} = Cu.import("chrome://marionette/content/wait.js", {});
Cu.importGlobalProperties(["URL"]);
this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
@@ -2944,30 +2944,105 @@ GeckoDriver.prototype.minimizeWindow = f
*
* @throws {UnsupportedOperationError}
* Not available for current application.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.maximizeWindow = function* (cmd, resp) {
+GeckoDriver.prototype.maximizeWindow = async function(cmd, resp) {
assert.firefox();
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
- yield new Promise(resolve => {
- win.addEventListener("resize", resolve, {once: true});
-
- if (win.windowState == win.STATE_MAXIMIZED) {
+ const {STATE_MAXIMIZED, STATE_NORMAL} = win;
+
+ let origSize = {
+ outerWidth: win.outerWidth,
+ outerHeight: win.outerHeight,
+ };
+
+ dump("before width=" + origSize.outerWidth + "\n");
+ dump("before height=" + origSize.outerHeight + "\n");
+
+ let expectedWindowState;
+ if (win.windowState == STATE_MAXIMIZED) {
+ expectedWindowState = STATE_NORMAL;
+ } else {
+ expectedWindowState = STATE_MAXIMIZED;
+ }
+ dump("expectedWindowState=" + expectedWindowState + "\n");
+
+ // Poll-wait for ChromeWindow.windowState to reach |state|.
+ async function windowState(state) {
+ return wait.until((resolve, reject) => {
+ dump("win.windowState=" + win.windowState + "\n");
+ if (win.windowState == state) {
+ dump("resolve\n");
+ resolve();
+ } else {
+ dump("reject\n");
+ reject();
+ }
+ });
+ }
+
+ // Wait for the window size to change |from|.
+ async function windowSizeChange(from) {
+ await wait.until((resolve, reject) => {
+ let curSize = {
+ outerWidth: win.outerWidth,
+ outerHeight: win.outerHeight,
+ };
+ dump("curSize=" + JSON.stringify(curSize) + "\n");
+ if (curSize.outerWidth != origSize.outerWidth ||
+ curSize.outerHeight != origSize.outerHeight) {
+ dump(" resolve\n");
+ resolve();
+ } else {
+ dump(" reject\n");
+ reject();
+ }
+ });
+ }
+
+ let modeChangeEv;
+ await new TimedPromise(resolve => {
+ dump("inside the timedpromise\n");
+
+ modeChangeEv = ev => {
+ dump("====> sizemodechange\n");
+ dump(" width=" + win.outerWidth + "\n");
+ dump(" height=" + win.outerHeight + "\n");
+
+ resolve();
+ };
+ win.addEventListener("sizemodechange", modeChangeEv, {once: true});
+
+ if (win.windowState == STATE_MAXIMIZED) {
+ dump("===> restoring\n");
win.restore();
} else {
+ dump("===> maximizing\n");
win.maximize();
}
- });
+
+ dump("returning from timedpromise\n");
+ }, {throws: null});
+ win.removeEventListener("sizemodechange", modeChangeEv);
+
+ dump("awaiting windowState to reach " + expectedWindowState + "\n");
+ await windowState(expectedWindowState);
+
+ dump("awaiting window size to change\n");
+ await windowSizeChange();
+
+ dump("after width=" + win.outerWidth + "\n");
+ dump("after height=" + win.outerHeight + "\n");
resp.body = {
x: win.screenX,
y: win.screenY,
width: win.outerWidth,
height: win.outerHeight,
};
};
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_maximize.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_maximize.py
@@ -1,14 +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/.
-from marionette_driver.errors import InvalidArgumentException
-
from marionette_harness import MarionetteTestCase
class TestWindowMaximize(MarionetteTestCase):
def setUp(self):
MarionetteTestCase.setUp(self)
self.max = self.marionette.execute_script("""
@@ -34,23 +32,23 @@ class TestWindowMaximize(MarionetteTestC
if self.marionette.session_capabilities["platformName"] == "windows_nt":
delta = 16
else:
delta = 8
self.assertGreaterEqual(
actual["width"], self.max["width"] - delta,
msg="Window width is not within {delta} px of availWidth: "
- "current width {expected} should be greater than max width {max}"
- .format(delta=delta, expected=actual["width"], max=self.max["width"] - delta))
+ "current width {current} should be greater than or equal to max width {max}"
+ .format(delta=delta, current=actual["width"], max=self.max["width"] - delta))
self.assertGreaterEqual(
- actual["height"], self.max["height"],
+ actual["height"], self.max["height"] - delta,
msg="Window height is not within {delta} px of availHeight: "
- "current height {expected} should be greater than max width {max}"
- .format(delta=delta, expected=actual["height"], max=self.max["height"] - delta))
+ "current height {current} should be greater than or equal to max height {max}"
+ .format(delta=delta, current=actual["height"], max=self.max["height"] - delta))
def assert_window_restored(self, actual):
self.assertEqual(self.original_size["width"], actual["width"])
self.assertEqual(self.original_size["height"], actual["height"])
def assert_window_rect(self, rect):
self.assertIn("width", rect)
self.assertIn("height", rect)
--- a/testing/marionette/wait.js
+++ b/testing/marionette/wait.js
@@ -1,29 +1,34 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-Cu.import("chrome://marionette/content/error.js");
+const {
+ error,
+ TimeoutError,
+} = Cu.import("chrome://marionette/content/error.js", {});
-this.EXPORTED_SYMBOLS = ["wait"];
+this.EXPORTED_SYMBOLS = ["wait", "TimedPromise"];
/**
* Poll-waiting utilities.
*
* @namespace
*/
this.wait = {};
+const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
+
/**
- * @callback WaitCondition
+ * @callback Condition
*
* @param {function(*)} resolve
* To be called when the condition has been met. Will return the
* resolved value.
* @param {function} reject
* To be called when the condition has not been met. Will cause
* the condition to be revaluated or time out.
*
@@ -57,17 +62,17 @@ this.wait = {};
* if (res.length > 0) {
* resolve(Array.from(res));
* } else {
* reject([]);
* }
* });
* </pre></code>
*
- * @param {WaitCondition} func
+ * @param {Condition} func
* Function to run off the main thread.
* @param {number=} timeout
* Desired timeout. If 0 or less than the runtime evaluation time
* of |func|, |func| is guaranteed to run at least once. The default
* is 2000 milliseconds.
* @param {number=} interval
* Duration between each poll of |func| in milliseconds. Defaults to
* 10 milliseconds.
@@ -99,19 +104,79 @@ wait.until = function(func, timeout = 20
}
}).catch(reject);
};
// the repeating slack timer waits |interval|
// before invoking |evalFn|
evalFn();
- timer.init(evalFn, interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
- // cancel timer and propagate result
}).then(res => {
timer.cancel();
return res;
}, err => {
timer.cancel();
throw err;
});
};
+
+/**
+ * The <code>TimedPromise</code> object represents the timed, eventual
+ * completion (or failure) of an asynchronous operation, and its
+ * resulting value.
+ *
+ * In contrast to a regular {@link Promise}, it times out after
+ * <var>timeout</var>.
+ *
+ * @param {Condition} func
+ * Function to run, which will have its <code>reject</code>
+ * callback invoked after the <var>timeout</var> duration is reached.
+ * It is presented with two callbacks: <code>resolve(value)</code>
+ * and <code>reject(error)</code>.
+ * @param {timeout=} [timeout=2000] timeout
+ * <var>condition</var>'s <code>reject</code> callback will be called
+ * after this timeout.
+ * @param {Error=} [throws=TimeoutError] throws
+ * When the <var>timeout</var> is hit, this error class will be
+ * thrown. If it is null, no error is thrown and the promise is
+ * instead resolved.
+ *
+ * @return {Promise.<*>}
+ * Timed promise.
+ */
+function TimedPromise(fn, {timeout = 1500, throws = TimeoutError} = {}) {
+ const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ return new Promise((resolve, reject) => {
+ // Reject only if |errorCls| is given. Otherwise, the user is OK
+ // with the promise timing out.
+ let bail = res => {
+ dump(" timed promise timed out!\n");
+ if (throws !== null) {
+ let err = new throws();
+ reject(err);
+ } else {
+ resolve(res);
+ }
+ };
+
+ timer.initWithCallback({notify: bail}, timeout, TYPE_ONE_SHOT);
+
+ try {
+ dump(" calling fn...\n");
+ fn(resolve, reject);
+ dump(" ...fn returned\n");
+ } catch (e) {
+ reject(e);
+ }
+
+ }).then(res => {
+ dump(" resolve res=" + res + "\n");
+ timer.cancel();
+ return res;
+ }, err => {
+ dump(" reject err=" + err + "\n");
+ timer.cancel();
+ throw err;
+ });
+}
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -402860,27 +402860,27 @@
]
],
"webdriver/tests/cookies.py": [
[
"/webdriver/tests/cookies.py",
{}
]
],
+ "webdriver/tests/maximize_window.py": [
+ [
+ "/webdriver/tests/maximize_window.py",
+ {}
+ ]
+ ],
"webdriver/tests/navigation.py": [
[
"/webdriver/tests/navigation.py",
{}
]
- ],
- "webdriver/tests/window_maximizing.py": [
- [
- "/webdriver/tests/window_maximizing.py",
- {}
- ]
]
}
},
"paths": {
"./.codecov.yml": [
"e2322808739a5977e90896b4755cfc20f4ab2046",
"support"
],
@@ -621651,26 +621651,30 @@
"webdriver/tests/contexts.py": [
"9c4be1b08b99945621b149d1aa2aa64167caad50",
"wdspec"
],
"webdriver/tests/cookies.py": [
"e31177e638269864031e44808945fa1e7c46031c",
"wdspec"
],
+ "webdriver/tests/maximize_window.py": [
+ "827b3a3e1a3ef628dae1480af029fc01ef5e9388",
+ "wdspec"
+ ],
"webdriver/tests/navigation.py": [
"cec2987258d9c807a247da9e0216b3af1f171484",
"wdspec"
],
"webdriver/tests/support/__init__.py": [
"5a31a3917a5157516c10951a3b3d5ffb43b992d9",
"support"
],
"webdriver/tests/support/asserts.py": [
- "cf1d298a9dc61b07eb9efe1ff3ed98a318d48bc4",
+ "693c848df03b46e7b4f5e17e49fd0879a29a0d1c",
"support"
],
"webdriver/tests/support/fixtures.py": [
"6ceec11f42cd9be53a92ad88aa07657c78779ce3",
"support"
],
"webdriver/tests/support/http_request.py": [
"01c4b525c27f77d253c75031a9cee3f17aca8df0",
@@ -621683,20 +621687,16 @@
"webdriver/tests/support/merge_dictionaries.py": [
"84a6d3c6f8f4afded0f21264bbaeebec38a7f827",
"support"
],
"webdriver/tests/support/wait.py": [
"a4b0c9c340ea7055139d9fcab3246ee836d6a441",
"support"
],
- "webdriver/tests/window_maximizing.py": [
- "ba6b9109f5baaf6eb300a3f89f984753e9d5adb9",
- "wdspec"
- ],
"webgl/OWNERS": [
"f8e0703fe2cc88edd21ef2c94fcb2e1a8889f5ae",
"support"
],
"webgl/bufferSubData.html": [
"526612470551a0eb157b310c587d50080087808d",
"testharness"
],
--- a/testing/web-platform/tests/tools/webdriver/webdriver/client.py
+++ b/testing/web-platform/tests/tools/webdriver/webdriver/client.py
@@ -258,17 +258,16 @@ class Window(object):
@position.setter
@command
def position(self, data):
data = x, y
body = {"x": x, "y": y}
self.session.send_session_command("POST", "window/rect", body)
- @property
@command
def maximize(self):
return self.session.send_session_command("POST", "window/maximize")
class Find(object):
def __init__(self, session):
self.session = session
@@ -385,20 +384,16 @@ class Session(object):
def end(self):
if self.session_id is None:
return
url = "session/%s" % self.session_id
self.send_command("DELETE", url)
self.session_id = None
- self.timeouts = None
- self.window = None
- self.find = None
- self.extension = None
def send_command(self, method, url, body=None):
"""
Send a command to the remote end and validate its success.
:param method: HTTP method to use in request.
:param uri: "Command part" of the HTTP request URL,
e.g. `window/rect`.
--- a/testing/web-platform/tests/webdriver/tests/maximize_window.py
+++ b/testing/web-platform/tests/webdriver/tests/maximize_window.py
@@ -4,35 +4,72 @@ from tests.support.asserts import assert
alert_doc = inline("<script>window.alert()</script>")
# 10.7.3 Maximize Window
def test_maximize_no_browsing_context(session, create_window):
# Step 1
session.window_handle = create_window()
session.close()
result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
-
assert_error(result, "no such window")
-def test_maximize_rect_alert_prompt(session):
+def test_handle_user_prompt(session):
# Step 2
session.url = alert_doc
-
result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
-
assert_error(result, "unexpected alert open")
-def test_maximize_payload(session):
- # step 5
+def test_maximize(session):
+ before = session.window.size
+
+ # step 4
+ result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
+ assert_success(result)
+
+ after = session.window.size
+ assert before != after
+
+
+def test_payload(session):
+ before = session.window.size
+
result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
+ # step 5
assert result.status == 200
assert isinstance(result.body["value"], dict)
- assert "width" in result.body["value"]
- assert "height" in result.body["value"]
- assert "x" in result.body["value"]
- assert "y" in result.body["value"]
- assert isinstance(result.body["value"]["width"], float)
- assert isinstance(result.body["value"]["height"], float)
- assert isinstance(result.body["value"]["x"], float)
- assert isinstance(result.body["value"]["y"], float)
+
+ rect = result.body["value"]
+ assert "width" in rect
+ assert "height" in rect
+ assert "x" in rect
+ assert "y" in rect
+ assert isinstance(rect["width"], float)
+ assert isinstance(rect["height"], float)
+ assert isinstance(rect["x"], float)
+ assert isinstance(rect["y"], float)
+
+ after = session.window.size
+ assert before != after
+
+
+def test_maximize_when_resized_to_max_size(session):
+ # Determine the largest available window size by first maximising
+ # the window and getting the window rect dimensions.
+ #
+ # Then resize the window to the maximum available size.
+ session.end()
+ available = session.window.maximize()
+ session.end()
+
+ session.window.size = (int(available["width"]), int(available["height"]))
+
+ # In certain window managers a window extending to the full available
+ # dimensions of the screen may not imply that the window is maximised,
+ # since this is often a special state. If a remote end expects a DOM
+ # resize event, this may not fire if the window has already reached
+ # its expected dimensions.
+ before = session.window.size
+ session.window.maximize()
+ after = session.window.size
+ assert before == after
--- a/testing/web-platform/tests/webdriver/tests/support/asserts.py
+++ b/testing/web-platform/tests/webdriver/tests/support/asserts.py
@@ -63,26 +63,29 @@ def assert_error(response, error_code):
:param error_code: string value of the expected "error code"
"""
assert response.status == errors[error_code]
assert "value" in response.body
assert response.body["value"]["error"] == error_code
assert isinstance(response.body["value"]["message"], basestring)
assert isinstance(response.body["value"]["stacktrace"], basestring)
-def assert_success(response, value):
+def assert_success(response, value=None):
"""Verify that the provided wdclient.Response instance described a valid
error response as defined by `dfn-send-an-error` and the provided error
code.
- :param response: wdclient.Response instance
- :param value: expected value of the response body
+
+ :param response: wdclient.Response instance.
+ :param value: Expected value of the response body, if any.
+
"""
-
assert response.status == 200
- assert response.body["value"] == value
+ assert "value" in response.body
+ if value is not None:
+ assert response.body["value"] == value
def assert_dialog_handled(session, expected_text):
result = session.transport.send("GET",
"session/%s/alert/text" % session.session_id)
# If there were any existing dialogs prior to the creation of this
# fixture's dialog, then the "Get Alert Text" command will return
# successfully. In that case, the text must be different than that