Electron Playwright测试因Flask后端导致挂起
Electron Playwright测试因Flask后端导致挂起
兄弟,我之前踩过一模一样的坑!Electron里先启动Flask后端再开窗口,用Playwright做自动化测试的时候老是挂起,试了好几种方法才搞定,给你分享下我的解决思路:
首先先把我当时的Electron核心代码贴出来(和你的场景几乎一致):
const { app, BrowserWindow } = require('electron'); const { spawn } = require('child_process'); const path = require("path"); // 路径配置 const rootDirectory = path.join(__dirname, "..","..","..","..") const pagesDirectory = path.join(rootDirectory, "frontend", "pages") let flaskProcess; app.whenReady().then(async () => { // 先启动Flask后端 flaskProcess = spawn('python', [path.join(rootDirectory, 'backend', 'app.py')], { stdio: 'inherit' // 这里是第一个坑! }); // 原来用固定延时等后端启动,很不稳定 await new Promise(resolve => setTimeout(resolve, 2000)); // 打开主窗口 const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true } }); await mainWindow.loadFile(path.join(pagesDirectory, 'index.html')); }); // 关闭app前杀后端进程 app.on('before-quit', (event) => { if (flaskProcess) { flaskProcess.kill(); // 这里是第二个坑! } });
问题根源分析
Playwright测试挂起90%的原因是Electron应用没有被正确终止,要么是Flask子进程还在后台跑,要么是Playwright没等到应用退出的信号,具体到我们的场景:
stdio: 'inherit'会让Flask的输出流绑定到Electron的主进程流,Playwright会一直监听这些流,只要流没关闭就不会认为应用退出;- 单纯用
flaskProcess.kill()可能杀不干净Flask的子进程(比如Flask开了调试模式会自动fork进程); - 固定延时等后端启动不可靠,有时候后端还没就绪,窗口已经打开,测试逻辑提前执行导致后续流程卡住。
逐个解决的办法
改掉
stdio配置:测试环境下别用inherit,换成['ignore', 'pipe', 'pipe'],让子进程的输出和主进程解耦,这样Electron退出时流能正常关闭:flaskProcess = spawn('python', [path.join(rootDirectory, 'backend', 'app.py')], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, FLASK_ENV: process.env.TEST_MODE ? 'production' : 'development' } });顺便在测试模式下把Flask改成生产模式,避免调试模式的fork行为。
彻底杀死Flask进程树:用
tree-kill包(先npm install tree-kill)来杀死整个进程树,确保没有残留的Flask进程:const treeKill = require('tree-kill'); app.on('before-quit', (event) => { if (flaskProcess) { treeKill(flaskProcess.pid); flaskProcess = null; } });用主动检测代替固定延时:在Electron里等后端真正就绪再开窗口,比如发请求检测health接口:
const fetch = require('node-fetch'); // 封装一个等后端就绪的函数 async function waitForBackendReady() { const maxRetries = 10; let retries = 0; while (retries < maxRetries) { try { const res = await fetch('http://localhost:5000/health'); if (res.ok) break; } catch (err) { // 后端还没起来,继续等 await new Promise(resolve => setTimeout(resolve, 500)); retries++; } } } app.whenReady().then(async () => { flaskProcess = spawn('python', [path.join(rootDirectory, 'backend', 'app.py')], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, FLASK_ENV: process.env.TEST_MODE ? 'production' : 'development' } }); // 等后端真正就绪 await waitForBackendReady(); // 再开窗口 const mainWindow = new BrowserWindow({...}); await mainWindow.loadFile(...); });给Playwright测试加保险:在Playwright配置里设置超时,并且测试结束后主动关闭应用,别等它自己退出:
// playwright.config.js module.exports = { use: { timeout: 30000, // 全局超时30秒 }, electron: { timeout: 30000, launchOptions: { env: { ...process.env, TEST_MODE: '1' } // 告诉Electron现在是测试模式 } } };测试代码里主动关闭应用:
const { test, expect } = require('@playwright/test'); test('Electron app with Flask backend', async ({ electron }) => { const app = await electron.launch({ args: ['.'] }); const window = await app.firstWindow(); // 测试逻辑,比如检查页面标题 await expect(window.title()).toBe('My App'); // 测试完主动关闭,别等Playwright自己处理 await app.close(); });
最后再提个小细节
如果你的Flask用了debug=True,测试模式下一定要关掉,因为调试模式会自动重启进程,很容易导致Electron无法正确杀死它,直接在启动命令里设置FLASK_ENV=production或者在代码里判断测试模式禁用调试。
备注:内容来源于stack exchange,提问作者user24995271
相关产品推荐
相关产品推荐

