80 lines
3.9 KiB
TypeScript
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(() => {});
|
|
});
|
|
}
|