Files
BoosAPI/desktop/libs/markstream-vue/scripts/e2e-diff-streaming-stability.mjs
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

644 lines
25 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import net from 'node:net'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright-core'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.resolve(__dirname, '..')
const playgroundDir = path.join(repoRoot, 'playground')
const host = '127.0.0.1'
const diffSample = [
'```diff json:package.json',
'{',
' "name": "markstream-vue",',
'- "version": "0.0.49",',
'+ "version": "0.0.54-beta.1",',
' "packageManager": "pnpm@10.16.1",',
'}',
'```',
].join('\n')
const sampleFrameIntervalMs = 25
const sampleStreamCharDelayMs = 80
const estimatedStreamingDurationMs
= diffSample.length * sampleStreamCharDelayMs
const samplingDurationMs = Math.max(
12000,
estimatedStreamingDurationMs + 4000,
)
const samplingFrameCount = Math.ceil(
samplingDurationMs / sampleFrameIntervalMs,
)
function isPortOpen(port) {
return new Promise((resolve) => {
const socket = net.createConnection({ host, port })
socket.on('connect', () => {
socket.end()
resolve(true)
})
socket.on('error', () => {
socket.destroy()
resolve(false)
})
})
}
async function findFreePort(start = 4370, end = 4410) {
for (let port = start; port <= end; port++) {
if (!await isPortOpen(port))
return port
}
throw new Error(`No free port found in ${start}-${end}`)
}
async function waitForPort(port, timeoutMs = 60000) {
const startedAt = Date.now()
while (Date.now() - startedAt < timeoutMs) {
if (await isPortOpen(port))
return
await new Promise(resolve => setTimeout(resolve, 150))
}
throw new Error(`Timed out waiting for ${host}:${port}`)
}
function killProcessTree(child) {
if (!child || child.killed)
return
try {
child.kill('SIGTERM')
}
catch {}
setTimeout(() => {
try {
if (!child.killed)
child.kill('SIGKILL')
}
catch {}
}, 3000).unref?.()
}
function resolveChromeLaunchOptions() {
const candidates = [
process.env.PLAYWRIGHT_CHROME_PATH,
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
'/usr/bin/google-chrome',
'/usr/bin/chromium',
'/usr/bin/chromium-browser',
].filter(Boolean)
for (const candidate of candidates) {
if (existsSync(candidate)) {
return {
executablePath: candidate,
headless: true,
}
}
}
return {
channel: 'chrome',
headless: true,
}
}
function startDevServer(port) {
const logs = []
const child = spawn(
'pnpm',
['-C', playgroundDir, 'exec', 'vite', '--host', host, '--port', String(port), '--strictPort'],
{
stdio: ['ignore', 'pipe', 'pipe'],
env: {
...process.env,
CI: '1',
},
},
)
child.stdout.on('data', chunk => logs.push(String(chunk)))
child.stderr.on('data', chunk => logs.push(String(chunk)))
return {
child,
getLogs() {
return logs.join('')
},
}
}
function range(values) {
if (!values.length)
return null
return Math.max(...values) - Math.min(...values)
}
async function main() {
const port = await findFreePort()
const server = startDevServer(port)
try {
await waitForPort(port)
const browser = await chromium.launch(resolveChromeLaunchOptions())
const context = await browser.newContext({
viewport: { width: 1600, height: 1200 },
storageState: {
cookies: [],
origins: [
{
origin: `http://${host}:${port}`,
localStorage: [
{ name: 'vmr-test-render-mode', value: 'monaco' },
{ name: 'vmr-test-code-stream', value: 'true' },
{ name: 'vmr-test-stream-chunk-size-min', value: '1' },
{ name: 'vmr-test-stream-chunk-size-max', value: '1' },
{ name: 'vmr-test-stream-delay-min', value: '80' },
{ name: 'vmr-test-stream-delay-max', value: '80' },
{ name: 'vmr-test-stream-burstiness', value: '0' },
{ name: 'vmr-test-stream-slice-mode', value: 'pure-random' },
{ name: 'vmr-test-stream-transport-mode', value: 'scheduler' },
],
},
],
},
})
const page = await context.newPage()
await page.goto(`http://${host}:${port}/test`, { waitUntil: 'load' })
await page.waitForSelector('.editor-textarea')
await page.evaluate((sample) => {
const textarea = document.querySelector('.editor-textarea')
textarea.value = sample
textarea.dispatchEvent(new Event('input', { bubbles: true }))
}, diffSample)
await page.getByRole('button', { name: '开始流式渲染' }).click()
await page.waitForSelector('.code-block-container')
await page.evaluate(() => {
document.querySelector('.code-block-container')?.scrollIntoView({
block: 'center',
inline: 'nearest',
})
})
const frames = []
for (let index = 0; index < samplingFrameCount; index++) {
await page.waitForTimeout(sampleFrameIntervalMs)
const frame = await page.evaluate(() => {
const preview = document.querySelector('.preview-surface')
const viewportCenter = window.innerHeight / 2
const diffRoots = Array.from(
document.querySelectorAll('.preview-surface .stream-monaco-diff-root'),
)
const diffRoot = diffRoots
.map((root) => {
const rect = root.getBoundingClientRect()
const visibleHeight = Math.max(
0,
Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0),
)
return {
root,
rect,
visibleHeight,
distance: Math.abs((rect.top + rect.bottom) / 2 - viewportCenter),
}
})
.filter(entry => entry.visibleHeight > 0)
.sort((a, b) => {
if (b.visibleHeight !== a.visibleHeight)
return b.visibleHeight - a.visibleHeight
return a.distance - b.distance
})[0]
?.root ?? null
const block = diffRoot?.closest('.code-block-container') ?? null
const diffRootDataset = diffRoot
? {
streamingInlineControlledRaw: diffRoot.dataset.streamingInlineControlledRaw ?? null,
streamingInlineNextSpecCount: diffRoot.dataset.streamingInlineNextSpecCount ?? null,
streamingInlineVisibleSpecCount: diffRoot.dataset.streamingInlineVisibleSpecCount ?? null,
streamingInlineRenderedSpecCount: diffRoot.dataset.streamingInlineRenderedSpecCount ?? null,
streamingInlineNodeCount: diffRoot.dataset.streamingInlineNodeCount ?? null,
streamingInlineNodesConnected: diffRoot.dataset.streamingInlineNodesConnected ?? null,
streamingInlineReuse: diffRoot.dataset.streamingInlineReuse ?? null,
streamingInlineDefer: diffRoot.dataset.streamingInlineDefer ?? null,
streamingInlineRenderedMode: diffRoot.dataset.streamingInlineRenderedMode ?? null,
}
: null
const modifiedScroll = diffRoot?.querySelector(
'.editor.modified .monaco-scrollable-element.editor-scrollable',
) ?? null
const diffStatsText = block?.querySelector('.code-diff-stats')?.textContent?.trim() ?? null
const versionLine = Array.from(
diffRoot?.querySelectorAll('.editor.modified .view-line') ?? [],
).find((element) => {
if (!(element.textContent || '').includes('0.0.54-beta.1'))
return false
const style = getComputedStyle(element)
return style.display !== 'none'
&& style.visibility !== 'hidden'
&& Number.parseFloat(style.opacity || '1') > 0.01
})
const versionLineDetail = versionLine
? (() => {
const rect = versionLine.getBoundingClientRect()
const style = getComputedStyle(versionLine)
const parent = versionLine.parentElement
const parentRect = parent?.getBoundingClientRect() ?? null
const parentStyle = parent ? getComputedStyle(parent) : null
return {
text: (versionLine.textContent || '').trim(),
top: Math.round(rect.top),
height: Math.round(rect.height),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
lineHeight: style.lineHeight,
innerHTML: versionLine.innerHTML.slice(0, 300),
parentClassName: typeof parent?.className === 'string' ? parent.className : '',
parentTop: parentRect ? Math.round(parentRect.top) : null,
parentHeight: parentRect ? Math.round(parentRect.height) : null,
parentDisplay: parentStyle?.display ?? null,
parentVisibility: parentStyle?.visibility ?? null,
parentOpacity: parentStyle?.opacity ?? null,
}
})()
: null
const visibleDiffTexts = Array.from(
diffRoot?.querySelectorAll(
'.editor.modified .view-line, .editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .inline-deleted-text, .editor.modified .view-lines.line-delete',
) ?? [],
)
.map((element) => {
const rect = element.getBoundingClientRect()
const text = (element.textContent || '').replace(/\u00A0/g, ' ').trim()
const style = getComputedStyle(element)
return {
text,
top: Math.round(rect.top),
height: Math.round(rect.height),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
}
})
.filter(entry =>
entry.height > 0
&& entry.display !== 'none'
&& entry.visibility !== 'hidden'
&& Number.parseFloat(entry.opacity || '1') > 0.01
&& (entry.text.includes('version')
|| entry.text.includes('0.0.49')
|| entry.text.includes('0.0.54')),
)
.sort((left, right) => {
if (left.top !== right.top)
return left.top - right.top
return left.text.localeCompare(right.text)
})
const uniqueVisibleDiffTexts = []
const visibleDiffKeys = new Set()
for (const entry of visibleDiffTexts) {
const key = `${entry.top}:${entry.text}`
if (visibleDiffKeys.has(key))
continue
visibleDiffKeys.add(key)
uniqueVisibleDiffTexts.push(entry)
}
const visibleOldNodes = Array.from(
diffRoot?.querySelectorAll(
'.editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .stream-monaco-fallback-inline-delete-line, .editor.modified .inline-deleted-text, .editor.modified .view-line.line-delete, .editor.modified .view-lines.line-delete',
) ?? [],
)
.map((element) => {
const rect = element.getBoundingClientRect()
const style = getComputedStyle(element)
return {
text: (element.textContent || '').trim(),
className: typeof element.className === 'string' ? element.className : '',
parentClassName: typeof element.parentElement?.className === 'string'
? element.parentElement.className
: '',
inViewZones: !!element.closest('.view-zones'),
inOverlayRoot: !!element.closest('.stream-monaco-fallback-inline-delete-overlay-root'),
width: rect.width,
height: rect.height,
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
}
})
.filter(entry =>
entry.width > 0
&& entry.height > 0
&& entry.display !== 'none'
&& entry.visibility !== 'hidden'
&& Number.parseFloat(entry.opacity || '1') > 0.01
&& entry.text.includes('0.0.49'),
)
const allFallbackZones = Array.from(
diffRoot?.querySelectorAll(
'.editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .stream-monaco-fallback-inline-delete-line',
) ?? [],
)
.map((element) => {
const rect = element.getBoundingClientRect()
const style = getComputedStyle(element)
return {
text: (element.textContent || '').trim(),
className: typeof element.className === 'string' ? element.className : '',
parentClassName: typeof element.parentElement?.className === 'string'
? element.parentElement.className
: '',
inViewZones: !!element.closest('.view-zones'),
inOverlayRoot: !!element.closest('.stream-monaco-fallback-inline-delete-overlay-root'),
width: Math.round(rect.width),
height: Math.round(rect.height),
top: Math.round(rect.top),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
}
})
const extraFallbackZones = allFallbackZones.filter((entry) => {
return entry.text.length > 0
&& !entry.text.includes('"version"')
&& !entry.text.includes('0.0.49')
})
const nearbyModifiedRows = Array.from(
diffRoot?.querySelectorAll(
'.editor.modified .view-line, .editor.modified .view-zones > div, .editor.modified .margin-view-zones > div',
) ?? [],
)
.map((element) => {
const rect = element.getBoundingClientRect()
const style = getComputedStyle(element)
return {
text: (element.textContent || '').trim(),
className: typeof element.className === 'string' ? element.className : '',
width: Math.round(rect.width),
height: Math.round(rect.height),
top: Math.round(rect.top),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
}
})
.filter(entry => entry.height > 0 && entry.top >= 740 && entry.top <= 880)
const visibleViewportRows = Array.from(
diffRoot?.querySelectorAll(
'.editor.modified .view-line, .editor.modified .view-zones > div, .editor.modified .margin-view-zones > div, .editor.modified .margin-view-overlays > div',
) ?? [],
)
.map((element) => {
const rect = element.getBoundingClientRect()
const style = getComputedStyle(element)
return {
text: (element.textContent || '').trim().slice(0, 160),
className: typeof element.className === 'string' ? element.className : '',
width: Math.round(rect.width),
height: Math.round(rect.height),
top: Math.round(rect.top),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
}
})
.filter(entry => entry.height > 0 && entry.top >= 620 && entry.top <= 1180)
const sampleVisibleLineParents = Array.from(
diffRoot?.querySelectorAll('.editor.modified .view-line') ?? [],
)
.filter((element) => {
const rect = element.getBoundingClientRect()
const style = getComputedStyle(element)
return rect.width > 0
&& rect.height > 0
&& style.display !== 'none'
&& style.visibility !== 'hidden'
&& Number.parseFloat(style.opacity || '1') > 0.01
})
.slice(0, 6)
.map((element) => {
const chain = []
let current = element
for (let index = 0; index < 6 && current; index++) {
chain.push({
tag: current.tagName,
className: typeof current.className === 'string' ? current.className : '',
})
current = current.parentElement
}
return {
text: (element.textContent || '').trim().slice(0, 80),
chain,
}
})
const visibleViewLineGroups = Array.from(
diffRoot?.querySelectorAll('.editor.modified .view-lines') ?? [],
)
.map((element) => {
const style = getComputedStyle(element)
const visibleLines = Array.from(
element.querySelectorAll < HTMLElement > ('.view-line'),
).filter((line) => {
const lineRect = line.getBoundingClientRect()
const lineStyle = getComputedStyle(line)
return lineRect.width > 0
&& lineRect.height > 0
&& lineStyle.display !== 'none'
&& lineStyle.visibility !== 'hidden'
&& Number.parseFloat(lineStyle.opacity || '1') > 0.01
}).length
const tops = Array.from(
element.querySelectorAll < HTMLElement > ('.view-line'),
)
.map((line) => {
const lineRect = line.getBoundingClientRect()
const lineStyle = getComputedStyle(line)
if (
lineRect.width <= 0
|| lineRect.height <= 0
|| lineStyle.display === 'none'
|| lineStyle.visibility === 'hidden'
|| Number.parseFloat(lineStyle.opacity || '1') <= 0.01
) {
return null
}
return Math.round(lineRect.top)
})
.filter(top => Number.isFinite(top))
return {
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
visibleLineCount: visibleLines,
minTop: tops.length ? Math.min(...tops) : null,
maxTop: tops.length ? Math.max(...tops) : null,
}
})
.filter(entry =>
entry.display !== 'none'
&& entry.visibility !== 'hidden'
&& Number.parseFloat(entry.opacity || '1') > 0.01
&& entry.visibleLineCount > 0,
)
return {
hasRoot: !!diffRoot,
targetRootTop: diffRoot ? Math.round(diffRoot.getBoundingClientRect().top) : null,
streaming: !!diffRoot && diffRoot.classList.contains('stream-monaco-diff-streaming-active'),
diffStatsText,
diffRootDataset,
previewScrollTop: preview ? Math.round(preview.scrollTop) : null,
blockTop: block ? Math.round(block.getBoundingClientRect().top) : null,
modifiedScrollTop: modifiedScroll ? Math.round(modifiedScroll.scrollTop) : null,
versionTop: versionLine ? Math.round(versionLine.getBoundingClientRect().top) : null,
versionLineDetail,
visibleDiffTexts: uniqueVisibleDiffTexts,
visibleOldNodes,
allFallbackZones,
extraFallbackZones,
nearbyModifiedRows,
visibleViewportRows,
visibleViewLineGroups,
sampleVisibleLineParents,
}
})
frames.push(frame)
}
const activeFrames = frames.filter(frame => frame.hasRoot || frame.diffStatsText?.includes('-1'))
const firstStablePairIndex = activeFrames.findIndex((frame) => {
if (frame.visibleDiffTexts.length !== 2)
return false
const texts = frame.visibleDiffTexts.map(item => item.text)
return texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
&& texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
})
const stableFrames = firstStablePairIndex === -1
? []
: activeFrames.slice(firstStablePairIndex)
const oldOnlyFramesBeforePair = (firstStablePairIndex === -1
? activeFrames
: activeFrames.slice(0, firstStablePairIndex))
.filter((frame) => {
return frame.visibleDiffTexts.some(item => item.text.includes('0.0.49'))
&& !frame.visibleDiffTexts.some(item => item.text.includes('0.0.54-beta.1'))
})
const newOnlyFramesBeforePair = (firstStablePairIndex === -1
? activeFrames
: activeFrames.slice(0, firstStablePairIndex))
.filter((frame) => {
return frame.visibleDiffTexts.some(item => item.text.includes('0.0.54-beta.1'))
&& !frame.visibleDiffTexts.some(item => item.text.includes('0.0.49'))
})
const versionFrames = stableFrames.filter(frame => Number.isFinite(frame.versionTop))
const missingVersionFrames = stableFrames.filter(frame => !Number.isFinite(frame.versionTop))
const signatureValues = versionFrames.map(frame =>
JSON.stringify(frame.visibleDiffTexts.map(item => item.text)),
)
const zeroDiffStatsFrames = activeFrames.filter((frame) => {
if (!frame.streaming)
return false
const stats = (frame.diffStatsText || '').replace(/\s+/g, '')
return stats === '-0+0' || stats === '-0+0…'
})
const blankViewportFrames = activeFrames.filter(frame =>
frame.visibleViewportRows.filter(row => !row.text).length >= 4,
)
const result = {
activeFrameCount: activeFrames.length,
stableFrameCount: stableFrames.length,
versionFrameCount: versionFrames.length,
oldOnlyFramesBeforePair: oldOnlyFramesBeforePair.length,
newOnlyFramesBeforePair: newOnlyFramesBeforePair.length,
previewScrollTopRange: range(versionFrames.map(frame => frame.previewScrollTop).filter(Number.isFinite)),
blockTopRange: range(versionFrames.map(frame => frame.blockTop).filter(Number.isFinite)),
modifiedScrollTopRange: range(versionFrames.map(frame => frame.modifiedScrollTop).filter(Number.isFinite)),
versionTopRange: range(versionFrames.map(frame => frame.versionTop).filter(Number.isFinite)),
diffSignatureCount: new Set(signatureValues).size,
duplicateOldVisibleFrames: versionFrames.filter(frame =>
frame.visibleOldNodes.filter(node => node.className.includes('stream-monaco-fallback-inline-delete-zone')).length > 1,
).length,
missingVersionFrames: missingVersionFrames.length,
zeroDiffStatsFrames: zeroDiffStatsFrames.length,
blankViewportFrames: blankViewportFrames.length,
extraFallbackZoneFrames: versionFrames.filter(frame => frame.extraFallbackZones.length > 0).length,
multipleVisibleViewLineGroupFrames: versionFrames.filter(frame => frame.visibleViewLineGroups.length > 1).length,
badSignatureFrames: versionFrames.filter((frame) => {
if (frame.visibleDiffTexts.length !== 2)
return true
const texts = frame.visibleDiffTexts.map(item => item.text)
return !texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
|| !texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
}).length,
shiftedFrames: versionFrames
.filter(frame =>
frame.versionTop !== versionFrames[0]?.versionTop,
)
.slice(0, 12),
firstVersionFrame: versionFrames[0] ?? null,
firstActiveFrame: activeFrames[0] ?? null,
firstBadOldFrame: versionFrames.find(frame =>
frame.visibleOldNodes.filter(node => node.className.includes('stream-monaco-fallback-inline-delete-zone')).length > 1,
) ?? null,
firstOldOnlyFrame: oldOnlyFramesBeforePair[0] ?? null,
firstNewOnlyFrame: newOnlyFramesBeforePair[0] ?? null,
firstMissingVersionFrame: missingVersionFrames[0] ?? null,
firstZeroDiffStatsFrame: zeroDiffStatsFrames[0] ?? null,
firstBlankViewportFrame: blankViewportFrames[0] ?? null,
firstBadSignatureFrame: versionFrames.find((frame) => {
if (frame.visibleDiffTexts.length !== 2)
return true
const texts = frame.visibleDiffTexts.map(item => item.text)
return !texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
|| !texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
}) ?? null,
}
const ok = result.activeFrameCount >= 12
&& result.stableFrameCount > 0
&& result.versionFrameCount > 0
&& result.oldOnlyFramesBeforePair === 0
&& result.newOnlyFramesBeforePair === 0
&& result.previewScrollTopRange === 0
&& result.blockTopRange === 0
&& result.modifiedScrollTopRange === 0
&& result.missingVersionFrames === 0
&& result.zeroDiffStatsFrames === 0
&& result.blankViewportFrames === 0
&& result.versionTopRange === 0
&& result.diffSignatureCount === 1
&& result.duplicateOldVisibleFrames === 0
&& result.extraFallbackZoneFrames === 0
&& result.multipleVisibleViewLineGroupFrames === 0
&& result.badSignatureFrames === 0
console.log(JSON.stringify({ ok, ...result }, null, 2))
await context.close()
await browser.close()
killProcessTree(server.child)
process.exit(ok ? 0 : 1)
}
catch (error) {
killProcessTree(server.child)
console.error(server.getLogs())
console.error(error)
process.exit(1)
}
}
main()