fad9760265
- 新增 Node 服务端(Hono + Hocuspocus + node:sqlite),数据保存在服务器 - 基于 Yjs 的多端实时同步,本机 IndexedDB 离线缓存,撤销只针对本机操作 - 空间(标签)有独立地址,增删改通过接口同步到所有设备 - 口令登录(签名 Cookie 会话、登录限流、同源校验) - 图片上传到服务器,过大照片上传前自动压缩 - 历史版本(自动/手动保存、一键恢复)、跨空间搜索、在线设备 - 随机油画加载动画(Painting Loaders,本地收录 p5 / p5.brush) - 选中样式与拖拽性能优化:拖动结束后才同步位置 - Docker / docker-compose 部署,README 与 CLAUDE.md 更新
59 lines
2.4 KiB
TypeScript
59 lines
2.4 KiB
TypeScript
import { expect, type Browser, type Page } from '@playwright/test'
|
|
|
|
export const E2E_PASSWORD = 'e2e-password'
|
|
|
|
export async function login(page: Page, path = '/') {
|
|
await page.goto(path)
|
|
await expect(page).toHaveURL(/\/login/)
|
|
await page.getByLabel('登录口令').fill(E2E_PASSWORD)
|
|
await page.getByLabel('登录口令').press('Enter')
|
|
await expect(page).toHaveURL(/\/s\//)
|
|
await expect(page.getByText('已同步', { exact: true }).first()).toBeVisible()
|
|
}
|
|
|
|
/** 新开一个独立的浏览器上下文,相当于另一台设备 */
|
|
export async function openDevice(browser: Browser, path = '/') {
|
|
const context = await browser.newContext({ reducedMotion: 'reduce', locale: 'zh-CN' })
|
|
const page = await context.newPage()
|
|
await login(page, path)
|
|
return { context, page }
|
|
}
|
|
|
|
/** 在当前空间正中新建一张便笺并改标题 */
|
|
export async function createNote(page: Page, title: string) {
|
|
const before = await page.locator('.note-card').count()
|
|
await page.getByRole('button', { name: '新便笺' }).click()
|
|
await expect(page.locator('.note-card')).toHaveCount(before + 1)
|
|
const card = page.locator('.note-card--selected')
|
|
await card.locator('.note-card__title').fill(title)
|
|
return card
|
|
}
|
|
|
|
/** 当前画布上所有卡片的标题(输入框的值不会出现在 DOM 属性里,只能这样读) */
|
|
export function noteTitles(page: Page) {
|
|
return page
|
|
.locator('.note-card__title')
|
|
.evaluateAll((inputs) => inputs.map((input) => (input as HTMLInputElement).value))
|
|
}
|
|
|
|
export async function expectNoteTitle(page: Page, title: string, present = true) {
|
|
const poll = expect.poll(() => noteTitles(page), { timeout: 10_000 })
|
|
if (present) await poll.toContain(title)
|
|
else await poll.not.toContain(title)
|
|
}
|
|
|
|
export async function noteByTitle(page: Page, title: string) {
|
|
await expectNoteTitle(page, title)
|
|
const index = (await noteTitles(page)).indexOf(title)
|
|
return page.locator('.note-card').nth(index)
|
|
}
|
|
|
|
/** 新建一个空间并切换过去,避免用例之间互相影响 */
|
|
export async function createSpace(page: Page, name: string) {
|
|
await page.getByRole('button', { name: '创建空间' }).click()
|
|
await page.getByPlaceholder('例如:灵感收纳盒、工作面板…').fill(name)
|
|
await page.getByRole('button', { name: '立即创建' }).click()
|
|
await expect(page.locator('.space-title')).toContainText(name)
|
|
await expect(page.getByText('已同步', { exact: true }).first()).toBeVisible()
|
|
}
|