diff --git a/src/browser-scripts/index.ts b/src/browser-scripts/index.ts index 97436f0..d15058a 100644 --- a/src/browser-scripts/index.ts +++ b/src/browser-scripts/index.ts @@ -3,3 +3,4 @@ export * from './scroll'; export * from './sizes'; export * from './events-spy'; +export * from './permutations'; diff --git a/src/browser-scripts/permutations.ts b/src/browser-scripts/permutations.ts new file mode 100644 index 0000000..dfa3b55 --- /dev/null +++ b/src/browser-scripts/permutations.ts @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ElementOffset, ElementSize } from '../page-objects/types'; + +export interface PermutationInfo extends ElementSize { + id: string; + offset: ElementOffset; +} + +export interface PageDimensions { + pageHeight: number; + viewportHeight: number; +} + +export function getPermutationSizes(): PermutationInfo[] { + var pixelRatio = window.devicePixelRatio || 1; + return Array.prototype.slice + .call(document.querySelectorAll('[data-permutation]')) + .map(function (element: HTMLElement) { + var rect = element.getBoundingClientRect(); + return { + id: element.getAttribute('data-permutation') || '', + width: rect.width * pixelRatio, + height: rect.height * pixelRatio, + offset: { + top: rect.top * pixelRatio, + left: rect.left * pixelRatio, + }, + }; + }); +} + +export function getPageDimensions(): PageDimensions { + return { + pageHeight: document.documentElement.scrollHeight, + viewportHeight: window.innerHeight, + }; +} diff --git a/src/page-objects/index.ts b/src/page-objects/index.ts index 5c6b173..43be3d8 100644 --- a/src/page-objects/index.ts +++ b/src/page-objects/index.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 export { default as BasePageObject } from './base'; -export { default as ScreenshotPageObject } from './screenshot'; +export { default as ScreenshotPageObject, PermutationScreenshot } from './screenshot'; export { default as EventsSpy } from './events-spy'; export { ScreenshotWithOffset, ElementSize, ElementRect, ElementOffset } from './types'; diff --git a/src/page-objects/screenshot.ts b/src/page-objects/screenshot.ts index b9f7a53..f2965cb 100644 --- a/src/page-objects/screenshot.ts +++ b/src/page-objects/screenshot.ts @@ -1,11 +1,21 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { ScrollAction, scrollAction } from '../browser-scripts'; +import { + ScrollAction, + scrollAction, + getPermutationSizes, + getPageDimensions, + PermutationInfo, +} from '../browser-scripts'; import { parsePng } from '../image-utils'; import BasePageObject from './base'; import { ElementOffset, ScreenshotCapturingOptions, ScreenshotWithOffset } from './types'; import fullPageScreenshot from './full-page-screenshot'; +export interface PermutationScreenshot extends ScreenshotWithOffset { + id: string; +} + export default class ScreenshotPageObject extends BasePageObject { constructor(browser: WebdriverIO.Browser, public readonly forceScrollAndMerge: boolean = false) { super(browser); @@ -65,4 +75,46 @@ export default class ScreenshotPageObject extends BasePageObject { const image = await parsePng(screenshot); return { image, offset, height, width }; } + + async capturePermutations(): Promise { + await this.windowScrollTo({ top: 0, left: 0 }); + + // Adapt viewport height to fit all elements before taking a screenshot + const originalWindowSize = await this.fitWindowHeightToContent(); + + const permutations = await this.browser.execute(getPermutationSizes); + + // Restore window size after taking the screenshot + await this.safeSetWindowSize(originalWindowSize.width, originalWindowSize.height); + + if (permutations.length === 0) { + throw new Error('No permutations found on current page.'); + } + + const screenshot = await this.fullPageScreenshot(); + const image = await parsePng(screenshot); + + return permutations.map((permutation: PermutationInfo) => ({ ...permutation, image })); + } + + private async fitWindowHeightToContent(): Promise<{ width: number; height: number }> { + const originalWindowSize = await this.browser.getWindowSize(); + const { viewportHeight, pageHeight } = await this.browser.execute(getPageDimensions); + const windowUIHeight = originalWindowSize.height - viewportHeight; + await this.safeSetWindowSize(originalWindowSize.width, pageHeight + windowUIHeight); + return originalWindowSize; + } + + /* istanbul ignore next -- setWindowSize is unsupported on some mobile browsers, not testable in CI */ + private async safeSetWindowSize(width: number, height: number): Promise { + try { + await this.browser.setWindowSize(width, height); + } catch (error) { + if (error instanceof Error && error.message.includes('Method has not yet been implemented')) { + console.log('setWindowSize is not supported on this device'); + } else { + throw error; + } + } + } } diff --git a/test/fixtures/test-permutations.html b/test/fixtures/test-permutations.html new file mode 100644 index 0000000..24c8659 --- /dev/null +++ b/test/fixtures/test-permutations.html @@ -0,0 +1,26 @@ + + + + + + Permutations test page + + + +
Permutation 1
+
Permutation 2
+
Permutation 3
+ + diff --git a/test/page-object.test.ts b/test/page-object.test.ts index a6a1450..204a4e2 100644 --- a/test/page-object.test.ts +++ b/test/page-object.test.ts @@ -6,9 +6,9 @@ import useBrowser from '../src/use-browser'; import './utils/setup-local-driver'; type TestFn = (page: ScreenshotPageObject) => Promise; -function setupTest(testFn: TestFn) { +function setupTest(testFn: TestFn, url = './test-page-object.html') { return useBrowser(async browser => { - await browser.url('./test-page-object.html'); + await browser.url(url); await testFn(new ScreenshotPageObject(browser)); }); } @@ -362,3 +362,34 @@ test( }); }) ); + +describe('capturePermutations', () => { + test( + 'captures all permutations with correct ids and dimensions', + setupTest(async page => { + const permutations = await page.capturePermutations(); + + expect(permutations).toHaveLength(3); + expect(permutations.map(p => p.id)).toEqual(['perm-1', 'perm-2', 'perm-3']); + + permutations.forEach(perm => { + expect(perm.image).toBeDefined(); + expect(perm.width).toBeGreaterThan(0); + expect(perm.height).toBeGreaterThan(0); + expect(perm.offset.top).toBeGreaterThanOrEqual(0); + expect(perm.offset.left).toBeGreaterThanOrEqual(0); + }); + + // Verify permutations are in order (top to bottom) + expect(permutations[0].offset.top).toBeLessThan(permutations[1].offset.top); + expect(permutations[1].offset.top).toBeLessThan(permutations[2].offset.top); + }, './test-permutations.html') + ); + + test( + 'throws error when no permutations found', + setupTest(async page => { + await expect(page.capturePermutations()).rejects.toThrowError('No permutations found on current page.'); + }) + ); +});