feat(feedback): disclose session sharing in the /feedback acknowledgement
The /feedback acknowledgement now echoes the receiving session id and reports the mounted telemetry backend's sharing policy: the telemetry seam exposes a backend-independent TelemetrySharingStatus through a required abstract sharing member on the Telemetry service, the OTel backend maps its mode onto it, and the command appends one policy-only sharing sentence (full / feedback-only / disabled / not configured) to the acknowledgement. The web client renders the text through the existing command row without a client change; a new assembled-browser e2e mounts the shipped telemetry row in FULL mode against a local dead endpoint and pins the shipped default sentence as a keyless golden.
This commit is contained in:
89
apps/web/tests/feedback-command.e2e.ts
Normal file
89
apps/web/tests/feedback-command.e2e.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// Keyless assembled-browser coverage for the /feedback command over the
|
||||
// shipped Web bundles and the real host wire. The command plane settles
|
||||
// without a model turn: the host appends the log-only command/run +
|
||||
// feedback/record + command/done lifecycle, and the transcript renders the
|
||||
// acknowledgement — the recorded session id plus the session-sharing
|
||||
// disclosure — as a persistent command row. The scaffold mounts the shipped
|
||||
// telemetry row in FULL mode against a local dead endpoint (no record leaves
|
||||
// the process), so the golden pins the shipped default sentence
|
||||
// `Session sharing is enabled.`; the per-status sentences are pinned by the
|
||||
// package and OTel unit tests.
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join } from 'node:path'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import {
|
||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
|
||||
} from './scaffold.ts'
|
||||
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/feedback-command', import.meta.url))
|
||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||
const ACK_EXPECTED = join(SNAPSHOT_DIR, 'ack.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
// Discard port: loopback listener never binds, so FULL telemetry discloses
|
||||
// the shipped default policy without any record reaching a collector.
|
||||
const TELEMETRY_URL = 'http://127.0.0.1:9/v1/logs'
|
||||
|
||||
const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.'
|
||||
|
||||
describe('web e2e: /feedback command acknowledgement', () => {
|
||||
let scaffold: WebScaffold
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({
|
||||
telemetryUrl: TELEMETRY_URL,
|
||||
...(MODE === 'record' ? {} : { replayFixture: FIXTURE }),
|
||||
})
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
// Fresh world: connecting a workspace births the blank session whose
|
||||
// live composer accepts the slash line.
|
||||
await connectFreshWorkspace(page, scaffold.workspaceCwd)
|
||||
}, 120_000)
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close()
|
||||
await scaffold?.close()
|
||||
})
|
||||
|
||||
it('records feedback and renders the acknowledgement with session id and sharing status', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-feedback-command'))
|
||||
if (MODE !== 'record') {
|
||||
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
|
||||
}
|
||||
|
||||
// First send the recorded prompt so the transcript is active — a command
|
||||
// row does not render while a fresh session is still blank.
|
||||
const input = page.locator('textarea').first()
|
||||
await input.fill(PROMPT)
|
||||
await input.press('Enter')
|
||||
await scaffold.whenTurnSettled()
|
||||
await page.getByText('LIGHTHOUSE', { exact: true }).waitFor({ timeout: 15_000 })
|
||||
|
||||
await input.fill('/feedback the diff view is unreadable')
|
||||
await input.press('Enter')
|
||||
// The command plane settles without a model turn: the ack row names the
|
||||
// recorded session and the mounted FULL backend's disclosure.
|
||||
await page.getByText(/Feedback recorded for session/).waitFor({ timeout: 10_000 })
|
||||
expect(await page.getByText(/Session sharing is enabled/).count()).toBe(1)
|
||||
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(ACK_EXPECTED, snapshot, MODE)
|
||||
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
}, 60_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps the fixture inventory closed', async () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl', 'ack.expected.md'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user