feat: 接入网页批二——文件上传/多标签/窗口状态/停止加载/相似元素数组/弹窗自动关闭

This commit is contained in:
yu 2026-10-28 17:40:35 +08:00
parent 9b8bc0eb1a
commit 560134171f
4 changed files with 128 additions and 1 deletions

View File

@ -768,6 +768,58 @@ export class BrowserSession implements BrowserLike {
return result.items;
}
maximize(): void {
if (!this.win.isDestroyed()) this.win.maximize();
}
minimize(): void {
if (!this.win.isDestroyed()) this.win.minimize();
}
stopLoading(): void {
if (!this.win.isDestroyed()) this.win.webContents.stop();
}
async uploadFileToInput(selector: string, filePath: string): Promise<number> {
const doc = await this.cdp<{ root?: { nodeId?: number } }>("DOM.getDocument");
const rootNodeId = doc.root?.nodeId;
if (!rootNodeId) throw new RuntimeError("browser_error", "无法定位页面 DOM 根节点");
const css = selector.replace(/^css:/, "").trim();
const found = await this.cdp<{ nodeId?: number }>("DOM.querySelector", { nodeId: rootNodeId, selector: css });
if (!found.nodeId) {
throw new RuntimeError("element_not_found", "未找到文件输入元素:" + css, { recommendation: "确认 input[type=file] 存在且已渲染" });
}
await this.cdp("DOM.setFileInputFiles", { files: [filePath], nodeId: found.nodeId });
return found.nodeId;
}
startAutoCloseDialogs(): boolean {
if (!this.win.isDestroyed() && !this.win.webContents.debugger.isAttached()) this.win.webContents.debugger.attach("1.3");
this.cdp("Page.enable").catch(() => {});
const flags = globalThis as { __rpaAutoCloseDialogs?: boolean; __rpaAutoCloseDialogListener?: unknown };
if (flags.__rpaAutoCloseDialogs) return false;
flags.__rpaAutoCloseDialogs = true;
const listener = (_event: unknown, method: string) => {
if (method === "Page.javascriptDialogOpening") {
this.cdp("Page.handleJavaScriptDialog", { accept: true }).catch(() => {});
}
};
flags.__rpaAutoCloseDialogListener = listener;
this.win.webContents.debugger.on("message", listener);
return true;
}
stopAutoCloseDialogs(): boolean {
const flags = globalThis as { __rpaAutoCloseDialogs?: boolean; __rpaAutoCloseDialogListener?: unknown };
if (!flags.__rpaAutoCloseDialogs) return false;
if (flags.__rpaAutoCloseDialogListener) {
this.win.webContents.debugger.removeListener("message", flags.__rpaAutoCloseDialogListener as never);
}
flags.__rpaAutoCloseDialogs = false;
flags.__rpaAutoCloseDialogListener = undefined;
return true;
}
async currentUrl(): Promise<string> {
return this.win.webContents.getURL();
}

View File

@ -18,7 +18,7 @@ function selectorOf(args: Record<string, unknown>): unknown {
return selector;
}
register(["WebBrowser_CreateV3", "WebBrowser_Create", "WebBrowser_CreateBackground"], async (ctx, node, args) => {
register(["WebBrowser_CreateV3", "WebBrowser_CreateV2", "WebBrowser_Create", "WebBrowser_CreateBackground"], async (ctx, node, args) => {
const browser = await session(ctx);
const url = String(args.url ?? "");
if (url) {

View File

@ -10,6 +10,7 @@ import "./browser-control";
import "./office";
import "./desktop";
import "./desktop-uia";
import "./web-batch2";
import "./pdf";
import "./word";
import "./word-extended";

View File

@ -0,0 +1,74 @@
import { browserSession } from "../browser/session";
import { register } from "../engine/registry";
import { RuntimeError } from "../engine/types";
function cssValue(selector: unknown): string {
const raw = typeof selector === "object" && selector !== null
? String((selector as { value?: string }).value ?? "")
: String(selector ?? "");
const text = raw.trim();
return text.startsWith("css:") ? text.slice(4).trim() : text;
}
function requireUploadPath(args: Record<string, unknown>): string {
const filePath = String(args.file_path ?? args.filePath ?? args.path ?? "").trim();
const folder = String(args.folder_path ?? args.folder ?? "").trim();
const target = filePath || folder;
if (!target) {
throw new RuntimeError("validation_error", "上传文件路径为空", { recommendation: "填写要上传的本地文件完整路径" });
}
return target;
}
register(["Element_FileUploader", "WebBrowser_Upload", "WebBrowser_UploadDialog"], async (ctx, node, args) => {
const browser = await browserSession();
const selector = cssValue(args.uiSelector ?? "css:input[type=file]") || "input[type=file]";
const filePath = requireUploadPath(args);
const nodeId = await browser.uploadFileToInput(selector, filePath);
ctx.log("已设置上传文件:" + filePath, "info", node.id);
return { uploaded: true, selector, filePath, nodeId };
});
register(["WebBrowser_GetTAB", "WebBrowser_GetTAB_V1", "WebBrowser_GetTAB_V2"], async (ctx, node) => {
const browser = await browserSession();
const url = await browser.currentUrl();
ctx.log("已获取标签页列表(单窗口模式)", "info", node.id);
return [{ index: 0, url, active: true }];
});
register("WebBrowser_Switch_V1", async (ctx, node, args) => {
const browser = await browserSession();
const url = await browser.currentUrl();
ctx.log("运行浏览器为单窗口模式,当前即唯一标签页", "info", node.id);
return { switched: true, index: 0, url, active: String(args.is_active ?? "1") === "1" };
});
register("WebBrowser_Maximize", async (ctx, node) => {
browser.maximize();
return true;
});
register("WebBrowser_Minimize", async (ctx, node) => {
browser.minimize();
return true;
});
register("WebBrowser_Stop", async (ctx, node) => {
browser.stopLoading();
return true;
});
register(["Element_ElementAarray", "Element_ElementCaptureSimilar"], async (ctx, node, args) => {
const browser = await browserSession();
const selector = args.uiSelector ?? args.selector ?? args.target;
if (!selector) {
throw new RuntimeError("validation_error", "元素选择器为空", { nodeId: node.id, componentId: node.componentId, recommendation: "请拾取或填写相似元素" });
}
const references = await browser.getSimilarElements(selector, {
start: Number.isFinite(Number(args.start_index)) ? Math.trunc(Number(args.start_index)) : 1,
end: Number.isFinite(Number(args.end_index)) ? Math.trunc(Number(args.end_index)) : -1,
reverse: String(args.reverse ?? "") === "1" || String(args.reverse ?? "") === "true"
});
ctx.log("已获取 " + references.length + " 个相似元素(数组)", "info", node.id);
return references;
});