feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,277 @@
|
||||
const fs = require('node:fs/promises')
|
||||
const path = require('node:path')
|
||||
const assert = require('node:assert/strict')
|
||||
if (!process.versions.electron) {
|
||||
;(async () => {
|
||||
const directory = await fs.mkdtemp(path.resolve('.tmp/project-page-'))
|
||||
const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_OMP_HOME: path.join(directory, 'home'), YCODE_DISABLE_GPU: '1', YCODE_DISABLE_SYSTEM_INTEGRATION: '1' }
|
||||
await fs.mkdir(env.YCODE_OMP_HOME, { recursive: true })
|
||||
delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL
|
||||
const child = require('node:child_process').spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', __filename], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
let log = ''
|
||||
child.stdout.on('data', bytes => { log += bytes; process.stdout.write(bytes) })
|
||||
child.stderr.on('data', bytes => { log += bytes })
|
||||
const timer = setTimeout(() => child.kill(), 90000)
|
||||
child.once('exit', async code => { clearTimeout(timer); await fs.writeFile(path.join(directory, 'process.log'), log); process.exitCode = code ?? 1 })
|
||||
})().catch(error => { console.error(error); process.exitCode = 1 })
|
||||
} else {
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const directory = process.env.YCODE_QA_DIRECTORY
|
||||
app.setPath('home', path.join(directory, 'home'))
|
||||
;(async () => {
|
||||
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href)
|
||||
await app.whenReady()
|
||||
let win
|
||||
const wait = async source => {
|
||||
for (let i = 0; i < 300; i++) {
|
||||
win = BrowserWindow.getAllWindows().find(item => item.getTitle() === 'YCode')
|
||||
if (win && !win.webContents.isLoading() && await win.webContents.executeJavaScript(source)) return
|
||||
await new Promise(resolve => setTimeout(resolve, 30))
|
||||
}
|
||||
throw Error(`Timeout: ${source}`)
|
||||
}
|
||||
await wait('!!window.ycode?.projects')
|
||||
win.setContentSize(1336, 902)
|
||||
await wait('innerHeight === 902')
|
||||
await new Promise(resolve => setTimeout(resolve, 250))
|
||||
const errors = []
|
||||
win.webContents.on('console-message', (_event, level, message) => { if (level >= 3) errors.push(message) })
|
||||
const read = source => win.webContents.executeJavaScript(source)
|
||||
const dialogSurfaces = {}
|
||||
const dialogBackgrounds = {}
|
||||
const pixelAt = (image, point, viewport) => {
|
||||
const size = image.getSize(), bitmap = image.toBitmap()
|
||||
const x = Math.floor(point[0] * size.width / viewport[0]), y = Math.floor(point[1] * size.height / viewport[1])
|
||||
const offset = (y * size.width + x) * 4
|
||||
return [bitmap[offset+2],bitmap[offset+1],bitmap[offset]]
|
||||
}
|
||||
const rememberDialogBackground = async theme => {
|
||||
// Theme surfaces transition for 150–200ms. Compare stable pixels, rather
|
||||
// than a halfway theme frame against the fully transitioned modal frame.
|
||||
await read(`Promise.all(document.getAnimations().filter(animation =>
|
||||
Number.isFinite(animation.effect?.getComputedTiming().endTime)).map(animation => animation.finished.catch(()=>{})))`)
|
||||
const region = await read(`(() => { const r=document.querySelector('.conversation-section-label-tasks').getBoundingClientRect();
|
||||
return {point:[r.left+r.width/2,r.top+r.height/2],viewport:[innerWidth,innerHeight]} })()`)
|
||||
dialogBackgrounds[theme] = {...region,pixel:pixelAt(await win.webContents.capturePage(),region.point,region.viewport)}
|
||||
}
|
||||
const verifyDialogSurface = async theme => {
|
||||
await read('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
|
||||
const surface = await read(`(() => {
|
||||
const overlay = document.querySelector('.collab-modal__overlay')
|
||||
const dialog = overlay.querySelector('.collab-modal__container')
|
||||
const backdrop = overlay.querySelector('.collab-modal__backdrop')
|
||||
const box = element => { const r = element.getBoundingClientRect(); return [r.x,r.y,r.width,r.height] }
|
||||
const colors = Object.fromEntries(['container','header','body','footer'].map(part => [part,getComputedStyle(overlay.querySelector('.collab-modal__'+part)).backgroundColor]))
|
||||
const dr = dialog.getBoundingClientRect()
|
||||
const points = [[40,110],[innerWidth-35,180],[dr.left+25,dr.top+20],[dr.right-40,dr.bottom-30]]
|
||||
return { theme:document.documentElement.dataset.theme, viewport:[innerWidth,innerHeight], overlay:box(overlay), dialog:box(dialog), colors,
|
||||
mask:getComputedStyle(overlay).backgroundColor,zIndex:getComputedStyle(overlay).zIndex,
|
||||
hitTargets:points.map(([x,y]) => { const hit=document.elementFromPoint(x,y); return {point:[x,y],className:hit?.className,
|
||||
blocked:hit===backdrop || dialog.contains(hit)} }),
|
||||
inputBackground:getComputedStyle(document.querySelector('#yc-project-name')).backgroundColor }
|
||||
})()`)
|
||||
const image = await win.webContents.capturePage()
|
||||
const background = dialogBackgrounds[theme]
|
||||
const alpha = Number(surface.mask.match(/,\s*([\d.]+)\)$/)?.[1] ?? 1)
|
||||
surface.backgroundSample = {point:background.point,before:background.pixel,
|
||||
expected:background.pixel.map(channel=>Math.round(channel*(1-alpha))),
|
||||
actual:pixelAt(image,background.point,background.viewport)}
|
||||
dialogSurfaces[theme] = surface
|
||||
await fs.writeFile(path.join(directory, `project-dialog-${theme}.png`), image.toPNG())
|
||||
await fs.writeFile(path.join(directory, 'dialog-surfaces.json'), JSON.stringify(dialogSurfaces,null,2))
|
||||
const opaque = value => /^rgb\(/.test(value) || /, 1\)$/.test(value)
|
||||
assert.ok(Object.values(surface.colors).every(opaque), `${theme}: dialog regions must be opaque`)
|
||||
assert.deepEqual(surface.overlay, [0,0,...surface.viewport], `${theme}: mask covers the whole viewport`)
|
||||
assert.ok(surface.hitTargets.every(item=>item.blocked), `${theme}: background chrome must stay below the modal mask`)
|
||||
assert.ok(Number(surface.zIndex)>=101, `${theme}: modal owns a layer above the page chrome`)
|
||||
assert.ok(surface.backgroundSample.actual.every((channel,index)=>Math.abs(channel-surface.backgroundSample.expected[index])<=2),
|
||||
`${theme}: sticky sidebar headings must be visually dimmed by the mask`)
|
||||
assert.equal(surface.dialog[2], 640)
|
||||
}
|
||||
const connector = await read(`window.ycode.mcp.save({name:'项目连接器',transport:'stdio',command:'node',args:['--version'],enabled:false})`)
|
||||
const enter = async () => {
|
||||
await wait('!!document.querySelector(".conversation-list-tab-button-more")')
|
||||
await read('document.querySelector(".conversation-list-tab-button-more").click()')
|
||||
await wait('!!document.querySelector(".yc-project-more button")')
|
||||
await read('document.querySelector(".yc-project-more button").click()')
|
||||
await wait('!!document.querySelector(".landing-new-btn")')
|
||||
}
|
||||
await enter()
|
||||
await wait('!!document.querySelector(".project-grid__empty")')
|
||||
assert.ok((await read('document.querySelector(".project-grid__empty").textContent')).includes('还没有项目'))
|
||||
assert.equal(await read('document.querySelectorAll(".landing-template-card").length'), 5)
|
||||
await rememberDialogBackground('light')
|
||||
await read('document.querySelector(".landing-new-btn").click()')
|
||||
await wait('!!document.querySelector("#yc-project-name")')
|
||||
await verifyDialogSurface('light')
|
||||
assert.equal(await read('document.querySelector(".collab-modal__footer button:last-child").disabled'), true)
|
||||
await read(`(() => { const input = document.querySelector('#yc-project-name'); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(input, '项目验收'); input.dispatchEvent(new Event('input', { bubbles: true })); })()`)
|
||||
await wait('!document.querySelector(".collab-modal__footer button:last-child").disabled')
|
||||
await read('document.querySelector("[data-capability-kind=connectors] .cp-row__action").click()')
|
||||
await wait('!!document.querySelector("[data-capability-kind=connectors] input[type=checkbox]")')
|
||||
await read('document.querySelector("[data-capability-kind=connectors] input[type=checkbox]").click()')
|
||||
await wait('document.querySelector("[data-capability-kind=connectors] .cp-chip__name")?.textContent === "项目连接器"')
|
||||
await read('document.querySelector("[data-capability-kind=connectors] .cp-chip__x").click()')
|
||||
await wait('!document.querySelector("[data-capability-kind=connectors] .cp-chip")')
|
||||
await read('document.querySelector("[data-capability-kind=connectors] input[type=checkbox]").click()')
|
||||
await read('document.querySelector(".collab-modal__footer button:last-child").click()')
|
||||
await wait('!document.querySelector(".collab-modal__container") && document.querySelector(".project-grid__card-name-text")?.textContent === "项目验收"')
|
||||
const records = JSON.parse(await fs.readFile(path.join(directory, 'data', 'projects.json'), 'utf8'))
|
||||
assert.equal(records.length, 1); assert.equal(records[0].name, '项目验收')
|
||||
assert.deepEqual(records[0].connectors, [connector.id])
|
||||
const task = await read(`window.ycode.session.create('项目任务验收',{projectId:${JSON.stringify('REPLACE_PROJECT_ID')}})`.replace(JSON.stringify('REPLACE_PROJECT_ID'), JSON.stringify(records[0].projectId)))
|
||||
assert.equal(task.projectId, records[0].projectId)
|
||||
const invalidProjectRejected = await read(`window.ycode.session.create('无效项目任务',{projectId:'missing-project'}).then(()=>false,()=>true)`)
|
||||
assert.equal(invalidProjectRejected, true)
|
||||
const loaded = new Promise(resolve => win.webContents.once('did-finish-load', resolve))
|
||||
win.webContents.reload()
|
||||
await loaded
|
||||
await wait('!!window.ycode?.projects')
|
||||
await enter()
|
||||
const restoredTask = await read(`window.ycode.session.list().then(items=>items.find(item=>item.id===${JSON.stringify('REPLACE_TASK_ID')}))`.replace(JSON.stringify('REPLACE_TASK_ID'), JSON.stringify(task.id)))
|
||||
assert.equal(restoredTask.projectId, records[0].projectId)
|
||||
await wait('document.querySelector(".project-grid__card-name-text")?.textContent === "项目验收"')
|
||||
await read('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
|
||||
await read(`(() => { const input = document.querySelector('input[aria-label="搜索项目"]'); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(input, '不存在'); input.dispatchEvent(new Event('input', { bubbles: true })); })()`)
|
||||
await wait('document.querySelector(".project-grid__empty")?.textContent.includes("没有匹配的项目")')
|
||||
await read(`(() => { const input = document.querySelector('input[aria-label="搜索项目"]'); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(input, ''); input.dispatchEvent(new Event('input', { bubbles: true })); })()`)
|
||||
await wait('document.querySelector(".project-grid__card-name-text")?.textContent === "项目验收"')
|
||||
await read('document.querySelector(".project-grid__card").click()')
|
||||
await wait('!!document.querySelector(".project-detail-view")')
|
||||
await read(`Array.from(document.querySelectorAll('.project-detail-view__top-tab')).find(item => item.textContent.trim() === '任务').click()`)
|
||||
await wait('document.querySelector(".task-item")?.dataset.taskStatus === "idle"')
|
||||
await read('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
|
||||
const detailGeometry = await read(`(() => {
|
||||
const rect = selector => { const el = document.querySelector(selector); if (!el) return null; const r = el.getBoundingClientRect(); return {x:r.x,y:r.y,width:r.width,height:r.height} }
|
||||
return {
|
||||
viewport: {width: innerWidth, height: innerHeight},
|
||||
shell: rect('.app-shell'),
|
||||
surface: rect('.yc-project-surface'),
|
||||
detail: rect('.project-detail-view'),
|
||||
topbar: rect('.project-detail-view > .workbuddy-topbar'),
|
||||
header: rect('.project-detail-view__header'),
|
||||
body: rect('.project-detail-view__body'),
|
||||
main: rect('.project-detail-view__main'),
|
||||
panel: rect('.project-detail-view__panel'),
|
||||
composer: rect('.project-detail-view__project-composer'),
|
||||
taskList: rect('.project-detail-view__task-list'),
|
||||
toolbar: rect('.project-detail-view__task-toolbar'),
|
||||
configCards: document.querySelectorAll('.context-panel .wb-config-card').length,
|
||||
configKinds: document.querySelectorAll('[data-config-kind]').length
|
||||
}
|
||||
})()`)
|
||||
console.log(`DETAIL_GEOMETRY ${JSON.stringify(detailGeometry)}`)
|
||||
assert.equal(detailGeometry.topbar.height, 64)
|
||||
assert.equal(detailGeometry.header, null)
|
||||
assert.equal(detailGeometry.viewport.height, 902)
|
||||
assert.equal(detailGeometry.body.height, 808)
|
||||
assert.equal(detailGeometry.body.y, detailGeometry.topbar.y + detailGeometry.topbar.height)
|
||||
assert.equal(detailGeometry.body.y + detailGeometry.body.height, detailGeometry.viewport.height)
|
||||
assert.equal(detailGeometry.panel.width, 320)
|
||||
assert.equal(detailGeometry.main.width + detailGeometry.panel.width, detailGeometry.body.width)
|
||||
assert.equal(detailGeometry.composer.height, 148)
|
||||
assert.equal(detailGeometry.composer.y, 730)
|
||||
assert.equal(detailGeometry.composer.y + detailGeometry.composer.height, detailGeometry.viewport.height - 24)
|
||||
assert.equal(detailGeometry.configCards, 5)
|
||||
assert.equal(detailGeometry.configKinds, 3)
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[0].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 3')
|
||||
assert.deepEqual(
|
||||
await read('Array.from(document.querySelectorAll(".wb-select__option")).map(el => el.textContent.trim())'),
|
||||
['全部任务', '个人任务', '协作任务'],
|
||||
)
|
||||
await read('document.querySelectorAll(".wb-select__option")[1].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover")')
|
||||
assert.equal(await read('document.querySelectorAll(".task-item").length'), 1)
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[0].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 3')
|
||||
await read('document.querySelectorAll(".wb-select__option")[2].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && !document.querySelector(".task-item")')
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[0].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 3')
|
||||
await read('document.querySelectorAll(".wb-select__option")[0].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && document.querySelectorAll(".task-item").length === 1')
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[1].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 4')
|
||||
assert.deepEqual(
|
||||
await read('Array.from(document.querySelectorAll(".wb-select__option")).map(el => el.textContent.trim())'),
|
||||
['全部来源', '手动创建', '定时任务', '本地'],
|
||||
)
|
||||
await read('document.querySelectorAll(".wb-select__option")[3].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && document.querySelectorAll(".task-item").length === 1')
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[1].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 4')
|
||||
await read('document.querySelectorAll(".wb-select__option")[1].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && !document.querySelector(".task-item")')
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[1].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 4')
|
||||
await read('document.querySelectorAll(".wb-select__option")[2].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && !document.querySelector(".task-item")')
|
||||
await read('document.querySelectorAll(".project-task-panel__filter-trigger")[1].click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 4')
|
||||
await read('document.querySelectorAll(".wb-select__option")[0].click()')
|
||||
await wait('!document.querySelector(".wb-select__popover") && document.querySelectorAll(".task-item").length === 1')
|
||||
await fs.writeFile(path.join(directory, 'project-detail-tasks.png'), (await win.webContents.capturePage()).toPNG())
|
||||
const initialTaskStatus = await read('document.querySelector(".task-item").dataset.taskStatus')
|
||||
assert.equal(initialTaskStatus, 'idle')
|
||||
assert.equal(await read('document.querySelectorAll("[data-config-kind]").length'), 3)
|
||||
await read('document.querySelector("[data-config-kind=connectors]").click()')
|
||||
await wait('!!document.querySelector(".project-capability-editor")')
|
||||
await wait('document.querySelector(".project-capability-editor")?.textContent.includes("项目连接器")')
|
||||
assert.equal(await read('document.querySelector(".project-capability-editor__choice input[type=checkbox]").checked'), true)
|
||||
await read('document.querySelector(".project-capability-editor__choice input[type=checkbox]").click()')
|
||||
await wait('!document.querySelector(".project-capability-editor__choice input[type=checkbox]").checked')
|
||||
await read('document.querySelector(".project-capability-editor .project-instruction-drawer__save").click()')
|
||||
await wait('!document.querySelector(".project-capability-editor")')
|
||||
const editedProject = await read(`window.ycode.projects.list().then(items => items.find(item => item.projectId === ${JSON.stringify(records[0].projectId)}))`)
|
||||
assert.deepEqual(editedProject.connectors, [])
|
||||
await read('document.querySelector(".project-detail-view__panel .wb-config-card .wb-config-card__hit-area").click()')
|
||||
await wait('!!document.querySelector(".project-instruction-drawer__textarea")')
|
||||
await read(`(() => { const input = document.querySelector(".project-instruction-drawer__textarea"); Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(input, '父对象同步测试'); input.dispatchEvent(new Event('input', { bubbles: true })); })()`)
|
||||
await wait('!document.querySelector(".project-instruction-drawer__save").disabled')
|
||||
await read('document.querySelector(".project-instruction-drawer__save").click()')
|
||||
await wait('!document.querySelector(".project-instruction-drawer") && document.querySelector(".project-detail-view__panel").textContent.includes("父对象同步测试")')
|
||||
await read('document.querySelector(".project-detail-view__breadcrumb button").click()')
|
||||
await wait('document.querySelector(".project-grid__card-name-text")?.textContent === "项目验收"')
|
||||
const geometry = await read(`(() => { const rect = selector => { const r = document.querySelector(selector).getBoundingClientRect(); return { x:r.x, y:r.y, width:r.width, height:r.height } }; return { topbar:rect('.workbuddy-topbar'), header:rect('.landing-header'), card:rect('.project-grid__card'), icon:rect('.project-grid__card-icon'), time:document.querySelector('.project-grid__card-desc').textContent } })()`)
|
||||
assert.equal(geometry.topbar.height, 64)
|
||||
assert.equal(geometry.header.y, geometry.topbar.y + 64 + 56)
|
||||
assert.equal(geometry.card.height, 80)
|
||||
assert.equal(geometry.icon.width, 36)
|
||||
assert.ok(geometry.time.startsWith('添加于 '))
|
||||
await fs.writeFile(path.join(directory, 'project-page.png'), (await win.webContents.capturePage()).toPNG())
|
||||
await read(`document.documentElement.dataset.theme = 'dark'`)
|
||||
await read('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
|
||||
const dark = await read(`(() => { const card = getComputedStyle(document.querySelector('.project-grid__card')); const hero = getComputedStyle(document.querySelector('.landing-hero')); return { card:card.backgroundColor, text:card.color, heroFilter:hero.filter, heroOpacity:hero.opacity } })()`)
|
||||
assert.notEqual(dark.card, 'rgb(255, 255, 255)')
|
||||
assert.ok(dark.heroFilter.includes('invert(1)'))
|
||||
assert.equal(dark.heroOpacity, '0.88')
|
||||
await fs.writeFile(path.join(directory, 'project-page-dark.png'), (await win.webContents.capturePage()).toPNG())
|
||||
await rememberDialogBackground('dark')
|
||||
await read('document.querySelector(".landing-new-btn").click()')
|
||||
await wait('!!document.querySelector(".collab-modal__container")')
|
||||
await verifyDialogSurface('dark')
|
||||
const dialog = await read(`(() => { const el = document.querySelector('.collab-modal__container'); const r=el.getBoundingClientRect(); return {width:r.width, background:getComputedStyle(el).backgroundColor, input:document.querySelector('#yc-project-name').maxLength} })()`)
|
||||
assert.equal(dialog.width, 640)
|
||||
assert.notEqual(dialog.background, 'rgb(255, 255, 255)')
|
||||
assert.equal(dialog.input, 15)
|
||||
await wait('!document.querySelector("button[aria-label=选择项目模板]").disabled')
|
||||
await read(`(() => { const input = document.querySelector('#yc-project-name'); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(input, '保留项目名'); input.dispatchEvent(new Event('input', {bubbles:true})); })()`)
|
||||
await read('document.querySelector("button[aria-label=选择项目模板]").click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 6')
|
||||
await read(`document.querySelector('.wb-select__option').dispatchEvent(new KeyboardEvent('keydown', {key:'Enter',bubbles:true}))`)
|
||||
await wait('document.querySelector("#yc-project-instructions").value.includes("角色") && document.querySelectorAll("[data-capability-kind=experts] .cp-chip").length > 0')
|
||||
assert.equal(await read('document.querySelector("#yc-project-name").value'), '保留项目名')
|
||||
await read('document.querySelector("button[aria-label=选择项目模板]").click()')
|
||||
await wait('document.querySelectorAll(".wb-select__option").length === 6')
|
||||
await read(`Array.from(document.querySelectorAll('.wb-select__option')).find(el => el.textContent.trim() === '自定义').dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true}))`)
|
||||
await wait('document.querySelector("#yc-project-instructions").value === "" && document.querySelectorAll(".cp-chip").length === 0')
|
||||
assert.equal(await read('document.querySelector("#yc-project-name").value'), '保留项目名')
|
||||
await fs.writeFile(path.join(directory, 'project-dialog-dark.png'), (await win.webContents.capturePage()).toPNG())
|
||||
assert.deepEqual(errors, [])
|
||||
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result: 'PASS', records, initialTaskStatus, detailGeometry, editedProject, geometry, dark, dialog, dialogSurfaces, rendererErrors: errors }, null, 2))
|
||||
console.log(`PROJECT_PAGE_PASS ${directory}`)
|
||||
app.exit(0)
|
||||
})().catch(error => { console.error(error); app.exit(1) })
|
||||
}
|
||||
Reference in new issue
Block a user