feat: 脚本录制功能完善
This commit is contained in:
@@ -66,6 +66,7 @@ const api: WindowApi = {
|
||||
run: (id: string) => ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RUN, id),
|
||||
startRecording: (url?: string) => ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_START, url),
|
||||
stopRecording: () => ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_STOP),
|
||||
codegen: (id: string, url?: string) => ipcRenderer.invoke(IPC_EVENTS.SCRIPT_CODEGEN, id, url),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,9 @@ import {
|
||||
toggleScript,
|
||||
} from '@electron/service/script-store-service';
|
||||
import { runScriptById } from '@electron/service/script-execution-service';
|
||||
import {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} from '@electron/service/script-recorder-service';
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { spawn } from 'child_process'
|
||||
import log from 'electron-log';
|
||||
|
||||
const openedTabIndexByChannelName = new Map<string, number>()
|
||||
@@ -29,6 +26,9 @@ function getScriptsDir() {
|
||||
|
||||
export function runTaskOperationService() {
|
||||
const executeScriptServiceInstance = new executeScriptService();
|
||||
const playwrightCoreDir = path.dirname(require.resolve('playwright-core'));
|
||||
const cliPath = path.join(playwrightCoreDir, 'cli.js');
|
||||
let recorderProc: ReturnType<typeof spawn> | null = null;
|
||||
|
||||
// 脚本管理 IPC
|
||||
ipcMain.handle(IPC_EVENTS.SCRIPT_LIST, async () => {
|
||||
@@ -88,7 +88,35 @@ export function runTaskOperationService() {
|
||||
|
||||
ipcMain.handle(IPC_EVENTS.SCRIPT_RECORD_START, async (_event, url?: string) => {
|
||||
try {
|
||||
return await startRecording(url);
|
||||
if (recorderProc) {
|
||||
recorderProc.kill('SIGINT');
|
||||
recorderProc = null;
|
||||
}
|
||||
|
||||
const targetUrl = url || 'about:blank';
|
||||
recorderProc = spawn(process.execPath, [cliPath, 'codegen', '--target', 'javascript', '--channel', 'chrome', '--viewport-size', '1920,1080', '--color-scheme', 'light', targetUrl], {
|
||||
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
recorderProc.on('error', (err) => {
|
||||
log.error('[SCRIPT_RECORD_START] Failed to start codegen process:', err);
|
||||
});
|
||||
|
||||
recorderProc.on('exit', (code, signal) => {
|
||||
log.info(`[SCRIPT_RECORD_START] Process exited code=${code} signal=${signal}`);
|
||||
recorderProc = null;
|
||||
});
|
||||
|
||||
recorderProc.stdout?.on('data', (data: Buffer) => {
|
||||
log.info(`[SCRIPT_RECORD_START] stdout: ${data.toString()}`);
|
||||
});
|
||||
|
||||
recorderProc.stderr?.on('data', (data: Buffer) => {
|
||||
log.error(`[SCRIPT_RECORD_START] stderr: ${data.toString()}`);
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
log.error('[SCRIPT_RECORD_START] error:', error);
|
||||
return { success: false, error: error?.message || 'Recording start failed' };
|
||||
@@ -97,13 +125,76 @@ export function runTaskOperationService() {
|
||||
|
||||
ipcMain.handle(IPC_EVENTS.SCRIPT_RECORD_STOP, async () => {
|
||||
try {
|
||||
return await stopRecording();
|
||||
if (recorderProc) {
|
||||
recorderProc.kill('SIGINT');
|
||||
recorderProc = null;
|
||||
}
|
||||
return { success: true, code: '' };
|
||||
} catch (error: any) {
|
||||
log.error('[SCRIPT_RECORD_STOP] error:', error);
|
||||
return { success: false, error: error?.message || 'Recording stop failed' };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_EVENTS.SCRIPT_CODEGEN, async (_event, id: string, url?: string) => {
|
||||
try {
|
||||
const script = getScript(id);
|
||||
if (!script) {
|
||||
return { success: false, error: 'Script not found' };
|
||||
}
|
||||
|
||||
const scriptsDir = getScriptsDir();
|
||||
const scriptPath = path.join(scriptsDir, script.filename);
|
||||
const targetUrl = url || 'about:blank';
|
||||
|
||||
log.info(`[SCRIPT_CODEGEN] Starting codegen for script ${id} at ${scriptPath} with url ${targetUrl}`);
|
||||
|
||||
return await new Promise<{ success: boolean; code?: string; error?: string }>((resolve) => {
|
||||
const proc = spawn(process.execPath, [cliPath, 'codegen', '--target', 'javascript', '--channel', 'chrome', '-o', scriptPath, targetUrl], {
|
||||
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
proc.on('exit', () => {
|
||||
try {
|
||||
let generatedCode = fs.readFileSync(scriptPath, 'utf-8');
|
||||
|
||||
// Playwright codegen --target javascript generates CommonJS code.
|
||||
// Since script files use .mjs extension, we inject createRequire for compatibility.
|
||||
if (generatedCode.includes("require('playwright')") && !generatedCode.includes('createRequire')) {
|
||||
generatedCode = `import { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\n\n${generatedCode}`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(scriptPath, generatedCode, 'utf-8');
|
||||
|
||||
// Update script store so the new code is reflected in metadata reads
|
||||
saveScript({
|
||||
id,
|
||||
name: script.name,
|
||||
description: script.description,
|
||||
code: generatedCode,
|
||||
channel: script.channel,
|
||||
enabled: script.enabled,
|
||||
});
|
||||
|
||||
resolve({ success: true, code: generatedCode });
|
||||
} catch (err: any) {
|
||||
log.error('[SCRIPT_CODEGEN] Failed to process generated code:', err);
|
||||
resolve({ success: false, error: err?.message || 'Failed to process generated code' });
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
log.error('[SCRIPT_CODEGEN] Failed to start codegen:', err);
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
log.error('[SCRIPT_CODEGEN] error:', error);
|
||||
return { success: false, error: error?.message || 'Codegen failed' };
|
||||
}
|
||||
});
|
||||
|
||||
// 打开渠道
|
||||
ipcMain.handle(IPC_EVENTS.OPEN_CHANNEL, async (_event, channels: any) => {
|
||||
try {
|
||||
|
||||
@@ -28,7 +28,12 @@
|
||||
"enabled": true,
|
||||
"channel": "fliggy",
|
||||
"createdAt": "2026-04-09T19:35:34.000Z",
|
||||
"updatedAt": "2026-04-09T19:35:34.000Z"
|
||||
"updatedAt": "2026-04-12T12:59:12.117Z",
|
||||
"lastRun": {
|
||||
"time": "2026-04-12T12:59:12.117Z",
|
||||
"success": false,
|
||||
"error": "Script exited with code 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "script-mt-trace",
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { chromium } from 'playwright';
|
||||
import log from 'electron-log';
|
||||
import { launchLocalChrome } from '@electron/utils/chrome/launchLocalChrome';
|
||||
|
||||
let recorderBrowser: any = null;
|
||||
let recorderContext: any = null;
|
||||
|
||||
export async function startRecording(url?: string): Promise<{ success: boolean; code?: string; error?: string }> {
|
||||
try {
|
||||
await launchLocalChrome();
|
||||
|
||||
if (recorderBrowser) {
|
||||
await stopRecording();
|
||||
}
|
||||
|
||||
recorderBrowser = await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||
recorderContext = recorderBrowser.contexts()[0] || (await recorderBrowser.newContext());
|
||||
const page = await recorderContext.newPage();
|
||||
const targetUrl = url || 'about:blank';
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// 唤起 Playwright Inspector,让用户手动录制并生成代码
|
||||
await page.pause();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
code: '',
|
||||
};
|
||||
} catch (error: any) {
|
||||
log.error('[script-recorder-service] Failed to start recording:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error?.message || 'Failed to start recording',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopRecording(): Promise<{ success: boolean; code?: string; error?: string }> {
|
||||
try {
|
||||
if (recorderContext) {
|
||||
await recorderContext.close().catch(() => {});
|
||||
recorderContext = null;
|
||||
}
|
||||
if (recorderBrowser) {
|
||||
await recorderBrowser.close().catch(() => {});
|
||||
recorderBrowser = null;
|
||||
}
|
||||
return { success: true, code: '' };
|
||||
} catch (error: any) {
|
||||
log.error('[script-recorder-service] Failed to stop recording:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error?.message || 'Failed to stop recording',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user