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
--- 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();