Bug 1213875 - Add ability to not scroll into view element on screen capture; r?whimboo draft
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 20 Dec 2016 14:30:48 +0000
changeset 455392 4b62172973091d01bfa37a37f01ddaf40d77c4db
parent 455391 97b99590b32ecc1e359a915a58dd0ec991fd9481
child 455393 3047d64c116d2115da1ae448b88655c451ee9f85
push id40223
push userbmo:ato@mozilla.com
push dateTue, 03 Jan 2017 18:25:16 +0000
reviewerswhimboo
bugs1213875
milestone53.0a1
Bug 1213875 - Add ability to not scroll into view element on screen capture; r?whimboo Implements a `scroll` argument for choosing the scroll into view behaviour when taking screen captures of elements. https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot MozReview-Commit-ID: BOKBrKqQ916
testing/marionette/capture.js
testing/marionette/driver.js
testing/marionette/listener.js
--- a/testing/marionette/capture.js
+++ b/testing/marionette/capture.js
@@ -12,16 +12,21 @@ this.EXPORTED_SYMBOLS = ["capture"];
 const CONTEXT_2D = "2d";
 const BG_COLOUR = "rgb(255,255,255)";
 const PNG_MIME = "image/png";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 /** Provides primitives to capture screenshots. */
 this.capture = {};
 
