context-meter.e2e.ts 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. /** Context details stay inside the viewport with and without Chat's statistics contribution. */
  2. import { readFile } from 'node:fs/promises'
  3. import { fileURLToPath } from 'node:url'
  4. import { chromium } from 'playwright'
  5. import { describe, expect, it } from 'vitest'
  6. import { fixtureUserPrompts, launchWebScaffold, selectedSessionFixture, watchConsole, webSnapshotMode } from './scaffold.ts'
  7. import { connectFreshWorkspace, newEnglishPage } from './support.ts'
  8. const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/session.v3.jsonl', import.meta.url))
  9. const NO_CHAT = fileURLToPath(new URL('./fixtures/context-meter-no-chat.patch.yml', import.meta.url))
  10. describe.skipIf(webSnapshotMode() === 'record')('web e2e: context details placement', () => {
  11. it.each([true, false])('keeps details visible across viewport changes (Chat statistics: %s)', async (withStats) => {
  12. const fixture = await selectedSessionFixture(FIXTURE, false)
  13. const scaffold = await launchWebScaffold({
  14. replayFixture: fixture,
  15. compareReplaySession: false,
  16. paceMs: 5,
  17. ...withStats ? {} : { extraOverlayPath: NO_CHAT },
  18. })
  19. try {
  20. const browser = await chromium.launch()
  21. try {
  22. const page = await newEnglishPage(browser)
  23. const tripwire = watchConsole(page)
  24. await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
  25. await connectFreshWorkspace(page, scaffold.workspaceCwd)
  26. const card = page.locator('[data-composer-card]')
  27. expect(await card.evaluate(element =>
  28. element.parentElement!.getBoundingClientRect().bottom - element.getBoundingClientRect().bottom,
  29. )).toBe(0)
  30. const prompts = fixtureUserPrompts(await readFile(fixture, 'utf8'))
  31. expect(prompts).toHaveLength(1)
  32. const settled = scaffold.whenTurnSettled()
  33. const input = page.locator('[data-composer-input]').first()
  34. await input.fill(prompts[0]!)
  35. await input.press('Enter')
  36. await settled
  37. const trigger = page.getByRole('button', { name: /% of context used$/ })
  38. await trigger.waitFor()
  39. expect(await trigger.textContent()).toMatch(/^\d+%$/)
  40. expect(await page.getByRole('button', { name: /tok · Cache hit/ }).count()).toBe(withStats ? 1 : 0)
  41. expect(await card.evaluate((element) => {
  42. const dock = element.nextElementSibling!
  43. return {
  44. above: getComputedStyle(dock).paddingTop,
  45. below: getComputedStyle(element.parentElement!).paddingBottom,
  46. }
  47. })).toEqual({ above: '4px', below: '4px' })
  48. await trigger.click()
  49. const panel = page.getByRole('dialog', { name: 'of context used', exact: true })
  50. await panel.waitFor()
  51. for (const width of [390, 800, 1280]) {
  52. await page.setViewportSize({ width, height: 900 })
  53. await page.locator('[data-sidebar-collapsed="true"]').waitFor({
  54. state: width <= 800 ? 'attached' : 'detached',
  55. })
  56. await page.evaluate(async () => {
  57. await Promise.all(document.getAnimations()
  58. .filter(animation => animation.effect?.getComputedTiming().iterations !== Infinity)
  59. .map(animation => animation.finished.catch(() => undefined)))
  60. })
  61. await expect.poll(async () => {
  62. const rect = await panel.boundingBox()
  63. return rect !== null && rect.x >= 12 && rect.x + rect.width <= width - 12
  64. && rect.y >= 12 && rect.y + rect.height <= 888
  65. }).toBe(true)
  66. }
  67. await panel.getByText('System prompt', { exact: true }).click()
  68. expect(await panel.isVisible()).toBe(true)
  69. await page.keyboard.press('Escape')
  70. await panel.waitFor({ state: 'hidden' })
  71. await trigger.click()
  72. await panel.waitFor()
  73. await page.mouse.click(1260, 400)
  74. await panel.waitFor({ state: 'hidden' })
  75. expect(tripwire.pageErrors).toEqual([])
  76. expect(tripwire.warnings).toEqual([])
  77. } finally {
  78. await browser.close()
  79. }
  80. } finally {
  81. await scaffold.close()
  82. }
  83. })
  84. })