| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- import { expect, test, type Page } from '@playwright/test'
- import { readFileSync } from 'node:fs'
- const active = (page: Page) => page.locator('.taro_page').last()
- async function openEntry(page: Page, title: string) {
- await page.goto('/')
- await page.locator('.workbench-card').filter({ hasText: title }).click()
- }
- async function example(page: Page) {
- if (await page.locator('.mistake-capture-page, .paper-capture-page').count()) {
- const chooser = page.waitForEvent('filechooser')
- await page.getByText('从相册选择', { exact: true }).click()
- await (await chooser).setFiles('src/assets/sample-paper.png')
- return
- }
- if (await page.locator('.answer-capture-page').count()) {
- const chooser = page.waitForEvent('filechooser')
- await page.locator('.answer-album-button').click()
- await (await chooser).setFiles('src/assets/sample-answer.png')
- return
- }
- await page.getByText('添加示例照片', { exact: true }).click()
- }
- async function chooseStudents(page: Page) {
- await active(page).getByText('选择学生', { exact: true }).click()
- await page.locator('.student-class-trigger').click()
- await page.locator('.student-class-option').filter({ hasText: '初一' }).click()
- await page.locator('.student-names-trigger').click()
- await page.locator('.student-option').filter({ hasText: '李一' }).click()
- await page.locator('.student-class-trigger').click()
- await page.locator('.student-class-option').filter({ hasText: '初二' }).click()
- await page.locator('.student-names-trigger').click()
- await page.locator('.student-option').filter({ hasText: '李一' }).click()
- await page.locator('.student-confirm-button').click()
- }
- test('student associations belong to one upload and clear when reopening the page', async ({ page }) => {
- await openEntry(page, '上传错题')
- await chooseStudents(page)
- await expect(page.locator('.student-link-copy')).toContainText('初一 · 李一、初二 · 李一')
- await page.getByText('查看示例', { exact: true }).click()
- await page.locator('.back-button').click()
- await expect(page.locator('.student-link-copy')).toContainText('初一 · 李一、初二 · 李一')
- await page.locator('.back-button').click()
- await page.locator('.workbench-card').filter({ hasText: '上传错题' }).click()
- await expect(page.getByText('识别前请选择学生,支持跨班级多选', { exact: true })).toBeVisible()
- await active(page).getByText('选择学生', { exact: true }).click()
- await expect(page.locator('.student-selected-tag')).toHaveCount(0)
- await page.locator('.student-cancel-button').click()
- await chooseStudents(page)
- await page.reload()
- await expect(page.getByText('识别前请选择学生,支持跨班级多选', { exact: true })).toBeVisible()
- await page.screenshot({ path: `test-results/fresh-upload-${test.info().project.name}.png`, scale: 'css' })
- })
- test('photos and documents can be added before students are required at recognition', async ({ page }) => {
- await openEntry(page, '上传错题')
- const camera = page.waitForEvent('filechooser')
- await page.locator('.capture-zone').click()
- await (await camera).setFiles('src/assets/paper-capture-reference.png')
- await expect(page.locator('.student-dialog')).toHaveCount(0)
- const album = page.waitForEvent('filechooser')
- await page.getByText('从相册选择', { exact: true }).click()
- await (await album).setFiles('src/assets/sample-paper.png')
- await expect(page.locator('.student-dialog')).toHaveCount(0)
- const documents = page.waitForEvent('filechooser')
- await page.getByText('Word / PDF 文件', { exact: true }).click()
- await (await documents).setFiles({ name: '练习卷.pdf', mimeType: 'application/pdf', buffer: Buffer.from('%PDF-1.4\n%%EOF') })
- await expect(page.locator('.student-dialog')).toHaveCount(0)
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.locator('.student-dialog')).toBeVisible()
- await expect(page.getByText('请先关联学生', { exact: true })).toHaveCount(0)
- await expect(page.locator('.question-card')).toHaveCount(0)
- await page.screenshot({ path: `test-results/recognition-students-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.student-cancel-button').click()
- await expect(page.getByText('已添加 3 张 · 查看 ›')).toBeVisible()
- await chooseStudents(page)
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.getByText('3 页 · 已识别 9 道题', { exact: false })).toBeVisible()
- })
- test('student dialog supports dropdowns, removable tags and cancelling draft selections', async ({ page }) => {
- await openEntry(page, '上传错题')
- await page.getByText('选择学生', { exact: true }).click()
- await page.locator('.student-confirm-button').click()
- await expect(page.locator('.student-dialog')).toBeVisible()
- await expect(page.locator('.student-class-trigger')).toHaveText('请选择班级▾')
- await page.locator('.student-names-trigger').click()
- await expect(page.locator('.student-class-option.selected')).toHaveCount(0)
- await expect(page.locator('.student-option')).toHaveCount(0)
- await page.locator('.student-class-option').filter({ hasText: '初一' }).click()
- await page.locator('.student-names-trigger').click()
- await page.locator('.student-option').filter({ hasText: '李一' }).click()
- await page.locator('.student-option').filter({ hasText: '李二' }).click()
- await page.locator('.student-names-trigger').click()
- await expect(page.locator('.student-names-trigger')).toHaveText('李一、李二▾')
- await expect(page.getByText('已选 2 名学生', { exact: true })).toBeVisible()
- await page.locator('.student-dialog').screenshot({ path: `test-results/student-dialog-${test.info().project.name}.png`, scale: 'css' })
- await page.getByLabel('移除初一 李二', { exact: true }).click()
- await expect(page.locator('.student-selected-tag')).toHaveCount(1)
- await page.locator('.student-cancel-button').click()
- await page.getByText('选择学生', { exact: true }).click()
- await expect(page.locator('.student-selected-tag')).toHaveCount(0)
- await page.locator('.student-cancel-button').click()
- await chooseStudents(page)
- await page.getByText('选择学生', { exact: true }).click()
- await expect(page.locator('.student-selected-tag')).toHaveCount(2)
- await expect(page.locator('.student-class-trigger')).toHaveText('初一▾')
- await expect(page.locator('.student-names-trigger')).toHaveText('李一▾')
- await page.locator('.student-class-trigger').click()
- await page.locator('.student-class-option').filter({ hasText: '初二' }).click()
- await expect(page.locator('.student-names-trigger')).toHaveText('李一▾')
- await page.locator('.student-cancel-button').click()
- await expect(page.locator('.student-link-copy')).toContainText('初一 · 李一、初二 · 李一')
- })
- test('reopening student dialog restores the selected class and students', async ({ page }) => {
- await openEntry(page, '上传错题')
- await page.getByText('选择学生', { exact: true }).click()
- await expect(page.locator('.student-class-trigger')).toHaveText('请选择班级▾')
- await page.locator('.student-class-trigger').click()
- await page.locator('.student-class-option').filter({ hasText: '初二' }).click()
- await page.locator('.student-names-trigger').click()
- await page.locator('.student-option').filter({ hasText: '李一' }).click()
- await page.locator('.student-option').filter({ hasText: '李二' }).click()
- await page.locator('.student-confirm-button').click()
- await page.getByText('选择学生', { exact: true }).click()
- await expect(page.locator('.student-class-trigger')).toHaveText('初二▾')
- await expect(page.locator('.student-names-trigger')).toHaveText('李一、李二▾')
- await expect(page.locator('.student-selected-tag')).toHaveCount(2)
- await page.locator('.student-dialog').screenshot({ path: `test-results/student-restored-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.student-names-trigger').click()
- await expect(page.locator('.student-option.selected')).toHaveCount(2)
- })
- test('mistakes retain edits across pages and retries, and persist linked students', async ({ page }) => {
- await openEntry(page, '上传错题')
- await chooseStudents(page)
- await example(page)
- await example(page)
- await page.getByText('已添加 2 张 · 查看 ›').click()
- await expect(page.locator('.gallery-row')).toHaveCount(2)
- await page.locator('.gallery-footer').getByText('完成', { exact: true }).click()
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.getByText('已识别 6 道题', { exact: false })).toBeVisible()
- await page.locator('.wrong-toggle').click()
- await page.locator('.question-card textarea').nth(0).fill('修改后的题干:求二次函数的交点。')
- await page.locator('.question-card .field-input input').first().fill('自定义选项 A')
- await page.getByText('下一题', { exact: true }).click()
- await page.getByText('上一题', { exact: true }).click()
- await expect(page.locator('.question-card textarea').nth(0)).toHaveValue('修改后的题干:求二次函数的交点。')
- await page.getByText('继续拍照', { exact: true }).click()
- await example(page)
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.locator('.question-card textarea').nth(0)).toHaveValue('修改后的题干:求二次函数的交点。')
- await page.getByText('体验上传失败与重试').click()
- await expect(page.getByText('本次模拟上传未完成,已填写和识别的内容已保留。')).toBeVisible()
- await page.getByText('返回重试', { exact: true }).click()
- await page.getByText('上传错题(1 道)').click()
- await expect(page.getByText('题目已存入题库')).toBeVisible()
- await expect(page.getByText('初一 · 李一、初二 · 李一')).toBeVisible()
- await page.getByText('查看上传记录', { exact: true }).click()
- await expect(page.getByText('关联学生:初一 李一、初二 李一')).toBeVisible()
- await page.reload()
- await expect(page.getByText('修改后的题干:求二次函数的交点。')).toBeVisible()
- await page.screenshot({ path: `test-results/mistakes-${test.info().project.name}.png`, fullPage: true })
- })
- test('mistake capture and photo guide match the reference layout and retain selected photos', async ({ page }) => {
- await openEntry(page, '上传错题')
- await expect(page.locator('.student-link-bar')).toBeVisible()
- expect((await page.locator('.student-link-copy').boundingBox())!.width).toBeGreaterThan(180)
- await expect(page.locator('.capture-import-card')).toHaveCount(2)
- await expect(page.getByText('识别全部照片', { exact: true })).toHaveCount(0)
- await page.locator('.mistake-capture-page').screenshot({ path: `test-results/capture-empty-${test.info().project.name}.png`, scale: 'css' })
- await chooseStudents(page)
- const chooser = page.waitForEvent('filechooser')
- await page.getByText('从相册选择', { exact: true }).click()
- await (await chooser).setFiles('src/assets/sample-paper.png')
- await expect(page.getByText('已添加 1 张 · 查看 ›')).toBeVisible()
- await page.locator('.mistake-capture-page').screenshot({ path: `test-results/capture-${test.info().project.name}.png`, scale: 'css' })
- await page.getByText('查看示例', { exact: true }).click()
- await expect(page.getByText('试卷拍摄规范', { exact: true })).toBeVisible()
- await expect(page.locator('.photo-guide-page .primary-button')).toHaveCount(0)
- await expect(page.locator('.photo-guide-page').getByText('添加示例照片', { exact: true })).toHaveCount(0)
- await expect(page.locator('.photo-guide-corner')).toHaveCount(4)
- await expect(page.locator('.photo-guide-rule')).toHaveCount(3)
- await expect(page.getByText('多页试卷怎么拍?', { exact: true })).toBeVisible()
- await expect.poll(() => page.locator('.photo-guide-reference img').evaluate((img) => (img as HTMLImageElement).naturalWidth)).toBe(600)
- await expect.poll(() => active(page).evaluate((element) => element.scrollTop)).toBe(0)
- expect(await active(page).evaluate((element) => element.scrollWidth <= window.innerWidth)).toBeTruthy()
- await page.locator('.photo-guide-page').screenshot({ path: `test-results/photo-guide-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.photo-guide-multipage').scrollIntoViewIfNeeded()
- await expect(page.locator('.photo-guide-multipage')).toBeInViewport()
- await page.screenshot({ path: `test-results/photo-guide-details-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.back-button').click()
- await expect(page.getByText('已添加 1 张 · 查看 ›')).toBeVisible()
- await page.getByText('已添加 1 张 · 查看 ›').click()
- await expect(page.locator('.gallery-row')).toHaveCount(1)
- })
- test('added photos use a mixed file list with preview, deletion, camera and done actions', async ({ page }) => {
- await openEntry(page, '上传错题')
- await chooseStudents(page)
- for (let i = 0; i < 2; i++) {
- const chooser = page.waitForEvent('filechooser')
- await page.locator('.capture-zone').click()
- await (await chooser).setFiles('src/assets/paper-capture-reference.png')
- }
- const album = page.waitForEvent('filechooser')
- await page.getByText('从相册选择', { exact: true }).click()
- await (await album).setFiles({ name: '初一中考.png', mimeType: 'image/png', buffer: readFileSync('src/assets/sample-paper.png') })
- const documents = page.waitForEvent('filechooser')
- await page.getByText('Word / PDF 文件', { exact: true }).click()
- await (await documents).setFiles({ name: '知了数学-阶段学案.docx', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', buffer: Buffer.from('local demo document') })
- await page.getByText('已添加 4 张 · 查看 ›').click()
- await expect(page.locator('.gallery-row')).toHaveCount(4)
- await expect(page.locator('.gallery-file-name').nth(0)).toHaveText('1. 拍摄照片 1')
- await expect(page.locator('.gallery-file-name').nth(2)).toHaveText('3. 初一中考.png')
- await expect(page.locator('.gallery-row.is-document .gallery-view')).toHaveCount(0)
- await page.screenshot({ path: `test-results/photo-gallery-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.gallery-view').first().click()
- await expect(page.locator('.preview-image img')).toHaveCount(3)
- await page.locator('.preview-image').click({ position: { x: 5, y: 5 } })
- await page.locator('.gallery-row').nth(1).locator('.gallery-delete').click()
- await expect(page.locator('.gallery-file-name').nth(1)).toHaveText('2. 初一中考.png')
- const camera = page.waitForEvent('filechooser')
- await page.locator('.nav-action').getByText('继续拍照', { exact: true }).click()
- await (await camera).setFiles('src/assets/paper-capture-reference.png')
- await expect(page.locator('.gallery-row')).toHaveCount(4)
- await page.locator('.gallery-footer').getByText('完成', { exact: true }).click()
- await expect(page.getByText('已添加 4 张 · 查看 ›')).toBeVisible()
- })
- test('answer cards support multiple photos, partial failure, retake and successful submission', async ({ page }) => {
- await openEntry(page, '上传答题卡')
- await expect(page.locator('.answer-capture-page .demo-banner')).toHaveCount(0)
- await expect(page.locator('.nav-action .text-button')).toHaveCount(0)
- await expect(page.locator('.answer-rule')).toHaveCount(3)
- await expect.poll(() => page.locator('.answer-reference img').evaluate((img) => (img as HTMLImageElement).naturalWidth)).toBe(606)
- await page.locator('.answer-capture-page').screenshot({ path: `test-results/answer-capture-${test.info().project.name}.png`, scale: 'css' })
- await page.locator('.answer-camera-button').scrollIntoViewIfNeeded()
- const albumButton = await page.locator('.answer-album-button').boundingBox()
- const cameraButton = await page.locator('.answer-camera-button').boundingBox()
- expect(Math.abs(albumButton!.y - cameraButton!.y)).toBeLessThan(1)
- await page.screenshot({ path: `test-results/answer-buttons-${test.info().project.name}.png`, scale: 'css' })
- expect(await active(page).evaluate((element) => element.scrollWidth <= window.innerWidth)).toBeTruthy()
- await example(page)
- await page.getByText('查看示例', { exact: true }).click()
- await expect(page.locator('.answer-guide-page .answer-reference')).toBeVisible()
- await page.locator('.back-button').click()
- await page.locator('.back-button').click()
- const camera = page.waitForEvent('filechooser')
- await page.locator('.answer-camera-button').click()
- await (await camera).setFiles('src/assets/sample-answer.png')
- await page.getByText('体验上传失败与重试').click()
- await expect(page.getByText('1 张图片识别失败')).toBeVisible()
- await page.getByText('重新拍摄失败照片').click()
- await page.getByText('用示例重新拍摄').click()
- await expect(page.getByText('确认上传(2 张)')).toBeVisible()
- await page.getByText('确认上传(2 张)').click()
- await expect(page.getByText('解析结果请到客户端的【批量解析】中查看')).toBeVisible()
- await page.getByText('查看上传记录').click()
- await expect(page.getByText('已上传 2 张答题卡')).toBeVisible()
- })
- test('video playback, unread synchronization and library originals work offline', async ({ page }) => {
- const errors: string[] = []
- page.on('pageerror', (e) => errors.push(e.message))
- await openEntry(page, '题目讲解')
- await page.getByText('观看讲解 →', { exact: true }).first().click()
- const video = page.locator('video')
- await expect(video).toBeVisible()
- await video.evaluate(async (element) => {
- const player = element as HTMLVideoElement
- player.muted = true
- await player.play()
- })
- await expect.poll(() => video.evaluate((element) => (element as HTMLVideoElement).currentTime)).toBeGreaterThan(0.5)
- await video.evaluate((element) => (element as HTMLVideoElement).pause())
- await page.screenshot({ path: `test-results/explanation-${test.info().project.name}.png`, fullPage: true })
- await page.locator('.back-button').click()
- await expect(
- page
- .locator('.panel-card')
- .filter({ has: page.getByText('函数图像平移', { exact: true }) })
- .first()
- .getByText('● 未读')
- ).toHaveCount(0)
- await page.locator('.back-button').click()
- await page.goto('/#/pages/library/index')
- await page.getByText('数学练习卷(一)', { exact: true }).click()
- await page.getByText('查看原图', { exact: true }).click()
- await page.getByText('第 2 页', { exact: true }).click()
- await expect(page.locator('.original-image')).toBeVisible()
- await page.screenshot({ path: `test-results/originals-${test.info().project.name}.png`, fullPage: true })
- expect(await active(page).evaluate((element) => element.scrollWidth <= window.innerWidth)).toBeTruthy()
- expect(errors).toEqual([])
- })
- test('paper metadata, local image and PDF imports, review approval and originals persist', async ({ page }) => {
- test.setTimeout(60000)
- await openEntry(page, '上传试卷')
- const imageChooser = page.waitForEvent('filechooser')
- await page.getByText('从相册选择', { exact: true }).click()
- await (await imageChooser).setFiles('src/assets/sample-paper.png')
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.getByText('请输入试卷名称', { exact: true })).toBeVisible()
- await page.locator('input[placeholder="请输入试卷名称"]').fill('回归测试卷')
- for (const label of ['年份', '试卷类型', '省份']) {
- await page
- .locator('.select-field')
- .filter({ has: page.getByText(label, { exact: true }) })
- .click()
- await page.locator('.weui-picker__action:visible').filter({ hasText: '确定' }).click()
- }
- const yearField = await page.locator('.paper-meta-field').filter({ hasText: '年份' }).boundingBox()
- const typeField = await page.locator('.paper-meta-field').filter({ hasText: '试卷类型' }).boundingBox()
- expect(Math.abs(yearField!.y - typeField!.y)).toBeLessThan(1)
- expect(typeField!.x).toBeGreaterThan(yearField!.x + yearField!.width)
- await expect(page.locator('.paper-capture-page .demo-banner')).toHaveCount(0)
- await expect(page.locator('.capture-zone').getByText('拍照', { exact: true })).toBeVisible()
- await page.getByText('查看示例', { exact: true }).click()
- await page.locator('.back-button').click()
- await expect(page.locator('input[placeholder="请输入试卷名称"]')).toHaveValue('回归测试卷')
- await page.getByText('已添加 1 张 · 查看 ›').click()
- await expect(page.locator('.gallery-row')).toHaveCount(1)
- await page.locator('.gallery-footer').getByText('完成', { exact: true }).click()
- await expect(page.getByText('已添加 1 张 · 查看 ›')).toBeVisible()
- await page.screenshot({ path: `test-results/paper-capture-${test.info().project.name}.png`, fullPage: true, scale: 'css' })
- expect(await active(page).evaluate((element) => element.scrollWidth <= window.innerWidth)).toBeTruthy()
- const documentChooser = page.waitForEvent('filechooser')
- await page.getByText('Word / PDF 文件', { exact: true }).click()
- await (
- await documentChooser
- ).setFiles({ name: '练习卷.pdf', mimeType: 'application/pdf', buffer: Buffer.from('%PDF-1.4\n%%EOF') })
- await page.getByText('识别全部照片', { exact: true }).click()
- await expect(page.getByText('2 页 · 已识别 6 道题', { exact: false })).toBeVisible()
- await page.screenshot({ path: `test-results/paper-review-${test.info().project.name}.png`, fullPage: true })
- await page.locator('.primary-button:visible').filter({ hasText: /^上传试卷$/ }).click()
- await expect(page.getByText('上传成功,待审核')).toBeVisible()
- await expect(page.getByText('试卷审核通过', { exact: true })).toBeVisible({ timeout: 25000 })
- await page.getByText('查看上传记录', { exact: true }).click()
- await page.reload()
- await expect(page.getByText('审核通过', { exact: true })).toBeVisible()
- await page.getByText('查看原图', { exact: true }).click()
- await expect(page.locator('.original-image')).toBeVisible()
- await expect.poll(() => page.locator('.original-image img').evaluate((img) => (img as HTMLImageElement).naturalWidth)).toBeGreaterThan(0)
- await page.getByText('第 2 页', { exact: true }).click()
- await expect(page.getByText('文档文件,暂不提供原图预览')).toBeVisible()
- })
|