import fs from 'node:fs' import path from 'node:path' import { expect, type Frame, type Page, type TestInfo } from '@playwright/test' // @ts-ignore Repository helper reads credentials privately; no trace/storageState is emitted. import { call, login, fixture, writeFixture, report, work } from '../tools/model-editor-closure-live.mjs' export { call, login, fixture, writeFixture, report, work } export async function signIn(page: Page, account = 'root') { const session = await login(account) await page.addInitScript(({ accessToken, refreshToken }) => { if (!sessionStorage.getItem('model-closure-initialized')) { sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken) sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken || '') sessionStorage.setItem('model-closure-initialized', '1') } }, { accessToken: session.accessToken, refreshToken: session.refreshToken }) return session.accessToken as string } export async function openEditor(page: Page, key: 'fox' | 'truck' | string = 'fox') { const id = fixture().models[key]?.id || key await page.goto(`/content/models/${id}/edit`) return readyFrame(page) } export async function readyFrame(page: Page): Promise { await expect.poll(() => page.frames().some(frame => frame.url().includes('/legacy-model-editor/editor.html')), { timeout: 60_000 }).toBe(true) const frame = page.frames().find(frame => frame.url().includes('/legacy-model-editor/editor.html'))! await frame.waitForFunction(() => { const editor = (window as any).editorApp return editor?.apiEditorHydrated && editor?.modelPivot && editor?.apiState === 'ready' && !editor.apiImportBusy && !editor.apiCoverSavePending }, undefined, { timeout: 90_000 }) await expect(frame.locator('.editor-shell')).toHaveAttribute('data-host-readonly', /true|false/) return frame } export async function saveEditor(frame: Frame) { await frame.locator('[data-action="save"]').first().click() await expect.poll(() => frame.evaluate(() => { const e = (window as any).editorApp return e.apiState === 'ready' && !e.apiCoverSavePending && !e.apiSession.savePending })).toBe(true) } export async function selectMesh(frame: Frame) { await frame.locator('[data-selection-scope="part"]').click() const mesh = await frame.evaluate(() => { const e = (window as any).editorApp let selected: any e.modelPivot.traverse((o: any) => { if (!selected && o.isMesh) selected = o }) return { uuid: selected.uuid, id: selected.userData.editorId, name: selected.name } }) await frame.locator(`[data-object-id="${mesh.uuid}"]`).click() return mesh } export async function setRange(frame: Frame, selector: string, value: string) { await frame.locator(selector).evaluate((element, next) => { (element as HTMLInputElement).value = next element.dispatchEvent(new Event('input', { bubbles: true })) element.dispatchEvent(new Event('change', { bubbles: true })) }, value) } export async function shot(page: Page, info: TestInfo, name: string) { const folder = path.join(report, 'screenshots') fs.mkdirSync(folder, { recursive: true }) await expect(page.locator('.el-loading-mask:visible')).toHaveCount(0) const file = path.join(folder, `${name}.png`) await page.screenshot({ path: file, fullPage: true, animations: 'disabled' }) await info.attach(name, { path: file, contentType: 'image/png' }) } export async function closeSession(page: Page) { const token = await page.evaluate(() => sessionStorage.getItem('unreal-tran:web:access-token:v1')).catch(() => null) if (token) await call('auth/v1/auth/logout', token, {}).catch(() => {}) } export function evidence(name: string, data: unknown) { const folder = path.join(report, 'evidence') fs.mkdirSync(folder, { recursive: true }) fs.writeFileSync(path.join(folder, `${name}.json`), JSON.stringify({ time: new Date().toISOString(), data }, null, 2)) }