From 9e5959e61509b124b74a109935d606027c18a983 Mon Sep 17 00:00:00 2001 From: Kabir-Ivan <110986400+Kabir-Ivan@users.noreply.github.com> Date: Mon, 12 Aug 2024 04:54:54 -0700 Subject: [PATCH] feat: screenshot viewport with assertView by default (#988) --- docs/writing-tests.md | 29 +++++++++++++++- src/browser/commands/assert-view/index.js | 31 +++++++++++++++-- src/browser/commands/types.ts | 10 +++++- .../src/browser/commands/assert-view/index.js | 33 +++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/docs/writing-tests.md b/docs/writing-tests.md index 630ef7e4f..0d0fafae7 100644 --- a/docs/writing-tests.md +++ b/docs/writing-tests.md @@ -281,6 +281,23 @@ it('some test', async ({ browser }) => { }); ``` +With skipped `selector` parameter, assertView will take a screenshot of the current viewport. Example: + +```js +it('some test', async ({ browser }) => { + await browser.url('some/url'); + // Scroll 1000 px down + await browser.execute(() => window.scrollTo(0, 1000)); + // Screen the viewport + await browser.assertView('plain'); + + // Click the button + await browser.$('.button').click(); + // Screen the viewport with custom options + await browser.assertView('clicked', { ignoreDiffPixelCount: 5 }); +}); +``` + Could also be used as element's method: ```js @@ -300,7 +317,17 @@ it('some test', async ({ browser }) => { Parameters: - state (required) `String` – state name; should be unique within one test -- selector (required) `String|String[]` – DOM-node selector that you need to capture +- selector (optional, can be skipped) `String|String[]` – DOM-node selector that you need to capture. If not specified or skipped, will be set to `body` and the following options will be automatically added to opts: + ``` + { + allowViewportOverflow: true, + compositeImage: false, + captureElementFromTop: false + } + ``` + + So, assertView without `selector` parameter will take a screenshot of the current viewport. + - opts (optional) `Object`: - ignoreElements (optional) `String|String[]` – elements, matching specified selectors will be ignored when comparing images - tolerance (optional) `Number` – overrides config [browsers](#browsers).[tolerance](#tolerance) value diff --git a/src/browser/commands/assert-view/index.js b/src/browser/commands/assert-view/index.js index 3ee59983b..2c0c22e18 100644 --- a/src/browser/commands/assert-view/index.js +++ b/src/browser/commands/assert-view/index.js @@ -155,18 +155,43 @@ module.exports.default = browser => { testplaneCtx.assertViewResults.add({ stateName: state, refImg: refImg }); }; - session.addCommand("assertView", async function (state, selectors, opts = {}) { + const waitSelectorsForExist = async (browser, selectors) => { await Promise.map([].concat(selectors), async selector => { - await this.$(selector) + await browser + .$(selector) .then(el => el.waitForExist()) .catch(() => { throw new Error( - `element ("${selector}") still not existing after ${this.options.waitforTimeout} ms`, + `element ("${selector}") still not existing after ${browser.options.waitforTimeout} ms`, ); }); }); + }; + + const assertViewBySelector = async (browser, state, selectors, opts) => { + await waitSelectorsForExist(browser, selectors); return assertView(state, selectors, opts); + }; + + const assertViewByViewport = async (state, opts) => { + opts = _.defaults(opts, { + allowViewportOverflow: true, + compositeImage: false, + captureElementFromTop: false, + }); + + return assertView(state, "body", opts); + }; + + const shouldAssertViewport = selectorsOrOpts => { + return !(typeof selectorsOrOpts === "string" || _.isArray(selectorsOrOpts)); + }; + + session.addCommand("assertView", async function (state, selectorsOrOpts, opts = {}) { + return shouldAssertViewport(selectorsOrOpts) + ? assertViewByViewport(state, selectorsOrOpts || opts) + : assertViewBySelector(this, state, selectorsOrOpts, opts); }); session.addCommand( diff --git a/src/browser/commands/types.ts b/src/browser/commands/types.ts index 712a6220c..7396231b9 100644 --- a/src/browser/commands/types.ts +++ b/src/browser/commands/types.ts @@ -82,13 +82,21 @@ export interface AssertViewOpts extends Partial { ignoreDiffPixelCount?: `${number}%` | number; } -export type AssertViewCommand = ( +export type AssertViewCommandWithSelector = ( this: WebdriverIO.Browser, state: string, selectors: string | string[], opts?: AssertViewOpts, ) => Promise; +export type AssertViewCommandWithoutSelector = ( + this: WebdriverIO.Browser, + state: string, + opts?: AssertViewOpts, +) => Promise; + +export type AssertViewCommand = AssertViewCommandWithSelector & AssertViewCommandWithoutSelector; + export type AssertViewElementCommand = ( this: WebdriverIO.Element | ChainablePromiseElement, state: string, diff --git a/test/src/browser/commands/assert-view/index.js b/test/src/browser/commands/assert-view/index.js index c135a7ab4..a3d34357b 100644 --- a/test/src/browser/commands/assert-view/index.js +++ b/test/src/browser/commands/assert-view/index.js @@ -149,6 +149,39 @@ describe("assertView command", () => { assert.calledOnceWith(browser.prepareScreenshot, [".selector1", ".selector2"]); }); + it("should screenshot the viewport if selector is not provided", async () => { + const browser = await initBrowser_(); + + await browser.publicAPI.assertView("plain"); + + assert.calledOnceWith( + browser.prepareScreenshot, + ["body"], + sinon.match({ + allowViewportOverflow: true, + captureElementFromTop: false, + }), + ); + }); + + it("should add custom options if selector is not provided", async () => { + const browser = await initBrowser_(); + + await browser.publicAPI.assertView("plain", { + disableAnimation: false, + }); + + assert.calledOnceWith( + browser.prepareScreenshot, + ["body"], + sinon.match({ + allowViewportOverflow: true, + captureElementFromTop: false, + disableAnimation: false, + }), + ); + }); + [ { scope: "browser", fn: assertViewBrowser }, { scope: "element", fn: assertViewElement },