Bug 1403428 - switch to window and close it atomically. r=maja_zf draft
authorBlake Kaplan <mrbkap@gmail.com>
Thu, 19 Oct 2017 18:08:48 -0700
changeset 684119 241f48336f8fbf17286d236a4f2ed09e57ac427f
parent 683662 a41b3e4bfb04c26b29de72c72f6efca565cee6ef
child 736820 c76d2b1fba34fc1e914d230fd901f24be1aecf79
push id85563
push userbmo:mrbkap@mozilla.com
push dateFri, 20 Oct 2017 21:51:39 +0000
reviewersmaja_zf
bugs1403428
milestone58.0a1
Bug 1403428 - switch to window and close it atomically. r=maja_zf This seems to do the trick, but I'm pretty lost in this code so there could be other things I'm not seeing. MozReview-Commit-ID: CDtAB119FQn
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/driver.js
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1479,16 +1479,20 @@ class Marionette(object):
     def close_chrome_window(self):
         """Close the currently selected chrome window, ending the session
         if it's the last window open.
 
         :returns: Unordered list of remaining unique chrome window handles as strings
         """
         return self._send_message("closeChromeWindow")
 
+    def close_named_window(self, name):
+        """An atomic version of switch_to_window followed by close"""
+        return self._send_message("closeNamedWindow", {"name": name})
+
     def set_context(self, context):
         """Sets the context that Marionette commands are running in.
 
         :param context: Context, may be one of the class properties
             `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -2752,16 +2752,40 @@ GeckoDriver.prototype.close = async func
     this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
   }
 
   await this.curBrowser.closeTab();
   return this.windowHandles.map(String);
 };
 
 /**
+ * Close the tab/window with name or id name.
+ *
+ * This is essentially an atomic way for Python to ask the browser to close a
+ * window with a given name (if the window could potentially be closed between
+ * Python calling switchToWindow and then close itself).
+ *
+ * @return {Array.<string>}
+ *     Unique window handles of remaining windows.
+ *
+ * @throws {NoSuchWindowError}
+ *     Top-level browsing context has been discarded.
+ * @throws {UnexpectedAlertOpenError}
+ *     A modal dialog is open, blocking this operation.
+ */
+GeckoDriver.prototype.closeNamedWindow = async function(cmd, resp) {
+  assert.contentBrowser(this.curBrowser);
+  assert.noUserPrompt(this.dialog);
+
+  let fakeCmd = {parameters: {name: cmd.parameters.name}};
+  await this.switchToWindow(fakeCmd, resp);
+  return await this.close(fakeCmd, resp);
+};
+
+/**
  * Close the currently selected chrome window.
  *
  * If it is the last window currently open, the chrome window will not be
  * closed to prevent a shutdown of the application. Instead the returned
  * list of chrome window handles is empty.
  *
  * @return {Array.<string>}
  *     Unique chrome window handles of remaining chrome windows.
@@ -3552,16 +3576,17 @@ GeckoDriver.prototype.commands = {
   "reftest:teardown": GeckoDriver.prototype.teardownReftest,
 
   // WebDriver service
   "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
   "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
   "WebDriver:Back": GeckoDriver.prototype.goBack,
   "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   "WebDriver:CloseWindow": GeckoDriver.prototype.close,
+  "WebDriver:CloseNamedWindow": GeckoDriver.prototype.closeNamedWindow,
   "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
   "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
   "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
   "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
   "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
   "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
   "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
@@ -3616,16 +3641,17 @@ GeckoDriver.prototype.commands = {
   // deprecated WebDriver commands, remove in Firefox 60
   "acceptDialog": GeckoDriver.prototype.acceptDialog,
   "actionChain": GeckoDriver.prototype.actionChain,
   "addCookie": GeckoDriver.prototype.addCookie,
   "clearElement": GeckoDriver.prototype.clearElement,
   "clickElement": GeckoDriver.prototype.clickElement,
   "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   "close": GeckoDriver.prototype.close,
+  "closeNamedWindow": GeckoDriver.prototype.closeNamedWindow,
   "deleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   "deleteCookie": GeckoDriver.prototype.deleteCookie,
   "deleteSession": GeckoDriver.prototype.deleteSession,
   "dismissDialog": GeckoDriver.prototype.dismissDialog,
   "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
   "executeScript": GeckoDriver.prototype.executeScript,
   "findElement": GeckoDriver.prototype.findElement,
   "findElements": GeckoDriver.prototype.findElements,
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -155,22 +155,21 @@ class MarionetteProtocol(Protocol):
             # This isn't supposed to happen, but marionette ids are not yet stable
             # We assume that the first handle returned corresponds to the runner,
             # but it hopefully doesn't matter too much if that assumption is
             # wrong since we reload the runner in that tab anyway.
             runner_handle = handles.pop(0)
 
         for handle in handles:
             try:
-                self.marionette.switch_to_window(handle)
+                self.marionette.close_named_window(handle)
             except errors.NoSuchWindowException:
                 # We might have raced with the previous test to close this
                 # window, skip it.
                 pass
-            self.marionette.close()
 
         self.marionette.switch_to_window(runner_handle)
         if runner_handle != self.runner_handle:
             self.load_runner(protocol)
 
     def wait(self):
         socket_timeout = self.marionette.client.sock.gettimeout()
         if socket_timeout: