fix(e2e): strict squad terminal and doc-free upload selectors

Only completed/pending_author_confirmation pass waitForSquadCompletion;
category trigger/data-category for private open; UI-11/16 pin exact
doc-free id without global attachment text clicks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 14:05:46 +00:00
co-authored by Cursor
parent 301e1f401e
commit c33a6d8406
2 changed files with 49 additions and 13 deletions
+47 -13
View File
@@ -269,12 +269,31 @@ async function waitForSquadCompletion(
);
const terminal = (await assistant.getAttribute('data-chat-status')) || '';
if (terminal === 'failed') {
fail('squad terminal status is failed');
if (
terminal === 'failed' ||
terminal === 'cancelled' ||
terminal === 'quality_failed'
) {
fail(`squad terminal status is ${terminal}`);
}
if (terminal !== 'completed' && terminal !== 'pending_author_confirmation') {
fail(`squad terminal status unexpected: ${terminal}`);
}
return Date.now() - startedAt;
}
async function selectDeliverableCategory(
page: Page,
category: 'workflow' | 'shared' | 'private',
) {
const trigger = page.getByTestId('doc-category-trigger');
await expect(trigger).toBeVisible({ timeout: 15_000 });
await trigger.click();
const item = page.locator(`.dropdown-menu button[data-category="${category}"]`).first();
if ((await item.count()) === 0) fail(`category menu missing data-category=${category}`);
await item.click();
}
async function openWorkflowDocByLabel(
page: Page,
label: RegExp,
@@ -408,6 +427,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
let versionBeforePending = '';
let secondDocTitle = '';
let secondDocId = '';
let uploadedDocId = '';
test.beforeAll(async ({ browser }) => {
context = await browser.newContext();
@@ -850,21 +870,27 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
const { v, r } = await readVersionBadge(page);
states.push(`${target.title}:${v}/${r}`);
}
// Page upload attachment
// Page upload attachment — assert registered doc-free id on pane (ignore any chat assistant side effects).
const fileInput = page.locator('input[type="file"]');
if ((await fileInput.count()) === 0) fail('no file input for page upload');
const tmp = path.join(ASSETS, `upload-${suffix}.txt`);
fs.writeFileSync(tmp, `attachment ${suffix}\n`, 'utf-8');
await fileInput.first().setInputFiles(tmp);
await expect(page.getByText(`upload-${suffix}`).or(page.getByText('attachment'))).toBeVisible({
timeout: 30_000,
});
const pane = page.getByTestId('active-document-pane');
await expect(pane).toHaveAttribute('data-doc-id', /^doc-free-/, { timeout: 45_000 });
uploadedDocId = (await pane.getAttribute('data-doc-id')) || '';
if (!uploadedDocId.startsWith('doc-free-')) {
fail(`UI-11 upload must activate doc-free-* pane, got ${uploadedDocId}`);
}
if (uploadedDocId.startsWith('doc-upload')) {
fail(`UI-11 must not leave doc-upload orphan active: ${uploadedDocId}`);
}
await shot(page, 'UI-11-multi-doc.png');
recordPass({
id: 'UI-11',
name: 'multi doc + upload',
input: 'chat second doc; upload file',
actual: states.join('; '),
actual: `${states.join('; ')}; uploadedDocId=${uploadedDocId}`,
networkLogs: logs,
durationMs: Date.now() - t0,
verdict: 'PASS',
@@ -1006,11 +1032,19 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
test('UI-16 registered upload uses server doc-free ledger (no doc-upload orphan)', async () => {
const t0 = Date.now();
logs = [];
const uploadTab = page.getByText(`upload-${suffix}`).or(page.getByText(/attachment/));
if ((await uploadTab.count()) === 0) fail('upload tab missing (UI-11 must have uploaded via page)');
await uploadTab.first().click();
if (!uploadedDocId || !uploadedDocId.startsWith('doc-free-')) {
fail(`UI-16 requires uploadedDocId from UI-11 (doc-free-*), got ${uploadedDocId || '(empty)'}`);
}
await selectDeliverableCategory(page, 'private');
await openWorkflowDocById(page, uploadedDocId);
const pane = page.getByTestId('active-document-pane');
await expect(pane).toHaveAttribute('data-doc-id', uploadedDocId, { timeout: 15_000 });
await expect(pane).toHaveAttribute('data-doc-id', /^doc-free-/);
const paneId = (await pane.getAttribute('data-doc-id')) || '';
if (paneId.startsWith('doc-upload')) {
fail(`UI-16 must not open doc-upload orphan: ${paneId}`);
}
await expect(page.locator('text=版本账本未就绪')).toHaveCount(0);
// Registered upload must mount real toolbar (doc-free), not orphan hint.
await expect(page.locator('[data-testid="doc-rev-version"]')).toBeVisible({ timeout: 30_000 });
const orphanHits = logs.filter(
(l) => l.path.includes('/revision-state') && /\/documents\/doc-upload-/.test(l.path),
@@ -1022,8 +1056,8 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
recordPass({
id: 'UI-16',
name: 'registered upload server ledger',
input: 'open page-uploaded attachment after createFreeform registration',
actual: 'toolbar V visible; no doc-upload revision-state',
input: `category=private; open exact ${uploadedDocId}`,
actual: `pane=${paneId}; toolbar V visible; no doc-upload revision-state`,
networkLogs: logs,
durationMs: Date.now() - t0,
verdict: 'PASS',
+2
View File
@@ -177,6 +177,7 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
<div style={{ position: 'relative', flexShrink: 0 }}>
<button
className="dropdown-trigger"
data-testid="doc-category-trigger"
onClick={() => {
setShowCategoryMenu(!showCategoryMenu);
setShowDocMenu(false);
@@ -239,6 +240,7 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
return (
<button
key={cat}
data-category={cat}
onClick={() => {
handleCategoryClick(cat);
setShowCategoryMenu(false);