From b7d831db8cfeac9dfbf441c623c6d4bf580f2cc5 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 28 Mar 2024 12:56:52 +0530 Subject: [PATCH] [bidi][js] Update the capture screenshot APIs to include all parameters and remove scroll parameter (#13744) --- .../bidi/browsingContext.js | 30 +++++-- .../bidi/captureScreenshotParameters.js | 64 ++++++++++++++ .../selenium-webdriver/bidi/clipRectangle.js | 87 +++++++++++++++++++ .../test/bidi/browsingcontext_test.js | 35 ++++++-- 4 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 javascript/node/selenium-webdriver/bidi/captureScreenshotParameters.js create mode 100644 javascript/node/selenium-webdriver/bidi/clipRectangle.js diff --git a/javascript/node/selenium-webdriver/bidi/browsingContext.js b/javascript/node/selenium-webdriver/bidi/browsingContext.js index ebb624448f754..eb9bb88160146 100644 --- a/javascript/node/selenium-webdriver/bidi/browsingContext.js +++ b/javascript/node/selenium-webdriver/bidi/browsingContext.js @@ -19,6 +19,7 @@ const { InvalidArgumentError, NoSuchFrameError } = require('../lib/error') const { BrowsingContextInfo } = require('./browsingContextTypes') const { SerializationOptions, ReferenceValue, RemoteValue } = require('./protocolValue') const { WebElement } = require('../lib/webdriver') +const { CaptureScreenshotParameters } = require('./captureScreenshotParameters') class Locator { static Type = Object.freeze({ @@ -203,12 +204,27 @@ class BrowsingContext { return new PrintResult(response.result.data) } - async captureScreenshot() { + async captureScreenshot(captureScreenshotParameters = undefined) { + if ( + captureScreenshotParameters !== undefined && + !(captureScreenshotParameters instanceof CaptureScreenshotParameters) + ) { + throw new InvalidArgumentError( + `Pass in a CaptureScreenshotParameters object. Received: ${captureScreenshotParameters}`, + ) + } + + const screenshotParams = new Map() + screenshotParams.set('context', this._id) + if (captureScreenshotParameters !== undefined) { + captureScreenshotParameters.asMap().forEach((value, key) => { + screenshotParams.set(key, value) + }) + } + let params = { method: 'browsingContext.captureScreenshot', - params: { - context: this._id, - }, + params: Object.fromEntries(screenshotParams), } const response = await this.bidi.send(params) @@ -231,15 +247,12 @@ class BrowsingContext { }, } - console.log(JSON.stringify(params)) - const response = await this.bidi.send(params) - console.log(JSON.stringify(response)) this.checkErrorInScreenshot(response) return response['result']['data'] } - async captureElementScreenshot(sharedId, handle = undefined, scrollIntoView = undefined) { + async captureElementScreenshot(sharedId, handle = undefined) { let params = { method: 'browsingContext.captureScreenshot', params: { @@ -250,7 +263,6 @@ class BrowsingContext { sharedId: sharedId, handle: handle, }, - scrollIntoView: scrollIntoView, }, }, } diff --git a/javascript/node/selenium-webdriver/bidi/captureScreenshotParameters.js b/javascript/node/selenium-webdriver/bidi/captureScreenshotParameters.js new file mode 100644 index 0000000000000..a894f1d89314f --- /dev/null +++ b/javascript/node/selenium-webdriver/bidi/captureScreenshotParameters.js @@ -0,0 +1,64 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +const { BoxClipRectangle, ElementClipRectangle } = require('./clipRectangle') +const Origin = { + VIEWPORT: 'viewport', + DOCUMENT: 'document', +} + +class CaptureScreenshotParameters { + #map = new Map() + + origin(origin) { + if (origin !== Origin.VIEWPORT && origin !== Origin.DOCUMENT) { + throw new Error(`Origin must be one of ${Object.values(Origin)}. Received:'${origin}'`) + } + this.#map.set('origin', origin) + return this + } + + imageFormat(type, quality = undefined) { + if (typeof type !== 'string') { + throw new Error(`Type must be an instance of String. Received:'${type}'`) + } + + this.#map.set('type', type) + + if (quality !== undefined) { + if (typeof quality !== 'number') { + throw new Error(`Quality must be a number. Received:'${quality}'`) + } + this.#map.set('quality', quality) + } + return this + } + + clipRectangle(clipRectangle) { + if (!(clipRectangle instanceof BoxClipRectangle || clipRectangle instanceof ElementClipRectangle)) { + throw new Error(`ClipRectangle must be an instance of ClipRectangle. Received:'${clipRectangle}'`) + } + this.#map.set('clip', Object.fromEntries(clipRectangle.asMap())) + return this + } + + asMap() { + return this.#map + } +} + +module.exports = { CaptureScreenshotParameters, Origin } diff --git a/javascript/node/selenium-webdriver/bidi/clipRectangle.js b/javascript/node/selenium-webdriver/bidi/clipRectangle.js new file mode 100644 index 0000000000000..272cc7d328445 --- /dev/null +++ b/javascript/node/selenium-webdriver/bidi/clipRectangle.js @@ -0,0 +1,87 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +class ClipRectangle { + clipType + + constructor(type) { + this.clipType = type + } + + get type() { + return this.clipType + } + + asMap() {} +} + +class ElementClipRectangle extends ClipRectangle { + #sharedId + #handleId + + constructor(sharedId, handleId = undefined) { + super('element') + this.#sharedId = sharedId + + if (handleId !== undefined) { + this.#handleId = handleId + } + } + + asMap() { + const map = new Map() + map.set('type', super.type) + + const sharedReference = new Map() + sharedReference.set('sharedId', this.#sharedId) + if (this.#handleId !== undefined) { + sharedReference.set('handleId', this.#handleId) + } + + map.set('element', Object.fromEntries(sharedReference)) + + return map + } +} + +class BoxClipRectangle extends ClipRectangle { + #x + #y + #width + #height + + constructor(x, y, width, height) { + super('box') + this.#x = x + this.#y = y + this.#width = width + this.#height = height + } + + asMap() { + const map = new Map() + map.set('type', super.type) + map.set('x', this.#x) + map.set('y', this.#y) + map.set('width', this.#width) + map.set('height', this.#height) + + return map + } +} + +module.exports = { BoxClipRectangle, ElementClipRectangle } diff --git a/javascript/node/selenium-webdriver/test/bidi/browsingcontext_test.js b/javascript/node/selenium-webdriver/test/bidi/browsingcontext_test.js index a11c9b92af8b6..1c7094accc2cb 100644 --- a/javascript/node/selenium-webdriver/test/bidi/browsingcontext_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/browsingcontext_test.js @@ -23,6 +23,8 @@ const { Browser, By } = require('../../') const { Pages, suite } = require('../../lib/test') const BrowsingContext = require('../../bidi/browsingContext') const until = require('../../lib/until') +const { Origin, CaptureScreenshotParameters } = require('../../bidi/captureScreenshotParameters') +const { BoxClipRectangle, ElementClipRectangle } = require('../../bidi/clipRectangle') suite( function (env) { @@ -190,19 +192,22 @@ suite( assert.equal(base64code, pngMagicNumber) }) - it('can take box screenshot', async function () { + it('can take screenshot with all parameters for box screenshot', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, }) - const response = await browsingContext.captureBoxScreenshot(5, 5, 10, 10) + let captureScreenshotParams = new CaptureScreenshotParameters() + captureScreenshotParams.origin(Origin.VIEWPORT).clipRectangle(new BoxClipRectangle(5, 5, 10, 10)) + + const response = await browsingContext.captureScreenshot(captureScreenshotParams) const base64code = response.slice(startIndex, endIndex) assert.equal(base64code, pngMagicNumber) }) - it('can take element screenshot', async function () { + it('can take screenshot with all parameters for element screenshot', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, @@ -211,22 +216,38 @@ suite( await driver.get(Pages.formPage) const element = await driver.findElement(By.id('checky')) const elementId = await element.getId() - const response = await browsingContext.captureElementScreenshot(elementId) + + let captureScreenshotParams = new CaptureScreenshotParameters() + captureScreenshotParams.origin(Origin.VIEWPORT).clipRectangle(new ElementClipRectangle(elementId)) + + const response = await browsingContext.captureScreenshot(captureScreenshotParams) + + const base64code = response.slice(startIndex, endIndex) + assert.equal(base64code, pngMagicNumber) + }) + + it('can take box screenshot', async function () { + const id = await driver.getWindowHandle() + const browsingContext = await BrowsingContext(driver, { + browsingContextId: id, + }) + + const response = await browsingContext.captureBoxScreenshot(5, 5, 10, 10) const base64code = response.slice(startIndex, endIndex) assert.equal(base64code, pngMagicNumber) }) - it('can scroll and take element screenshot', async function () { + it('can take element screenshot', async function () { const id = await driver.getWindowHandle() const browsingContext = await BrowsingContext(driver, { browsingContextId: id, }) await driver.get(Pages.formPage) - const element = await driver.findElement(By.id('checkbox-with-label')) + const element = await driver.findElement(By.id('checky')) const elementId = await element.getId() - const response = await browsingContext.captureElementScreenshot(elementId, undefined, true) + const response = await browsingContext.captureElementScreenshot(elementId) const base64code = response.slice(startIndex, endIndex) assert.equal(base64code, pngMagicNumber)