mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 00:26:21 +03:00
test(harness): session 5 runner + drag-drop bridge fingerprint (1 new spec, 79% → 80% coverage)
Coverage 60/76 → 61/76. One new spec lands. No new primitives — session 5 ran light because the runtime probe + bundled-source trace consumed half the budget (load-bearing finding documented in the docs commit that follows). New spec: - T18 (Drag-and-drop files into prompt) — Tier 1 / asar fingerprint against bundled mainView.js (first runner to read a non-index.js source — lib/asar.ts's readAsarFile already supports it). Four needles pin the preload-bridged path-resolution wiring: the property key `getPathForFile` + the `webUtils.getPathForFile(` call (both at case-doc :9267 — count 2× combined), `webUtils` (1×, :9267), `filePickers` (1×, :9267), `claudeAppSettings` (1×, :9552 — the contextBridge.exposeInMainWorld namespace the renderer accesses as window.claudeAppSettings). Per-needle occurrence counts attached as JSON for drift detection (mirrors T36's pattern). Bundle form matches case-doc form verbatim — no minified-vs-beautified gotcha (unlike T35's ~/.claude.json → .claude.json). Why Tier 1, not Tier 2/3: A real OS-level drag-drop test needs to put file URIs on the desktop's drag selection so Chromium's drop handler fires the path-resolution bridge with a file payload. Both backends are dead-ends with the primitives we have: - X11: xdotool can simulate mouse motion + button press but cannot put file URIs on the X11 XDND selection. A simulated drag against a marker window arrives at Chromium as a mouse drag with no file payload — the bridge is never exercised. A real OS-level XDND test needs a custom XDND source app (heavy primitive build); deferred. - Wayland: same shape — per-compositor IPC plus libei input injection. Same primitive gap. Since the load-bearing surface is the bridge wiring (preload expose + the webUtils.getPathForFile call), pinning the bundle strings catches every regression that would matter to the case-doc claim, without faking OS drag-drop. Same pattern as T35/T36 from session 4: when Tier 2 readback isn't reachable, ship the Tier 1 fingerprint against the actual load-bearing strings. README inventory updated to 61 specs (24 cross-env T-tests, 32 env-specific S-tests, 5 H-prefix harness self-tests). T18 row added; the `app.asar content reads` footnote calls out that T18 reads mainView.js (every other asar-fingerprint runner reads index.js). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ architecture, decisions, and rationale.
|
||||
|
||||
## Status
|
||||
|
||||
Sixty specs wired (23 cross-env T-tests, 32 env-specific S-tests,
|
||||
Sixty-one specs wired (24 cross-env T-tests, 32 env-specific S-tests,
|
||||
5 H-prefix harness self-tests). See
|
||||
[`docs/testing/runner-implementation-plan.md`](../../docs/testing/runner-implementation-plan.md)
|
||||
for the tiered triage of remaining tests and the per-spec rationale
|
||||
@@ -32,6 +32,7 @@ behind tier classification.
|
||||
| [T14b](../../docs/testing/cases/launch.md#t14--multi-instance-behavior) | Second invocation under same isolation exits cleanly; primary pid stays alive (runtime probe) | spawn delta + pgrep |
|
||||
| [T16](../../docs/testing/cases/code-tab-foundations.md#t16--code-tab-loads) | After `seedFromHost` + `userLoaded`, `CodeTab.activate()` resolves and ≥1 compact pill renders (env pill = Code-body mounted) | L1 + AX-tree |
|
||||
| [T17](../../docs/testing/cases/code-tab-foundations.md#t17--folder-picker-opens) | Code df-pill → env pill → Local → Select folder → Open folder triggers `dialog.showOpenDialog` (requires `CLAUDE_TEST_USE_HOST_CONFIG=1`) | L1 |
|
||||
| [T18](../../docs/testing/cases/code-tab-foundations.md#t18--drag-and-drop-files-into-prompt) | Bundled `mainView.js` preload contains the path-resolution bridge fingerprints: `getPathForFile` (2× — property key + the `webUtils.getPathForFile(` call, both at case-doc :9267), `webUtils`, `filePickers`, and the `claudeAppSettings` `contextBridge.exposeInMainWorld` namespace (case-doc :9552) — pins the load-bearing wiring without faking OS-level XDND drag (xdotool can't put file URIs on the X11 selection; Wayland needs per-compositor IPC + libei) | file probe |
|
||||
| [T22](../../docs/testing/cases/code-tab-workflow.md#t22--pr-monitoring-via-gh) | Bundled `index.js` contains `LocalSessions_$_getPrChecks` eipc channel name *and* `gh CLI not found in PATH` Linux-fallthrough throw site (Tier 1 fingerprint — eipc registry not introspectable from main) | file probe |
|
||||
| [T23](../../docs/testing/cases/code-tab-handoff.md#t23--desktop-notifications-fire) | Firing `new Notification({title})` from main reaches the session bus's `org.freedesktop.Notifications.Notify` (observed via `dbus-monitor`) | L1 + DBus subprocess |
|
||||
| [T24](../../docs/testing/cases/code-tab-handoff.md#t24--open-in-external-editor) | After `installOpenExternalMock` mirroring T25's pattern, `evalInMain` calls `shell.openExternal('vscode://file/...')`; mock records the URL verbatim, no real editor launch | L1 (mocked egress) |
|
||||
@@ -85,7 +86,7 @@ These specs exercise the substrate primitives in `lib/`: `xprop`
|
||||
shell-outs (T01, T04), `dbus-next` (T03), `dbus-monitor` subprocess
|
||||
eavesdrop (T23), Node-inspector runtime-attach
|
||||
(T07/T16/T17/T26/S10/S29-S35/T05-T14b L1 specs), `app.asar` content reads
|
||||
(S08/S09/S21/S22/S26/S27/S28/T11/T14a/T22/T30/T31/T32/T33/T35/T36/T37/T38/H02/H03/S33),
|
||||
(S08/S09/S21/S22/S26/S27/S28/T11/T14a/T18/T22/T30/T31/T32/T33/T35/T36/T37/T38/H02/H03/S33 — mostly `index.js`; T18 reads `mainView.js`),
|
||||
`/proc/$pid/cmdline` reads (S07/S12), pgrep-based pid deltas
|
||||
(T10/T14b/H04/S16/S30), `mount(8)` parsing (S16), source-tree probes
|
||||
against `scripts/launcher-common.sh` (S02), `dpkg-query` / `rpm -qR` /
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { readAsarFile, resolveAsarPath } from '../lib/asar.js';
|
||||
import { captureSessionEnv } from '../lib/diagnostics.js';
|
||||
|
||||
// T18 — Drag-and-drop files into prompt (Tier 1 / asar fingerprint).
|
||||
//
|
||||
// Backs T18 in docs/testing/cases/code-tab-foundations.md
|
||||
// ("Drag-and-drop files into prompt"). The case-doc's load-bearing
|
||||
// assertion is that the renderer resolves dropped File objects to
|
||||
// absolute paths via the preload-bridged
|
||||
// `claudeAppSettings.filePickers.getPathForFile`, which wraps
|
||||
// Electron's `webUtils.getPathForFile`. That wiring lives entirely
|
||||
// in the bundled `mainView.js` preload — case-doc anchors:
|
||||
//
|
||||
// - mainView.js:9267 — `filePickers.getPathForFile` wraps
|
||||
// `webUtils.getPathForFile`
|
||||
// - mainView.js:9552 — exposed to the renderer as
|
||||
// `window.claudeAppSettings`
|
||||
//
|
||||
// **Why Tier 1, not Tier 2/3.** A Tier 2/3 OS-level drag-drop test
|
||||
// would need to put file URIs on the desktop's drag selection so
|
||||
// Chromium's drop handler fires the path-resolution bridge. Both
|
||||
// backends are dead-ends with the primitives we have:
|
||||
//
|
||||
// - X11: `xdotool` can simulate mouse motion + button press but
|
||||
// cannot put file URIs on the X11 XDND selection. A simulated
|
||||
// drag against a marker window arrives at Chromium as a mouse
|
||||
// drag with no file payload — the bridge is never exercised.
|
||||
// A real OS-level XDND test needs a custom XDND source app
|
||||
// (heavy primitive build); deferred.
|
||||
// - Wayland: same shape — per-compositor IPC plus libei input
|
||||
// injection. Same primitive gap.
|
||||
//
|
||||
// Since the load-bearing surface is the bridge wiring (preload
|
||||
// expose + the `webUtils.getPathForFile` call), pinning the bundle
|
||||
// strings catches every regression that would matter to the
|
||||
// case-doc claim, without faking OS drag-drop. Same pattern as
|
||||
// T35/T36 from session 4: when Tier 2 readback isn't reachable,
|
||||
// ship the Tier 1 fingerprint against the actual load-bearing
|
||||
// strings.
|
||||
//
|
||||
// **What this catches.** Any rename / removal of the four needles
|
||||
// in the shipped `mainView.js` preload — i.e. a regression in the
|
||||
// path-resolution bridge wiring (the property key
|
||||
// `filePickers.getPathForFile`, the underlying
|
||||
// `webUtils.getPathForFile` call, or the `claudeAppSettings`
|
||||
// expose namespace).
|
||||
//
|
||||
// **What this does NOT catch.** The OS-level drop handler itself
|
||||
// — i.e. whether Chromium's drag-drop event actually reaches the
|
||||
// renderer with file payload on the host's compositor / window
|
||||
// system. That's a Tier 2/3 concern and stays manual until a
|
||||
// drag-source primitive lands.
|
||||
//
|
||||
// **Bundle vs case-doc anchor form.** The case-doc anchors point
|
||||
// at the beautified `build-reference/.../mainView.js` line numbers
|
||||
// (:9267 / :9552); the shipped minified bundle preserves the
|
||||
// property name and the `webUtils.getPathForFile(` call shape, so
|
||||
// the case-doc strings are also the bundle needles. No translation
|
||||
// needed (unlike T35's `~/.claude.json` → `.claude.json`).
|
||||
//
|
||||
// Observed counts in the installed asar at write time:
|
||||
// `getPathForFile` = 2 (property key + the actual call),
|
||||
// `webUtils` = 1, `filePickers` = 1, `claudeAppSettings` = 1.
|
||||
// Per-needle occurrence counts are recorded in the attached JSON
|
||||
// for drift detection (mirrors T36's pattern).
|
||||
//
|
||||
// Pure file probe — no app launch. Applies to all rows; no
|
||||
// skipUnlessRow gate.
|
||||
|
||||
interface FingerprintEntry {
|
||||
needle: string;
|
||||
caseDocAnchor: string;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
const FINGERPRINTS: FingerprintEntry[] = [
|
||||
{
|
||||
needle: 'getPathForFile',
|
||||
caseDocAnchor: 'mainView.js:9267',
|
||||
rationale:
|
||||
'the renderer-bridged method name. Both occurrences in the ' +
|
||||
'bundle (property key + the underlying ' +
|
||||
'`webUtils.getPathForFile(` call) live on this single line ' +
|
||||
'in the beautified reference. Renaming this is the most ' +
|
||||
'load-bearing single regression for the path-resolution ' +
|
||||
'bridge.',
|
||||
},
|
||||
{
|
||||
needle: 'webUtils',
|
||||
caseDocAnchor: 'mainView.js:9267',
|
||||
rationale:
|
||||
'the Electron module the bridge wraps. If the preload ever ' +
|
||||
'switches away from `webUtils.getPathForFile` (e.g. back to ' +
|
||||
'the deprecated `File.path`), this needle disappears even ' +
|
||||
'when `getPathForFile` survives elsewhere.',
|
||||
},
|
||||
{
|
||||
needle: 'filePickers',
|
||||
caseDocAnchor: 'mainView.js:9267',
|
||||
rationale:
|
||||
'the property under `claudeAppSettings` the renderer reads. ' +
|
||||
'Pins the namespace nesting — if the bridge moves out from ' +
|
||||
'under `filePickers`, the renderer call site breaks even ' +
|
||||
'when the underlying `webUtils.getPathForFile` wrap is ' +
|
||||
'still wired.',
|
||||
},
|
||||
{
|
||||
needle: 'claudeAppSettings',
|
||||
caseDocAnchor: 'mainView.js:9552',
|
||||
rationale:
|
||||
'the `contextBridge.exposeInMainWorld` namespace the ' +
|
||||
'renderer accesses as `window.claudeAppSettings`. Without ' +
|
||||
'this expose, the renderer never reaches the bridge at all.',
|
||||
},
|
||||
];
|
||||
|
||||
const SOURCE_FILE = '.vite/build/mainView.js';
|
||||
|
||||
test.setTimeout(15_000);
|
||||
|
||||
test('T18 — drag-drop path-resolution bridge asar fingerprints', async ({}, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'severity', description: 'Critical' });
|
||||
testInfo.annotations.push({
|
||||
type: 'surface',
|
||||
description: 'Code tab → Prompt area',
|
||||
});
|
||||
|
||||
await testInfo.attach('session-env', {
|
||||
body: JSON.stringify(captureSessionEnv(), null, 2),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
|
||||
let asarPath: string;
|
||||
try {
|
||||
asarPath = resolveAsarPath();
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
test.skip(true, `asar not resolvable: ${msg}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await testInfo.attach('asar-path', {
|
||||
body: asarPath,
|
||||
contentType: 'text/plain',
|
||||
});
|
||||
|
||||
let contents: string;
|
||||
try {
|
||||
contents = readAsarFile(SOURCE_FILE, asarPath);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`[T18] failed to read ${SOURCE_FILE} from ${asarPath}: ${msg}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Per-needle occurrence count for drift detection — a future
|
||||
// regression that drops the count from N→N-1 (without dropping
|
||||
// it to zero) is still load-bearing signal worth surfacing in
|
||||
// the attachment. Mirrors T36's pattern.
|
||||
const results = FINGERPRINTS.map((entry) => {
|
||||
let occurrences = 0;
|
||||
let idx = contents.indexOf(entry.needle);
|
||||
while (idx !== -1) {
|
||||
occurrences += 1;
|
||||
idx = contents.indexOf(entry.needle, idx + 1);
|
||||
}
|
||||
return {
|
||||
name: entry.needle,
|
||||
anchorRef: entry.caseDocAnchor,
|
||||
rationale: entry.rationale,
|
||||
count: occurrences,
|
||||
found: occurrences > 0,
|
||||
};
|
||||
});
|
||||
|
||||
await testInfo.attach('drag-drop-bridge-fingerprints', {
|
||||
body: JSON.stringify(
|
||||
{ asarPath, sourceFile: SOURCE_FILE, needles: results },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
|
||||
const missing = results.filter((r) => !r.found).map((r) => r.name);
|
||||
expect(
|
||||
missing,
|
||||
'every drag-drop path-resolution bridge needle is present in ' +
|
||||
'the bundled `mainView.js` preload (per ' +
|
||||
'code-tab-foundations.md T18 code anchors :9267 / :9552). ' +
|
||||
'Missing needle(s) indicate the preload-bridged ' +
|
||||
'`claudeAppSettings.filePickers.getPathForFile` wiring has ' +
|
||||
'been refactored — re-anchor against the new bundle form.',
|
||||
).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user