340 lines
12 KiB
TypeScript
340 lines
12 KiB
TypeScript
import { test, expect, mockApi, mockApiError, clearApiMocks } from "../fixtures";
|
||
|
||
test.describe("错误状态 - API 500 错误测试", () => {
|
||
test.afterEach(async ({ authenticatedPage }) => {
|
||
await clearApiMocks(authenticatedPage);
|
||
});
|
||
|
||
test("健康评分页面API 500错误显示错误状态和重试按钮", async ({ authenticatedPage }) => {
|
||
// 拦截健康评分API,返回500错误
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/brands\//, 500);
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/scoring\//, 500);
|
||
|
||
await authenticatedPage.goto("/dashboard/health-score");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证错误状态显示
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasError) {
|
||
// 可能显示其他错误提示
|
||
const errorIcon = authenticatedPage.locator("svg.lucide-alert-circle");
|
||
const hasErrorIcon = await errorIcon.isVisible({ timeout: 5000 }).catch(() => false);
|
||
if (!hasErrorIcon) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 验证错误消息可见
|
||
await expect(errorTitle).toBeVisible();
|
||
|
||
// 验证重试按钮存在
|
||
const retryBtn = authenticatedPage.getByRole("button", { name: /重试/ });
|
||
const hasRetry = await retryBtn.isVisible({ timeout: 5000 }).catch(() => false);
|
||
expect(hasRetry).toBeTruthy();
|
||
});
|
||
|
||
test("竞品分析页面API 500错误显示错误状态", async ({ authenticatedPage }) => {
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/brands\//, 500);
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/competitors\//, 500);
|
||
|
||
await authenticatedPage.goto("/dashboard/competitors");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证错误状态
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasError) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
await expect(errorTitle).toBeVisible();
|
||
|
||
// 验证重试按钮
|
||
const retryBtn = authenticatedPage.getByRole("button", { name: /重试/ });
|
||
const hasRetry = await retryBtn.isVisible({ timeout: 5000 }).catch(() => false);
|
||
expect(hasRetry).toBeTruthy();
|
||
});
|
||
|
||
test("引用记录页面API 500错误显示错误提示", async ({ authenticatedPage }) => {
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/citations\//, 500);
|
||
|
||
await authenticatedPage.goto("/dashboard/citations");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 引用页面可能在统计区域或列表区域显示错误
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const errorMessage = authenticatedPage.getByText(/Internal Server Error|加载失败/);
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
const hasErrorMessage = await errorMessage.isVisible({ timeout: 5000 }).catch(() => false);
|
||
|
||
if (!hasError && !hasErrorMessage) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
expect(hasError || hasErrorMessage).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
test.describe("错误状态 - 网络离线模拟测试", () => {
|
||
test.afterEach(async ({ authenticatedPage }) => {
|
||
await clearApiMocks(authenticatedPage);
|
||
});
|
||
|
||
test("所有API请求失败时显示错误状态", async ({ authenticatedPage }) => {
|
||
// 拦截所有API请求,模拟网络离线
|
||
await authenticatedPage.route(/\/api\/v1\//, async (route) => {
|
||
await route.abort("failed");
|
||
});
|
||
|
||
await authenticatedPage.goto("/dashboard/health-score");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证错误状态或加载失败提示
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const loadingSpinner = authenticatedPage.locator(".animate-pulse, .animate-spin");
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
const isLoading = await loadingSpinner.isVisible({ timeout: 5000 }).catch(() => false);
|
||
|
||
// 网络错误时应该显示错误状态或仍在加载
|
||
expect(hasError || isLoading).toBeTruthy();
|
||
});
|
||
|
||
test("Dashboard页面网络错误时显示错误状态", async ({ authenticatedPage }) => {
|
||
await authenticatedPage.route(/\/api\/v1\//, async (route) => {
|
||
await route.abort("failed");
|
||
});
|
||
|
||
await authenticatedPage.goto("/dashboard");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasError) {
|
||
// Dashboard可能有骨架屏持续加载
|
||
const loadingState = authenticatedPage.locator(".animate-pulse");
|
||
const isLoading = await loadingState.isVisible({ timeout: 5000 }).catch(() => false);
|
||
if (!isLoading) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
}
|
||
|
||
expect(hasError).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
test.describe("错误状态 - 空数据测试", () => {
|
||
test.afterEach(async ({ authenticatedPage }) => {
|
||
await clearApiMocks(authenticatedPage);
|
||
});
|
||
|
||
test("引用记录API返回空数组时显示空状态", async ({ authenticatedPage }) => {
|
||
// 模拟引用记录API返回空数据
|
||
await mockApi(authenticatedPage, /\/api\/v1\/citations\/\?/, { items: [] });
|
||
await mockApi(authenticatedPage, /\/api\/v1\/citations\/stats/, {
|
||
citation_rate: null,
|
||
avg_position: null,
|
||
total_citations: 0,
|
||
platform_distribution: [],
|
||
trend: [],
|
||
});
|
||
await mockApi(authenticatedPage, /\/api\/v1\/queries\//, { items: [] });
|
||
|
||
await authenticatedPage.goto("/dashboard/citations");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证空状态显示
|
||
const emptyState = authenticatedPage.getByText("暂无引用记录");
|
||
const hasEmpty = await emptyState.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasEmpty) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
await expect(emptyState).toBeVisible();
|
||
|
||
// 验证引导文案
|
||
const guidanceText = authenticatedPage.getByText("添加查询词并执行查询后将在此显示结果");
|
||
const hasGuidance = await guidanceText.isVisible({ timeout: 5000 }).catch(() => false);
|
||
expect(hasGuidance).toBeTruthy();
|
||
});
|
||
|
||
test("竞品分析API返回空数组时显示空状态", async ({ authenticatedPage }) => {
|
||
// 模拟竞品API返回空数据
|
||
await mockApi(authenticatedPage, /\/api\/v1\/brands\//, {
|
||
items: [{ id: "test-brand-id", name: "测试品牌" }],
|
||
});
|
||
await mockApi(authenticatedPage, /\/api\/v1\/brands\/[^/]+\/competitors\//, {
|
||
items: [],
|
||
total: 0,
|
||
});
|
||
await mockApi(authenticatedPage, /\/api\/v1\/brands\/[^/]+\/competitors\/recommendations\//, []);
|
||
await mockApi(authenticatedPage, /\/api\/v1\/competitor\//, []);
|
||
|
||
await authenticatedPage.goto("/dashboard/competitors");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证空状态
|
||
const emptyState = authenticatedPage.getByText("暂无竞品");
|
||
const hasEmpty = await emptyState.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasEmpty) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
await expect(emptyState).toBeVisible();
|
||
|
||
// 验证引导文案
|
||
const guidanceText = authenticatedPage.getByText(/点击上方.*添加竞品.*按钮开始/);
|
||
const hasGuidance = await guidanceText.isVisible({ timeout: 5000 }).catch(() => false);
|
||
expect(hasGuidance).toBeTruthy();
|
||
});
|
||
|
||
test("知识库API返回空数组时显示空状态", async ({ authenticatedPage }) => {
|
||
// 模拟知识库API返回空数据
|
||
await mockApi(authenticatedPage, /\/api\/v1\/knowledge\/bases\//, []);
|
||
|
||
await authenticatedPage.goto("/dashboard/knowledge");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证空状态
|
||
const emptyState = authenticatedPage.getByText("还没有知识库");
|
||
const hasEmpty = await emptyState.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasEmpty) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
await expect(emptyState).toBeVisible();
|
||
|
||
// 验证引导文案
|
||
const guidanceText = authenticatedPage.getByText("创建您的第一个知识库");
|
||
const hasGuidance = await guidanceText.isVisible({ timeout: 5000 }).catch(() => false);
|
||
expect(hasGuidance).toBeTruthy();
|
||
});
|
||
|
||
test("健康评分API返回空数据时显示空状态", async ({ authenticatedPage }) => {
|
||
// 模拟品牌API返回空数据
|
||
await mockApi(authenticatedPage, /\/api\/v1\/brands\//, { items: [] });
|
||
|
||
await authenticatedPage.goto("/dashboard/health-score");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 验证空状态或加载状态
|
||
const emptyState = authenticatedPage.getByText("暂无健康评分数据");
|
||
const loadingState = authenticatedPage.locator(".animate-pulse");
|
||
const hasEmpty = await emptyState.isVisible({ timeout: 15000 }).catch(() => false);
|
||
const isLoading = await loadingState.isVisible({ timeout: 5000 }).catch(() => false);
|
||
|
||
if (!hasEmpty && !isLoading) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
expect(hasEmpty || isLoading).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
test.describe("错误状态 - 认证过期(401)测试", () => {
|
||
test.afterEach(async ({ authenticatedPage }) => {
|
||
await clearApiMocks(authenticatedPage);
|
||
});
|
||
|
||
test("API返回401时重定向到登录页面", async ({ authenticatedPage }) => {
|
||
// 拦截所有API请求,返回401
|
||
await mockApiError(authenticatedPage, /\/api\/v1\//, 401);
|
||
|
||
await authenticatedPage.goto("/dashboard/health-score");
|
||
|
||
// 等待可能的重定向
|
||
await authenticatedPage.waitForTimeout(5000);
|
||
|
||
// 验证是否重定向到登录页面
|
||
const currentUrl = authenticatedPage.url();
|
||
const isLoginPage = currentUrl.includes("/login") || currentUrl.includes("/auth");
|
||
const hasLoginError = await authenticatedPage.getByText(/登录|认证|权限/).isVisible({ timeout: 5000 }).catch(() => false);
|
||
|
||
// 401应该触发重定向或显示认证错误
|
||
expect(isLoginPage || hasLoginError).toBeTruthy();
|
||
});
|
||
|
||
test("Dashboard页面401错误重定向到登录", async ({ authenticatedPage }) => {
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/dashboard/, 401);
|
||
|
||
await authenticatedPage.goto("/dashboard");
|
||
|
||
await authenticatedPage.waitForTimeout(5000);
|
||
|
||
const currentUrl = authenticatedPage.url();
|
||
const isLoginPage = currentUrl.includes("/login") || currentUrl.includes("/auth");
|
||
|
||
// 验证重定向或错误提示
|
||
if (!isLoginPage) {
|
||
const authError = authenticatedPage.getByText(/登录|认证|权限|过期/);
|
||
const hasAuthError = await authError.isVisible({ timeout: 5000 }).catch(() => false);
|
||
if (!hasAuthError) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
}
|
||
|
||
expect(isLoginPage).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
test.describe("错误状态 - 重试按钮交互测试", () => {
|
||
test.afterEach(async ({ authenticatedPage }) => {
|
||
await clearApiMocks(authenticatedPage);
|
||
});
|
||
|
||
test("点击重试按钮后重新请求数据", async ({ authenticatedPage }) => {
|
||
// 先模拟错误
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/brands\//, 500);
|
||
await mockApiError(authenticatedPage, /\/api\/v1\/scoring\//, 500);
|
||
|
||
await authenticatedPage.goto("/dashboard/health-score");
|
||
await authenticatedPage.waitForLoadState("networkidle");
|
||
|
||
// 等待错误状态
|
||
const errorTitle = authenticatedPage.getByText("数据加载失败");
|
||
const hasError = await errorTitle.isVisible({ timeout: 15000 }).catch(() => false);
|
||
|
||
if (!hasError) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
// 清除错误mock,恢复正常响应
|
||
await clearApiMocks(authenticatedPage);
|
||
|
||
// 模拟正常响应
|
||
await mockApi(authenticatedPage, /\/api\/v1\/brands\//, {
|
||
items: [{ id: "test-brand-id", name: "测试品牌" }],
|
||
});
|
||
|
||
// 点击重试按钮
|
||
const retryBtn = authenticatedPage.getByRole("button", { name: /重试/ });
|
||
const hasRetry = await retryBtn.isVisible({ timeout: 5000 }).catch(() => false);
|
||
|
||
if (!hasRetry) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
|
||
await retryBtn.click();
|
||
|
||
// 验证错误状态消失(页面重新加载)
|
||
await authenticatedPage.waitForTimeout(3000);
|
||
});
|
||
});
|