/** * 邮件详情页地址行的真实渲染验收。 * * 锁的是那次事故:发件行显示成 `jianf.<会话别名>` —— 别名拼给了发件人, * 而且 path 为空时拼出了 ParseAddress 会整串当成名字的非法形态。 */ import { openApp, WIDE } from './narrow-probe-helper.mjs'; const { browser, page } = await openApp(WIDE); let fail = 0; const check = (name, ok, detail = '') => { if (ok) console.log(` 通过 ${name}`); else { fail++; console.log(` 失败 ${name}${detail ? ' — ' + detail : ''}`); } }; // 走**发件**箱:报这个 bug 的那封信是人发出去的(jianf → pi,抄送 pi@….new), // 收件箱里只有 Agent 的回信 —— 而回信没有抄送、也不带 `.new`, // 探不到要验的那三处。 await page.waitForSelector('button', { timeout: 20000 }); await page.click('button:has-text("发件")'); await page.waitForTimeout(2000); // 收件箱/发件箱按会话分组:先点组头展开,再点里面的邮件行。 // 两者 className 都含 `w-full text-left`,靠「点完有没有出现含『发件』的 dl」区分。 let opened = false; for (let round = 0; round < 3 && !opened; round++) { const btns = await page.$$('button.w-full.text-left'); for (const b of btns) { const t = await b.innerText().catch(() => ''); if (!t) continue; await b.click().catch(() => {}); await page.waitForTimeout(350); const hasMeta = await page.evaluate(() => [...document.querySelectorAll('dl')].some(dl => dl.textContent.includes('发件'))); if (hasMeta) { opened = true; break; } } } check('打开了一封邮件', opened); await page.waitForTimeout(800); // 读元信息 dl const meta = await page.evaluate(() => { const dls = [...document.querySelectorAll('dl')]; for (const dl of dls) { const pairs = []; const kids = [...dl.children]; for (const div of kids) { const dt = div.querySelector('dt'), dd = div.querySelector('dd'); if (dt && dd) pairs.push([dt.textContent.trim(), dd.textContent.trim()]); } if (pairs.some(([k]) => k === '发件')) return Object.fromEntries(pairs); } return null; }); if (!meta) { check('找到元信息区', false, '没有含「发件」的 dl'); } else { console.log(' 元信息:', JSON.stringify(meta)); const from = meta['发件'] || ''; const to = meta['收件'] || ''; const cc = meta['抄送'] || ''; // 判「哪一方是人」:人的地址里既无 @ 也无 . const isHuman = a => a && !a.includes('@') && !a.includes('.'); const human = isHuman(from) ? from : (isHuman(to) ? to : ''); const agent = isHuman(from) ? to : from; check('有一方是人(裸名字,无 @ 无 .)', !!human, `发件=${from} 收件=${to}`); check('人的地址不含会话位', !human || !/[@.]/.test(human), `人=${human}`); // Agent 必须三段齐全:name@path.session const threeSeg = /^[^@]+@\/[^@]*\.[^.@]+$/.test(agent); check('Agent 带完整三段 name@path.session', threeSeg, `agent=${agent}`); // path 不能是 Agent 名(`dsh@dsh` 那个 bug) if (agent.includes('@')) { const [n, rest] = [agent.slice(0, agent.indexOf('@')), agent.slice(agent.indexOf('@') + 1)]; check('Agent 的 path 是真路径而不是 Agent 名', rest.startsWith('/'), `${n}@${rest}`); } // 别名不能挂在人身上 check('会话别名跟着 Agent 而不是人', !human || !agent || agent.includes('.'), `人=${human} agent=${agent}`); // 抄送里不能残留 .new check('抄送里没有 .new', !cc.split('、').some(a => a.trim().endsWith('.new')), `抄送=${cc}`); if (cc) console.log(` 抄送实际值: ${cc}`); // 不该再有独立的「会话」行(别名已在 Agent 地址里) check('没有多余的「会话」行', !('会话' in meta), `会话=${meta['会话']}`); } await browser.close(); console.log(fail ? `\n!! ${fail} 项不达标` : '\n全部达标'); process.exit(fail ? 1 : 0);