363 lines
12 KiB
TypeScript
363 lines
12 KiB
TypeScript
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);
|
||
});
|
||
});
|