Files
2026-07-13 13:20:22 +08:00

80 lines
3.9 KiB
TypeScript

/**
* E2E: Team empty state greeting.
*
* Validates that a freshly-created team's leader chat shows the self-detecting
* TeamChatEmptyState — the same "Describe your goal…" hero that AcpChat and
* GeminiChat used to render individually. We now mount it once from
* TeamChatView, so this spec is the cross-platform smoke test for that wiring.
*
* Flow: create team via bridge → navigate to team page → assert greeting,
* suggestion chips, and that clicking a suggestion fills the chat input.
*/
import { test, expect } from '../fixtures';
import { createTeam, deleteTeam, navigateTo, cleanupTeamsByName, TEAM_SUPPORTED_BACKENDS } from '../helpers';
const SUGGESTION_KEYS = ['debate', 'interview', 'expert_review'] as const;
const SUGGESTION_CLICK_KEY = 'debate';
const EXPECTED_DEBATE_VALUE = /Organize a debate|组织.*辩论/;
for (const leaderType of TEAM_SUPPORTED_BACKENDS) {
const teamName = `E2E Empty State (${leaderType})`;
test(`team empty state: ${leaderType} leader renders greeting and suggestions`, async ({ page }) => {
test.setTimeout(120_000);
// [setup] Always start from a clean slate — a half-initialized team from a prior
// run can leave the leader conversation stuck on the loading spinner, which
// prevents the empty state from ever mounting. Remove any same-named team first.
await cleanupTeamsByName(page, teamName);
let teamId: string;
try {
teamId = await createTeam(page, teamName, leaderType);
} catch (error) {
test.skip(true, `Could not create an assistant-led team for ${leaderType}: ${(error as Error).message}`);
return;
}
expect(teamId).toBeTruthy();
await navigateTo(page, `#/team/${teamId}`);
await page.waitForURL(new RegExp(`/team/${teamId}`), { timeout: 10000 });
// Scope every assertion to the empty-state container. Without this, a leftover
// sidebar conversation with the same name ("组织一场辩论赛…") will match
// text-based locators first and the click will navigate away from the team.
const emptyState = page.locator('[data-testid="team-chat-empty-state"]').first();
await expect(emptyState).toBeVisible({ timeout: 15000 });
await expect(emptyState.locator('[data-testid="team-chat-empty-state-subtitle"]')).toHaveText(
/Describe your goal.*team working|描述你的目标/
);
await page.screenshot({ path: `tests/e2e/results/team-empty-${leaderType}-greeting.png` });
// All three suggestion chips should render in an empty conversation.
for (const key of SUGGESTION_KEYS) {
await expect(emptyState.locator(`[data-testid="team-chat-empty-state-suggestion-${key}"]`)).toBeVisible();
}
// Clicking a suggestion should populate the chat textarea. This exercises the
// per-DetectedAgentKind draft wiring introduced when the empty state started
// self-detecting the conversation type.
const suggestion = emptyState.locator(`[data-testid="team-chat-empty-state-suggestion-${SUGGESTION_CLICK_KEY}"]`);
await suggestion.scrollIntoViewIfNeeded();
await suggestion.click();
// The chat send-box textarea placeholder starts with "Send message to" / "发送消息到".
// We scope via this placeholder so the sidebar search textarea never matches first.
const chatInput = page.locator('textarea[placeholder^="Send message"], textarea[placeholder^="发送消息"]').first();
await expect(chatInput).toBeVisible({ timeout: 5000 });
await page.screenshot({ path: `tests/e2e/results/team-empty-${leaderType}-after-click.png` });
await expect(chatInput).toHaveValue(EXPECTED_DEBATE_VALUE, { timeout: 5000 });
await page.screenshot({ path: `tests/e2e/results/team-empty-${leaderType}-filled.png` });
// Cleanup: remove the team so the sidebar (and global conversation list) doesn't
// accumulate leftover items that can shadow locators in later runs.
await deleteTeam(page, teamId).catch(() => {});
});
}