session-tab-switch-benchmark.spec.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import type { Page } from "@playwright/test"
  2. import { expectSessionTitle } from "../../utils/waits"
  3. import { benchmark, expect, withBenchmarkPage } from "../benchmark"
  4. import { fixture } from "./session-timeline-stress.fixture"
  5. import {
  6. createReviewDiffs,
  7. installStressSessionTabs,
  8. installTimelineSettings,
  9. mockStressTimeline,
  10. stressSessionHref,
  11. } from "./timeline-test-helpers"
  12. import { measureSessionSwitch, waitForStableTimeline } from "./session-tab-switch-probe"
  13. type Result = Awaited<ReturnType<typeof measureSessionSwitch>>
  14. benchmark("benchmarks cold and hot session tab switching", async ({ browser, report }, testInfo) => {
  15. benchmark.setTimeout(180_000)
  16. const results = { cold: [] as Result[], hot: [] as Result[] }
  17. for (const mode of ["cold", "hot"] as const) {
  18. for (let run = 0; run < 5; run++) {
  19. results[mode].push(
  20. await withBenchmarkPage(browser, `session-tab-switch-${mode}-${run}`, (page) => trial(page, mode), testInfo),
  21. )
  22. }
  23. }
  24. report({ results, summary: summarize(results) })
  25. })
  26. benchmark(
  27. "benchmarks v2 session tab switching with and without the review pane",
  28. async ({ browser, report }, testInfo) => {
  29. benchmark.setTimeout(360_000)
  30. const runs = Number(process.env.SESSION_TAB_SWITCH_RUNS ?? 5)
  31. const results = {
  32. closed: { cold: [] as Result[], hot: [] as Result[] },
  33. open: { cold: [] as Result[], hot: [] as Result[] },
  34. }
  35. for (const reviewPane of ["closed", "open"] as const) {
  36. for (const mode of ["cold", "hot"] as const) {
  37. for (let run = 0; run < runs; run++) {
  38. results[reviewPane][mode].push(
  39. await withBenchmarkPage(
  40. browser,
  41. `session-tab-switch-v2-${reviewPane}-${mode}-${run}`,
  42. (page) => trial(page, mode, { newLayoutDesigns: true, reviewPane }),
  43. testInfo,
  44. ),
  45. )
  46. }
  47. }
  48. }
  49. report({ results, summary: summarizeReviewPane(results) }, { runs, reviewDiffs: createReviewDiffs().length })
  50. },
  51. )
  52. async function trial(
  53. page: Page,
  54. mode: "cold" | "hot",
  55. options?: { newLayoutDesigns?: boolean; reviewPane?: "closed" | "open" },
  56. ) {
  57. const reviewDiffs = options?.newLayoutDesigns ? createReviewDiffs() : undefined
  58. await mockStressTimeline(page, { vcsDiff: reviewDiffs })
  59. if (options?.newLayoutDesigns) await installTimelineSettings(page)
  60. await installStressSessionTabs(page)
  61. if (mode === "hot") {
  62. await page.goto(stressSessionHref(fixture.targetID))
  63. await expectSessionTitle(page, fixture.expected.targetTitle)
  64. await waitForStableTimeline(page, fixture.expected.targetMessageIDs.at(-1)!)
  65. await switchSession(page, fixture.sourceID, fixture.expected.sourceTitle)
  66. } else {
  67. await page.goto(stressSessionHref(fixture.sourceID))
  68. await expectSessionTitle(page, fixture.expected.sourceTitle)
  69. }
  70. await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
  71. if (options?.reviewPane === "open") {
  72. await openReviewPane(page)
  73. await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
  74. }
  75. const destinationIDs = fixture.messages[fixture.targetID].map((message) => message.info.id)
  76. const sourceIDs = fixture.messages[fixture.sourceID].map((message) => message.info.id)
  77. const lastID = fixture.expected.targetMessageIDs.at(-1)!
  78. const href = stressSessionHref(fixture.targetID)
  79. const result = await measureSessionSwitch(page, {
  80. destinationIDs,
  81. sourceIDs,
  82. lastID,
  83. href,
  84. switch: () => switchSession(page, fixture.targetID, fixture.expected.targetTitle),
  85. })
  86. return result
  87. }
  88. function summarize(results: Record<"cold" | "hot", Result[]>) {
  89. const stats = (values: (number | null)[]) => {
  90. const sorted = values.filter((value): value is number => value !== null).sort((a, b) => a - b)
  91. return {
  92. min: sorted[0] ?? null,
  93. median: sorted[Math.floor(sorted.length / 2)] ?? null,
  94. max: sorted.at(-1) ?? null,
  95. missing: values.length - sorted.length,
  96. }
  97. }
  98. return Object.fromEntries(
  99. Object.entries(results).map(([mode, values]) => [
  100. mode,
  101. {
  102. firstDestinationObservedMs: stats(values.map((value) => value.firstDestinationObservedMs)),
  103. firstCorrectObservedMs: stats(values.map((value) => value.firstCorrectObservedMs)),
  104. stableObservedMs: stats(values.map((value) => value.stableObservedMs)),
  105. },
  106. ]),
  107. )
  108. }
  109. function summarizeReviewPane(results: Record<"closed" | "open", Record<"cold" | "hot", Result[]>>) {
  110. return Object.fromEntries(
  111. Object.entries(results).map(([reviewPane, values]) => [
  112. reviewPane,
  113. summarize(values as Record<"cold" | "hot", Result[]>),
  114. ]),
  115. )
  116. }
  117. async function switchSession(page: Page, sessionID: string, title: string) {
  118. const href = stressSessionHref(sessionID)
  119. const tab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`).first()
  120. await expect(tab).toBeVisible()
  121. await tab.click()
  122. await expectSessionTitle(page, title)
  123. }
  124. async function openReviewPane(page: Page) {
  125. await page.getByRole("button", { name: "Toggle review" }).click()
  126. const panel = page.locator("#review-panel")
  127. await expect(panel).toBeVisible()
  128. // Text-based readiness works across review implementations; the legacy list mounts
  129. // diff viewers lazily while V2 mounts the active preview eagerly.
  130. await page.waitForFunction(() => {
  131. const panel = document.querySelector<HTMLElement>("#review-panel")
  132. const text = panel?.textContent ?? ""
  133. return text.includes("generated-000.ts") && text.includes("+3")
  134. })
  135. }