environment-matrix.spec.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { test } from "@playwright/test"
  2. import {
  3. defineVisualRegions,
  4. reportVisualStability,
  5. startVisualProbe,
  6. stopVisualProbe,
  7. visualPlan,
  8. } from "../../utils/visual-stability"
  9. import {
  10. assistantMessage,
  11. partUpdated,
  12. setupTimeline,
  13. shell,
  14. textPart,
  15. userMessage,
  16. waitForVisualSettle,
  17. } from "./fixture"
  18. // Fractional scaling exercises different browser rounding than the baseline.
  19. for (const deviceScaleFactor of [1, 1.25]) {
  20. test(`keeps shell growth ordered at device scale ${deviceScaleFactor}`, async ({ page }, testInfo) => {
  21. const shellID = `prt_dpr_${String(deviceScaleFactor).replace(".", "_")}_01_shell`
  22. const followingID = `prt_dpr_${String(deviceScaleFactor).replace(".", "_")}_02_following`
  23. const timeline = await setupTimeline(page, {
  24. messages: [
  25. userMessage(),
  26. assistantMessage([shell(shellID, "running"), textPart(followingID, "Following scaled shell")], {
  27. completed: false,
  28. }),
  29. ],
  30. settings: { shellToolPartsExpanded: true },
  31. cpuRate: 4,
  32. deviceScaleFactor,
  33. seedHistory: true,
  34. })
  35. await waitForVisualSettle(page, [
  36. `[data-timeline-part-id="${shellID}"]`,
  37. `[data-timeline-part-id="${followingID}"]`,
  38. ])
  39. const regions = defineVisualRegions({
  40. shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
  41. following: {
  42. selector: `[data-timeline-part-id="${followingID}"]`,
  43. closest: '[data-timeline-row="AssistantPart"]',
  44. },
  45. })
  46. await startVisualProbe(page, regions)
  47. await timeline.send(partUpdated(shell(shellID, "running", lines(20))), 180)
  48. await timeline.send(partUpdated(shell(shellID, "completed", lines(20))), 500)
  49. const trace = await stopVisualProbe<keyof typeof regions>(page)
  50. await reportVisualStability(testInfo, `dpr-${deviceScaleFactor}`, trace, shellPlan(regions))
  51. })
  52. }
  53. for (const reducedMotion of [true]) {
  54. test(`keeps shell and status transitions ordered with reduced motion ${reducedMotion}`, async ({
  55. page,
  56. }, testInfo) => {
  57. const shellID = `prt_motion_${reducedMotion}_01_shell`
  58. const followingID = `prt_motion_${reducedMotion}_02_following`
  59. const timeline = await setupTimeline(page, {
  60. messages: [
  61. userMessage(),
  62. assistantMessage([shell(shellID, "running"), textPart(followingID, "Following motion profile")], {
  63. completed: false,
  64. }),
  65. ],
  66. settings: { shellToolPartsExpanded: true },
  67. reducedMotion,
  68. cpuRate: 4,
  69. seedHistory: true,
  70. })
  71. await waitForVisualSettle(page, [
  72. `[data-timeline-part-id="${shellID}"]`,
  73. `[data-timeline-part-id="${followingID}"]`,
  74. ])
  75. const regions = defineVisualRegions({
  76. shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
  77. following: {
  78. selector: `[data-timeline-part-id="${followingID}"]`,
  79. closest: '[data-timeline-row="AssistantPart"]',
  80. },
  81. })
  82. await startVisualProbe(page, regions)
  83. await timeline.send(partUpdated(shell(shellID, "completed", lines(10))), 500)
  84. const trace = await stopVisualProbe<keyof typeof regions>(page)
  85. await reportVisualStability(testInfo, `reduced-motion-${reducedMotion}`, trace, shellPlan(regions))
  86. })
  87. }
  88. function lines(count: number) {
  89. return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n")
  90. }
  91. function shellPlan<Regions extends ReturnType<typeof defineVisualRegions>>(
  92. regions: Regions & Record<"shell" | "following", { selector: string }>,
  93. ) {
  94. return visualPlan(
  95. regions,
  96. [
  97. { type: "required", regions: ["shell", "following"] },
  98. { type: "unique", regions: ["shell", "following"] },
  99. { type: "stable", regions: ["shell", "following"] },
  100. { type: "opacity", regions: "all" },
  101. { type: "continuity", regions: "all" },
  102. { type: "motion", regions: "all", maxPositionReversals: 0 },
  103. { type: "label-stability", regions: "all" },
  104. { type: "preserve-bottom-anchor" },
  105. { type: "flow", regions: ["shell", "following"] },
  106. ],
  107. { perMarker: true },
  108. )
  109. }