/ clis / gemini / reply-state.test.js
reply-state.test.js
  1  import { describe, expect, it, vi } from 'vitest';
  2  import { __test__, waitForGeminiResponse, waitForGeminiSubmission } from './utils.js';
  3  function snapshot(overrides = {}) {
  4      return {
  5          turns: [],
  6          transcriptLines: [],
  7          composerHasText: false,
  8          isGenerating: false,
  9          structuredTurnsTrusted: true,
 10          ...overrides,
 11      };
 12  }
 13  function createPageMock() {
 14      return {
 15          goto: vi.fn().mockResolvedValue(undefined),
 16          evaluate: vi.fn(),
 17          getCookies: vi.fn().mockResolvedValue([]),
 18          snapshot: vi.fn().mockResolvedValue(undefined),
 19          click: vi.fn().mockResolvedValue(undefined),
 20          typeText: vi.fn().mockResolvedValue(undefined),
 21          pressKey: vi.fn().mockResolvedValue(undefined),
 22          scrollTo: vi.fn().mockResolvedValue(undefined),
 23          getFormState: vi.fn().mockResolvedValue({}),
 24          wait: vi.fn().mockResolvedValue(undefined),
 25          tabs: vi.fn().mockResolvedValue([]),
 26          selectTab: vi.fn().mockResolvedValue(undefined),
 27          networkRequests: vi.fn().mockResolvedValue([]),
 28          consoleMessages: vi.fn().mockResolvedValue([]),
 29          scroll: vi.fn().mockResolvedValue(undefined),
 30          autoScroll: vi.fn().mockResolvedValue(undefined),
 31          installInterceptor: vi.fn().mockResolvedValue(undefined),
 32          getInterceptedRequests: vi.fn().mockResolvedValue([]),
 33          waitForCapture: vi.fn().mockResolvedValue(undefined),
 34          screenshot: vi.fn().mockResolvedValue(''),
 35          nativeType: vi.fn().mockResolvedValue(undefined),
 36          nativeKeyPress: vi.fn().mockResolvedValue(undefined),
 37      };
 38  }
 39  describe('Gemini snapshot diff helpers', () => {
 40      it('reports appended trusted turns when the current snapshot extends the baseline', () => {
 41          const before = snapshot({
 42              turns: [{ Role: 'Assistant', Text: '旧回答' }],
 43          });
 44          const current = snapshot({
 45              turns: [
 46                  { Role: 'Assistant', Text: '旧回答' },
 47                  { Role: 'User', Text: '请只回复:OK' },
 48              ],
 49          });
 50          expect(__test__.diffTrustedStructuredTurns(before, current)).toEqual({
 51              appendedTurns: [{ Role: 'User', Text: '请只回复:OK' }],
 52              hasTrustedAppend: true,
 53              hasNewUserTurn: true,
 54              hasNewAssistantTurn: false,
 55          });
 56      });
 57      it('treats restored structured turns as untrusted when the pre-send snapshot had no trustworthy turns', () => {
 58          const before = snapshot({
 59              turns: [],
 60              transcriptLines: ['旧问题', '旧回答'],
 61              structuredTurnsTrusted: false,
 62          });
 63          const current = snapshot({
 64              turns: [
 65                  { Role: 'User', Text: '旧问题' },
 66                  { Role: 'Assistant', Text: '旧回答' },
 67              ],
 68              transcriptLines: ['旧问题', '旧回答'],
 69              structuredTurnsTrusted: true,
 70          });
 71          expect(__test__.diffTrustedStructuredTurns(before, current)).toEqual({
 72              appendedTurns: [],
 73              hasTrustedAppend: false,
 74              hasNewUserTurn: false,
 75              hasNewAssistantTurn: false,
 76          });
 77      });
 78      it('keeps transcript delta lines raw for later conservative fallback checks', () => {
 79          const before = snapshot({
 80              transcriptLines: ['baseline'],
 81          });
 82          const current = snapshot({
 83              transcriptLines: ['baseline', '关于“请只回复:OK”,这里是解释。'],
 84              structuredTurnsTrusted: false,
 85          });
 86          expect(__test__.diffTranscriptLines(before, current)).toEqual([
 87              '关于“请只回复:OK”,这里是解释。',
 88          ]);
 89      });
 90  });
 91  describe('Gemini submission state', () => {
 92      it('confirms submission from a trusted appended user turn', async () => {
 93          const page = createPageMock();
 94          const evaluate = vi.mocked(page.evaluate);
 95          evaluate
 96              .mockResolvedValueOnce('https://gemini.google.com/app')
 97              .mockResolvedValueOnce({
 98              turns: [
 99                  { Role: 'Assistant', Text: '旧回答' },
100                  { Role: 'User', Text: '请只回复:OK' },
101              ],
102              transcriptLines: ['baseline', '请只回复:OK'],
103              composerHasText: false,
104              isGenerating: true,
105              structuredTurnsTrusted: true,
106          });
107          const result = await waitForGeminiSubmission(page, snapshot({
108              turns: [{ Role: 'Assistant', Text: '旧回答' }],
109              transcriptLines: ['baseline'],
110              composerHasText: true,
111              structuredTurnsTrusted: true,
112          }), 4);
113          expect(result).toEqual({
114              snapshot: {
115                  turns: [
116                      { Role: 'Assistant', Text: '旧回答' },
117                      { Role: 'User', Text: '请只回复:OK' },
118                  ],
119                  transcriptLines: ['baseline', '请只回复:OK'],
120                  composerHasText: false,
121                  isGenerating: true,
122                  structuredTurnsTrusted: true,
123              },
124              preSendAssistantCount: 1,
125              userAnchorTurn: { Role: 'User', Text: '请只回复:OK' },
126              reason: 'user_turn',
127          });
128      });
129      it('confirms submission from composer cleared plus generating even when transcript has not changed yet', async () => {
130          const page = createPageMock();
131          const evaluate = vi.mocked(page.evaluate);
132          evaluate
133              .mockResolvedValueOnce('https://gemini.google.com/app')
134              .mockResolvedValueOnce({
135              turns: [],
136              transcriptLines: ['baseline'],
137              composerHasText: false,
138              isGenerating: true,
139              structuredTurnsTrusted: false,
140          });
141          const result = await waitForGeminiSubmission(page, snapshot({
142              transcriptLines: ['baseline'],
143              composerHasText: true,
144              structuredTurnsTrusted: false,
145          }), 2);
146          expect(result).toEqual({
147              snapshot: {
148                  turns: [],
149                  transcriptLines: ['baseline'],
150                  composerHasText: false,
151                  isGenerating: true,
152                  structuredTurnsTrusted: false,
153              },
154              preSendAssistantCount: 0,
155              userAnchorTurn: null,
156              reason: 'composer_generating',
157          });
158      });
159      it('confirms submission from generating state even when the pre-send baseline composer was empty', async () => {
160          const page = createPageMock();
161          const evaluate = vi.mocked(page.evaluate);
162          evaluate
163              .mockResolvedValueOnce('https://gemini.google.com/app')
164              .mockResolvedValueOnce({
165              turns: [
166                  { Role: 'User', Text: '你说\n\n请只回复:DBG2' },
167                  { Role: 'User', Text: '请只回复:DBG2' },
168              ],
169              transcriptLines: ['baseline', '请只回复:DBG2'],
170              composerHasText: false,
171              isGenerating: true,
172              structuredTurnsTrusted: true,
173          });
174          const result = await waitForGeminiSubmission(page, snapshot({
175              turns: [{ Role: 'Assistant', Text: '需要我为你做些什么?' }],
176              transcriptLines: ['baseline'],
177              composerHasText: false,
178              structuredTurnsTrusted: true,
179          }), 2);
180          expect(result).toEqual({
181              snapshot: {
182                  turns: [
183                      { Role: 'User', Text: '你说\n\n请只回复:DBG2' },
184                      { Role: 'User', Text: '请只回复:DBG2' },
185                  ],
186                  transcriptLines: ['baseline', '请只回复:DBG2'],
187                  composerHasText: false,
188                  isGenerating: true,
189                  structuredTurnsTrusted: true,
190              },
191              preSendAssistantCount: 1,
192              userAnchorTurn: { Role: 'User', Text: '请只回复:DBG2' },
193              reason: 'composer_generating',
194          });
195      });
196      it('confirms submission from composer cleared plus transcript growth when generation state is unavailable', async () => {
197          const page = createPageMock();
198          const evaluate = vi.mocked(page.evaluate);
199          // This transcript delta may be only a prompt echo. It is allowed to confirm
200          // submission only because the composer has already cleared, and it must never
201          // be reused later as reply ownership evidence.
202          evaluate
203              .mockResolvedValueOnce('https://gemini.google.com/app')
204              .mockResolvedValueOnce({
205              turns: [],
206              transcriptLines: ['baseline', '请只回复:OK'],
207              composerHasText: false,
208              isGenerating: false,
209              structuredTurnsTrusted: false,
210          });
211          const result = await waitForGeminiSubmission(page, snapshot({
212              transcriptLines: ['baseline'],
213              composerHasText: true,
214              structuredTurnsTrusted: false,
215          }), 2);
216          expect(result).toEqual({
217              snapshot: {
218                  turns: [],
219                  transcriptLines: ['baseline', '请只回复:OK'],
220                  composerHasText: false,
221                  isGenerating: false,
222                  structuredTurnsTrusted: false,
223              },
224              preSendAssistantCount: 0,
225              userAnchorTurn: null,
226              reason: 'composer_transcript',
227          });
228      });
229      it('does not confirm submission when old structured turns only reappear after an untrusted pre-send snapshot', async () => {
230          const page = createPageMock();
231          const evaluate = vi.mocked(page.evaluate);
232          evaluate
233              .mockResolvedValueOnce('https://gemini.google.com/app')
234              .mockResolvedValueOnce({
235              turns: [
236                  { Role: 'User', Text: '旧问题' },
237                  { Role: 'Assistant', Text: '旧回答' },
238              ],
239              transcriptLines: ['旧问题', '旧回答'],
240              composerHasText: false,
241              isGenerating: false,
242              structuredTurnsTrusted: true,
243          })
244              .mockResolvedValueOnce('https://gemini.google.com/app')
245              .mockResolvedValueOnce({
246              turns: [
247                  { Role: 'User', Text: '旧问题' },
248                  { Role: 'Assistant', Text: '旧回答' },
249              ],
250              transcriptLines: ['旧问题', '旧回答'],
251              composerHasText: false,
252              isGenerating: false,
253              structuredTurnsTrusted: true,
254          });
255          const result = await waitForGeminiSubmission(page, snapshot({
256              turns: [],
257              transcriptLines: ['旧问题', '旧回答'],
258              composerHasText: true,
259              structuredTurnsTrusted: false,
260          }), 2);
261          expect(result).toBeNull();
262      });
263      it('does not confirm submission from transcript growth alone when the composer never clears', async () => {
264          const page = createPageMock();
265          const evaluate = vi.mocked(page.evaluate);
266          evaluate
267              .mockResolvedValueOnce('https://gemini.google.com/app')
268              .mockResolvedValueOnce({
269              turns: [],
270              transcriptLines: ['baseline', '请只回复:OK'],
271              composerHasText: true,
272              isGenerating: false,
273              structuredTurnsTrusted: false,
274          })
275              .mockResolvedValueOnce('https://gemini.google.com/app')
276              .mockResolvedValueOnce({
277              turns: [],
278              transcriptLines: ['baseline', '请只回复:OK'],
279              composerHasText: true,
280              isGenerating: false,
281              structuredTurnsTrusted: false,
282          });
283          const result = await waitForGeminiSubmission(page, snapshot({
284              transcriptLines: ['baseline'],
285              composerHasText: true,
286              structuredTurnsTrusted: false,
287          }), 2);
288          expect(result).toBeNull();
289      });
290      it('does not confirm transcript-only submission while url remains /app root', async () => {
291          const page = createPageMock();
292          const evaluate = vi.mocked(page.evaluate);
293          evaluate
294              .mockResolvedValueOnce('https://gemini.google.com/app')
295              .mockResolvedValueOnce({
296              url: 'https://gemini.google.com/app',
297              turns: [],
298              transcriptLines: ['baseline', 'prompt'],
299              composerHasText: false,
300              isGenerating: false,
301              structuredTurnsTrusted: false,
302          })
303              .mockResolvedValueOnce('https://gemini.google.com/app')
304              .mockResolvedValueOnce({
305              url: 'https://gemini.google.com/app',
306              turns: [],
307              transcriptLines: ['baseline', 'prompt'],
308              composerHasText: false,
309              isGenerating: false,
310              structuredTurnsTrusted: false,
311          });
312          const result = await waitForGeminiSubmission(page, snapshot({
313              url: 'https://gemini.google.com/app',
314              transcriptLines: ['baseline'],
315              composerHasText: true,
316              structuredTurnsTrusted: false,
317          }), 2);
318          expect(result).toBeNull();
319      });
320      it('keeps polling past ten seconds when the overall timeout budget still allows submission confirmation', async () => {
321          const page = createPageMock();
322          const evaluate = vi.mocked(page.evaluate);
323          for (let index = 0; index < 10; index += 1) {
324              evaluate
325                  .mockResolvedValueOnce('https://gemini.google.com/app')
326                  .mockResolvedValueOnce({
327                  turns: [],
328                  transcriptLines: ['baseline'],
329                  composerHasText: true,
330                  isGenerating: false,
331                  structuredTurnsTrusted: false,
332              });
333          }
334          evaluate
335              .mockResolvedValueOnce('https://gemini.google.com/app')
336              .mockResolvedValueOnce({
337              turns: [],
338              transcriptLines: ['baseline', '请只回复:OK'],
339              composerHasText: false,
340              isGenerating: false,
341              structuredTurnsTrusted: false,
342          });
343          const result = await waitForGeminiSubmission(page, snapshot({
344              transcriptLines: ['baseline'],
345              composerHasText: true,
346              structuredTurnsTrusted: false,
347          }), 12);
348          expect(result?.reason).toBe('composer_transcript');
349      });
350  });
351  describe('Gemini reply state', () => {
352      it('does not reuse an older identical reply when the submission baseline has no structured user anchor', async () => {
353          const page = createPageMock();
354          const evaluate = vi.mocked(page.evaluate);
355          evaluate
356              .mockResolvedValueOnce('https://gemini.google.com/app')
357              .mockResolvedValueOnce({
358              turns: [{ Role: 'Assistant', Text: 'OK' }],
359              transcriptLines: ['baseline', 'OK'],
360              composerHasText: false,
361              isGenerating: false,
362              structuredTurnsTrusted: true,
363          })
364              .mockResolvedValueOnce('https://gemini.google.com/app')
365              .mockResolvedValueOnce({
366              turns: [
367                  { Role: 'Assistant', Text: 'OK' },
368                  { Role: 'Assistant', Text: 'OK' },
369              ],
370              transcriptLines: ['baseline', 'OK'],
371              composerHasText: false,
372              isGenerating: false,
373              structuredTurnsTrusted: true,
374          })
375              .mockResolvedValueOnce('https://gemini.google.com/app')
376              .mockResolvedValueOnce({
377              turns: [
378                  { Role: 'Assistant', Text: 'OK' },
379                  { Role: 'Assistant', Text: 'OK' },
380              ],
381              transcriptLines: ['baseline', 'OK'],
382              composerHasText: false,
383              isGenerating: false,
384              structuredTurnsTrusted: true,
385          });
386          const result = await waitForGeminiResponse(page, {
387              snapshot: snapshot({
388                  turns: [{ Role: 'Assistant', Text: 'OK' }],
389                  transcriptLines: ['baseline', 'OK'],
390                  composerHasText: false,
391                  isGenerating: true,
392                  structuredTurnsTrusted: true,
393              }),
394              preSendAssistantCount: 1,
395              userAnchorTurn: null,
396              reason: 'composer_generating',
397          }, '请只回复:OK', 6);
398          expect(result).toBe('OK');
399      });
400      it('does not treat prepended older history as the current round reply when reply ownership has no user anchor', async () => {
401          const page = createPageMock();
402          const evaluate = vi.mocked(page.evaluate);
403          evaluate
404              .mockResolvedValueOnce('https://gemini.google.com/app')
405              .mockResolvedValueOnce({
406              turns: [
407                  { Role: 'Assistant', Text: '更早的问题' },
408                  { Role: 'Assistant', Text: '旧回答' },
409              ],
410              transcriptLines: ['baseline'],
411              composerHasText: false,
412              isGenerating: false,
413              structuredTurnsTrusted: true,
414          })
415              .mockResolvedValueOnce('https://gemini.google.com/app')
416              .mockResolvedValueOnce({
417              turns: [
418                  { Role: 'Assistant', Text: '更早的问题' },
419                  { Role: 'Assistant', Text: '旧回答' },
420              ],
421              transcriptLines: ['baseline'],
422              composerHasText: false,
423              isGenerating: false,
424              structuredTurnsTrusted: true,
425          })
426              .mockResolvedValueOnce('https://gemini.google.com/app')
427              .mockResolvedValueOnce({
428              turns: [
429                  { Role: 'Assistant', Text: '更早的问题' },
430                  { Role: 'Assistant', Text: '旧回答' },
431              ],
432              transcriptLines: ['baseline'],
433              composerHasText: false,
434              isGenerating: false,
435              structuredTurnsTrusted: true,
436          });
437          const result = await waitForGeminiResponse(page, {
438              snapshot: snapshot({
439                  turns: [{ Role: 'Assistant', Text: '旧回答' }],
440                  transcriptLines: ['baseline'],
441                  composerHasText: false,
442                  isGenerating: true,
443                  structuredTurnsTrusted: true,
444              }),
445              preSendAssistantCount: 1,
446              userAnchorTurn: null,
447              reason: 'composer_generating',
448          }, '请只回复:OK', 6);
449          expect(result).toBe('');
450      });
451      it('accepts a reply when the submission snapshot contains only the current round user turns and later appends a new assistant', async () => {
452          const page = createPageMock();
453          const evaluate = vi.mocked(page.evaluate);
454          evaluate
455              .mockResolvedValueOnce('https://gemini.google.com/app')
456              .mockResolvedValueOnce({
457              turns: [
458                  { Role: 'User', Text: '你说\n\n请只回复:DBGREG' },
459                  { Role: 'User', Text: '请只回复:DBGREG' },
460                  { Role: 'Assistant', Text: 'DBGREG' },
461              ],
462              transcriptLines: ['baseline'],
463              composerHasText: false,
464              isGenerating: false,
465              structuredTurnsTrusted: true,
466          })
467              .mockResolvedValueOnce('https://gemini.google.com/app')
468              .mockResolvedValueOnce({
469              turns: [
470                  { Role: 'User', Text: '你说\n\n请只回复:DBGREG' },
471                  { Role: 'User', Text: '请只回复:DBGREG' },
472                  { Role: 'Assistant', Text: 'DBGREG' },
473              ],
474              transcriptLines: ['baseline'],
475              composerHasText: false,
476              isGenerating: false,
477              structuredTurnsTrusted: true,
478          });
479          const result = await waitForGeminiResponse(page, {
480              snapshot: snapshot({
481                  turns: [
482                      { Role: 'User', Text: '你说\n\n请只回复:DBGREG' },
483                      { Role: 'User', Text: '请只回复:DBGREG' },
484                  ],
485                  transcriptLines: ['baseline'],
486                  composerHasText: false,
487                  isGenerating: true,
488                  structuredTurnsTrusted: true,
489              }),
490              preSendAssistantCount: 1,
491              userAnchorTurn: { Role: 'User', Text: '请只回复:DBGREG' },
492              reason: 'composer_generating',
493          }, '请只回复:DBGREG', 6);
494          expect(result).toBe('DBGREG');
495      });
496      it('does not trust an assistant-only submission snapshot without a stable post-submission owner', async () => {
497          const page = createPageMock();
498          const evaluate = vi.mocked(page.evaluate);
499          evaluate
500              .mockResolvedValueOnce('https://gemini.google.com/app')
501              .mockResolvedValueOnce({
502              turns: [{ Role: 'Assistant', Text: '完整回答' }],
503              transcriptLines: ['baseline'],
504              composerHasText: false,
505              isGenerating: false,
506              structuredTurnsTrusted: true,
507          })
508              .mockResolvedValueOnce('https://gemini.google.com/app')
509              .mockResolvedValueOnce({
510              turns: [{ Role: 'Assistant', Text: '完整回答' }],
511              transcriptLines: ['baseline'],
512              composerHasText: false,
513              isGenerating: false,
514              structuredTurnsTrusted: true,
515          });
516          const result = await waitForGeminiResponse(page, {
517              snapshot: snapshot({
518                  turns: [{ Role: 'Assistant', Text: '半截回答' }],
519                  transcriptLines: ['baseline'],
520                  composerHasText: false,
521                  isGenerating: true,
522                  structuredTurnsTrusted: true,
523              }),
524              preSendAssistantCount: 0,
525              userAnchorTurn: null,
526              reason: 'composer_generating',
527          }, '请解释', 4);
528          expect(result).toBe('');
529      });
530      it('accepts an assistant reply that appears after a structured user anchor only after it stabilizes and generation stops', async () => {
531          const page = createPageMock();
532          const evaluate = vi.mocked(page.evaluate);
533          evaluate
534              .mockResolvedValueOnce('https://gemini.google.com/app')
535              .mockResolvedValueOnce({
536              turns: [
537                  { Role: 'Assistant', Text: '旧回答' },
538                  { Role: 'User', Text: '请解释' },
539                  { Role: 'Assistant', Text: '半截回答' },
540              ],
541              transcriptLines: ['baseline'],
542              composerHasText: false,
543              isGenerating: true,
544              structuredTurnsTrusted: true,
545          })
546              .mockResolvedValueOnce('https://gemini.google.com/app')
547              .mockResolvedValueOnce({
548              turns: [
549                  { Role: 'Assistant', Text: '旧回答' },
550                  { Role: 'User', Text: '请解释' },
551                  { Role: 'Assistant', Text: '完整回答' },
552              ],
553              transcriptLines: ['baseline'],
554              composerHasText: false,
555              isGenerating: true,
556              structuredTurnsTrusted: true,
557          })
558              .mockResolvedValueOnce('https://gemini.google.com/app')
559              .mockResolvedValueOnce({
560              turns: [
561                  { Role: 'Assistant', Text: '旧回答' },
562                  { Role: 'User', Text: '请解释' },
563                  { Role: 'Assistant', Text: '完整回答' },
564              ],
565              transcriptLines: ['baseline'],
566              composerHasText: false,
567              isGenerating: false,
568              structuredTurnsTrusted: true,
569          });
570          const result = await waitForGeminiResponse(page, {
571              snapshot: snapshot({
572                  turns: [
573                      { Role: 'Assistant', Text: '旧回答' },
574                      { Role: 'User', Text: '请解释' },
575                  ],
576                  transcriptLines: ['baseline'],
577                  composerHasText: false,
578                  isGenerating: true,
579                  structuredTurnsTrusted: true,
580              }),
581              preSendAssistantCount: 1,
582              userAnchorTurn: { Role: 'User', Text: '请解释' },
583              reason: 'user_turn',
584          }, '请解释', 6);
585          expect(result).toBe('完整回答');
586      });
587      it('uses transcript fallback only after two identical post-submission deltas and after generation stops', async () => {
588          const page = createPageMock();
589          const evaluate = vi.mocked(page.evaluate);
590          evaluate
591              .mockResolvedValueOnce('https://gemini.google.com/app')
592              .mockResolvedValueOnce({
593              turns: [{ Role: 'User', Text: '请只回复:OK' }],
594              transcriptLines: ['baseline', 'OK'],
595              composerHasText: false,
596              isGenerating: true,
597              structuredTurnsTrusted: true,
598          })
599              .mockResolvedValueOnce('https://gemini.google.com/app')
600              .mockResolvedValueOnce({
601              turns: [{ Role: 'User', Text: '请只回复:OK' }],
602              transcriptLines: ['baseline', 'OK'],
603              composerHasText: false,
604              isGenerating: false,
605              structuredTurnsTrusted: true,
606          })
607              .mockResolvedValueOnce('https://gemini.google.com/app')
608              .mockResolvedValueOnce({
609              turns: [{ Role: 'User', Text: '请只回复:OK' }],
610              transcriptLines: ['baseline', 'OK'],
611              composerHasText: false,
612              isGenerating: false,
613              structuredTurnsTrusted: true,
614          });
615          const result = await waitForGeminiResponse(page, {
616              snapshot: snapshot({
617                  turns: [{ Role: 'User', Text: '请只回复:OK' }],
618                  transcriptLines: ['baseline'],
619                  composerHasText: false,
620                  isGenerating: true,
621                  structuredTurnsTrusted: true,
622              }),
623              preSendAssistantCount: 0,
624              userAnchorTurn: { Role: 'User', Text: '请只回复:OK' },
625              reason: 'user_turn',
626          }, '请只回复:OK', 6);
627          expect(result).toBe('OK');
628      });
629      it('ignores transcript lines that appeared before submission confirmation and only accepts post-submission transcript deltas', async () => {
630          const page = createPageMock();
631          const evaluate = vi.mocked(page.evaluate);
632          evaluate
633              .mockResolvedValueOnce('https://gemini.google.com/app')
634              .mockResolvedValueOnce({
635              turns: [{ Role: 'User', Text: '请只回复:OK' }],
636              transcriptLines: ['baseline', '早到的提示词回声', 'OK'],
637              composerHasText: false,
638              isGenerating: false,
639              structuredTurnsTrusted: true,
640          })
641              .mockResolvedValueOnce('https://gemini.google.com/app')
642              .mockResolvedValueOnce({
643              turns: [{ Role: 'User', Text: '请只回复:OK' }],
644              transcriptLines: ['baseline', '早到的提示词回声', 'OK'],
645              composerHasText: false,
646              isGenerating: false,
647              structuredTurnsTrusted: true,
648          })
649              .mockResolvedValueOnce('https://gemini.google.com/app')
650              .mockResolvedValueOnce({
651              turns: [{ Role: 'User', Text: '请只回复:OK' }],
652              transcriptLines: ['baseline', '早到的提示词回声', 'OK'],
653              composerHasText: false,
654              isGenerating: false,
655              structuredTurnsTrusted: true,
656          });
657          const result = await waitForGeminiResponse(page, {
658              snapshot: snapshot({
659                  turns: [{ Role: 'User', Text: '请只回复:OK' }],
660                  transcriptLines: ['baseline', '早到的提示词回声'],
661                  composerHasText: false,
662                  isGenerating: true,
663                  structuredTurnsTrusted: true,
664              }),
665              preSendAssistantCount: 0,
666              userAnchorTurn: { Role: 'User', Text: '请只回复:OK' },
667              reason: 'composer_transcript',
668          }, '请只回复:OK', 6);
669          expect(result).toBe('OK');
670      });
671  });