圈选插件有,显示流程图的桌面应用也有,现在就可以把它们的数据关联起来。
实现思路
1、 创建新任务时,使用ipc通信在后台起一个websocket服务
2、启动一个带有元素圈选chrome插件的浏览器,并连接上websocket
实现一个websocket服务
const WebSocket = require('ws');
const http = require('http');
import { globalLogger } from "./logger";
const WS_PORT = 8899
class WsServer {
server: any;
connectNum: number // 连接人数
httpserver: any
sockets: any
constructor() {
this.connectNum = 0
this.sockets = []
}
startWs(onListen, onMessage) {
const httpserver = http.createServer();
this.httpserver = httpserver
this.server = new WebSocket.Server({ noServer: true });
// this.server = new WebSocket.Server({ port: WS_PORT });
this.server?.on('connection', (socket) => {
this.sockets.push(socket)
console.log('Client connected');
// 向客户端发送消息
socket.send('jest_pro');
this.connectNum = this.connectNum + 1
// 监听客户端发送的消息
socket.on('message', (message) => {
console.log('Received:');
if (typeof onMessage === 'function') {
onMessage(socket, message)
}
});
// 监听客户端断开连接
socket.on('close', () => {
console.log('Client disconnected');
globalLogger.info('客户端断开连接.')
// this.closeServer()
});
});
this.server?.on('error', (err) => {
console.log('websocket error', err)
globalLogger.info('ws报错,ws服务关闭.')
this.closeServer()
})
const that = this
httpserver.on('upgrade', function upgrade(request, socket, head) {
that.server.handleUpgrade(request, socket, head, function done(ws) {
that.server.emit('connection', ws, request);
});
});
httpserver.listen(WS_PORT, () => {
if (typeof onListen === 'function') {
onListen()
}
})
}
closeServer() {
this.connectNum = 0
this.httpserver && this.httpserver.close()
this.server && this.server.close()
this.sockets.forEach(sk => sk && sk.destroy && sk.destroy())
this.sockets = []
}
static getInstance() {
if (!WsServer.instance) {
WsServer.instance = new WsServer()
}
return WsServer.instance
}
}
export default WsServer
开启websocket通信
监听:
const handelRunSetting = (win) => {
ipcMain.handle('start-task-setting', async (event, url) => {
const wsServer = WsServer.getInstance()
// console.log('start-task-setting====', url)
const taslPuppeteer = TaslPuppeteer.getInstance()
if (wsServer.connectNum !== 0) {
return [false, wsServer.connectNum]
};
return [await startServer(wsServer, taslPuppeteer, url, win), 0]
})
}
发起:
const [status, num] = await window.ipcRenderer.invoke('start-task-setting', taskUrl);
启动浏览器
const puppeteer = require('puppeteer');
import path from 'node:path'
class TaslPuppeteer {
browser: any
async runPuppeteer(url) {
// const buildCrx = path.join(__dirname, `${process.env.CHROME_DIST}/chrome_win64/chrome_extension/XPathHelper`)
const jestProCrx = path.join(__dirname, `${process.env.CHROME_DIST}/chrome_win64/chrome_extension/JestPro`)
// console.log('buildCrx---', buildCrx)
const config = {
headless: false, // 关闭无头模式
// timeout: 0,
args: [
// '--disable-gpu',
// '--disable-dev-shm-usage',
// '--disable-setuid-sandbox',
// '--no-first-run',
// '--no-sandbox',
// '--no-zygote',
// '--single-process', '--no-sandbox','--disable-setuid-sandbox',
'--start-maximized',
'--disable-dev-shm-usage',
'--no-sandbox',
`--disable-extensions-except=${jestProCrx}`,
`--load-extension=${jestProCrx}`,
],
ignoreHTTPSErrors: false, // 在导航期间忽略 HTTPS 错误
defaultViewport: null,
// args: ['--start-maximized', ], // 最大化启动,开启vue-devtools插件
// defaultViewport: { // 为每个页面设置一个默认视口大小
// width: 1920,
// height: 1080
// }
}
// if (process.platform === 'win32') {
// config['executablePath'] = path.join(__dirname, '../chrome_win64/chrome.exe')
// }
const browser = await puppeteer.launch(config);
this.browser = browser
browser.on('disconnected', (fn) => {
console.log('brower disconnected')
this._disconnected()
});
const page = await browser.newPage()
await page.goto(url, {
waitUntil: 'domcontentloaded',
});
}
closeBrowser() {
this.browser && this.browser.close()
}
_disconnected() {
}
static getInstance() {
if (!TaslPuppeteer.instance) {
TaslPuppeteer.instance = new TaslPuppeteer()
}
return TaslPuppeteer.instance
}
}
export default TaslPuppeteer
chrome 插件连接websoket
class WsClient {
constructor() {
const socket = new WebSocket('ws://localhost:8899');
this.status = false
socket.addEventListener('open', () => {
console.log('Connected to WebSocket server');
});
socket.addEventListener('message', (event) => {
console.log('Received:', event.data);
if (event.data === 'jest_pro') {
this.status = true
}
});
socket.addEventListener('close', () => {
console.log('WebSocket server close');
this.status = false
});
this.socket = socket
}
send(value) {
this.socket && this.socket.send(value)
}
getStatus() {
return this.status
}
static getInstance() {
if (!WsClient.instance) {
WsClient.instance = new WsClient()
}
return WsClient.instance
}
}
export default WsClient
当完成参数填写时,发送送定义好的数据给websocket,然后下发到electron的view,渲染节点
// chrome 发送数据到websocket服务
const wsclient = WsClient.getInstance()
console.log('handleFinish----', wsclient.getStatus())
if (wsclient.getStatus()) {
const other = optRef.current
wsclient.send(getNodeData(optType, xpath, waitTime, optReName, { ...other }))
typeof onClose === 'function' && onClose();
} else {
messageApi.open({
type: 'error',
content: '连接不上ws服务',
});
}
通过 ipc的通信方式,把chrome插件发送过来的数据,转发到 electron 页面去。
function startServer(wsServer, taslPuppeteer, url, win) {
return new Promise(resovle => {
wsServer.startWs(async () => {
console.log('启动成功')
globalLogger.info('ws服务启动成功')
try {
await taslPuppeteer.runPuppeteer(url)
globalLogger.info('浏览器启动成功')
win?.webContents.send('browser-close', false)
taslPuppeteer._disconnected = () => {
console.log('browser close')
win?.webContents.send('browser-close', true)
globalLogger.info('浏览器已关闭')
wsServer.closeServer()
}
resovle(true)
} catch (error) {
globalLogger.info('浏览器启动失败, ws服务关闭')
win?.webContents.send('browser-close', true)
wsServer.closeServer()
resovle(false)
}
}, (socket, message) => {
console.log('socket, message', message.toString())
win?.webContents.send('task-flow-data', message.toString())
})
})
}
最后
完成这一系列的操作,一个通过圈选元素构建流程图的桌面应用就诞生了。到这里还差最后一步,那就执行我们自动化的程序。
有兴趣的可以看看源码(记得给个star)