Files
WebDrop/scripts/browser-account-channel-smoke.js

150 lines
11 KiB
JavaScript

// Run with the Playwright browser_run_code tool's filename option against an isolated development API.
async (page) => {
const origin = new URL(page.url()).origin;
const password = 'Architecture-test-password-2026';
const suffix = Date.now().toString();
const ownerName = `smoke_owner_${suffix}`;
const memberName = `smoke_member_${suffix}`;
const browser = page.context().browser();
const contexts = [];
const results = [];
async function login(target, username, create = false, path = '/') {
await target.goto(origin + path);
if (!create) await target.getByRole('button', { name: '已有账号', exact: true }).click();
await target.getByLabel('用户名', { exact: true }).fill(username);
await target.getByLabel('密码', { exact: true }).fill(password);
await target.getByRole('button', { name: create ? '创建账号' : '登录并同步', exact: true }).click();
await target.getByRole('button', { name: '新建空间', exact: true }).waitFor();
}
async function newDevice() {
const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
contexts.push(context); return context.newPage();
}
try {
const owner = await newDevice();
await login(owner, ownerName, true);
await owner.getByLabel('个人名称', { exact: true }).fill('多设备测试用户');
const avatar = await owner.evaluate(() => {
const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32;
const context = canvas.getContext('2d'); context.fillRect(0, 0, 32, 32); return canvas.toDataURL('image/png').split(',')[1];
});
await owner.getByLabel('头像', { exact: true }).setInputFiles({ name: 'avatar.png', mimeType: 'image/png', buffer: Buffer.from(avatar, 'base64') });
await owner.getByRole('img', { name: '个人头像', exact: true }).waitFor();
await owner.getByRole('button', { name: '保存个人资料', exact: true }).click();
await owner.getByText('个人资料已保存并同步。', { exact: true }).waitFor();
await owner.reload();
await owner.getByRole('button', { name: '新建空间', exact: true }).waitFor();
if (await owner.getByLabel('个人名称', { exact: true }).inputValue() !== '多设备测试用户') throw new Error('Profile did not survive reload');
await owner.getByRole('img', { name: '个人头像', exact: true }).waitFor();
results.push('server-owned profile and avatar survive login and reload');
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();
await owner.getByRole('button', { name: '新建频道分组', exact: true }).click();
await owner.getByLabel('分组名称', { exact: true }).fill('工作');
await owner.getByRole('button', { name: '创建', exact: true }).click();
await owner.getByRole('button', { name: '在 工作 新建频道', exact: true }).click();
await owner.getByLabel('频道名称', { exact: true }).fill('讨论');
await owner.getByRole('button', { name: '创建', exact: true }).click();
await owner.getByRole('button', { name: '讨论', exact: true }).waitFor();
await owner.getByRole('button', { name: '常规', exact: true }).click();
await owner.getByRole('button', { name: '频道设置 常规', exact: true }).click();
await owner.getByLabel('所属分组').selectOption({ label: '工作' });
await owner.getByRole('button', { name: '保存', exact: true }).click();
await owner.getByRole('button', { name: '保存', exact: true }).waitFor({ state: 'hidden' });
await owner.getByRole('button', { name: '重命名分组 工作', exact: true }).click();
await owner.getByLabel('分组名称', { exact: true }).fill('项目');
await owner.getByRole('button', { name: '保存', exact: true }).click();
await owner.getByRole('button', { name: '保存', exact: true }).waitFor({ state: 'hidden' });
await owner.getByRole('button', { name: '分组 项目', exact: true }).click();
if (await owner.getByRole('button', { name: '讨论', exact: true }).isVisible()) throw new Error('Group did not collapse');
await owner.getByRole('button', { name: '分组 项目', exact: true }).click();
await owner.reload();
await owner.getByRole('region', { name: '频道分组 项目', exact: true }).getByRole('button', { name: '常规', exact: true }).waitFor();
await owner.getByRole('region', { name: '频道分组 项目', exact: true }).getByRole('button', { name: '讨论', exact: true }).waitFor();
await owner.getByRole('button', { name: '讨论', exact: true }).click();
await owner.getByRole('button', { name: '频道设置 讨论', exact: true }).click();
await owner.getByLabel('所属分组').selectOption({ label: '未分组' });
await owner.getByRole('button', { name: '保存', exact: true }).click();
await owner.getByRole('button', { name: '保存', exact: true }).waitFor({ state: 'hidden' });
if (await owner.getByRole('region', { name: '频道分组 项目', exact: true }).getByRole('button', { name: '讨论', exact: true }).count()) throw new Error('Ungrouping did not remove group membership');
await owner.getByRole('button', { name: '频道设置 讨论', exact: true }).click();
await owner.getByLabel('所属分组').selectOption({ label: '项目' });
await owner.getByRole('button', { name: '保存', exact: true }).click();
await owner.getByRole('button', { name: '保存', exact: true }).waitFor({ state: 'hidden' });
await owner.getByRole('button', { name: '常规', exact: true }).click();
results.push('backend channels and groups create, rename, collapse and survive reload');
const path = new URL(owner.url()).pathname;
if (owner.url().includes('#')) throw new Error('Account channel navigation retained a secret fragment');
await owner.locator('textarea').last().fill('常规 thread 聊天验证');
await owner.getByRole('button', { name: '发送消息', exact: true }).click();
await owner.locator('article p').filter({ hasText: '常规 thread 聊天验证' }).waitFor();
const payload = '端到端文件验证:只有成员设备可解密。'.repeat(60000);
await owner.getByRole('region', { name: '频道文件管理器' }).locator('input[type=file]').setInputFiles({ name: 'architecture-file.txt', mimeType: 'text/plain', buffer: Buffer.from(payload) });
await owner.getByRole('button', { name: '继续上传', exact: true }).click();
await owner.getByRole('button', { name: 'architecture-file.txt', exact: true }).waitFor();
results.push('default thread, center chat and right file explorer work');
const second = await newDevice();
await login(second, ownerName, false, path);
await second.getByRole('button', { name: 'architecture-file.txt', exact: true }).waitFor();
await second.locator('article p').filter({ hasText: '常规 thread 聊天验证' }).waitFor();
await second.getByRole('region', { name: '频道分组 项目', exact: true }).getByRole('button', { name: '讨论', exact: true }).waitFor();
results.push('second independent device recovers owner channel without a secret link');
const member = await newDevice();
await login(member, memberName, true);
await owner.getByRole('button', { name: '空间设置', exact: true }).click();
await owner.getByLabel('被邀请用户的用户名').fill(memberName);
await owner.getByRole('button', { name: '发送邀请', exact: true }).click();
await owner.getByText(`${memberName} · 已邀请`, { exact: true }).waitFor();
await owner.getByRole('dialog').getByRole('button', { name: '关闭', exact: true }).click();
let downloads = 0;
member.on('request', (request) => { if (/\/items\/[^/]+\/frames/.test(request.url())) downloads++; });
await member.goto(origin + path);
await member.getByRole('button', { name: '接受并加入空间', exact: true }).click();
await member.getByRole('button', { name: 'architecture-file.txt', exact: true }).waitFor();
if (downloads !== 0) throw new Error('File catalog consumed a gated download');
await member.getByRole('button', { name: 'architecture-file.txt', exact: true }).dblclick();
await member.getByRole('button', { name: '解密并预览文件', exact: true }).click();
await member.locator('pre').filter({ hasText: '端到端文件验证' }).waitFor();
if (downloads !== 1) throw new Error(`Expected one explicit gated read, got ${downloads}`);
await member.reload();
await member.getByRole('button', { name: 'architecture-file.txt', exact: true }).waitFor();
if (downloads !== 1) throw new Error('Refreshing consumed another download');
results.push('account invitation automatically unlocks ciphertext and catalog never auto-downloads');
await owner.getByRole('button', { name: '新建文件夹', exact: true }).click();
await owner.getByLabel('文件夹名称', { exact: true }).fill('资料目录');
await owner.getByRole('button', { name: '创建', exact: true }).click();
await owner.getByRole('button', { name: '资料目录', exact: true }).waitFor();
await owner.getByRole('checkbox', { name: '选择文件 architecture-file.txt', exact: true }).check();
await owner.getByLabel('移动所选文件到文件夹').selectOption({ label: '资料目录' });
await owner.getByRole('button', { name: '资料目录', exact: true }).dblclick();
await owner.getByRole('button', { name: 'architecture-file.txt', exact: true }).waitFor();
results.push('backend folders and file locations update without re-encrypting content');
await second.goto(origin);
const selfRow = second.getByRole('region', { name: '账号设备' }).locator('div').filter({ has: second.getByText('本机', { exact: true }) });
await selfRow.locator('input').fill('验收副设备');
await selfRow.getByRole('button', { name: '重命名', exact: true }).click();
await second.getByLabel('验收副设备 的设备名称', { exact: true }).waitFor();
await owner.goto(origin);
const otherRow = owner.getByRole('region', { name: '账号设备' }).locator('div').filter({ has: owner.getByLabel('验收副设备 的设备名称', { exact: true }) });
await owner.evaluate(() => { window.confirm = () => true; });
await otherRow.getByRole('button', { name: '移除设备', exact: true }).click();
await second.reload();
await second.getByRole('button', { name: '创建账号', exact: true }).waitFor();
await owner.reload();
await owner.getByRole('button', { name: '新建空间', exact: true }).waitFor();
results.push('revoked device loses remembered login while the other device stays logged in');
await member.goto(origin + path);
for (const width of [1440, 390]) {
await member.setViewportSize({ width, height: 900 });
for (const colorScheme of ['light', 'dark']) {
await member.emulateMedia({ colorScheme });
if (await member.evaluate(() => document.documentElement.scrollWidth > innerWidth)) throw new Error(`Horizontal overflow at ${width}/${colorScheme}`);
}
}
results.push('desktop/mobile and light/dark layouts fit viewport');
return { passed: results };
} finally { for (const context of contexts) await context.close(); }
}