import { expect, type Locator, type Page, test } from "@playwright/test"; async function dragWithPointer( page: Page, source: Locator, target: Locator, ): Promise { const sourceBox = await source.boundingBox(); const targetBox = await target.boundingBox(); if (!sourceBox || !targetBox) { throw new Error("Drag source or target is not visible"); } const sourcePosition = { x: sourceBox.x + sourceBox.width / 2, y: sourceBox.y + sourceBox.height / 2, }; const targetPosition = { x: targetBox.x + targetBox.width / 2, y: targetBox.y + targetBox.height / 2, }; await page.mouse.move(sourcePosition.x, sourcePosition.y); await page.mouse.down(); await page.mouse.move(sourcePosition.x + 10, sourcePosition.y, { steps: 5 }); await page.waitForTimeout(150); await page.mouse.move(targetPosition.x, targetPosition.y, { steps: 20 }); await page.waitForTimeout(150); await page.mouse.up(); } function getResource(page: Page, title: string): Locator { return page.getByRole("button", { name: new RegExp(title) }); } function getCategory(page: Page, title: string): Locator { return page.getByRole("heading", { name: title, exact: true }).locator(".."); } test.describe("CourseDemo connected flow", () => { test.beforeEach(async ({ page }) => { await page.goto("/#/course-demo"); await page.waitForLoadState("networkidle"); }); test("renders header and progress", async ({ page }) => { await expect(page.locator("text=学生资助政策 · 互动学习")).toBeVisible(); await expect(page.locator("text=建立你的课程资源地图")).toBeVisible(); await expect(page.locator('[data-guide="course-progress"]')).toBeVisible(); await expect(page.locator("text=本节进度")).toBeVisible(); }); test("starts guide dialog and navigates steps", async ({ page }) => { await page.click("text=进入课程导入"); await page.waitForTimeout(500); // First step should appear await expect(page.locator("text=欢迎进入学生资助政策微课")).toBeVisible(); await expect(page.locator("text=1 / 4")).toBeVisible(); // Click next await page.click("button:has-text('了解任务')"); await page.waitForTimeout(300); await expect(page.locator("text=选择你的学习方式")).toBeVisible(); await expect(page.locator("text=2 / 4")).toBeVisible(); // Choose direct path await page.click("button:has-text('直接开始分类')"); await page.waitForTimeout(300); await expect(page.locator("text=准备开始")).toBeVisible(); await expect(page.locator("text=4 / 4")).toBeVisible(); // Finish await page.click("button:has-text('开始挑战')"); await page.waitForTimeout(300); // Dialog should close await expect( page.locator("text=欢迎进入学生资助政策微课"), ).not.toBeVisible(); }); test("starts spotlight guide and highlights elements", async ({ page }) => { await page.click("text=查看操作引导"); await page.waitForTimeout(500); // First spotlight step await expect(page.locator("text=先阅读资源卡片")).toBeVisible(); await page.waitForTimeout(300); // Next step await page.click("button:has-text('下一步')"); await page.waitForTimeout(500); await expect(page.locator("text=放入对应学习阶段")).toBeVisible(); // Next step await page.click("button:has-text('下一步')"); await page.waitForTimeout(500); await expect(page.locator("text=自动记录学习进度")).toBeVisible(); // Finish await page.click("button:has-text('开始分类')"); await page.waitForTimeout(300); await expect(page.locator("text=先阅读资源卡片")).not.toBeVisible(); }); test("drag and drop resource classification", async ({ page }) => { const policyResource = getResource(page, "资助政策微课"); const policyZone = getCategory(page, "政策学习"); await expect(policyResource).toBeVisible(); await expect(policyZone).toBeVisible(); await dragWithPointer(page, policyResource, policyZone); await expect(page.getByText("分类正确:资助政策微课")).toBeVisible(); await expect(page.getByText("已完成 1/6")).toBeVisible(); await expect(policyResource).not.toBeVisible(); }); test("wrong category shows error toast", async ({ page }) => { const policyResource = getResource(page, "资助政策微课"); const wrongZone = getCategory(page, "互动实践"); await expect(policyResource).toBeVisible(); await expect(wrongZone).toBeVisible(); await dragWithPointer(page, policyResource, wrongZone); await expect( page.getByText("“资助政策微课”不属于这个分类,再想一想。"), ).toBeVisible(); await expect(page.getByText("已完成 0/6")).toBeVisible(); await expect(policyResource).toBeVisible(); }); test("completion overlay appears after all resources placed", async ({ page, }) => { const placements = [ ["资助政策微课", "政策学习"], ["申请流程图", "政策学习"], ["诚信申请案例", "互动实践"], ["材料自查清单", "互动实践"], ["常见问题答疑", "学习支持"], ["校园咨询渠道", "学习支持"], ] as const; for (const [resourceTitle, categoryTitle] of placements) { await dragWithPointer( page, getResource(page, resourceTitle), getCategory(page, categoryTitle), ); } await expect(page.getByText("资源地图已建立")).toBeVisible(); await expect(page.getByText("已完成 6/6")).toBeVisible(); await expect(page.getByText("100%")).toBeVisible(); }); test("persists progress with session fallback", async ({ page }) => { await dragWithPointer( page, getResource(page, "资助政策微课"), getCategory(page, "政策学习"), ); await expect(page.getByText("已完成 1/6")).toBeVisible(); await page.reload(); await expect(page.getByText("进度已恢复")).toBeVisible(); await expect(page.getByText("已完成 1/6")).toBeVisible(); await expect(getResource(page, "资助政策微课")).not.toBeVisible(); }); });