@cloudscape-design/browser-test-tools 3.0.135 → 3.0.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { PNG } from 'pngjs';
2
- import { ElementSize, ScreenshotWithOffset } from '../page-objects/types';
2
+ import { ElementSize, RawScreenshot, ScreenshotWithOffset } from '../page-objects/types';
3
+ type Screenshot = ScreenshotWithOffset | RawScreenshot;
3
4
  export declare function compareImages(firstImage: PNG, secondImage: PNG, { width, height }: ElementSize): {
4
5
  diffPixels: number;
5
6
  diffImage: null;
@@ -14,4 +15,5 @@ export interface CropAndCompareResult {
14
15
  isEqual: boolean;
15
16
  diffPixels: number;
16
17
  }
17
- export declare function cropAndCompare(firstScreenshot: ScreenshotWithOffset, secondScreenshot: ScreenshotWithOffset): Promise<CropAndCompareResult>;
18
+ export declare function cropAndCompare(firstScreenshot: Screenshot, secondScreenshot: Screenshot): Promise<CropAndCompareResult>;
19
+ export {};
@@ -29,37 +29,50 @@ function normalizeSize(firstScreenshot, secondScreenshot) {
29
29
  width: Math.round(Math.max(firstScreenshot.width, secondScreenshot.width)),
30
30
  };
31
31
  }
32
+ function isScreenshotWithOffset(s) {
33
+ return 'offset' in s && s.offset !== undefined;
34
+ }
32
35
  function scaleSize(size, pixelRatio) {
33
36
  return {
34
37
  width: Math.ceil(size.width * pixelRatio),
35
38
  height: Math.ceil(size.height * pixelRatio),
36
39
  };
37
40
  }
