test(course-package): add picker persistence and edit scenario tests
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -605,6 +605,254 @@ test.describe('音乐教室端(huike-front)课包 UI', () => {
|
||||
await expect(page).toHaveURL(/\/course\/group\/pick/, { timeout: 10_000 });
|
||||
});
|
||||
|
||||
test('课包新增:选择课节 → 确认 → 课节应持久保留在新增页', async ({ page, request }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: '选择课节后确认,回到新增页课节标签不显示,保存后未关联课节' });
|
||||
|
||||
// ---- Login (select "教师" role, not the first grid item) ----
|
||||
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
||||
await page.goto(`/?${q.toString()}`, { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
await page.waitForFunction(
|
||||
() => !window.location.search.includes('status=2'),
|
||||
{ timeout: 90_000 },
|
||||
);
|
||||
if (await page.getByText('请选择您的登录身份').isVisible().catch(() => false)) {
|
||||
// 课包需要教师/主管教师权限,选择"教师"角色
|
||||
await page.locator('.van-grid-item').filter({ hasText: /^教师$/ }).click();
|
||||
await page.waitForFunction(
|
||||
() => !window.location.search.includes('status=2'),
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
}
|
||||
// Extract JWT for API calls
|
||||
const authToken = await page.evaluate(() => window.localStorage.getItem('Authorization'));
|
||||
expect(authToken, '登录后应有 JWT').toBeTruthy();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: authToken!,
|
||||
HtySudoerToken: authToken!,
|
||||
HtyHost: new URL(kcBase).hostname,
|
||||
};
|
||||
|
||||
// ---- Create a course_group FIRST (before picker loads data) ----
|
||||
const groupName = `e2e-ui-picker-${Date.now()}`;
|
||||
const createRes = await request.post(`${kcBase}/api/v1/ws/create_course_group`, {
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: { group_name: groupName, org_visible: true },
|
||||
});
|
||||
expect(createRes.ok(), `CREATE GROUP HTTP ${createRes.status()}`).toBeTruthy();
|
||||
const createBody = await createRes.json();
|
||||
expect(createBody.r, `CREATE GROUP failed: ${JSON.stringify(createBody)}`).toBe(true);
|
||||
const groupId = createBody.d;
|
||||
test.info().annotations.push({ type: 'group', description: `created groupId=${groupId}` });
|
||||
|
||||
// ---- Resolve org context ----
|
||||
const { resolveOrgContextForCoursePage } = await import('./helpers/music-room-session');
|
||||
await resolveOrgContextForCoursePage(page);
|
||||
|
||||
let createdPkgId: string | undefined;
|
||||
|
||||
try {
|
||||
// ---- Navigate to course-package add page ----
|
||||
await page.goto('/course/course-package/add', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// ---- Fill package name (required for form submit) ----
|
||||
const pkgName = `e2e-ui-pkg-${Date.now()}`;
|
||||
await page.locator('input[name="package_name"]').fill(pkgName);
|
||||
|
||||
// ---- Click "包含课节" to open picker ----
|
||||
const pickerCell = page.locator('.van-cell').filter({ hasText: '包含课节' });
|
||||
await expect(pickerCell).toBeVisible({ timeout: 10_000 });
|
||||
await pickerCell.click();
|
||||
await expect(page).toHaveURL(/\/course\/group\/pick/, { timeout: 10_000 });
|
||||
|
||||
// ---- Switch to "我的课节" tab (default is "机构可见课节" for course-package) ----
|
||||
await page.locator('.van-tab').filter({ hasText: '我的课节' }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// ---- Select the group we created — it should be in "我的课节" tab ----
|
||||
const groupItem = page.locator('.course-item').filter({ hasText: groupName });
|
||||
await expect(groupItem).toBeVisible({ timeout: 30_000 });
|
||||
const circleIcon = groupItem.locator('.van-icon-circle');
|
||||
await expect(circleIcon).toBeVisible({ timeout: 5000 });
|
||||
await circleIcon.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// ---- Verify selection visual feedback ----
|
||||
await expect(groupItem.locator('.van-icon-checked')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// ---- Click "确认" button and wait for navigation ----
|
||||
const confirmBtn = page.locator('.btn-pick');
|
||||
await expect(confirmBtn).toBeEnabled({ timeout: 5000 });
|
||||
|
||||
// Wait for navigation (confirmBtn click → router.back())
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/course\/course-package\/add/, { timeout: 15_000 }),
|
||||
confirmBtn.click(),
|
||||
]);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForSelector('input[name="package_name"]', { timeout: 10_000 });
|
||||
|
||||
// ---- CRITICAL CHECK: Should see the selected group tag ----
|
||||
await expect(
|
||||
page.locator('.van-tag').filter({ hasText: groupName })
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// ---- Re-fill package name (component was re-created after navigation) ----
|
||||
await page.locator('input[name="package_name"]').fill(pkgName);
|
||||
|
||||
// ---- Submit the form ----
|
||||
await page.getByText('保存').click();
|
||||
|
||||
// ---- Verify via API that package was created with this group ----
|
||||
await page.waitForTimeout(2000);
|
||||
const myRes = await request.get(
|
||||
`${kcBase}/api/v1/clazz/course-package/my-packages?page=1&page_size=50`,
|
||||
{ headers },
|
||||
);
|
||||
expect(myRes.ok()).toBeTruthy();
|
||||
const myBody = await myRes.json();
|
||||
const myList: any[] = myBody.d?.[0] ?? [];
|
||||
const newPkg = myList.find((p: any) => p.package_name === pkgName);
|
||||
expect(newPkg, '创建的课包应出现在我的课包列表中').toBeTruthy();
|
||||
createdPkgId = newPkg.id;
|
||||
|
||||
const itemsRes = await request.get(
|
||||
`${kcBase}/api/v1/clazz/course-package/item/list/${createdPkgId}`,
|
||||
{ headers },
|
||||
);
|
||||
expect(itemsRes.ok()).toBeTruthy();
|
||||
const itemsBody = await itemsRes.json();
|
||||
const items: any[] = itemsBody.d ?? [];
|
||||
const matched = items.some((g: any) => g.course_group_id === groupId);
|
||||
expect(matched, '保存后课包应包含选中的课节分组').toBe(true);
|
||||
} finally {
|
||||
// ---- Cleanup ----
|
||||
if (createdPkgId) {
|
||||
await request.post(`${kcBase}/api/v1/clazz/course-package/delete/${createdPkgId}`, { headers }).catch(() => {});
|
||||
}
|
||||
await request.post(`${kcBase}/api/v1/ws/delete_course_group/${groupId}`, { headers }).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
test('课包编辑:加载已有课节 → 选择新增课节 → 保存后两组均应存在', async ({ page, request }) => {
|
||||
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
||||
await page.goto(`/?${q.toString()}`, { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
await page.waitForFunction(
|
||||
() => !window.location.search.includes('status=2'),
|
||||
{ timeout: 90_000 },
|
||||
);
|
||||
if (await page.getByText('请选择您的登录身份').isVisible().catch(() => false)) {
|
||||
await page.locator('.van-grid-item').filter({ hasText: /^教师$/ }).click();
|
||||
await page.waitForFunction(
|
||||
() => !window.location.search.includes('status=2'),
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
}
|
||||
const authToken = await page.evaluate(() => window.localStorage.getItem('Authorization'));
|
||||
expect(authToken).toBeTruthy();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: authToken!,
|
||||
HtySudoerToken: authToken!,
|
||||
HtyHost: new URL(kcBase).hostname,
|
||||
};
|
||||
|
||||
const groupName1 = `e2e-edit-g1-${Date.now()}`;
|
||||
const groupName2 = `e2e-edit-g2-${Date.now()}`;
|
||||
|
||||
const createG1 = await request.post(`${kcBase}/api/v1/ws/create_course_group`, {
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: { group_name: groupName1, org_visible: true },
|
||||
});
|
||||
const body1 = await createG1.json();
|
||||
expect(body1.r).toBe(true);
|
||||
const groupId1 = body1.d;
|
||||
|
||||
const createG2 = await request.post(`${kcBase}/api/v1/ws/create_course_group`, {
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: { group_name: groupName2, org_visible: true },
|
||||
});
|
||||
const body2 = await createG2.json();
|
||||
expect(body2.r).toBe(true);
|
||||
const groupId2 = body2.d;
|
||||
|
||||
const pkgName = `e2e-edit-pkg-${Date.now()}`;
|
||||
const createPkg = await request.post(`${kcBase}/api/v1/clazz/course-package/create`, {
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: { package_name: pkgName },
|
||||
});
|
||||
const pkgBody = await createPkg.json();
|
||||
expect(pkgBody.r).toBe(true);
|
||||
const pkgId = pkgBody.d.id;
|
||||
|
||||
// Link group1 to package
|
||||
const syncRes = await request.post(`${kcBase}/api/v1/clazz/course-package/item/sync`, {
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: { package_id: pkgId, course_group_ids: [groupId1] },
|
||||
});
|
||||
expect(syncRes.ok()).toBeTruthy();
|
||||
|
||||
try {
|
||||
const { resolveOrgContextForCoursePage } = await import('./helpers/music-room-session');
|
||||
await resolveOrgContextForCoursePage(page);
|
||||
|
||||
await page.goto(`/course/course-package/edit?id=${pkgId}`, { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 });
|
||||
await page.waitForSelector('input[name="package_name"]', { timeout: 10_000 });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify group1 shown (loaded from existing items)
|
||||
await expect(page.locator('.van-tag').filter({ hasText: groupName1 })).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Open picker
|
||||
await page.locator('.van-cell').filter({ hasText: '包含课节' }).click();
|
||||
await expect(page).toHaveURL(/\/course\/group\/pick/, { timeout: 10_000 });
|
||||
|
||||
// Switch to "我的课节" tab and select group2
|
||||
await page.locator('.van-tab').filter({ hasText: '我的课节' }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
const group2Item = page.locator('.course-item').filter({ hasText: groupName2 });
|
||||
await expect(group2Item).toBeVisible({ timeout: 30_000 });
|
||||
await group2Item.locator('.van-icon-circle').click();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(group2Item.locator('.van-icon-checked')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Confirm
|
||||
const confirmBtn = page.locator('.btn-pick');
|
||||
await expect(confirmBtn).toBeEnabled({ timeout: 5000 });
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/course\/course-package\/edit/, { timeout: 15_000 }),
|
||||
confirmBtn.click(),
|
||||
]);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForSelector('input[name="package_name"]', { timeout: 10_000 });
|
||||
|
||||
// Verify both groups shown
|
||||
await expect(page.locator('.van-tag').filter({ hasText: groupName1 })).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.locator('.van-tag').filter({ hasText: groupName2 })).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Submit
|
||||
await page.getByText('保存').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Verify via API
|
||||
const itemsRes = await request.get(`${kcBase}/api/v1/clazz/course-package/item/list/${pkgId}`, { headers });
|
||||
expect(itemsRes.ok()).toBeTruthy();
|
||||
const itemsBody = await itemsRes.json();
|
||||
const items: any[] = itemsBody.d ?? [];
|
||||
const matchedIds = items.map((i: any) => i.course_group_id);
|
||||
expect(matchedIds).toContain(groupId1);
|
||||
expect(matchedIds).toContain(groupId2);
|
||||
} finally {
|
||||
await request.post(`${kcBase}/api/v1/clazz/course-package/delete/${pkgId}`, { headers }).catch(() => {});
|
||||
await request.post(`${kcBase}/api/v1/ws/delete_course_group/${groupId1}`, { headers }).catch(() => {});
|
||||
await request.post(`${kcBase}/api/v1/ws/delete_course_group/${groupId2}`, { headers }).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
test('课节新增页面:开放给机构开关可见', async ({ page }) => {
|
||||
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
||||
await page.goto(`/?${q.toString()}`, {
|
||||
|
||||
Reference in New Issue
Block a user