// Works against either the development proxy or the embedded production frontend. async (page) => { const origin = new URL(page.url()).origin; const context = await page.context().browser().newContext({ viewport: { width: 1440, height: 900 } }); const owner = await context.newPage(); const results = []; try { await owner.goto(origin); await owner.getByLabel('用户名', { exact: true }).fill(`backend_${Date.now()}`); await owner.getByLabel('密码', { exact: true }).fill('Backend-data-test-password'); await owner.getByRole('button', { name: '创建账号', exact: true }).click(); await owner.getByRole('button', { name: '新建空间', exact: true }).click(); await owner.getByLabel('空间名称(加入者和服务端可见)').fill('后端数据源测试'); await owner.getByRole('button', { name: '创建', exact: true }).click(); await owner.getByRole('button', { name: '常规', exact: true }).waitFor(); const spaceId = new URL(owner.url()).pathname.split('/').pop(); await owner.getByRole('button', { name: '新建频道分组', exact: true }).click(); await owner.getByLabel('分组名称', { exact: true }).fill('前端分组'); const requestPromise = owner.waitForRequest(request => request.method() === 'POST' && request.url().endsWith(`/spaces/${spaceId}/groups`)); await owner.getByRole('button', { name: '创建', exact: true }).click(); const request = await requestPromise; const allHeaders = await request.allHeaders(); const headers = Object.fromEntries(['authorization', 'x-wd-role', 'x-wd-endpoint', 'x-wd-account', 'content-type'].filter(key => allHeaders[key]).map(key => [key, allHeaders[key]])); await owner.getByRole('button', { name: '分组 前端分组', exact: true }).waitFor(); const initial = await context.request.get(`${origin}/api/v1/spaces/${spaceId}/structure`, { headers }).then(r => r.json()); const groupId = initial.structure.groups[0].id; const renamed = await context.request.patch(`${origin}/api/v1/spaces/${spaceId}/groups/${groupId}`, { headers, data: { name: '后端分组' } }); if (!renamed.ok()) throw new Error(`Backend rename failed ${renamed.status()}`); await owner.getByRole('button', { name: '分组 后端分组', exact: true }).waitFor(); const created = await context.request.post(`${origin}/api/v1/spaces/${spaceId}/channels`, { headers, data: { name: '后端频道', groupId } }); if (created.status() !== 201) throw new Error('Backend channel creation failed'); await owner.getByRole('button', { name: '后端频道', exact: true }).waitFor(); results.push('direct backend group and channel edits update the UI without client structure records'); const locationResponse = owner.waitForResponse(response => response.request().method() === 'PUT' && response.url().endsWith(`/spaces/${spaceId}/locations`)); await owner.getByRole('region', { name: '常规 聊天', exact: true }).locator('textarea').fill('后端作者归属测试'); await owner.getByRole('button', { name: '发送消息', exact: true }).click(); if (!(await locationResponse).ok()) throw new Error('Backend item association failed'); const profile = await context.request.patch(`${origin}/api/v1/accounts/profile`, { headers, data: { displayName: '后端昵称' } }); if (!profile.ok()) throw new Error('Backend profile update failed'); await owner.getByRole('article', { name: '后端昵称 的消息', exact: true }).getByText('后端作者归属测试', { exact: true }).waitFor(); results.push('backend account profile and stored authors determine message identity'); const spaces = await context.request.get(`${origin}/api/v1/accounts/spaces`, { headers }).then(r => r.json()); if (!spaces.some(space => space.id === spaceId) || spaces.some(space => 'read_hash' in space || 'ownerLink' in space || 'memberLink' in space)) throw new Error('Space metadata catalog is invalid'); for (const path of ['/accounts/profile/import', `/spaces/${spaceId}/structure/import`]) { const response = await context.request.post(`${origin}/api/v1${path}`, { headers, data: {} }); if (response.status() !== 404 && response.status() !== 405) throw new Error('Unexpected legacy import route'); } await owner.reload(); await owner.getByRole('button', { name: '后端频道', exact: true }).waitFor(); await owner.getByRole('article', { name: '后端昵称 的消息', exact: true }).waitFor(); results.push('server-owned state survives reload and there are no migration endpoints'); return { passed: results }; } finally { await context.close(); } }