mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-01-24 13:30:59 -08:00
feat: capture webview screenshot
This commit is contained in:
parent
867940516b
commit
0ee7b634ed
3 changed files with 62 additions and 10 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue