import fs from 'node:fs' import path from 'node:path' import assert from 'node:assert/strict' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import { login, call } from './role-permissions-live.mjs' const require = createRequire(new URL('../package.json', import.meta.url)) const { chromium, expect } = require('@playwright/test') const report = fileURLToPath(new URL('../reports/advanced-drag-layout-20260909/', import.meta.url)) fs.mkdirSync(report, { recursive: true }) const session = await login('root') const project = (await call('tran/v1/content/projects?type=TRAINING&size=100', session.accessToken)).data.records.find(row => row.name.includes('APE2E-数字人讲解-狐狸')) assert.ok(project) const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--enable-unsafe-swiftshader'] }) let page const checks = [] try { const context = await browser.newContext({ viewport: { width: 1920, height: 911 } }) await context.addInitScript(({ accessToken, refreshToken }) => { sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken) sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken) localStorage.setItem('unreal-tran:web:preferences:v1', JSON.stringify({ themeMode: 'military' })) }, session) page = await context.newPage() await page.goto(`http://127.0.0.1:6180/content/training-projects/${project.id}/legacy`) await page.getByRole('radio', { name: '高级编排', exact: true }).click({ timeout: 60000 }) const canvas = page.locator('[data-role="flow-canvas"]') const node = page.locator('[data-flow-node="flow-start"]') const head = node.locator('.tfa-node-head') await head.scrollIntoViewIfNeeded() const initial = await node.evaluate(el => ({ x: parseFloat(el.style.left), y: parseFloat(el.style.top) })) const box = await head.boundingBox(), area = await canvas.boundingBox() await page.mouse.move(box.x + 45, box.y + 22) await page.mouse.down() const pointerY = area.y + area.height - 100 await page.mouse.move(box.x + 45, pointerY, { steps: 12 }) const before = await node.evaluate(el => parseFloat(el.style.top)) const scrollDelta = await canvas.evaluate(el => { const old = el.scrollTop; el.scrollTop += 180; return el.scrollTop - old }) assert.ok(scrollDelta > 80) await page.mouse.move(box.x + 45, pointerY + 5, { steps: 2 }) const after = await node.evaluate(el => parseFloat(el.style.top)) assert.ok(Math.abs(after - before - scrollDelta - 5) < 3, 'Node follows pointer in scrolled coordinates') const oldLimit = await canvas.evaluate(el => el.clientHeight - 110) assert.ok(after > oldLimit + 80, 'Node reaches grid below the former viewport limit') await page.mouse.up() checks.push({ name: '拖入下方网格,拖动中滚动不跳位', result: 'PASS', before, after, scrollDelta, formerLimit: oldLimit }) const scrollBefore = await canvas.evaluate(el => el.scrollTop) await head.click() await expect.poll(() => canvas.evaluate(el => el.scrollTop)).toBe(scrollBefore) await expect.poll(() => node.evaluate(el => parseFloat(el.style.top))).toBe(after) await page.screenshot({ path: path.join(report, '01-lower-grid.png'), animations: 'disabled' }) checks.push({ name: '选择节点后保留画布滚动位置', result: 'PASS' }) await page.setViewportSize({ width: 1366, height: 768 }) const last = page.locator('.tfa-node').last(), lastHead = last.locator('.tfa-node-head') await lastHead.scrollIntoViewIfNeeded() const previous = await last.evaluate(el => ({ x: parseFloat(el.style.left), y: parseFloat(el.style.top) })) const lastBox = await lastHead.boundingBox() await page.mouse.move(lastBox.x + 45, lastBox.y + 22) await page.mouse.down() await page.mouse.move(lastBox.x + 55, lastBox.y + 32, { steps: 4 }) await page.mouse.up() const next = await last.evaluate(el => ({ x: parseFloat(el.style.left), y: parseFloat(el.style.top) })) assert.ok(Math.abs(next.x - previous.x - 10) < 2 && Math.abs(next.y - previous.y - 10) < 2, 'Small drag after horizontal scroll must not jump to viewport limit') await page.screenshot({ path: path.join(report, '02-horizontal-scroll.png'), animations: 'disabled' }) checks.push({ name: '窄窗口横向滚动后拖动不跳位', result: 'PASS', previous, next }) await page.getByRole('radio', { name: '基础编排', exact: true }).click() await page.getByRole('radio', { name: '高级编排', exact: true }).click() assert.equal(await node.evaluate(el => parseFloat(el.style.top)), after) checks.push({ name: '切换编排模式保留节点坐标', result: 'PASS' }) fs.writeFileSync(path.join(report, 'results.json'), JSON.stringify({ passed: true, projectId: project.id, accountType: '内部维护', initial, checks, dataChanges: '仅调整已有测试工程的浏览器草稿,未点击保存或发布。' }, null, 2)) console.log('Advanced drag layout checks passed') } catch (error) { if (page) await page.screenshot({ path: path.join(report, 'failure.png'), animations: 'disabled' }).catch(() => {}) throw error } finally { await browser.close() }