Skip to content

Commit 5cfb960

Browse files
authored
[e2e] Improve teardown - full desktop screenshot (#993)
* [e2e] Always dispose temp dir * [e2e] Screenshot entire desktop on close failure * nit
1 parent c7ed8f7 commit 5cfb960

File tree

5 files changed

+67
-10
lines changed

5 files changed

+67
-10
lines changed

tests/integration/post-install.teardown.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { pathExists } from '../shared/utils';
22
import { TestEnvironment } from './testEnvironment';
33
import { assertPlaywrightEnabled, expect, test as teardown } from './testExtensions';
44

5-
// This "test" is a setup process. Any failure here should break all post-install tests.
6-
// After running, the test environment will contain an installed ComfyUI app, ready for other tests to use as a base.
5+
// This "test" is a setup process.
6+
// After running, the test environment should be completely uninstalled.
77

88
teardown('Completely uninstalls the app', async ({}) => {
99
assertPlaywrightEnabled();

tests/integration/testApp.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { type ElectronApplication, type JSHandle } from '@playwright/test';
1+
import type { ElectronApplication, JSHandle, TestInfo } from '@playwright/test';
22
import electronPath, { type BrowserWindow } from 'electron';
33
import { _electron as electron } from 'playwright';
44

5+
import { createDesktopScreenshot } from '../shared/utils';
56
import { TestEnvironment } from './testEnvironment';
67

78
// eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -17,6 +18,16 @@ async function localTestQoL(app: ElectronApplication) {
1718
window.on('console', console.log);
1819
}
1920

21+
/** Screen shot entire desktop */
22+
async function attachScreenshot(testInfo: TestInfo, name: string) {
23+
try {
24+
const filePath = await createDesktopScreenshot(name);
25+
await testInfo.attach(name, { path: filePath });
26+
} catch (error) {
27+
console.error(error);
28+
}
29+
}
30+
2031
/**
2132
* Base class for desktop e2e tests.
2233
*/
@@ -27,14 +38,17 @@ export class TestApp implements AsyncDisposable {
2738
/** Remove the install directory when disposed. */
2839
shouldDisposeTestEnvironment: boolean = false;
2940

30-
private constructor(readonly app: ElectronApplication) {
41+
private constructor(
42+
readonly app: ElectronApplication,
43+
readonly testInfo: TestInfo
44+
) {
3145
app.once('close', () => (this.#appProcessTerminated = true));
3246
}
3347

3448
/** Async static factory */
35-
static async create() {
49+
static async create(testInfo: TestInfo) {
3650
const app = await TestApp.launchElectron();
37-
return new TestApp(app);
51+
return new TestApp(app, testInfo);
3852
}
3953

4054
/** Get the first window that the app opens. Wait if necessary. */
@@ -78,9 +92,15 @@ export class TestApp implements AsyncDisposable {
7892
const windows = this.app.windows();
7993
if (windows.length === 0) return;
8094

81-
const close = this.app.waitForEvent('close', { timeout: 60 * 1000 });
82-
await Promise.all(windows.map((x) => x.close()));
83-
await close;
95+
try {
96+
const close = this.app.waitForEvent('close', { timeout: 60 * 1000 });
97+
await Promise.all(windows.map((x) => x.close()));
98+
await close;
99+
} catch (error) {
100+
console.error('App failed to close; attaching screenshot to TestInfo');
101+
await attachScreenshot(this.testInfo, 'test-app-close-failure');
102+
throw error;
103+
}
84104
}
85105

86106
#appProcessTerminated = false;

tests/integration/testEnvironment.ts

+1
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,6 @@ export class TestEnvironment implements AsyncDisposable {
125125
await this.restoreInstallPath();
126126
await this.restoreVenv();
127127
await this.restoreServerStart();
128+
await this.installLocation[Symbol.asyncDispose]();
128129
}
129130
}

tests/integration/testExtensions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const test = baseTest.extend<DesktopTestOptions & DesktopTestFixtures>({
5555
// Fixtures
5656
app: async ({ disposeTestEnvironment }, use, testInfo) => {
5757
// Launch Electron app.
58-
await using app = await TestApp.create();
58+
await using app = await TestApp.create(testInfo);
5959
app.shouldDisposeTestEnvironment = disposeTestEnvironment;
6060
await use(app);
6161

tests/shared/utils.ts

+36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import envPaths from 'env-paths';
2+
import { exec } from 'node:child_process';
23
import { randomUUID } from 'node:crypto';
34
import { access, constants } from 'node:fs/promises';
45
import { homedir } from 'node:os';
@@ -49,3 +50,38 @@ export function getDefaultInstallLocation() {
4950
export function addRandomSuffix(str: string) {
5051
return `${str}-${randomUUID().substring(0, 8)}`;
5152
}
53+
54+
/**
55+
* Create a screenshot of the entire desktop.
56+
*
57+
* Hard-coded to 1920x1080 resolution.
58+
* @param filename - The name of the file to save the screenshot as.
59+
* @returns The path to the screenshot file.
60+
*/
61+
export async function createDesktopScreenshot(filename: string) {
62+
const width = 1920;
63+
const height = 1080;
64+
const powerShellScript = `
65+
Add-Type -AssemblyName System.Drawing
66+
67+
$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, ${width}, ${height})
68+
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
69+
$graphics = [Drawing.Graphics]::FromImage($bmp)
70+
71+
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
72+
$bmp.Save("${filename}.png", "Png")
73+
74+
$graphics.Dispose()
75+
$bmp.Dispose()
76+
`;
77+
78+
const process = exec(powerShellScript, { shell: 'powershell.exe' }, (error, stdout, stderr) => {
79+
if (error) console.error(error);
80+
if (stderr) console.error('Screenshot std error', stderr);
81+
if (stdout) console.log('Screenshot std out', stdout);
82+
});
83+
await new Promise((resolve) => process.on('close', resolve));
84+
85+
const name = `${filename}.png`;
86+
return path.resolve(globalThis.process.cwd(), name);
87+
}

0 commit comments

Comments
 (0)