/** Keyless document-preview smoke through a real Session, Files tab, shipped renderers, and the default-application controls. */ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { nativeFileManager } from '@deepseek-ai/dsh-native-command' import { delimiter, join, relative } from 'node:path' import { fileURLToPath } from 'node:url' import type { Browser, Locator, Page } from 'playwright' import { chromium } from 'playwright' import { afterAll, beforeAll, describe, expect, it, onTestFailed, vi } from 'vitest' import { createLaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment' import { realOfficeBytes } from './office-fixture.ts' import { pdfFixture, selectionPdfFixture } from '../../../packages/client/ui-sidebar-documentpreview/tests/pdf-fixture.ts' import { assertFixtureInventory, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold } from './scaffold.ts' import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts' const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/lifecycle-chrome/session.v3.jsonl', import.meta.url)) const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/document-preview', import.meta.url)) const EXPECTED = join(SNAPSHOT_DIR, 'document.expected.md') const PAGING_PATCH = join(SNAPSHOT_DIR, 'paging.patch.yml') const PAGE_LINES = 64 const SHOT_DIR = fileURLToPath(new URL('../../../.artifacts/screenshots/0908-document-preview', import.meta.url)) const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.' const MODE = webSnapshotMode() /** The stubbed opener runs as a POSIX script; Windows and WSL keep their real file associations out of the lane. */ const STUB_OPENER = nativeFileManager() !== 'explorer' const TINY_PNG = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64', ) /** Successful render evidence stays outside the committed snapshot inventory. */ async function successShot(page: Page, name: string): Promise { await mkdir(SHOT_DIR, { recursive: true }) await page.screenshot({ path: join(SHOT_DIR, `${name}-${MODE}-${process.pid}.png`), fullPage: true }) } /** Exercise native browser selection and copy, including the text overlay's canvas alignment. */ async function copyPdfText(page: Page, preview: Locator, expected: string): Promise { const text = preview.locator('[data-pdf-text] span:not(.markedContent)').filter({ hasText: expected }).first() await text.waitFor({ state: 'visible' }) await expect.poll(() => text.evaluate(node => getComputedStyle(node).userSelect)).toBe('text') await text.click({ clickCount: 3 }) await expect.poll(() => page.evaluate(() => window.getSelection()?.toString().trim())).toBe(expected) await page.context().grantPermissions(['clipboard-read', 'clipboard-write'], { origin: new URL(page.url()).origin }) await page.keyboard.press('ControlOrMeta+C') await expect.poll(() => page.evaluate(async () => (await navigator.clipboard.readText()).trim())).toBe(expected) await expect.poll(() => preview.locator('[data-pdf-page]').first().evaluate((node) => { const canvas = node.querySelector('canvas')!.getBoundingClientRect() const layer = node.querySelector('.textLayer')!.getBoundingClientRect() return Math.max(Math.abs(layer.width - canvas.width), Math.abs(layer.height - canvas.height), Math.abs(layer.left - canvas.left), Math.abs(layer.top - canvas.top)) })).toBeLessThan(1) } /** Trigger the document owner's native scroll handler after a real first page overflows. */ async function scrollForNextPage(body: Locator): Promise { await body.evaluate((node) => { if (node.scrollHeight <= node.clientHeight) throw new Error('paging fixture does not overflow the document body') node.scrollTop = node.scrollHeight }) } /** Read the solid vector fill away from antialiased page edges. */ async function canvasColor(canvas: Locator): Promise { return await canvas.evaluate((node) => { const surface = node as HTMLCanvasElement const context = surface.getContext('2d') if (context === null) throw new Error('PDF canvas has no 2D context') const pixel = context.getImageData(Math.floor(surface.width / 2), Math.floor(surface.height / 2), 1, 1).data if (Number(pixel[3]) !== 255) return 'transparent' if (Number(pixel[0]) - Number(pixel[2]) > 150) return 'red' if (Number(pixel[2]) - Number(pixel[0]) > 150) return 'blue' return 'other' }) } /** Select a workspace file through the Files tab and wait for its preview identity. */ async function openPreviewFile(column: Locator, filesTab: Locator, preview: Locator, name: string): Promise { await filesTab.click() await column.locator('[data-files-entry="file"]').getByRole('button', { name, exact: true }).click() await expect.poll(async () => (await preview.getAttribute('data-textpreview-url'))?.endsWith(`/${name}`)).toBe(true) } describe.skipIf(MODE === 'record')('web e2e: document preview through Files', () => { let scaffold: WebScaffold let browser: Browser let page: Page let tripwire: ReturnType let outsideRoot: string | undefined let nativeRoot: string | undefined let openLog = '' const opened = async (): Promise> => (await readFile(openLog, 'utf8')).split('\n').filter(Boolean).map(line => JSON.parse(line) as { path: string; action: 'open' | 'reveal' }) beforeAll(async () => { outsideRoot = await mkdtemp(join(tmpdir(), 'dsh-preview-outside-')) if (STUB_OPENER) { // Exercise the built Host through its actual OS command, replacing only the desktop application. nativeRoot = await mkdtemp(join(tmpdir(), 'dsh-preview-native-')) openLog = join(nativeRoot, 'opened.jsonl') await writeFile(openLog, '') const command = process.platform === 'darwin' ? 'open' : 'xdg-open' await writeFile(join(nativeRoot, command), `#!/usr/bin/env node const fs = require('node:fs'); const path = process.argv[2] === '-R' ? process.argv[3] : process.argv[2]; const action = process.argv[2] === '-R' || fs.statSync(path).isDirectory() ? 'reveal' : 'open'; fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action }) + '\\n'); `, { mode: 0o700 }) vi.stubEnv('PATH', `${nativeRoot}${delimiter}${process.env.PATH ?? ''}`) } // The Open In rows carry the default-application controls; the SSH marker // keeps the host's application catalog empty, so the Session-header split // button stays off every platform while the pinned desktop serves the file controls. scaffold = await launchWebScaffold({ developerTools: false, replayFixture: FIXTURE, paceMs: 5, compareReplaySession: false, extraOverlayPath: [PAGING_PATCH, fileURLToPath(new URL('./fixtures/native-open-on.patch.yml', import.meta.url))], openInAppEnvironment: createLaunchEnvironmentSnapshot([{ source: 'process', values: { SSH_CONNECTION: '10.0.0.2 55000 10.0.0.9 22' } }]), }) browser = await chromium.launch() page = await newEnglishPage(browser) tripwire = watchConsole(page) await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) await connectFreshWorkspace(page, scaffold.workspaceCwd) }) afterAll(async () => { try { await browser?.close() } finally { try { await scaffold?.close() } finally { vi.unstubAllEnvs() if (outsideRoot !== undefined) await rm(outsideRoot, { recursive: true, force: true }) if (nativeRoot !== undefined) await rm(nativeRoot, { recursive: true, force: true }) } } }) it('opens text, isolated HTML, width-fitted images, and rendered PDF from the Session workspace', async () => { onTestFailed(async () => { await mkdir(SHOT_DIR, { recursive: true }) await saveFailureShot(page, `screenshots/0908-document-preview/smoke-${process.pid}`) }) expect(await page.locator('[data-slot="conversation.hero.agentPreset"] button').count()).toBe(0) expect(scaffold.ctx.settings.get('ui-developer-tools')).toEqual({ enabled: false }) const settled = scaffold.whenTurnSettled() const input = page.locator('[data-composer-input]').first() await input.fill(PROMPT) await input.press('Enter') const sessionId = await settled await page.getByText('LIGHTHOUSE', { exact: true }).waitFor({ timeout: 15_000 }) expect(await page.getByRole('tablist').count()).toBe(0) const cwd = scaffold.ctx.agents.get(sessionId)?.session.header.cwd if (cwd === undefined) throw new Error('settled Session has no workspace cwd') if (outsideRoot === undefined) throw new Error('outside fixture directory is unavailable') const outsideScript = join(outsideRoot, 'outside.js') const outsideReference = relative(cwd, outsideScript).replace(/\\/g, '/') let previewNetworkRequests = 0 await page.route('https://preview.invalid/developer-tools.png', async (route) => { previewNetworkRequests += 1 await route.fulfill({ status: 200, contentType: 'image/png', body: TINY_PNG }) }) const blockedRequests: string[] = [] await page.route('https://blocked-preview.invalid/**', async (route) => { blockedRequests.push(route.request().url()) await route.fulfill({ status: 200, contentType: 'text/html', body: 'ESCAPED' }) }) const markdownText = [ '# Markdown smoke', '', 'Rendered from the workspace.', '', ...Array.from({ length: (PAGE_LINES - 4) / 2 }, (_, index) => [`Paragraph ${index + 1}: ${'visible prefix '.repeat(20)}`, '']).flat(), '# Markdown tail', '', '![relative image](preview-images/local%20image.png)', '', `![absolute image](<${join(cwd, 'tiny.png').replaceAll('\\', '/')}>)`, '', '![reference image][local-image]', '', '[local-image]: preview-images/local%20image.png', ].join('\n') await mkdir(join(cwd, 'preview-images')) const codeLines = [ ...Array.from({ length: PAGE_LINES }, (_, index) => index === 0 ? 'const prefix = "CODE_PREFIX";' : `// prefix line ${index + 1}`), 'const tail = "CODE_TAIL";', ] await Promise.all([ writeFile(join(cwd, 'hostile.html'), `

Static adversarial preview

Plain link
SVG link x