41
+ async function getDecodedImage(screenshot) {
42
+ if (isScreenshotWithOffset(screenshot)) {
43
+ return screenshot.image;
44
+ }
45
+ return (0, utils_1.parsePng)(screenshot.rawBase64);
46
+ }
47
+ async function cropIfNeeded(screenshot, size) {
48
+ const image = await getDecodedImage(screenshot);
49
+ if (isScreenshotWithOffset(screenshot)) {
50
+ return (0, utils_1.cropImage)(image, buildCropRect(screenshot, size), screenshot.pixelRatio);
51
+ }
52
+ else {
53
+ return image;
54
+ }
55
+ }
38
56
  async function cropAndCompare(firstScreenshot, secondScreenshot) {
57
+ // Fast path: if rawBase64 is present on both, identical, and no cropping needed, skip all decoding entirely.
58
+ if (!isScreenshotWithOffset(firstScreenshot) &&
59
+ !isScreenshotWithOffset(secondScreenshot) &&
60
+ firstScreenshot.rawBase64 === secondScreenshot.rawBase64) {
61
+ const buffer = Buffer.from(firstScreenshot.rawBase64, 'base64');
62
+ return { firstImage: buffer, secondImage: buffer, diffImage: null, isEqual: true, diffPixels: 0 };
63
+ }
39
64
  const pixelRatio = firstScreenshot.pixelRatio || 1;
40
65
  const size = normalizeSize(firstScreenshot, secondScreenshot);
41
- const firstImageCropRect = {
42
- height: size.height,
43
- width: size.width,
44
- bottom: firstScreenshot.offset.top + size.height,
45
- right: firstScreenshot.offset.left + size.width,
46
- top: firstScreenshot.offset.top,
47
- left: firstScreenshot.offset.left,
48
- };
49
- const secondImageCropRect = {
50
- height: size.height,
51
- width: size.width,
52
- bottom: secondScreenshot.offset.top + size.height,
53
- right: secondScreenshot.offset.left + size.width,
54
- top: secondScreenshot.offset.top,
55
- left: secondScreenshot.offset.left,
56
- };
57
- const firstImage = (0, utils_1.cropImage)(firstScreenshot.image, firstImageCropRect, pixelRatio);
58
- const secondImage = (0, utils_1.cropImage)(secondScreenshot.image, secondImageCropRect, pixelRatio);
59
- const { diffImage, diffPixels } = compareImages(firstImage, secondImage, scaleSize(size, pixelRatio));
66
+ const scaledSize = scaleSize(size, pixelRatio);
67
+ const firstImage = await cropIfNeeded(firstScreenshot, size);
68
+ const secondImage = await cropIfNeeded(secondScreenshot, size);
69
+ const { diffImage, diffPixels } = compareImages(firstImage, secondImage, scaledSize);
70
+ // Skip packPng when no cropping was needed and rawBase64 is available
60
71
  const [firstPacked, secondPacked, diffPacked] = await Promise.all([
61
- (0, utils_1.packPng)(firstImage),
62
- (0, utils_1.packPng)(secondImage),
72
+ !isScreenshotWithOffset(firstScreenshot) ? Buffer.from(firstScreenshot.rawBase64, 'base64') : (0, utils_1.packPng)(firstImage),
73
+ !isScreenshotWithOffset(secondScreenshot)
74
+ ? Buffer.from(secondScreenshot.rawBase64, 'base64')
75
+ : (0, utils_1.packPng)(secondImage),
63
76
  diffImage && (0, utils_1.packPng)(diffImage),
64
77
  ]);
65
78
  return {
@@ -70,3 +83,16 @@ async function cropAndCompare(firstScreenshot, secondScreenshot) {
70
83
  diffPixels,
71
84
  };
72
85
  }
86
+ function buildCropRect(screenshot, size) {
87
+ var _a, _b, _c, _d;
88
+ const top = (_b = (_a = screenshot.offset) === null || _a === void 0 ? void 0 : _a.top) !== null && _b !== void 0 ? _b : 0;
89
+ const left = (_d = (_c = screenshot.offset) === null || _c === void 0 ? void 0 : _c.left) !== null && _d !== void 0 ? _d : 0;
90
+ return {
91
+ height: size.height,
92
+ width: size.width,
93
+ bottom: top + size.height,
94
+ right: left + size.width,
95
+ top,
96
+ left,
97
+ };
98
+ }
@@ -1,3 +1,3 @@
1
1
  {
2
- "commit": "bead6a8bf3fe674ef2dc8e17050fff1a046529ec"
2
+ "commit": "e9d80de15a044bf7259bbc3f8f4219a89fdf6b78"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudscape-design/browser-test-tools",
3
- "version": "3.0.135",
3
+ "version": "3.0.136",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudscape-design/browser-test-tools.git"
@@ -1,4 +1,6 @@
1
1
  export { default as BasePageObject } from './base';
2
+ export { default as ScreenshotBasePageObject } from './screenshot-base';
2
3
  export { default as ScreenshotPageObject, PermutationScreenshot } from './screenshot';
4
+ export { default as RawScreenshotPageObject, RawPermutationScreenshot } from './raw-screenshot';
3
5
  export { default as EventsSpy } from './events-spy';
4
- export { ScreenshotWithOffset, ElementSize, ElementRect, ElementOffset } from './types';
6
+ export { ScreenshotWithOffset, RawScreenshot, ElementSize, ElementRect, ElementOffset } from './types';
@@ -3,12 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EventsSpy = exports.ScreenshotPageObject = exports.BasePageObject = void 0;
6
+ exports.EventsSpy = exports.RawScreenshotPageObject = exports.ScreenshotPageObject = exports.ScreenshotBasePageObject = exports.BasePageObject = void 0;
7
7
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
8
8
  // SPDX-License-Identifier: Apache-2.0
9
9
  var base_1 = require("./base");
10
10
  Object.defineProperty(exports, "BasePageObject", { enumerable: true, get: function () { return __importDefault(base_1).default; } });
11
+ var screenshot_base_1 = require("./screenshot-base");
12
+ Object.defineProperty(exports, "ScreenshotBasePageObject", { enumerable: true, get: function () { return __importDefault(screenshot_base_1).default; } });
11
13
  var screenshot_1 = require("./screenshot");
12
14
  Object.defineProperty(exports, "ScreenshotPageObject", { enumerable: true, get: function () { return __importDefault(screenshot_1).default; } });
15
+ var raw_screenshot_1 = require("./raw-screenshot");
16
+ Object.defineProperty(exports, "RawScreenshotPageObject", { enumerable: true, get: function () { return __importDefault(raw_screenshot_1).default; } });
13
17
  var events_spy_1 = require("./events-spy");
14
18
  Object.defineProperty(exports, "EventsSpy", { enumerable: true, get: function () { return __importDefault(events_spy_1).default; } });
@@ -0,0 +1,19 @@
1
+ import ScreenshotBasePageObject from './screenshot-base';
2
+ import { RawScreenshot } from './types';
3
+ /**
4
+ * Raw permutation screenshot captured via takeElementScreenshot.
5
+ * No decoded image, no offset — just the raw base64 PNG per element.
6
+ */
7
+ export interface RawPermutationScreenshot extends RawScreenshot {
8
+ id: string;
9
+ }
10
+ /**
11
+ * A page object that captures screenshots using takeElementScreenshot.
12
+ * Returns raw base64 PNGs without decoding, cropping, or re-encoding —
13
+ * significantly faster when pixel-level comparison can be done on raw bytes.
14
+ */
15
+ export default class RawScreenshotPageObject extends ScreenshotBasePageObject {
16
+ captureBySelector(selector: string): Promise<RawScreenshot>;
17
+ captureViewport(): Promise<RawScreenshot>;
18
+ capturePermutations(): Promise<RawPermutationScreenshot[]>;
19
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
7
+ // SPDX-License-Identifier: Apache-2.0
8
+ const screenshot_base_1 = __importDefault(require("./screenshot-base"));
9
+ /**
10
+ * A page object that captures screenshots using takeElementScreenshot.
11
+ * Returns raw base64 PNGs without decoding, cropping, or re-encoding —
12
+ * significantly faster when pixel-level comparison can be done on raw bytes.
13
+ */
14
+ class RawScreenshotPageObject extends screenshot_base_1.default {
15
+ async captureBySelector(selector) {
16
+ await this.waitForVisible(selector);
17
+ const { pixelRatio } = await this.getViewportSize();
18
+ const box = await this.getBoundingBox(selector);
19
+ const originalWindowSize = await this.fitWindowHeightToContent();
20
+ const element = this.browser.$(selector);
21
+ const rawBase64 = await this.browser.takeElementScreenshot(await element.elementId);
22
+ await this.safeSetWindowSize(originalWindowSize.width, originalWindowSize.height);
23
+ return { rawBase64, pixelRatio, height: box.height, width: box.width };
24
+ }
25
+ async captureViewport() {
26
+ const { height, width } = await this.getViewportSize();
27
+ const rawBase64 = await this.browser.takeScreenshot();
28
+ return { rawBase64, height, width };
29
+ }
30
+ async capturePermutations() {
31
+ await this.windowScrollTo({ top: 0, left: 0 });
32
+ // Adapt viewport height to fit all elements before taking screenshots
33
+ const originalWindowSize = await this.fitWindowHeightToContent();
34
+ const elements = await this.browser.$$('[data-permutation]').map(el => el);
35
+ if (elements.length === 0) {
36
+ await this.safeSetWindowSize(originalWindowSize.width, originalWindowSize.height);
37
+ throw new Error('No permutations found on current page.');
38
+ }
39
+ const { pixelRatio } = await this.getViewportSize();
40
+ const results = [];
41
+ for (const element of elements) {
42
+ const id = (await element.getAttribute('data-permutation')) || '';
43
+ const rawBase64 = await this.browser.takeElementScreenshot(await element.elementId);
44
+ const size = await element.getSize();
45
+ results.push({
46
+ id,
47
+ rawBase64,
48
+ width: size.width * pixelRatio,
49
+ height: size.height * pixelRatio,
50
+ });
51
+ }
52
+ await this.safeSetWindowSize(originalWindowSize.width, originalWindowSize.height);
53
+ return results;
54
+ }
55
+ }
56
+ exports.default = RawScreenshotPageObject;
@@ -0,0 +1,18 @@
1
+ import BasePageObject from './base';
2
+ /**
3
+ * Base class for screenshot page objects. Provides scroll helpers,
4
+ * full-page screenshot support, and window size management.
5
+ */
6
+ export default class ScreenshotBasePageObject extends BasePageObject {
7
+ readonly forceScrollAndMerge: boolean;
8
+ constructor(browser: WebdriverIO.Browser, forceScrollAndMerge?: boolean);
9
+ focusNextElement(): Promise<void>;
10
+ scrollToBottom(selector: string): Promise<void>;
11
+ scrollToRight(selector: string): Promise<void>;
12
+ fullPageScreenshot(): Promise<string>;
13
+ protected fitWindowHeightToContent(): Promise<{
14
+ width: number;
15
+ height: number;
16
+ }>;
17
+ protected safeSetWindowSize(width: number, height: number): Promise<void>;
18
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
7
+ // SPDX-License-Identifier: Apache-2.0
8
+ const browser_scripts_1 = require("../browser-scripts");
9
+ const base_1 = __importDefault(require("./base"));
10
+ const full_page_screenshot_1 = __importDefault(require("./full-page-screenshot"));
11
+ /**
12
+ * Base class for screenshot page objects. Provides scroll helpers,
13
+ * full-page screenshot support, and window size management.
14
+ */
15
+ class ScreenshotBasePageObject extends base_1.default {
16
+ constructor(browser, forceScrollAndMerge = false) {
17
+ super(browser);
18
+ this.forceScrollAndMerge = forceScrollAndMerge;
19
+ }
20
+ async focusNextElement() {
21
+ return this.keys('Tab');
22
+ }
23
+ async scrollToBottom(selector) {
24
+ const action = 'scrollToBottom';
25
+ await this.browser.execute(browser_scripts_1.scrollAction, { action, selector });
26
+ }
27
+ async scrollToRight(selector) {
28
+ const action = 'scrollToRight';
29
+ await this.browser.execute(browser_scripts_1.scrollAction, { action, selector });
30
+ }
31
+ async fullPageScreenshot() {
32
+ // preserve scroll position in order to avoid side effects after screenshot taking
33
+ const scrollPosition = await this.getWindowScroll();
34
+ // Wait for the page to settle before taking a screenshot
35
+ await this.waitForJsTimers();
36
+ const screenshot = await (0, full_page_screenshot_1.default)(this.browser, this.forceScrollAndMerge);
37
+ // restore scroll position
38
+ await this.windowScrollTo(scrollPosition);
39
+ return screenshot;
40
+ }
41
+ async fitWindowHeightToContent() {
42
+ const originalWindowSize = await this.browser.getWindowSize();
43
+ const { viewportHeight, pageHeight } = await this.browser.execute(browser_scripts_1.getPageDimensions);
44
+ const windowUIHeight = originalWindowSize.height - viewportHeight;
45
+ await this.safeSetWindowSize(originalWindowSize.width, pageHeight + windowUIHeight);
46
+ return originalWindowSize;
47
+ }
48
+ /* istanbul ignore next -- setWindowSize is unsupported on some mobile browsers, not testable in CI */
49
+ async safeSetWindowSize(width, height) {
50
+ try {
51
+ await this.browser.setWindowSize(width, height);
52
+ }
53
+ catch (error) {
54
+ if (error instanceof Error && error.message.includes('Method has not yet been implemented')) {
55
+ console.log('setWindowSize is not supported on this device');
56
+ }
57
+ else {
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ exports.default = ScreenshotBasePageObject;
@@ -1,18 +1,10 @@
1
- import BasePageObject from './base';
2
1
  import { ScreenshotCapturingOptions, ScreenshotWithOffset } from './types';
2
+ import ScreenshotBasePageObject from './screenshot-base';
3
3
  export interface PermutationScreenshot extends ScreenshotWithOffset {
4
4
  id: string;
5
5
  }
6
- export default class ScreenshotPageObject extends BasePageObject {
7
- readonly forceScrollAndMerge: boolean;
8
- constructor(browser: WebdriverIO.Browser, forceScrollAndMerge?: boolean);
9
- focusNextElement(): Promise<void>;
10
- scrollToBottom(selector: string): Promise<void>;
11
- scrollToRight(selector: string): Promise<void>;
12
- fullPageScreenshot(): Promise<string>;
6
+ export default class ScreenshotPageObject extends ScreenshotBasePageObject {
13
7
  captureBySelector(selector: string, options?: ScreenshotCapturingOptions): Promise<ScreenshotWithOffset>;
14
8
  captureViewport(): Promise<ScreenshotWithOffset>;
15
9
  capturePermutations(): Promise<PermutationScreenshot[]>;
16
- private fitWindowHeightToContent;
17
- private safeSetWindowSize;
18
10
  }
@@ -7,34 +7,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  // SPDX-License-Identifier: Apache-2.0
8
8
  const browser_scripts_1 = require("../browser-scripts");
9
9
  const image_utils_1 = require("../image-utils");
10
- const base_1 = __importDefault(require("./base"));
11
- const full_page_screenshot_1 = __importDefault(require("./full-page-screenshot"));
12
- class ScreenshotPageObject extends base_1.default {
13
- constructor(browser, forceScrollAndMerge = false) {
14
- super(browser);
15
- this.forceScrollAndMerge = forceScrollAndMerge;
16
- }
17
- async focusNextElement() {
18
- return this.keys('Tab');
19
- }
20
- async scrollToBottom(selector) {
21
- const action = 'scrollToBottom';
22
- await this.browser.execute(browser_scripts_1.scrollAction, { action, selector });
23
- }
24
- async scrollToRight(selector) {
25
- const action = 'scrollToRight';
26
- await this.browser.execute(browser_scripts_1.scrollAction, { action, selector });
27
- }
28
- async fullPageScreenshot() {
29
- // preserve scroll position in order to avoid side effects after screenshot taking
30
- const scrollPosition = await this.getWindowScroll();
31
- // Wait for the page to settle before taking a screenshot
32
- await this.waitForJsTimers();
33
- const screenshot = await (0, full_page_screenshot_1.default)(this.browser, this.forceScrollAndMerge);
34
- // restore scroll position
35
- await this.windowScrollTo(scrollPosition);
36
- return screenshot;
37
- }
10
+ const screenshot_base_1 = __importDefault(require("./screenshot-base"));
11
+ class ScreenshotPageObject extends screenshot_base_1.default {
38
12
  async captureBySelector(selector, options = {}) {
39
13
  await this.waitForVisible(selector);
40
14
  const { pixelRatio, top, left } = await this.getViewportSize();
@@ -73,26 +47,5 @@ class ScreenshotPageObject extends base_1.default {
73
47
  }
74
48
  return permutations.map((permutation) => ({ ...permutation, image }));
75
49
  }
76
- async fitWindowHeightToContent() {
77
- const originalWindowSize = await this.browser.getWindowSize();
78
- const { viewportHeight, pageHeight } = await this.browser.execute(browser_scripts_1.getPageDimensions);
79
- const windowUIHeight = originalWindowSize.height - viewportHeight;
80
- await this.safeSetWindowSize(originalWindowSize.width, pageHeight + windowUIHeight);
81
- return originalWindowSize;
82
- }
83
- /* istanbul ignore next -- setWindowSize is unsupported on some mobile browsers, not testable in CI */
84
- async safeSetWindowSize(width, height) {
85
- try {
86
- await this.browser.setWindowSize(width, height);
87
- }
88
- catch (error) {
89
- if (error instanceof Error && error.message.includes('Method has not yet been implemented')) {
90
- console.log('setWindowSize is not supported on this device');
91
- }
92
- else {
93
- throw error;
94
- }
95
- }
96
- }
97
50
  }
98
51
  exports.default = ScreenshotPageObject;
@@ -24,4 +24,14 @@ export interface ScreenshotWithOffset extends ElementSize {
24
24
  image: PNG;
25
25
  offset: ElementOffset;
26
26
  pixelRatio?: number;
27
+ /** Optional raw base64 PNG for fast byte-equality comparison. */
28
+ rawBase64?: string;
29
+ }
30
+ /**
31
+ * A raw screenshot with base64 data and dimensions. No decoded image, no offset.
32
+ * Returned by RawScreenshotPageObject which uses takeElementScreenshot.
33
+ */
34
+ export interface RawScreenshot extends ElementSize {
35
+ rawBase64: string;
36
+ pixelRatio?: number;
27
37
  }