2026-08-06 13:59:05 +08:00
// Web e2e scenario: the real skill-load recording, seeded cold through the
// persistence seam, renders through ui-skill's keyed toolview without a model
// call. The disclosure proves replay-stable naming and exact durable output.
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
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 , seedSession , watchConsole , webSnapshotMode , type WebScaffold ,
} from './scaffold.ts'
import { newEnglishPage , saveFailureShot } from './support.ts'
2026-08-24 06:09:45 +08:00
const FIXTURE = fileURLToPath ( new URL ( '../../../snapshots/session/skill-load/session.jsonl' , import . meta . url ) )
2026-08-24 03:46:58 +08:00
const SNAPSHOT_DIR = fileURLToPath ( new URL ( '../../../snapshots/web/skill-tool-row' , import . meta . url ) )
const UI_EXPECTED = fileURLToPath ( new URL ( '../../../snapshots/web/skill-tool-row/ui.expected.md' , import . meta . url ) )
2026-08-06 13:59:05 +08:00
const MODE = webSnapshotMode ( )
const SEED_ID = 'skill-tool-row-web-e2e'
2026-08-13 18:45:16 +08:00
const PROMPT = 'Load the editing-cordis-compositions skill with the skill tool, then reply DONE.'
2026-08-06 13:59:05 +08:00
describe . skipIf ( MODE === 'record' ) ( 'web e2e: dedicated Skill tool row' , ( ) = > {
let scaffold : WebScaffold
let browser : Browser
let page : Page
let tripwire : ReturnType < typeof watchConsole >
beforeAll ( async ( ) = > {
const fixture = await readFile ( FIXTURE , 'utf8' )
expect ( fixtureUserPrompts ( fixture ) ) . toEqual ( [ PROMPT ] )
scaffold = await launchWebScaffold ( { } )
await seedSession ( scaffold , fixture , SEED_ID )
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 } )
const groupRow = page . locator ( '[role="treeitem"]' ) . first ( )
await groupRow . waitFor ( { timeout : 15_000 } )
await groupRow . click ( )
const sessionRow = page . locator ( '[role="treeitem"]' ) . nth ( 1 )
await sessionRow . waitFor ( { timeout : 10_000 } )
await sessionRow . click ( )
await page . locator ( '[data-tool="skill"]' ) . waitFor ( { timeout : 15_000 } )
} , 120 _000 )
afterAll ( async ( ) = > {
await browser ? . close ( )
await scaffold ? . close ( )
} )
it ( 'expands the loaded skill to its exact recorded instructions' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-skill-tool-row' ) )
const call = page . locator ( '[data-tool="skill"]' )
2026-08-13 18:45:16 +08:00
const row = call . getByRole ( 'button' , { name : 'Skill editing-cordis-compositions' } )
2026-08-06 13:59:05 +08:00
await expect . poll ( ( ) = > row . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' )
2026-08-13 18:45:16 +08:00
expect ( await call . getByText ( 'editing-cordis-compositions' , { exact : true } ) . count ( ) ) . toBe ( 1 )
2026-08-06 13:59:05 +08:00
await row . click ( )
await expect . poll ( ( ) = > row . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' )
await call . getByText ( 'Instructions' , { exact : true } ) . waitFor ( )
const output = call . locator ( 'pre' )
await output . waitFor ( )
2026-08-13 18:45:16 +08:00
expect ( await output . textContent ( ) ) . toContain ( '<skill_content name="editing-cordis-compositions">' )
2026-08-18 19:19:58 +08:00
expect ( await output . textContent ( ) ) . toContain ( 'Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI' )
2026-08-06 13:59:05 +08:00
expect ( await output . evaluate ( element = > getComputedStyle ( element . parentElement ! ) . maxHeight ) ) . toBe ( '260px' )
const snapshot = ( await captureStableAria ( page , '[class*="centerCol"]' , scaffold . workspaceCwd ) )
. replace ( /\b\d{1,2}\/\d{1,2}(?= \{\{clock\}\})/g , '{{date}}' )
2026-08-22 01:47:43 +08:00
. replace ( /\{\{date\}\} (?=\{\{clock\}\} Ran for)/g , '' )
2026-08-06 13:59:05 +08:00
. split ( SEED_ID ) . join ( '{{seededId}}' )
await compareOrRefreshGolden ( UI_EXPECTED , snapshot , MODE )
expect ( tripwire . pageErrors ) . toEqual ( [ ] )
expect ( tripwire . warnings ) . toEqual ( [ ] )
} , 60 _000 )
it ( 'keeps its snapshot inventory closed' , async ( ) = > {
await assertFixtureInventory ( SNAPSHOT_DIR , [ 'ui.expected.md' ] )
} )
} )