Browse Source

fix(web): make replayProvidersOnly self-consistent and poll for the real model label

The option now fails loud without replayFixture instead of silently
mounting nothing, and its JSDoc states the interplay with the
consumption check. The fixture is a non-empty header row (no longer a
0-byte placeholder), and the model-label assertion polls for
DeepSeek-V4-Flash (the directory loads asynchronously) instead of
reading the attribute once.
Chinesezjc 1 tháng trước cách đây
mục cha
commit
ff52c253b1

+ 7 - 6
apps/web/tests/plan-control-row.e2e.ts

@@ -53,10 +53,10 @@ describe('web e2e: plan chip click area at the narrow viewport', () => {
   const sessionEvents: SessionEvent[] = []
 
   beforeAll(async () => {
-    // The fixture carries the deterministic provider catalog (no model call
-    // happens — the /plan command never steers a message), so the model
-    // trigger renders its real long label, which is what made the reported
-    // overlap measurable.
+    // replayProvidersOnly mounts the provider catalog without any recorded
+    // script to consume (no model call happens — the /plan command never
+    // steers a message), so the model trigger renders its real long label,
+    // which is what made the reported overlap measurable.
     scaffold = await launchWebScaffold({ replayFixture: FIXTURE, replayProvidersOnly: true })
     scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
     browser = await chromium.launch()
@@ -88,8 +88,9 @@ describe('web e2e: plan chip click area at the narrow viewport', () => {
     await chip.waitFor({ timeout: 30_000 })
     await trigger.waitFor({ timeout: 10_000 })
     // The regression depends on the real model label width: a bare fallback
-    // trigger would fit beside the chip even on the pre-fix layout.
-    expect(await trigger.getAttribute('aria-label')).toContain('DeepSeek-V4-Flash')
+    // trigger would fit beside the chip even on the pre-fix layout. The
+    // directory loads asynchronously, so poll for the real label.
+    await expect.poll(() => trigger.getAttribute('aria-label'), { timeout: 10_000 }).toContain('DeepSeek-V4-Flash')
     const chipBox = await chip.boundingBox()
     const triggerBox = await trigger.boundingBox()
     expect(chipBox).not.toBeNull()

+ 8 - 3
apps/web/tests/scaffold.ts

@@ -131,9 +131,11 @@ export interface LaunchOptions {
   replayFixture?: string
   /**
    * Mount the replay provider catalog (the model directory the UI shows)
-   * without any recorded script to consume: for scenarios that never call a
-   * model but need the real provider/model labels rendered. The teardown
-   * consumption check is skipped for this mode.
+   * without consuming any recorded script: for scenarios that never call a
+   * model but need the real provider/model labels rendered. Requires
+   * {@link replayFixture} (its file is read for the header); the teardown
+   * consumption check is skipped for this mode. `replayFixture` without this
+   * flag keeps the consumption check.
    */
   replayProvidersOnly?: boolean
   /**
@@ -358,6 +360,9 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
     // disable llm-deepseek; the first-run lane keeps it mounted but has no
     // replay fixture and never streams. The direct install, unlike the plugin
     // row, returns the ReplayHandle for the teardown consumption check.
+    if (options.replayProvidersOnly && options.replayFixture === undefined) {
+      throw new Error('replayProvidersOnly requires replayFixture (its file supplies the header)')
+    }
     if (mode !== 'record' && options.replayFixture !== undefined) {
       replayHandle = installLlmReplay(ctx, {
         file: options.replayFixture,

+ 1 - 0
apps/web/tests/snapshots/plan-narrow-viewport/session.jsonl

@@ -0,0 +1 @@
+{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785015039278,"cwd":"{{cwd}}/workspace"}