geo/frontend/e2e/tests/onboarding.spec.ts

372 lines
11 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";
import { Page } from "@playwright/test";
class OnboardingPage {
page: Page;
// Step 0
step0BrandInput: ReturnType<Page["locator"]>;
step0CheckButton: ReturnType<Page["getByRole"]>;
// Step 1
step1BrandInput: ReturnType<Page["locator"]>;
step1StartButton: ReturnType<Page["getByRole"]>;
step1SkipButton: ReturnType<Page["getByRole"]>;
// Step 2
nextButton: ReturnType<Page["getByRole"]>;
backButton: ReturnType<Page["getByRole"]>;
skipStepButton: ReturnType<Page["getByRole"]>;
// Step 3
selectAllButton: ReturnType<Page["getByRole"]>;
clearAllButton: ReturnType<Page["getByRole"]>;
// Progress
progressSteps: ReturnType<Page["locator"]>;
constructor(page: Page) {
this.page = page;
// Step 0: 健康分检测 — input 没有 id用 placeholder 定位
this.step0BrandInput = this.page.locator(
'input[placeholder="输入品牌名称,例如:华为"]',
);
this.step0CheckButton = this.page.getByRole("button", {
name: "检测",
exact: true,
});
// Step 1: 品牌名称 — input id="brandName"
this.step1BrandInput = this.page.locator("#brandName");
this.step1StartButton = this.page.getByRole("button", {
name: /开始分析/,
});
this.step1SkipButton = this.page.getByRole("button", {
name: /跳过.*Dashboard/,
});
// Step 2 & 3 通用按钮
this.nextButton = this.page.getByRole("button", { name: /继续/ });
this.backButton = this.page.getByRole("button", { name: /上一步/ });
this.skipStepButton = this.page.getByRole("button", {
name: /跳过此步骤/,
});
// Step 3
this.selectAllButton = this.page.getByRole("button", { name: "全选" });
this.clearAllButton = this.page.getByRole("button", { name: "清空" });
// 进度指示器
this.progressSteps = this.page.locator(".flex.flex-1.items-center");
}
async goto() {
await this.page.goto("/onboarding");
await this.page.waitForLoadState("networkidle");
}
async goToOnboarding() {
await this.page.goto("/onboarding");
await this.page.waitForLoadState("networkidle");
// 等待 Step 0 页面完全渲染
await expect(
this.page.getByRole("heading", { name: /免费检测您的GEO健康分/ }),
).toBeVisible({ timeout: 15000 });
}
/**
* 从 Step 0 走到 Step 1。
* Step 0 的 "检测" 按钮调用后端 API可能失败。
* 如果 API 成功,会出现 "注册查看完整报告" 按钮,点击后进入 Step 1。
* 如果 API 失败,返回 false 表示无法继续。
*/
async tryReachStep1(brandName: string): Promise<"reached" | "api-failed"> {
// 确保在 Step 0
await expect(
this.page.getByRole("heading", { name: /免费检测您的GEO健康分/ }),
).toBeVisible({ timeout: 10000 });
await this.step0BrandInput.fill(brandName);
await this.step0CheckButton.click();
// 等待结果或错误
const resultCard = this.page.getByText("注册查看完整报告");
const errorAlert = this.page.locator(
".bg-destructive\\/10.text-destructive",
);
const sawResult = await resultCard
.isVisible({ timeout: 15000 })
.catch(() => false);
if (sawResult) {
await resultCard.click();
// 现在应该到 Step 1
await expect(
this.page.getByRole("heading", { name: /输入您的品牌名称/ }),
).toBeVisible({ timeout: 10000 });
return "reached";
}
const sawError = await errorAlert
.isVisible({ timeout: 3000 })
.catch(() => false);
if (sawError) {
return "api-failed";
}
// 超时,可能 API 一直没返回
return "api-failed";
}
/**
* 从 Step 1 走到 Step 2确认竞品
*/
async reachStep2(brandName: string) {
await this.step1BrandInput.fill(brandName);
await this.step1StartButton.click();
await expect(
this.page.getByRole("heading", { name: /确认竞品/ }),
).toBeVisible({ timeout: 10000 });
}
/**
* 从 Step 2 走到 Step 3选择监控平台
*/
async reachStep3() {
await this.skipStepButton.click();
await expect(
this.page.getByRole("heading", { name: /选择监控平台/ }),
).toBeVisible({ timeout: 10000 });
}
}
test.describe("新用户引导向导 - Step 0 & Step 1 测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.goToOnboarding();
});
test("Step 0: 应该显示健康分检测页面", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await expect(
authenticatedPage.getByRole("heading", { name: /免费检测您的GEO健康分/ }),
).toBeVisible();
await expect(onboardingPage.step0BrandInput).toBeVisible();
await expect(onboardingPage.step0CheckButton).toBeVisible();
});
test("Step 0: 品牌名称验证 - 太短应显示错误", async ({
authenticatedPage,
}) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.step0BrandInput.fill("a");
await onboardingPage.step0CheckButton.click();
await expect(
authenticatedPage.getByText(/请输入至少2个字符/),
).toBeVisible();
});
test("Step 1: 输入品牌名称后进入下一步", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
// 已经在 Step 1
await onboardingPage.reachStep2("华为");
await expect(
authenticatedPage.getByRole("heading", { name: /确认竞品/ }),
).toBeVisible();
});
test("Step 1: 跳过直接进入Dashboard", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
// 在 Step 1 点击跳过
await onboardingPage.step1SkipButton.click();
await expect(authenticatedPage).toHaveURL(/\/dashboard/, {
timeout: 15000,
});
});
});
test.describe("新用户引导向导 - Step 2 & Step 3 测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.goToOnboarding();
});
test("Step 2: 可以选择竞品并继续", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
await onboardingPage.reachStep2("华为");
// 等待竞品推荐加载完成
await authenticatedPage.waitForTimeout(2000);
// 尝试选择第一个竞品卡片
const firstCompetitor = authenticatedPage
.locator(".border.rounded-lg")
.filter({ has: authenticatedPage.locator(".flex.h-5.w-5") })
.first();
if (await firstCompetitor.isVisible().catch(() => false)) {
await firstCompetitor.click();
}
await onboardingPage.nextButton.click();
await expect(
authenticatedPage.getByRole("heading", { name: /选择监控平台/ }),
).toBeVisible({ timeout: 10000 });
});
test("Step 2: 跳过应进入下一步", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
await onboardingPage.reachStep2("华为");
await onboardingPage.skipStepButton.click();
await expect(
authenticatedPage.getByRole("heading", { name: /选择监控平台/ }),
).toBeVisible({ timeout: 10000 });
});
test("Step 3: 显示已选择平台数量", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
await onboardingPage.reachStep2("华为");
await onboardingPage.reachStep3();
await expect(authenticatedPage.getByText(/\d+\/\d+ 个平台/)).toBeVisible();
});
test("Step 3: 可以选择不同频率", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
await onboardingPage.reachStep2("华为");
await onboardingPage.reachStep3();
// 频率选项是按钮,在"查询频率"卡片中
const dailyOption = authenticatedPage
.getByRole("button", { name: /每日/ })
.first();
await dailyOption.click();
// 选中后应有 border-primary 样式
await expect(
authenticatedPage
.locator("button.border-primary")
.filter({ hasText: "每日" }),
).toBeVisible();
});
test("Step 3: 返回上一步", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const step0Result = await onboardingPage.tryReachStep1("华为");
if (step0Result === "api-failed") {
test.skip();
return;
}
await onboardingPage.reachStep2("华为");
await onboardingPage.reachStep3();
await onboardingPage.backButton.click();
await expect(
authenticatedPage.getByRole("heading", { name: /确认竞品/ }),
).toBeVisible({
timeout: 10000,
});
});
});
test.describe("新用户引导向导 - 进度指示器测试", () => {
test.beforeEach(async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.goToOnboarding();
});
test("进度指示器显示6个步骤", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
await expect(onboardingPage.progressSteps).toHaveCount(6);
});
test("初始应显示Step 0为当前步骤", async ({ authenticatedPage }) => {
const onboardingPage = new OnboardingPage(authenticatedPage);
const firstStepIndicator = authenticatedPage
.locator(".flex.flex-col.items-center")
.first();
await expect(
firstStepIndicator.locator(".border-primary.bg-primary\\/10"),
).toBeVisible();
});
});
test.describe("新用户引导向导 - 响应式设计测试", () => {
test("移动端视图应该正常显示", async ({ authenticatedPage }) => {
await authenticatedPage.setViewportSize({ width: 375, height: 667 });
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.goToOnboarding();
await expect(
authenticatedPage.getByRole("heading", { name: /免费检测您的GEO健康分/ }),
).toBeVisible();
await expect(onboardingPage.step0CheckButton).toBeVisible();
});
test("平板视图应该正常显示", async ({ authenticatedPage }) => {
await authenticatedPage.setViewportSize({ width: 768, height: 1024 });
const onboardingPage = new OnboardingPage(authenticatedPage);
await onboardingPage.goToOnboarding();
await expect(
authenticatedPage.getByRole("heading", { name: /免费检测您的GEO健康分/ }),
).toBeVisible();
});
});