Files
Agent-System/web/e2e/conversation-intent-dual-tab.spec.ts
T
weichenandCursor 8a8f64a931 docs(test): close intent dual-tab busy gate and release the mutex
Record project 6275 PI 409 DOC_BUSY plus Docker screenshots, mark CHAT-INTENT-STATE PASS, and archive the short-term so CURRENT has no in_progress rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 06:22:58 +00:00

100 lines
4.4 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const runDualTab = process.env.INTENT_DUAL_TAB === '1';
const ASSETS = process.env.INTENT_ASSETS
? path.resolve(process.env.INTENT_ASSETS)
: path.resolve(__dirname, '../../.doc/test-reports/assets/2026-09-15-conversation-intent-dual-tab');
const SCREENSHOTS = path.join(ASSETS, 'screenshots');
fs.mkdirSync(SCREENSHOTS, { recursive: true });
const OWNER_USER = process.env.OWNER_USERNAME || process.env.ADMIN_USERNAME || '';
const OWNER_PASS = process.env.OWNER_PASSWORD || process.env.ADMIN_PASSWORD || '';
const PROJECT_NAME = (process.env.INTENT_PROJECT_NAME || '').trim();
test.skip(!runDualTab, 'set INTENT_DUAL_TAB=1 against rebuilt dev; do not use project 143');
async function login(page: Page, username: string, password: string) {
await page.goto('/');
await page.fill('input[placeholder*="系统用户名"]', username);
await page.fill('input[placeholder*="登录密码"]', password);
await page.click('button:has-text("进入医疗协同工作台")');
await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 30_000 });
}
async function openProject(page: Page, name: string) {
const entry = page.getByTitle(name, { exact: true });
await expect(entry.first()).toBeVisible({ timeout: 30_000 });
await entry.first().click();
await expect(page.getByRole('banner').getByText(name, { exact: true })).toBeVisible({ timeout: 30_000 });
}
test.describe.serial('conversation intent dual-tab busy', () => {
test('tab B shows busy without reload and keeps empty actions', async ({ browser }) => {
if (!OWNER_USER || !OWNER_PASS) {
throw new Error('OWNER_USERNAME and OWNER_PASSWORD are required');
}
if (!PROJECT_NAME) {
throw new Error('INTENT_PROJECT_NAME is required');
}
if (/143/.test(PROJECT_NAME) || process.env.INTENT_PROJECT_ID === '143') {
throw new Error('project 143 is read-only');
}
const context = await browser.newContext();
const tabA = await context.newPage();
const posts: { status: number; body: string }[] = [];
const attachRunProbe = (page: Page) => {
page.on('response', async (res) => {
if (res.url().includes('/api/v1/tasks/runs') && res.request().method() === 'POST') {
posts.push({ status: res.status(), body: await res.text().catch(() => '') });
}
});
};
attachRunProbe(tabA);
await login(tabA, OWNER_USER, OWNER_PASS);
await openProject(tabA, PROJECT_NAME);
const tabB = await context.newPage();
attachRunProbe(tabB);
await tabB.goto('/');
await openProject(tabB, PROJECT_NAME);
const busyBanner = tabB.getByText(/本文档正由【(项目助手|其他用户)】编辑/)
const busyBadge = tabB.getByText('编辑中', { exact: true })
await expect(busyBanner.or(busyBadge).first()).toBeVisible({ timeout: 20_000 })
await tabA.screenshot({ path: path.join(SCREENSHOTS, 'tab-a-running.png'), fullPage: true })
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-busy.png'), fullPage: true })
await expect(tabB.getByRole('button', { name: /发送/ })).toBeDisabled()
await expect(tabB.getByText('DOC_BUSY')).toBeVisible()
const box = tabB.getByPlaceholder(/向项目助手提问/)
await expect(box).toBeVisible({ timeout: 15_000 })
await box.fill('请开始编写用户初始需求表')
const send = tabB.getByRole('button', { name: /发送/ })
if (await send.isEnabled()) {
await send.click()
}
await tabB.waitForTimeout(2000)
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-after-write.png'), fullPage: true })
await expect(tabB.getByRole('button', { name: /重试|重新开始/ })).toHaveCount(0)
fs.writeFileSync(
path.join(ASSETS, 'playwright-network.json'),
JSON.stringify({ posts, project: PROJECT_NAME }, null, 2),
)
await tabB.reload()
await expect(tabB.getByText(/本文档正由【(项目助手|其他用户)】编辑/).or(tabB.getByText('DOC_BUSY')).first()).toBeVisible({ timeout: 20_000 })
await expect(tabB.getByRole('button', { name: /重试|重新开始/ })).toHaveCount(0)
await tabB.screenshot({ path: path.join(SCREENSHOTS, 'tab-b-after-reload.png'), fullPage: true })
await context.close();
});
});