Created
September 27, 2026 11:34
-
-
Save hediet/a3c33b5506f287e75dd4e567b6179e2f to your computer and use it in GitHub Desktop.
Standalone visible-Chrome dbgjs repro for microsoft/vscode#338189 (synthetic data, no sign-in)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -Version 7.0 | |
| <# | |
| Reproduces microsoft/vscode#338189 in a new, visible Chrome/Edge window. | |
| Requires global dbgjs on PATH, Chrome or Edge, and internet access. | |
| Run: pwsh -File .\repro-vscode-338189.ps1 | |
| No VS Code checkout, sign-in, agent host, or actual command execution is needed. | |
| Uses an isolated browser profile and synthetic in-memory session data. | |
| Leaves the browser open with the bug visible; close it manually when finished. | |
| Reload removes the fixture. The output folder contains the disposable profile. | |
| The debugger capture is pinned to the verified VS Code Insiders build below. | |
| The script fails explicitly if the deployed build changes. | |
| #> | |
| param( | |
| [string]$BrowserPath, | |
| [string]$OutputDirectory | |
| ) | |
| $ErrorActionPreference = 'Stop' | |
| Get-Command dbgjs -ErrorAction Stop | Out-Null | |
| if (!$BrowserPath) { | |
| $BrowserPath = @( | |
| "$env:ProgramFiles\Google\Chrome\Application\chrome.exe" | |
| "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" | |
| "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe" | |
| "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe" | |
| "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe" | |
| ) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1 | |
| } | |
| if (!$BrowserPath -or !(Test-Path -LiteralPath $BrowserPath -PathType Leaf)) { | |
| throw 'Chrome/Edge was not found. Pass -BrowserPath with its executable path.' | |
| } | |
| $contextName = 'vscode-338189-' + [guid]::NewGuid().ToString('N').Substring(0, 10) | |
| $context = ":$contextName" | |
| if (!$OutputDirectory) { $OutputDirectory = Join-Path $env:TEMP $contextName } | |
| $OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory) | |
| if (Test-Path -LiteralPath $OutputDirectory) { | |
| throw "Use a new output directory to avoid reusing browser state: $OutputDirectory" | |
| } | |
| New-Item -ItemType Directory -Path $OutputDirectory | Out-Null | |
| function Invoke-Dbgjs { | |
| param([string[]]$Arguments, [string]$Program) | |
| if ($Program) { $Program | & dbgjs @Arguments } | |
| else { & dbgjs @Arguments } | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "dbgjs failed (exit $LASTEXITCODE): $($Arguments -join ' ')" | |
| } | |
| } | |
| Write-Host "Launching isolated browser. Output: $OutputDirectory" | |
| Invoke-Dbgjs -Arguments @('context', 'create', $context, 'VS Code approval-row repro') | Out-Host | |
| Invoke-Dbgjs -Arguments @( | |
| 'connection', 'add', '--chrome', 'https://vscode.dev/agents?vscode-quality=insider', | |
| '--connection', 'chrome', '--context', $context, '--executable', $BrowserPath, | |
| '--user-data-dir', (Join-Path $OutputDirectory 'chrome-profile'), | |
| '--headed', '--connect', '--set' | |
| ) | Out-Host | |
| $scope = @('--context', $context, '--connection', 'chrome') | |
| $targetText = Invoke-Dbgjs -Arguments (@('--json', 'target', 'list', '--type', 'page', '--url', 'https://vscode.dev/agents') + $scope) | |
| $targets = @(($targetText -join "`n" | ConvertFrom-Json).targets) | |
| if ($targets.Count -ne 1) { throw "Expected one Agents page, found $($targets.Count)." } | |
| $scope += @('--target', $targets[0].targetId) | |
| Invoke-Dbgjs -Arguments (@('target', 'attach', '--set') + $scope) | Out-Host | |
| $prepare = @' | |
| await page.waitForSelector('.sessions-list-control', { timeout: 60000 }); | |
| return await page.evaluate(() => { | |
| const version = document.querySelector('meta[name="vscode:versionInfo"]')?.content; | |
| if (!version?.endsWith(';4a6a3612a562c50f54c2bd5211f0ea42915b974e')) { | |
| throw new Error('Capture is pinned to 4a6a3612a562c50f54c2bd5211f0ea42915b974e; deployed build is ' + version); | |
| } | |
| return { version }; | |
| }); | |
| '@ | |
| Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $prepare | Out-Host | |
| # Capture real objects in SessionsList.layout without pausing the application. | |
| $url = 'https://main.vscode-cdn.net/insider/4a6a3612a562c50f54c2bd5211f0ea42915b974e/out/vs/sessions/sessions.web.main.internal.js' | |
| $parameters = @{ | |
| lineNumber = 4238 | |
| columnNumber = 58772 | |
| url = $url | |
| condition = '(globalThis.__webList=this,globalThis.__reproObservable=ge,globalThis.__reproURI=M,false)' | |
| } | ConvertTo-Json -Compress | |
| $breakpointText = Invoke-Dbgjs -Arguments (@('target', 'cdp', 'Debugger.setBreakpointByUrl', '--params', $parameters) + $scope) | |
| $breakpoint = ($breakpointText -join "`n") | ConvertFrom-Json | |
| try { | |
| $capture = @' | |
| const height = await page.evaluate(() => innerHeight); | |
| await page.setViewportSize({ width: 1250, height: height === 700 ? 701 : 700 }); | |
| await page.waitForFunction(() => globalThis.__webList && globalThis.__reproObservable && globalThis.__reproURI, null, { timeout: 10000 }); | |
| return { captured: true }; | |
| '@ | |
| Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $capture | Out-Host | |
| } finally { | |
| $remove = @{ breakpointId = $breakpoint.breakpointId } | ConvertTo-Json -Compress | |
| Invoke-Dbgjs -Arguments (@('target', 'cdp', 'Debugger.removeBreakpoint', '--params', $remove) + $scope) | Out-Host | |
| } | |
| Write-Host 'Injecting fake sessions; testing original CSS, hotfix, then original CSS again.' | |
| $fixture = @' | |
| return await page.evaluate(async () => { | |
| const list = __webList; | |
| const obs = value => __reproObservable('height-repro', value); | |
| const URI = __reproURI; | |
| if (list.sessions.length) throw new Error('Expected an isolated tab with no real sessions.'); | |
| const sessions = Array.from({ length: 40 }, (_, index) => { | |
| const id = `height-repro-${String(index).padStart(2, '0')}`; | |
| const resource = URI.parse(`test-session://${id}`); | |
| const now = new Date(Date.now() - index * 1000); | |
| const status = obs(index === 0 ? 2 : 3); | |
| const title = obs(index === 0 ? 'REPRO: three-line approval' : `Filler session ${index}`); | |
| const main = { | |
| resource: resource.with({ fragment: 'main' }), title, status, | |
| createdAt: now, updatedAt: obs(now), workspace: obs(undefined), | |
| changes: obs([]), changesets: obs([]), isArchived: obs(false), | |
| isRead: obs(true), description: obs(undefined), lastTurnEnd: obs(undefined), | |
| origin: { kind: 'user' }, interactivity: obs('interactive'), | |
| }; | |
| return { | |
| sessionId: id, resource, providerId: 'height-repro', sessionType: 'height-repro', | |
| icon: { id: 'beaker' }, createdAt: now, | |
| workspace: obs({ | |
| uri: URI.parse('file:///height-repro'), label: 'Synthetic repro only', | |
| icon: { id: 'folder' }, folders: [], requiresWorkspaceTrust: false, | |
| isVirtualWorkspace: false, | |
| }), | |
| isQuickChat: obs(false), title, updatedAt: obs(now), status, | |
| changesets: obs([]), changes: obs([]), changesSummary: obs(undefined), | |
| modelId: obs(undefined), mode: obs(undefined), loading: obs(false), | |
| isArchived: obs(false), isRead: obs(true), description: obs(undefined), | |
| lastTurnEnd: obs(undefined), chats: obs([main]), mainChat: obs(main), | |
| capabilities: obs({ supportsMultipleChats: false, supportsRename: false }), | |
| }; | |
| }); | |
| const target = sessions[0], main = target.mainChat.get(); | |
| target.capabilities.set({ supportsMultipleChats: true, supportsRename: false }, undefined); | |
| target.chats.set([main, ...Array.from({ length: 30 }, (_, index) => ({ | |
| ...main, resource: target.resource.with({ fragment: `child-${index}` }), | |
| title: obs(`Nested chat ${index}`), status: obs(3), | |
| }))], undefined); | |
| list._approvalModel.getApproval(main.resource).set({ | |
| approvalId: 'fake-approval', kind: 'terminal', | |
| label: 'echo one\necho two\necho three', languageId: 'shellscript', | |
| since: new Date(), confirm() { console.info('Fake approval; nothing executes.'); }, | |
| }, undefined); | |
| // Keep host discovery from replacing the fixture during background refreshes. | |
| list._sessionsManagementService.getSessions = () => sessions; | |
| list.sessions = sessions; | |
| list.options.compact = () => true; | |
| list.workspaceGroupCapped = false; | |
| list.sessionGroupLimit.set(100, undefined); | |
| list.setCompact(); | |
| list.tree.expand(target); | |
| // Hide only the sign-in overlay for the signed-out synthetic demonstration. | |
| // This does not authenticate or enable any backend functionality. | |
| const demoStyle = document.createElement('style'); | |
| demoStyle.textContent = '.monaco-dialog-modal-block:has(.sessions-welcome-dialog) { display: none !important; }'; | |
| document.head.appendChild(demoStyle); | |
| const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
| const findRow = () => [...list.listContainer.querySelectorAll('.session-title')] | |
| .find(element => element.textContent === 'REPRO: three-line approval')?.closest('.monaco-list-row'); | |
| const expectedHeight = list._delegate.getHeight(target); | |
| async function run(fixed) { | |
| document.getElementById('web-approval-height-fix')?.remove(); | |
| if (fixed) { | |
| const style = document.createElement('style'); | |
| style.id = 'web-approval-height-fix'; | |
| style.textContent = '.session-approval-row { line-height: 18px; }'; | |
| document.head.appendChild(style); | |
| } | |
| list.tree.scrollTop = 0; | |
| list.tree.updateElementHeight(target, expectedHeight); | |
| await delay(100); | |
| const measurements = [], cycles = []; | |
| const prototype = HTMLElement.prototype; | |
| const descriptor = Object.getOwnPropertyDescriptor(prototype, 'offsetHeight'); | |
| Object.defineProperty(prototype, 'offsetHeight', { | |
| ...descriptor, | |
| get() { | |
| const height = descriptor.get.call(this); | |
| if (this.matches('.monaco-list-row') | |
| && this.querySelector('.session-title')?.textContent === 'REPRO: three-line approval' | |
| && measurements.length < 30) { | |
| measurements.push(height); | |
| } | |
| return height; | |
| }, | |
| }); | |
| try { | |
| for (let cycle = 0; cycle < 3; cycle++) { | |
| for (let position = 0; position < 1500; position += 75) { | |
| list.tree.scrollTop = position; | |
| await delay(10); | |
| } | |
| list.tree.scrollTop = 0; | |
| await delay(100); | |
| const row = findRow(); | |
| if (!row) throw new Error('Target row disappeared.'); | |
| cycles.push(row.getBoundingClientRect().height); | |
| } | |
| } finally { | |
| Object.defineProperty(prototype, 'offsetHeight', descriptor); | |
| } | |
| return { measurements, cycles }; | |
| } | |
| const original = await run(false); | |
| const fixed = await run(true); | |
| const restored = await run(false); | |
| if (!original.cycles.every(height => height > expectedHeight * 3) | |
| || !restored.cycles.every(height => height > expectedHeight * 3)) { | |
| throw new Error('Bug did not reproduce: ' + JSON.stringify({ original, restored })); | |
| } | |
| if (!fixed.cycles.every(height => Math.abs(height - expectedHeight) <= 1)) { | |
| throw new Error('Hotfix did not stabilize height: ' + JSON.stringify(fixed)); | |
| } | |
| list.tree.setSelection([target]); | |
| // Show the displaced title in the final screenshot while retaining the bad height. | |
| list.tree.scrollTop = 600; | |
| globalThis.agentsRowRepro = { run, sessions, results: { original, fixed, restored } }; | |
| return { expectedHeight, original, fixed, restored }; | |
| }); | |
| '@ | |
| $resultText = Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program $fixture | |
| $result = ($resultText -join "`n") | ConvertFrom-Json | |
| $result | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'results.json') | |
| foreach ($name in 'original', 'fixed', 'restored') { | |
| Write-Host "$name measurements: $($result.$name.measurements -join ', ') px; final heights: $($result.$name.cycles -join ', ') px" | |
| } | |
| Invoke-Dbgjs -Arguments (@('playwright', '-') + $scope) -Program 'await page.mouse.move(1000, 100); return { ready: true };' | Out-Host | |
| Invoke-Dbgjs -Arguments (@('screenshot', 'capture', '--output', (Join-Path $OutputDirectory 'bug.png')) + $scope) | Out-Host | |
| Write-Host "PASS. Screenshot and results: $OutputDirectory" | |
| Write-Host "The browser remains open. Close it when done. Debug context: $context" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment