import assert from 'node:assert/strict' import { chromium } from 'playwright' const browser = await chromium.launch({ headless: true, channel: 'chrome' }) const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } }) const page = await context.newPage() page.setDefaultTimeout(20000) page.on('console', (msg) => { if (msg.type() === 'error') console.log('CONSOLE', msg.text()) }) page.on('requestfailed', (req) => console.log('REQUESTFAILED', req.url(), req.failure())) const errors = [] page.on('pageerror', (error) => errors.push(error.message)) let sharedUsers = [] let uploads = 0 let lastUploadBody = '' let lastParentId = '' let deletes = 0 let mineDeleted = false let failList = false let folders = [] const makeRoot = (scope, name, children = []) => ({ id: `scope:${scope}`, name, scope, folderId: '', fullPath: `${name} / 全部文件`, ownerId: '', canManage: scope === 'mine', count: 1, children }) const file = { id: 'mine-1', name: '产品资料.pdf', folderId: '', folderPath: '我的文档 / 根目录', size: 1234, contentType: 'application/pdf', ownerId: 'fixture', ownerName: '测试', createTime: '2026-10-26 10:00:00', updateTime: '2026-10-26 10:00:00', canManage: true, shares: [] } const sharedFile = { ...file, id: 'shared-1', name: '共享制度.pdf', ownerId: 'other', ownerName: '同事', canManage: false, shares: [{ id: 'fixture', name: '测试', deptName: '研发部' }] } await context.addInitScript(() => { localStorage.setItem( 'sys-v3.0.2-user', JSON.stringify({ isLogin: true, accessToken: 'fixture', info: { id: 'fixture', userId: 'fixture', userName: '测试', role: 'admin', roles: ['admin'] } }) ) localStorage.setItem('sys-version', '3.0.2') }) await context.route('**/api/**', async (route) => { const req = route.request(), url = new URL(req.url()), path = url.pathname.slice(4) if (!url.pathname.startsWith('/api/')) return route.continue() const ok = (data) => route.fulfill({ json: { code: 200, data } }) if (path === '/web/auth/current-user') return ok({ id: 'fixture', userId: 'fixture', userName: '测试', role: 'admin', roles: ['admin'] }) if (path === '/web/auth/menu-codes') return ok({ all: true, menuCodes: [] }) if (path === '/web/doclib/tree') return ok({ nodes: [makeRoot('mine', '我的文档', folders), makeRoot('shared', '分享给我')], isSuperAdmin: false, maxUploadBytes: 100000 }) if (path === '/web/doclib/files') { if (failList) return route.fulfill({ status: 400, json: { code: 400, message: '加载失败' } }) const row = url.searchParams.get('scope') === 'shared' ? sharedFile : file const list = !mineDeleted && (!url.searchParams.get('keyword') || row.name.includes(url.searchParams.get('keyword'))) ? [row] : [] return ok({ list, total: list.length }) } if (path === '/web/doclib/users') return ok([{ id: 'other', name: '同事', deptName: '生产部' }]) if (path === '/web/doclib/files/batch-shares' && req.method() === 'POST') { const body = req.postDataJSON() sharedUsers = body.userIds file.shares = sharedUsers.map((id) => ({ id, name: '同事', deptName: '生产部' })) return ok([file]) } if (path.endsWith('/shares')) { sharedUsers = req.postDataJSON().userIds file.shares = sharedUsers.map((id) => ({ id, name: '同事', deptName: '生产部' })) return ok(file) } if (path === '/web/doclib/folders' && req.method() === 'POST') { const body = req.postDataJSON() lastParentId = body.parentId const folder = { id: `mine:f${folders.length + 1}`, folderId: `f${folders.length + 1}`, name: body.name, fullPath: `我的文档 / ${body.name}`, scope: 'mine', ownerId: 'fixture', canManage: true, count: 0, children: [] } folders.push(folder) return ok(folder) } if (path === '/web/doclib/upload') { lastUploadBody = req.postData() || '' uploads++ return ok({ ...file, id: `uploaded-${uploads}`, name: '上传测试.txt' }) } if (path === '/web/doclib/files/batch-delete') { mineDeleted = true deletes++ return ok(null) } if (path.endsWith('/download')) return route.fulfill({ body: 'fixture file', contentType: 'application/octet-stream' }) if (path === '/web/doclib/files/mine-1') return ok(file) if (path === '/web/doclib/files/shared-1') return ok(sharedFile) return ok(path.includes('favorites') || path.includes('upgrade-logs') ? [] : {}) }) try { await page.goto('http://localhost:3006/#/doclib/enter') await page.getByText('文档库', { exact: true }).first().click() await page.locator('.doclib-page').waitFor() assert.equal(context.pages().length, 1) await page.locator('.doclib-file-text .el-link').filter({ hasText: '产品资料.pdf' }).waitFor() assert.equal(await page.locator('.doclib-tree-node').count(), 2) await page.locator('.doclib-tree-node').filter({ hasText: '分享给我' }).click() await page.locator('.doclib-file-text .el-link').filter({ hasText: '共享制度.pdf' }).waitFor() await page.locator('.el-table__body .el-checkbox').first().click() assert.equal(await page.getByRole('button', { name: '设置分享', exact: true }).isDisabled(), true) assert.equal(await page.getByRole('button', { name: '删除', exact: true }).isDisabled(), true) assert.equal(await page.getByRole('button', { name: '下载', exact: true }).isEnabled(), true) assert.equal(await page.getByRole('button', { name: '上传文档', exact: true }).count(), 0) await page.locator('.doclib-file-text .el-link').filter({ hasText: '共享制度.pdf' }).click() const detail = page.locator('.system-view-dialog:visible') await detail.getByRole('button', { name: '下载文档', exact: true }).waitFor() assert.equal(await detail.getByRole('button', { name: '设置分享', exact: true }).count(), 0) const downloaded = page.waitForEvent('download') await detail.getByRole('button', { name: '下载文档', exact: true }).click() assert.equal((await downloaded).suggestedFilename(), '共享制度.pdf') await detail.locator('.el-dialog__headerbtn').click() await page.locator('.doclib-tree-node').filter({ hasText: '我的文档' }).click() await page.locator('.doclib-file-text .el-link').filter({ hasText: '产品资料.pdf' }).waitFor() assert.match(await page.locator('.doclib-selected').innerText(), /0/) await page.locator('.el-table__body .el-checkbox').first().click() await page.getByRole('button', { name: '设置分享', exact: true }).click() const share = page.locator('.doclib-dialog:visible') await share.getByRole('combobox').click() await page.getByRole('option', { name: /同事/ }).click() await share.getByRole('button', { name: '保存分享' }).click() await share.waitFor({ state: 'hidden' }) assert.deepEqual(sharedUsers, ['other']) await page.getByRole('button', { name: '新建文件夹', exact: true }).click() const folder = page.locator('.doclib-dialog:visible') await folder.getByPlaceholder('请输入文件夹名称').fill('测试目录') await folder.getByRole('button', { name: '保存', exact: true }).click() await page.locator('.doclib-tree-node').filter({ hasText: '测试目录' }).waitFor() assert.equal(lastParentId, '') await page.getByRole('button', { name: '新建文件夹', exact: true }).click() await folder.getByPlaceholder('请输入文件夹名称').fill('子目录') await folder.getByRole('combobox', { name: '上级文件夹', exact: true }).click() await page.getByRole('option', { name: '测试目录', exact: true }).click() await folder.getByRole('button', { name: '保存', exact: true }).click() await page.locator('.doclib-tree-node').filter({ hasText: '子目录' }).waitFor() assert.equal(lastParentId, 'f1') await page.getByRole('button', { name: '上传文档', exact: true }).click() const upload = page.locator('.doclib-dialog:visible') await upload.getByRole('combobox', { name: '上传到', exact: true }).click() await page.getByRole('option', { name: '测试目录', exact: true }).click() await upload.locator('input[type=file]').setInputFiles([ { name: '上传测试.txt', mimeType: 'text/plain', buffer: Buffer.from('上传测试') }, { name: '空文件.txt', mimeType: 'text/plain', buffer: Buffer.from('') } ]) await upload.getByText('已上传', { exact: true }).waitFor() await upload.getByText('不能上传空文件', { exact: true }).waitFor() assert.equal(uploads, 1) assert.match(lastUploadBody, /name="folderId"\r\n\r\nf1/) assert.equal( await upload.getByRole('combobox', { name: '上传到', exact: true }).isDisabled(), true ) await page.waitForFunction(() => !document.querySelector('.el-message')) await page.screenshot({ path: '/tmp/doclib-upload.png', fullPage: true }) await upload.getByRole('button', { name: '完成', exact: true }).click() await page.screenshot({ path: '/tmp/doclib-desktop.png', fullPage: true }) await page.getByPlaceholder('搜索文档名称').fill('不存在') await page.getByRole('button', { name: '搜索', exact: true }).click() await page.getByText('没有匹配的文档').waitFor() failList = true await page.getByRole('button', { name: '刷新', exact: true }).click() await page.getByText('文档加载失败,请点击刷新重试').waitFor() failList = false await page.getByRole('button', { name: '刷新', exact: true }).click() await page.getByText('没有匹配的文档').waitFor() await page.getByPlaceholder('搜索文档名称').fill('') await page.getByRole('button', { name: '搜索', exact: true }).click() await page.locator('.doclib-file-text .el-link').filter({ hasText: '产品资料.pdf' }).waitFor() await page.locator('.el-table__body .el-checkbox').first().click() await page.getByRole('button', { name: '删除', exact: true }).click() await page.getByRole('button', { name: '确认删除', exact: true }).click() await page.getByText('这里还没有文档', { exact: true }).waitFor() assert.match(await page.locator('.doclib-selected').innerText(), /0/) await page.setViewportSize({ width: 700, height: 900 }) assert.equal( await page.locator('.doclib-layout').evaluate((el) => getComputedStyle(el).flexDirection), 'column' ) assert.equal(deletes, 1) assert.deepEqual(errors, []) console.log( 'PASS doclib browser: roots, shared permissions, selection reset, detail, sharing, folders, upload/validation, search, failed load/retry, responsive layout' ) } catch (error) { console.log('PAGE', await page.locator('body').innerText(), errors) await page.screenshot({ path: '/tmp/doclib-browser-failure.png', fullPage: true }) throw error } finally { await browser.close() }