+capture.Format = {
+  Base64: 0,
+  Hash: 1,
+};
+
 /**
  * Take a screenshot of a single element.
  *
  * @param {Node} node
  *     The node to take a screenshot of.
  * @param {Array.<Node>=} highlights
  *     Optional array of nodes, around which a border will be marked to
  *     highlight them in the screenshot.
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -2386,79 +2386,78 @@ GeckoDriver.prototype.clearImportedScrip
  * If called in the content context, the <code>id</code> argument is not null
  * and refers to a present and visible web element's ID, the capture area
  * will be limited to the bounding box of that element. Otherwise, the
  * capture area will be the bounding box of the current frame.
  *
  * If called in the chrome context, the screenshot will always represent the
  * entire viewport.
  *
- * @param {string} id
- *     Reference to a web element.
- * @param {string} highlights
+ * @param {string=} id
+ *     Optional web element reference to take a screenshot of.
+ *     If undefined, a screenshot will be taken of the document element.
+ * @param {Array.<string>=} highlights
  *     List of web elements to highlight.
  * @param {boolean} full
  *     True to take a screenshot of the entire document element. Is not
  *     considered if {@code id} is not defined. Defaults to true.
- * @param {boolean} hash
+ * @param {boolean=} hash
  *     True if the user requests a hash of the image data.
+ * @param {boolean=} scroll
+ *     Scroll to element if |id| is provided.  If undefined, it will
+ *     scroll to the element.
  *
  * @return {string}
  *     If {@code hash} is false, PNG image encoded as base64 encoded string. If
  *     'hash' is True, hex digest of the SHA-256 hash of the base64 encoded
  *     string.
  */
 GeckoDriver.prototype.takeScreenshot = function (cmd, resp) {
-  let {id, highlights, full, hash} = cmd.parameters;
+  let {id, highlights, full, hash, scroll} = cmd.parameters;
   highlights = highlights || [];
+  let format = hash ? capture.Format.Hash : capture.Format.Base64;
 
   switch (this.context) {
     case Context.CHROME:
-      let canvas;
-      let highlightEls = [];
-
       let container = {frame: this.getCurrentWindow().document.defaultView};
-
       if (!container.frame) {
-        throw new NoSuchWindowError('Unable to locate window');
+        throw new NoSuchWindowError("Unable to locate window");
       }
 
-      for (let h of highlights) {
-        let el = this.curBrowser.seenEls.get(h, container);
-        highlightEls.push(el);
-      }
+      let highlightEls = highlights.map(
+          ref => this.curBrowser.seenEls.get(ref, container));
 
       // viewport
+      let canvas;
       if (!id && !full) {
         canvas = capture.viewport(container.frame, highlightEls);
 
       // element or full document element
       } else {
         let node;
         if (id) {
           node = this.curBrowser.seenEls.get(id, container);
         } else {
           node = container.frame.document.documentElement;
         }
 
         canvas = capture.element(node, highlightEls);
       }
 
-      if (hash) {
-        return capture.toHash(canvas);
-      } else {
-        return capture.toBase64(canvas);
+      switch (format) {
+        case capture.Format.Hash:
+          return capture.toHash(canvas);
+
+        case capture.Format.Base64:
+          return capture.toBase64(canvas);
       }
+      break;
 
     case Context.CONTENT:
-      if (hash) {
-        return this.listener.getScreenshotHash(id, full, highlights);
-      } else {
-        return this.listener.takeScreenshot(id, full, highlights);
-      }
+      return this.listener.takeScreenshot(format, cmd.parameters);
   }
 };
 
 /**
  * Get the current browser orientation.
  *
  * Will return one of the valid primary orientation values
  * portrait-primary, landscape-primary, portrait-secondary, or
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -234,17 +234,16 @@ var findElementsContentFn = dispatch(fin
 var isElementSelectedFn = dispatch(isElementSelected);
 var clearElementFn = dispatch(clearElement);
 var isElementDisplayedFn = dispatch(isElementDisplayed);
 var getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
 var switchToShadowRootFn = dispatch(switchToShadowRoot);
 var getCookiesFn = dispatch(getCookies);
 var singleTapFn = dispatch(singleTap);
 var takeScreenshotFn = dispatch(takeScreenshot);
-var getScreenshotHashFn = dispatch(getScreenshotHash);
 var performActionsFn = dispatch(performActions);
 var releaseActionsFn = dispatch(releaseActions);
 var actionChainFn = dispatch(actionChain);
 var multiActionFn = dispatch(multiAction);
 var addCookieFn = dispatch(addCookie);
 var deleteCookieFn = dispatch(deleteCookie);
 var deleteAllCookiesFn = dispatch(deleteAllCookies);
 var executeFn = dispatch(execute);
@@ -292,17 +291,16 @@ function startListeners() {
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   addMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
   addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
   addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   addMessageListenerId("Marionette:setTestName", setTestName);
   addMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
-  addMessageListenerId("Marionette:getScreenshotHash", getScreenshotHashFn);
   addMessageListenerId("Marionette:addCookie", addCookieFn);
   addMessageListenerId("Marionette:getCookies", getCookiesFn);
   addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
   addMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
 }
 
 /**
  * Used during newSession and restart, called to set up the modal dialog listener in b2g
@@ -398,17 +396,16 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   removeMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
   removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   removeMessageListenerId("Marionette:setTestName", setTestName);
   removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
-  removeMessageListenerId("Marionette:getScreenshotHash", getScreenshotHashFn);
   removeMessageListenerId("Marionette:addCookie", addCookieFn);
   removeMessageListenerId("Marionette:getCookies", getCookiesFn);
   removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
   removeMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
   if (isB2G) {
     content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   }
   seenEls.clear();
@@ -1615,94 +1612,74 @@ function deleteAllCookies() {
 function getAppCacheStatus(msg) {
   sendResponse(
       curContainer.frame.applicationCache.status, msg.json.command_id);
 }
 
 /**
  * Perform a screen capture in content context.
  *
- * @param {UUID=} id
- *     Optional web element reference of an element to take a screenshot
- *     of.
- * @param {boolean=} full
- *     True to take a screenshot of the entire document element.  Is not
- *     considered if {@code id} is not defined.  Defaults to true.
- * @param {Array.<UUID>=} highlights
- *     Draw a border around the elements found by their web element
- *     references.
+ * Accepted values for |opts|:
+ *
+ *     @param {UUID=} id
+ *         Optional web element reference of an element to take a screenshot
+ *         of.
+ *     @param {boolean=} full
+ *         True to take a screenshot of the entire document element.  Is not
+ *         considered if {@code id} is not defined.  Defaults to true.
+ *     @param {Array.<UUID>=} highlights
+ *         Draw a border around the elements found by their web element
+ *         references.
+ *     @param {boolean=} scroll
+ *         When |id| is given, scroll it into view before taking the
+ *         screenshot.  Defaults to true.
+ *
+ * @param {capture.Format} format
+ *     Format to return the screenshot in.
+ * @param {Object.<string, ?>} opts
+ *     Options.
  *
  * @return {string}
- *     Base64 encoded string of an image/png type.
+ *     Base64 encoded string or a SHA-256 hash of the screenshot.
  */
-function takeScreenshot(id, full=true, highlights=[]) {
-  let canvas = screenshot(id, full, highlights);
-  return capture.toBase64(canvas);
-}
+function takeScreenshot(format, opts = {}) {
+  let id = opts.id;
+  let full = !!opts.full;
+  let highlights = opts.highlights || [];
+  let scroll = !!opts.scroll;
 
-/**
-* Perform a screen capture in content context.
-*
-* @param {UUID=} id
-*     Optional web element reference of an element to take a screenshot
-*     of.
-* @param {boolean=} full
-*     True to take a screenshot of the entire document element.  Is not
-*     considered if {@code id} is not defined.  Defaults to true.
-* @param {Array.<UUID>=} highlights
-*     Draw a border around the elements found by their web element
-*     references.
-*
-* @return {string}
-*     Hex Digest of a SHA-256 hash of the base64 encoded string of an
-*     image/png type.
-*/
-function getScreenshotHash(id, full=true, highlights=[]) {
-  let canvas = screenshot(id, full, highlights);
-  return capture.toHash(canvas);
-}
+  let highlightEls = highlights.map(ref => seenEls.get(ref, curContainer));
 
-/**
-* Perform a screen capture in content context.
-*
-* @param {UUID=} id
-*     Optional web element reference of an element to take a screenshot
-*     of.
-* @param {boolean=} full
-*     True to take a screenshot of the entire document element.  Is not
-*     considered if {@code id} is not defined.  Defaults to true.
-* @param {Array.<UUID>=} highlights
-*     Draw a border around the elements found by their web element
-*     references.
-*
-* @return {HTMLCanvasElement}
-*     The canvas element to be encoded or hashed.
-*/
-function screenshot(id, full=true, highlights=[]) {
   let canvas;
 
-  let highlightEls = [];
-  for (let h of highlights) {
-    let el = seenEls.get(h, curContainer);
-    highlightEls.push(el);
-  }
-
   // viewport
   if (!id && !full) {
     canvas = capture.viewport(curContainer.frame, highlightEls);
 
   // element or full document element
   } else {
-    let node;
+    let el;
     if (id) {
-      node = seenEls.get(id, curContainer);
+      el = seenEls.get(id, curContainer);
+      if (scroll) {
+        element.scrollIntoView(el);
+      }
     } else {
-      node = curContainer.frame.document.documentElement;
+      el = curContainer.frame.document.documentElement;
     }
 
-    canvas = capture.element(node, highlightEls);
+    canvas = capture.element(el, highlightEls);
   }
 
-  return canvas;
+  switch (format) {
+    case capture.Format.Base64:
+      return capture.toBase64(canvas);
+
+    case capture.Format.Hash:
+      return capture.toHash(canvas);
+
+    default:
+      throw new TypeError("Unknown screenshot format: " + format);
+  }
 }
 
 // Call register self when we get loaded
 registerSelf();