|
| 1 | +import { readFile, writeFile } from 'fs/promises'; |
| 2 | +import path from 'path'; |
| 3 | +import { PNG } from 'pngjs'; |
| 4 | +import { type APIClient } from '../APIClient.js'; |
| 5 | +import { type TestScenario } from '../TestScenario.js'; |
| 6 | + |
| 7 | +export interface ITakeScreenshotParams { |
| 8 | + 'part-id': string; |
| 9 | + 'save-to'?: string; |
| 10 | + 'compare-with'?: string; |
| 11 | +} |
| 12 | + |
| 13 | +export class TakeScreenshotCommand { |
| 14 | + constructor(readonly scenarioDir: string) {} |
| 15 | + |
| 16 | + validate(value: ITakeScreenshotParams) { |
| 17 | + if (!value['part-id']) { |
| 18 | + throw new Error('take-screenshot: `part-id` is required'); |
| 19 | + } |
| 20 | + if (!value['save-to'] && !value['compare-with']) { |
| 21 | + throw new Error('take-screenshot: `save-to` or `compare-with` is required'); |
| 22 | + } |
| 23 | + } |
| 24 | + |
| 25 | + async run(scenario: TestScenario, client: APIClient, params: ITakeScreenshotParams) { |
| 26 | + const framebuffer = await client.framebufferRead(params['part-id']); |
| 27 | + const png = Buffer.from(framebuffer.png, 'base64'); |
| 28 | + const saveTo = params['save-to']; |
| 29 | + const compareWith = params['compare-with']; |
| 30 | + if (saveTo) { |
| 31 | + await writeFile(path.join(this.scenarioDir, saveTo), png); |
| 32 | + } |
| 33 | + if (compareWith) { |
| 34 | + const compareWithPath = path.join(this.scenarioDir, compareWith); |
| 35 | + const originalPng = PNG.sync.read(png); |
| 36 | + |
| 37 | + let compareWithData; |
| 38 | + try { |
| 39 | + compareWithData = await readFile(compareWithPath); |
| 40 | + } catch (error) { |
| 41 | + scenario.fail(`Failed to read comparison file for screenshot: ${compareWith}`); |
| 42 | + return; |
| 43 | + } |
| 44 | + |
| 45 | + const compareWithPng = PNG.sync.read(compareWithData); |
| 46 | + |
| 47 | + if ( |
| 48 | + compareWithPng.width !== originalPng.width || |
| 49 | + compareWithPng.height !== originalPng.height |
| 50 | + ) { |
| 51 | + scenario.fail( |
| 52 | + `Comparison file for screenshot (${compareWith}) has a different size (${compareWithPng.width}x${compareWithPng.height}) than the part's screen (${originalPng.width}x${originalPng.height})`, |
| 53 | + ); |
| 54 | + return; |
| 55 | + } |
| 56 | + |
| 57 | + for (let i = 0; i < originalPng.data.length; i++) { |
| 58 | + const original = originalPng.data[i]; |
| 59 | + const compare = compareWithPng.data[i]; |
| 60 | + if (original !== compare) { |
| 61 | + const x = (i >> 2) % originalPng.width; |
| 62 | + const y = Math.floor((i >> 2) / originalPng.width); |
| 63 | + scenario.fail(`Screenshot mismatch for ${compareWith} at x,y=(${x},${y})`); |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | +} |
0 commit comments