feat: capture webview screenshot

This commit is contained in:
lin onetwo 2025-10-30 13:57:23 +08:00
parent 867940516b
commit 0ee7b634ed
3 changed files with 62 additions and 10 deletions

View file

@ -9,6 +9,7 @@ import { MockOAuthServer } from '../supports/mockOAuthServer';
import { MockOpenAIServer } from '../supports/mockOpenAI';
import { makeSlugPath, screenshotsDirectory } from '../supports/paths';
import { getPackedAppPath } from '../supports/paths';
import { captureScreenshot } from '../supports/webContentsViewHelper';
// Backoff configuration for retries
const BACKOFF_OPTIONS = {
@ -200,7 +201,7 @@ AfterStep(async function(this: ApplicationWorld, { pickle, pickleStep, result })
try {
const stepText = pickleStep.text;
// Skip screenshots for wait steps to avoid too many screenshots
if (stepText.match(/^I wait for \d+(\.\d+)? seconds?$/i)) {
return;
@ -244,10 +245,17 @@ AfterStep(async function(this: ApplicationWorld, { pickle, pickleStep, result })
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotPath = path.resolve(featureDirectory, `${timestamp}-${cleanStepText}-${stepStatus}.jpg`);
// Try to capture both WebContentsView and Page screenshots
let webViewCaptured = false;
if (this.app) {
const webViewScreenshotPath = path.resolve(featureDirectory, `${timestamp}-${cleanStepText}-${stepStatus}-webview.png`);
webViewCaptured = await captureScreenshot(this.app, webViewScreenshotPath);
}
// Use conservative screenshot options for CI
await pageToUse.screenshot({ path: screenshotPath, fullPage: true, type: 'jpeg', quality: 10 });
// Always capture page screenshot (UI chrome/window)
const pageScreenshotPath = path.resolve(featureDirectory, `${timestamp}-${cleanStepText}-${stepStatus}${webViewCaptured ? '-page' : ''}.png`);
await pageToUse.screenshot({ path: pageScreenshotPath, fullPage: true, type: 'png' });
} catch (screenshotError) {
console.warn('Failed to take screenshot:', screenshotError);
}

View file

@ -1,4 +1,5 @@
import { WebContentsView } from 'electron';
import fs from 'fs-extra';
import type { ElectronApplication } from 'playwright';
/**
@ -288,3 +289,46 @@ export async function elementExists(app: ElectronApplication, selector: string):
return false;
}
}
/**
* Capture screenshot of WebContentsView
* Returns true if screenshot was taken successfully, false if WebContentsView not found
*/
export async function captureScreenshot(app: ElectronApplication, screenshotPath: string): Promise<boolean> {
try {
const webContentsId = await getFirstWebContentsView(app);
if (!webContentsId) {
return false;
}
const pngBufferData = await app.evaluate(
async ({ webContents }, id: number) => {
const targetWebContents = webContents.fromId(id);
if (!targetWebContents) {
return null;
}
try {
const image = await targetWebContents.capturePage();
const pngBuffer = image.toPNG();
return Array.from(pngBuffer);
} catch (error) {
console.error('Failed to capture screenshot:', error);
return null;
}
},
webContentsId,
);
if (!pngBufferData) {
return false;
}
await fs.writeFile(screenshotPath, Buffer.from(pngBufferData));
return true;
} catch (error) {
console.error('Error capturing screenshot:', error);
return false;
}
}

View file

@ -185,16 +185,16 @@ export class IpcServerRoutes {
}
}
tiddlerFieldsToPut.title = title;
// Mark this tiddler as recently saved to prevent echo
this.recentlySavedTiddlers.add(title);
this.wikiInstance.wiki.addTiddler(new this.wikiInstance.Tiddler(tiddlerFieldsToPut));
// Note: The change event is triggered synchronously by addTiddler
// The event handler in getWikiChangeObserver$ will check recentlySavedTiddlers
// and remove the mark after filtering
const changeCount = this.wikiInstance.wiki.getChangeCount(title).toString();
return { statusCode: 204, headers: { 'Content-Type': 'text/plain', Etag: `"default/${encodeURIComponent(title)}/${changeCount}:"` }, data: 'OK' };
}
@ -239,7 +239,7 @@ export class IpcServerRoutes {
// Filter out tiddlers that were just saved via IPC to prevent echo
const filteredChanges: IChangedTiddlers = {};
let hasChanges = false;
for (const title in changes) {
if (this.recentlySavedTiddlers.has(title)) {
// This change was caused by our own putTiddler, skip it to prevent echo
@ -249,7 +249,7 @@ export class IpcServerRoutes {
filteredChanges[title] = changes[title];
hasChanges = true;
}
// Only notify if there are actual changes after filtering
if (hasChanges) {
observer.next(filteredChanges);