test(webui): 全流程演练的两处判据修正(同主题请求的定位、工作区目录)

第三次跑通 **37/37(0 失败 0 无法判定)**,含真 Agent 闭环:写信带附件 →
pi 请求 bash 授权 → **在界面点同意** → 决策落库 → pi 回信把附件原样带回
(sha256 一致)→ 回信出现在收件箱。

两处都是**判据/前置条件**的问题,不是产品缺陷,但都会伪装成产品故障:

1. 授权页上同一 Agent 的请求主题**一模一样**("是否允许执行 bash?")。原先按主题
   `.first()` 选行,于是点到了**别的会话那条旧请求**上 —— 网关回 `expired` 警告,
   界面看起来"点了但没生效"。现在按**本轮会话别名**定位分组容器
   (`header.locator('xpath=..')`,DOM 探针确认头与请求行同容器),且**只在折叠时**
   才点头按钮(已展开时再点会把它折起来,那正是第二次又落到别人行上的原因)。

2. **工作区寻址里的 path 必须是已存在的目录**,否则桥按设计回退到自己的兜底目录
   (`~/.pi/mail-sessions/<会话>`),产物就落在别处而不是我指定的目录。脚本现在
   先 mkdir —— zcode 那次也栽在同一条规则上。

顺带记录一个**产品层面值得决策**的现象:pi 的 worker 池上限是 3,而**"等人点头"的
worker 占着池子**。我上一轮的误点让 3 个 worker 全卡在等决策上 → 池满 → 新邮件排队
(设计如此:满载排队不丢信),于是我 300s 等不到回信。清掉卡住的请求后队列**立即
排空**、排队那封信被取出并起了一轮。也就是说:人在开会时,同一个 Agent 接不了新活。
是否把等待授权的 worker 挪出池子(或单独给额度),需要你定。
This commit is contained in:
2026-09-13 12:06:40 +08:00
parent 2922fb711f
commit 35f71d5144

View File

@ -213,6 +213,10 @@ try {
if (composeOpened && composeText.includes('新建邮件')) {
pass('写信页打开', '看到「新建邮件」');
const sessionPath = `/root/gotmp/webui-full-flow/${MARK}.new`;
// ★ 先建目录:工作区寻址里的 path **必须是已存在的目录**,否则桥按设计回退到
// 自己的兜底目录(`~/.pi/mail-sessions/<会话>`),产物就落在别处 ——
// 实测撞过两次zcode 一次、pi 一次),脚本这边必须先建。
mkdirSync(`/root/gotmp/webui-full-flow/${MARK}`, { recursive: true });
await page.locator('input[placeholder*="@"]').first().fill(`${AGENT}@${sessionPath}`);
await page.locator('input[placeholder*="特性"], input[placeholder*="分支"]').first().fill(`全流程演练 ${MARK}`).catch(() => {});
await page.locator('textarea').first().fill(`这是全流程演练 ${MARK}。请回信并**原样附回**我发的附件。`);
@ -248,6 +252,13 @@ try {
console.log('\n— E. Agent 闭环(权限请求 → 批准 → 回传附件,最多 300s—');
const mine = (sent.body?.mails || []).find(m => String(m.subject || '').includes(MARK));
const sid = mine?.session_id;
// 本轮会话别名:授权页上用它把"点哪条请求"限定到这一轮(同主题的旧请求太多)
let sessionAlias = '';
if (sid) {
const sess = await api(`/api/v1/sessions/${sid}`, {}, adminKey).catch(() => ({ body: {} }));
sessionAlias = String(sess.body?.session?.session_alias || sess.body?.session_alias || '').trim();
if (sessionAlias) console.log(` · 本轮会话别名:${sessionAlias}`);
}
if (!sid) {
undec('Agent 闭环', '发件箱里没找到刚发出的那封(拿不到会话 id');
} else {
@ -280,11 +291,33 @@ try {
await page.locator('text=授权').first().click().catch(() => {});
});
await page.waitForTimeout(2000);
// ★ 必须**限定到本轮会话**再点。
// 授权页上同一 Agent 的请求主题常常一模一样("是否允许执行 bash"
// 点开本轮那条请求。
// 授权页上同一 Agent 的请求主题一模一样("是否允许执行 bash"),所以必须
// **限定到本轮会话的容器里**再点:按主题 .first() 会点到别的会话那条(多半早已
// 过期)——症状是"点了、但库里没有这条的决策结果"(网关回的是 expired 警告)。
const reqText = String(perm.subject || '').replace(/^权限请求:\s*/, '').slice(0, 12);
const rowById = page.locator(`text=${String(perm.subject || '').slice(0, 18)}`).first();
const rowByText = page.locator(`text=${reqText}`).first();
if ((await rowById.count()) > 0) await rowById.click().catch(() => {});
else await rowByText.click().catch(() => {});
const header = page.locator(`button:has-text("${sessionAlias}")`).first();
let clickedRow = false;
if (sessionAlias && (await header.count()) > 0) {
// 分组容器 = 头按钮的父节点DOM 里头与请求行同容器,探针确认过)
const group = header.locator('xpath=..');
const rows = group.locator('button').filter({ hasText: '权限请求' });
const visible = (await rows.count()) > 0 && (await rows.first().isVisible().catch(() => false));
if (!visible) {
// 只有折叠状态才点开;已经展开时再点会把它折起来(我踩过:于是又落到别人的行上)
await header.click().catch(() => {});
await page.waitForTimeout(700);
}
if ((await rows.count()) > 0) {
clickedRow = await rows.first().click().then(() => true).catch(() => false);
}
}
if (!clickedRow) {
const byText = page.locator(`text=${reqText}`).first();
if ((await byText.count()) > 0) await byText.click().catch(() => {});
}
await page.waitForTimeout(2000);
await shot('44-permission-detail');
// ★ 决策按钮必须用**精确文本**匹配。