geo/frontend/e2e/tests/competitor-interaction.spec.ts

363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from "../fixtures";
test.describe("竞品分析 - 添加竞品交互测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
await authenticatedPage.goto("/dashboard/competitors");
await authenticatedPage.waitForLoadState("networkidle");
});
test("点击添加竞品按钮打开对话框", async ({ authenticatedPage }) => {
const addBtn = authenticatedPage.getByRole("button", { name: /添加竞品/ });
await expect(addBtn).toBeVisible({ timeout: 15000 });
await addBtn.click();
const dialog = authenticatedPage.getByRole("dialog");
await expect(dialog).toBeVisible({ timeout: 10000 });
// 验证对话框标题
await expect(dialog.getByRole("heading", { name: "添加竞品" })).toBeVisible();
});
test("添加竞品对话框切换到手动输入Tab", async ({ authenticatedPage }) => {
const addBtn = authenticatedPage.getByRole("button", { name: /添加竞品/ });
await expect(addBtn).toBeVisible({ timeout: 15000 });
await addBtn.click();
const dialog = authenticatedPage.getByRole("dialog");
await expect(dialog).toBeVisible({ timeout: 10000 });
// 切换到手动输入Tab
const manualTab = dialog.getByRole("tab", { name: "手动输入" });
await expect(manualTab).toBeVisible();
await manualTab.click();
// 验证手动输入表单
const nameInput = dialog.locator("#competitor-name");
const hasInput = await nameInput.isVisible({ timeout: 5000 }).catch(() => false);
if (!hasInput) {
test.skip();
return;
}
await expect(nameInput).toBeVisible();
await expect(nameInput).toHaveAttribute("placeholder", "输入竞品名称");
});
test("手动输入竞品名称并添加", async ({ authenticatedPage }) => {
const addBtn = authenticatedPage.getByRole("button", { name: /添加竞品/ });
await expect(addBtn).toBeVisible({ timeout: 15000 });
// 检查是否已达上限5个竞品
const isDisabled = await addBtn.isDisabled();
if (isDisabled) {
test.skip();
return;
}
await addBtn.click();
const dialog = authenticatedPage.getByRole("dialog");
await expect(dialog).toBeVisible({ timeout: 10000 });
// 切换到手动输入Tab
const manualTab = dialog.getByRole("tab", { name: "手动输入" });
await manualTab.click();
// 输入竞品名称
const nameInput = dialog.locator("#competitor-name");
const hasInput = await nameInput.isVisible({ timeout: 5000 }).catch(() => false);
if (!hasInput) {
test.skip();
return;
}
await nameInput.fill("测试竞品E2E");
// 点击添加按钮
const submitBtn = dialog.getByRole("button", { name: "添加" });
await expect(submitBtn).toBeEnabled();
await submitBtn.click();
// 验证对话框关闭或竞品列表刷新
await authenticatedPage.waitForTimeout(2000);
});
test("从推荐选择Tab查看推荐竞品", async ({ authenticatedPage }) => {
const addBtn = authenticatedPage.getByRole("button", { name: /添加竞品/ });
await expect(addBtn).toBeVisible({ timeout: 15000 });
const isDisabled = await addBtn.isDisabled();
if (isDisabled) {
test.skip();
return;
}
await addBtn.click();
const dialog = authenticatedPage.getByRole("dialog");
await expect(dialog).toBeVisible({ timeout: 10000 });
// 默认在推荐Tab
const recommendTab = dialog.getByRole("tab", { name: "从推荐选择" });
await expect(recommendTab).toBeVisible();
// 验证推荐内容加载(可能是推荐列表或空状态)
const loadingSpinner = dialog.locator(".animate-spin");
const emptyState = dialog.getByText("暂无推荐竞品");
const recommendItem = dialog.locator("div.grid.gap-2 > div.flex.items-center");
const hasLoading = await loadingSpinner.isVisible({ timeout: 3000 }).catch(() => false);
const hasEmpty = await emptyState.isVisible({ timeout: 5000 }).catch(() => false);
const hasItems = await recommendItem.first().isVisible({ timeout: 5000 }).catch(() => false);
expect(hasLoading || hasEmpty || hasItems).toBeTruthy();
});
});
test.describe("竞品分析 - 竞品对比数据交互测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
await authenticatedPage.goto("/dashboard/competitors");
await authenticatedPage.waitForLoadState("networkidle");
});
test("竞品列表显示已添加的竞品", async ({ authenticatedPage }) => {
// 等待竞品列表加载
await authenticatedPage.waitForTimeout(2000);
const emptyState = authenticatedPage.getByText("暂无竞品");
const hasEmpty = await emptyState.isVisible({ timeout: 10000 }).catch(() => false);
if (hasEmpty) {
test.skip();
return;
}
// 验证竞品卡片存在
const competitorCards = authenticatedPage.locator("div.grid.gap-3 > div.flex.items-center.justify-between");
const cardCount = await competitorCards.count();
if (cardCount === 0) {
test.skip();
return;
}
expect(cardCount).toBeGreaterThan(0);
});
test("竞品列表显示数量Badge", async ({ authenticatedPage }) => {
const countBadge = authenticatedPage.locator("span.text-xs").filter({ hasText: /^\d\/5$/ });
const hasBadge = await countBadge.isVisible({ timeout: 10000 }).catch(() => false);
if (!hasBadge) {
test.skip();
return;
}
const badgeText = await countBadge.textContent();
expect(badgeText).toMatch(/^\d\/5$/);
});
test("选择竞品和分析类型后可执行分析", async ({ authenticatedPage }) => {
await authenticatedPage.waitForTimeout(2000);
// 验证竞品列表非空
const emptyState = authenticatedPage.getByText("暂无竞品");
const hasEmpty = await emptyState.isVisible({ timeout: 5000 }).catch(() => false);
if (hasEmpty) {
test.skip();
return;
}
// 查找"选择竞品"下拉框
const competitorSelectLabel = authenticatedPage.getByText("选择竞品");
const hasSelectLabel = await competitorSelectLabel.isVisible({ timeout: 10000 }).catch(() => false);
if (!hasSelectLabel) {
test.skip();
return;
}
// 点击选择竞品下拉框
const competitorSelectTrigger = competitorSelectLabel.locator("..").locator("button");
const hasTrigger = await competitorSelectTrigger.isVisible({ timeout: 3000 }).catch(() => false);
if (!hasTrigger) {
test.skip();
return;
}
await competitorSelectTrigger.click();
// 选择第一个竞品选项
const selectContent = authenticatedPage.locator("[data-radix-popper-content-wrapper], [role='listbox']").first();
const hasOptions = await selectContent.isVisible({ timeout: 5000 }).catch(() => false);
if (!hasOptions) {
test.skip();
return;
}
const firstOption = selectContent.locator("[role='option'], [data-radix-collection-item]").first();
const hasFirstOption = await firstOption.isVisible({ timeout: 3000 }).catch(() => false);
if (hasFirstOption) {
await firstOption.click();
}
// 选择分析类型
const analysisTypeLabel = authenticatedPage.getByText("分析类型");
const hasTypeLabel = await analysisTypeLabel.isVisible({ timeout: 5000 }).catch(() => false);
if (!hasTypeLabel) {
test.skip();
return;
}
const analysisTypeTrigger = analysisTypeLabel.locator("..").locator("button");
const hasTypeTrigger = await analysisTypeTrigger.isVisible({ timeout: 3000 }).catch(() => false);
if (!hasTypeTrigger) {
test.skip();
return;
}
await analysisTypeTrigger.click();
const typeSelectContent = authenticatedPage.locator("[data-radix-popper-content-wrapper], [role='listbox']").first();
const hasTypeOptions = await typeSelectContent.isVisible({ timeout: 5000 }).catch(() => false);
if (hasTypeOptions) {
const firstTypeOption = typeSelectContent.locator("[role='option'], [data-radix-collection-item]").first();
const hasFirstType = await firstTypeOption.isVisible({ timeout: 3000 }).catch(() => false);
if (hasFirstType) {
await firstTypeOption.click();
}
}
// 验证"开始分析"按钮
const analyzeBtn = authenticatedPage.getByRole("button", { name: /开始分析/ });
const hasAnalyzeBtn = await analyzeBtn.isVisible({ timeout: 3000 }).catch(() => false);
if (!hasAnalyzeBtn) {
test.skip();
return;
}
// 按钮应该可点击
await expect(analyzeBtn).toBeEnabled();
});
test("竞品雷达图或空状态显示", async ({ authenticatedPage }) => {
await authenticatedPage.waitForTimeout(2000);
const radarChart = authenticatedPage.locator(".recharts-radar");
const emptyRadar = authenticatedPage.getByText("暂无对比数据");
const hasChart = await radarChart.isVisible({ timeout: 10000 }).catch(() => false);
const hasEmpty = await emptyRadar.isVisible({ timeout: 5000 }).catch(() => false);
if (!hasChart && !hasEmpty) {
test.skip();
return;
}
expect(hasChart || hasEmpty).toBeTruthy();
});
test("差距评分区域显示", async ({ authenticatedPage }) => {
await authenticatedPage.waitForTimeout(2000);
const gapTitle = authenticatedPage.getByText("差距评分");
const hasTitle = await gapTitle.isVisible({ timeout: 10000 }).catch(() => false);
if (!hasTitle) {
test.skip();
return;
}
const emptyGap = authenticatedPage.getByText("暂无差距评分");
const gapItem = authenticatedPage.locator("div.rounded-lg.border.p-4.space-y-2");
const hasEmpty = await emptyGap.isVisible({ timeout: 5000 }).catch(() => false);
const hasItems = await gapItem.first().isVisible({ timeout: 5000 }).catch(() => false);
expect(hasEmpty || hasItems).toBeTruthy();
});
});
test.describe("竞品分析 - 删除竞品交互测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
await authenticatedPage.goto("/dashboard/competitors");
await authenticatedPage.waitForLoadState("networkidle");
});
test("竞品卡片显示删除按钮", async ({ authenticatedPage }) => {
await authenticatedPage.waitForTimeout(2000);
const emptyState = authenticatedPage.getByText("暂无竞品");
const hasEmpty = await emptyState.isVisible({ timeout: 5000 }).catch(() => false);
if (hasEmpty) {
test.skip();
return;
}
// 查找竞品卡片中的删除按钮Trash2 图标按钮)
const deleteButtons = authenticatedPage.locator("button").filter({ has: authenticatedPage.locator("svg.lucide-trash-2") });
const count = await deleteButtons.count();
if (count === 0) {
test.skip();
return;
}
expect(count).toBeGreaterThan(0);
});
test("点击删除按钮移除竞品", async ({ authenticatedPage }) => {
await authenticatedPage.waitForTimeout(2000);
const emptyState = authenticatedPage.getByText("暂无竞品");
const hasEmpty = await emptyState.isVisible({ timeout: 5000 }).catch(() => false);
if (hasEmpty) {
test.skip();
return;
}
// 查找竞品卡片
const competitorCards = authenticatedPage.locator("div.grid.gap-3 > div.flex.items-center.justify-between");
const cardCount = await competitorCards.count();
if (cardCount === 0) {
test.skip();
return;
}
// 记录删除前的竞品数量
const countBefore = cardCount;
// 点击第一个竞品的删除按钮
const firstDeleteBtn = competitorCards.first().locator("button").filter({ has: authenticatedPage.locator("svg.lucide-trash-2") });
const hasDeleteBtn = await firstDeleteBtn.isVisible({ timeout: 3000 }).catch(() => false);
if (!hasDeleteBtn) {
test.skip();
return;
}
await firstDeleteBtn.click();
// 等待竞品列表刷新
await authenticatedPage.waitForTimeout(2000);
// 验证竞品数量减少或列表刷新
const newCardCount = await competitorCards.count();
// 删除后数量应减少(或列表为空时显示空状态)
expect(newCardCount).toBeLessThanOrEqual(countBefore);
});
});