Files
NextEdu/webtest/elective_0.1.0.json
SpecialX d884c6d513
Some checks failed
CI / scheduled-backup (push) Failing after 36s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled
test: update and add E2E, integration, visual, and webapp tests
- Update E2E tests: announcements, auth, auth-business-flow, full-route-regression, grades, navigation, smoke-auth, teacher-web-test

- Update integration tests: api-ai-chat, api-onboarding-complete, api-onboarding-status, proxy-guard, integration setup

- Update visual regression tests: admin-dashboard, homepage, student-dashboard, teacher-dashboard, visual config, helpers

- Update webapp tests: admin, parent, student full tests and debug scripts

- Add new webapp tests: announcements_messages, settings_profile, debug scripts

- Add webtest directory with test plans, screenshots, and diagnostic scripts
2026-06-23 17:39:40 +08:00

249 lines
22 KiB
JSON
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.
{
"test_date": "2026-06-22 19:24:47",
"module": "选修课 (Elective)",
"version": "0.1.0",
"base_url": "http://localhost:3000",
"summary": {
"total": 22,
"passed": 21,
"failed": 0,
"warnings": 1
},
"roles": {
"admin": {
"role": "admin",
"login_success": true,
"pages": [
{
"url": "http://localhost:3000/admin/elective",
"route": "/admin/elective",
"category": "选修课程列表",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/admin/elective",
"checks": [
{
"name": "标题关键词匹配 (选修课程)",
"passed": true,
"detail": "实际匹配: True"
}
],
"errors": [],
"warnings": [],
"console_errors": [],
"screenshot": "webtest\\screenshots\\elective\\admin_admin_elective.png"
},
{
"url": "http://localhost:3000/admin/elective/create",
"route": "/admin/elective/create",
"category": "创建选修课程",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/admin/elective/create",
"checks": [
{
"name": "标题关键词匹配 (创建课程)",
"passed": true,
"detail": "实际匹配: True"
}
],
"errors": [],
"warnings": [],
"console_errors": [],
"screenshot": "webtest\\screenshots\\elective\\admin_admin_elective_create.png"
}
],
"interactions": [
{
"name": "管理员选修课 - 创建课程按钮存在",
"passed": true,
"detail": ""
},
{
"name": "管理员选修课 - 内容显示(卡片或空状态)",
"passed": true,
"detail": ""
},
{
"name": "创建选修课 - 课程名称输入框",
"passed": true,
"detail": ""
},
{
"name": "创建选修课 - 选课模式选择器",
"passed": true,
"detail": ""
},
{
"name": "创建选修课 - 容量输入框",
"passed": true,
"detail": ""
},
{
"name": "创建选修课 - 提交按钮",
"passed": true,
"detail": ""
}
],
"errors": [],
"warnings": []
},
"teacher": {
"role": "teacher",
"login_success": true,
"pages": [
{
"url": "http://localhost:3000/teacher/elective",
"route": "/teacher/elective",
"category": "我的选修课",
"status": "warning",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/elective",
"checks": [
{
"name": "标题关键词匹配 (我的选修课)",
"passed": true,
"detail": "实际匹配: True"
}
],
"errors": [],
"warnings": [
"控制台错误 1 条"
],
"console_errors": [
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client branch `if (typeof window !== 'undefined')`.\n- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n- Date formatting in a user's locale which doesn't match the server.\n- External changing data without sending a snapshot of it along with the HTML.\n- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n%s%s https://react.dev/link/hydration-mismatch \n\n ...\n <div className=\"flex items...\">\n <GlobalSearch>\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" size=\"icon\" className=\"relative t...\" aria-label=\"通知\" type=\"button\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded...\"}\n aria-label=\"通知\"\n type=\"button\"\n+ id=\"radix-_R_lebn6lb_\"\n- id=\"radix-_R_2lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" className=\"relative s...\" type=\"button\" id=\"radix-_R_t...\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm...\"}\n type=\"button\"\n+ id=\"radix-_R_tebn6lb_\"\n- id=\"radix-_R_3lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n"
],
"screenshot": "webtest\\screenshots\\elective\\teacher_teacher_elective.png"
}
],
"interactions": [
{
"name": "教师选修课 - 创建课程按钮存在",
"passed": true,
"detail": ""
},
{
"name": "教师选修课 - 内容显示(卡片或空状态)",
"passed": true,
"detail": ""
}
],
"errors": [],
"warnings": []
},
"student": {
"role": "student",
"login_success": true,
"pages": [
{
"url": "http://localhost:3000/student/elective",
"route": "/student/elective",
"category": "选课中心",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/student/elective",
"checks": [
{
"name": "标题关键词匹配 (选课中心)",
"passed": true,
"detail": "实际匹配: True"
}
],
"errors": [],
"warnings": [],
"console_errors": [],
"screenshot": "webtest\\screenshots\\elective\\student_student_elective.png"
}
],
"interactions": [
{
"name": "学生选课 - 我的选课区域",
"passed": true,
"detail": ""
},
{
"name": "学生选课 - 可选课程区域",
"passed": true,
"detail": ""
},
{
"name": "学生选课 - 搜索筛选器",
"passed": true,
"detail": ""
}
],
"errors": [],
"warnings": []
},
"parent": {
"role": "parent",
"login_success": true,
"pages": [],
"interactions": [],
"errors": [],
"warnings": []
}
},
"cross_role_tests": [
{
"role": "teacher",
"forbidden_route": "/admin/elective",
"final_url": "http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /teacher/dashboard拒绝访问"
},
{
"role": "teacher",
"forbidden_route": "/student/elective",
"final_url": "http://localhost:3000/teacher/dashboard?from=%2Fstudent%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /teacher/dashboard拒绝访问"
},
{
"role": "student",
"forbidden_route": "/admin/elective",
"final_url": "http://localhost:3000/student/dashboard?from=%2Fadmin%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /student/dashboard拒绝访问"
},
{
"role": "student",
"forbidden_route": "/teacher/elective",
"final_url": "http://localhost:3000/student/dashboard?from=%2Fteacher%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /student/dashboard拒绝访问"
},
{
"role": "parent",
"forbidden_route": "/admin/elective",
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /parent/dashboard拒绝访问"
},
{
"role": "parent",
"forbidden_route": "/teacher/elective",
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fteacher%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /parent/dashboard拒绝访问"
},
{
"role": "parent",
"forbidden_route": "/student/elective",
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Felective&reason=forbidden",
"passed": true,
"error": "重定向回 /parent/dashboard拒绝访问"
}
],
"interactions": [],
"console_errors_global": [
{
"role": "teacher",
"error": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client branch `if (typeof window !== 'undefined')`.\n- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n- Date formatting in a user's locale which doesn't match the server.\n- External changing data without sending a snapshot of it along with the HTML.\n- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n%s%s https://react.dev/link/hydration-mismatch \n\n ...\n <div className=\"flex items...\">\n <GlobalSearch>\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" size=\"icon\" className=\"relative t...\" aria-label=\"通知\" type=\"button\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded...\"}\n aria-label=\"通知\"\n type=\"button\"\n+ id=\"radix-_R_lebn6lb_\"\n- id=\"radix-_R_2lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" className=\"relative s...\" type=\"button\" id=\"radix-_R_t...\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm...\"}\n type=\"button\"\n+ id=\"radix-_R_tebn6lb_\"\n- id=\"radix-_R_3lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n"
},
{
"role": "student",
"error": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client branch `if (typeof window !== 'undefined')`.\n- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n- Date formatting in a user's locale which doesn't match the server.\n- External changing data without sending a snapshot of it along with the HTML.\n- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n%s%s https://react.dev/link/hydration-mismatch \n\n ...\n <div className=\"flex items...\">\n <GlobalSearch>\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_l...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" size=\"icon\" className=\"relative t...\" aria-label=\"通知\" type=\"button\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded...\"}\n aria-label=\"通知\"\n type=\"button\"\n+ id=\"radix-_R_lebn6lb_\"\n- id=\"radix-_R_2lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n ...\n <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n <DropdownMenuTrigger asChild={true}>\n <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n <Primitive.div asChild={true} ref={function}>\n <Primitive.div.Slot ref={function}>\n <Primitive.div.SlotClone ref={function}>\n <Primitive.button type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n <Primitive.button.Slot type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_t...\" aria-haspopup=\"menu\" ...>\n <Button variant=\"ghost\" className=\"relative s...\" type=\"button\" id=\"radix-_R_t...\" ...>\n <button\n data-slot=\"dropdown-menu-trigger\"\n className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm...\"}\n type=\"button\"\n+ id=\"radix-_R_tebn6lb_\"\n- id=\"radix-_R_3lqbn6lb_\"\n aria-haspopup=\"menu\"\n aria-expanded={false}\n aria-controls={undefined}\n data-state=\"closed\"\n data-disabled={undefined}\n disabled={false}\n onPointerDown={function handleEvent}\n onKeyDown={function handleEvent}\n ref={function}\n >\n ...\n"
}
]
}