Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5470801

Browse files
Add Playwright timeline height parity tests to CI
- add browser E2E tests validating timeline height estimator against rendered DOM - configure Playwright test runner and scripts in `apps/web` - run browser tests in CI with Playwright browser caching and install step
1 parent 2ad2d3e commit 5470801

6 files changed

Lines changed: 193 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
quality:
11-
name: Lint, Typecheck, Test, Build
11+
name: Lint, Typecheck, Test, Browser Test, Build
1212
runs-on: blacksmith-4vcpu-ubuntu-2404
1313
steps:
1414
- name: Checkout
@@ -34,6 +34,14 @@ jobs:
3434
restore-keys: |
3535
${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}-
3636
37+
- name: Cache Playwright browsers
38+
uses: actions/cache@v4
39+
with:
40+
path: ~/.cache/ms-playwright
41+
key: ${{ runner.os }}-playwright-${{ hashFiles('bun.lock') }}
42+
restore-keys: |
43+
${{ runner.os }}-playwright-
44+
3745
- name: Install dependencies
3846
run: bun install --frozen-lockfile
3947

@@ -46,6 +54,14 @@ jobs:
4654
- name: Test
4755
run: bun run test
4856

57+
- name: Install browser test runtime
58+
run: |
59+
cd apps/web
60+
bunx playwright install --with-deps chromium
61+
62+
- name: Browser test
63+
run: bun run --cwd apps/web test:browser
64+
4965
- name: Build desktop pipeline
5066
run: bun run build:desktop
5167

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ packages/*/dist
1111
build/
1212
.logs/
1313
release/
14-
.t3
14+
.t3
15+
apps/web/.playwright
16+
apps/web/playwright-report
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { expect, test, type Page } from "@playwright/test";
2+
3+
import { estimateTimelineMessageHeight } from "../src/components/timelineHeight";
4+
5+
interface HeightCase {
6+
timelineWidthPx: number;
7+
text: string;
8+
attachmentCount: number;
9+
}
10+
11+
async function measureUserRowHeight(page: Page, testCase: HeightCase) {
12+
const { timelineWidthPx, text, attachmentCount } = testCase;
13+
await page.setContent("<!doctype html><html><body style='margin:0'></body></html>");
14+
15+
return page.evaluate(({ width, messageText, attachments }) => {
16+
const timeline = document.createElement("div");
17+
timeline.style.width = `${width}px`;
18+
timeline.style.maxWidth = `${width}px`;
19+
20+
const row = document.createElement("div");
21+
row.style.paddingBottom = "16px";
22+
23+
const alignment = document.createElement("div");
24+
alignment.style.display = "flex";
25+
alignment.style.justifyContent = "flex-end";
26+
27+
const bubble = document.createElement("div");
28+
bubble.style.boxSizing = "border-box";
29+
bubble.style.maxWidth = "80%";
30+
bubble.style.padding = "12px 16px";
31+
bubble.style.border = "1px solid rgba(0, 0, 0, 0.12)";
32+
bubble.style.borderRadius = "16px";
33+
bubble.style.background = "rgba(0, 0, 0, 0.03)";
34+
35+
if (attachments > 0) {
36+
const attachmentGrid = document.createElement("div");
37+
attachmentGrid.style.marginBottom = "8px";
38+
attachmentGrid.style.maxWidth = "420px";
39+
attachmentGrid.style.display = "grid";
40+
attachmentGrid.style.gridTemplateColumns = "repeat(2, minmax(0, 1fr))";
41+
attachmentGrid.style.gap = "8px";
42+
for (let index = 0; index < attachments; index += 1) {
43+
const tile = document.createElement("div");
44+
tile.style.height = "220px";
45+
tile.style.border = "1px solid rgba(0, 0, 0, 0.12)";
46+
tile.style.borderRadius = "8px";
47+
tile.style.background = "rgba(0, 0, 0, 0.06)";
48+
attachmentGrid.append(tile);
49+
}
50+
bubble.append(attachmentGrid);
51+
}
52+
53+
if (messageText.length > 0) {
54+
const pre = document.createElement("pre");
55+
pre.textContent = messageText;
56+
pre.style.margin = "0";
57+
pre.style.whiteSpace = "pre-wrap";
58+
pre.style.overflowWrap = "anywhere";
59+
pre.style.fontFamily =
60+
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace";
61+
pre.style.fontSize = "14px";
62+
pre.style.lineHeight = "22px";
63+
bubble.append(pre);
64+
}
65+
66+
const meta = document.createElement("div");
67+
meta.style.marginTop = "6px";
68+
meta.style.height = "16px";
69+
bubble.append(meta);
70+
71+
alignment.append(bubble);
72+
row.append(alignment);
73+
timeline.append(row);
74+
document.body.append(timeline);
75+
return row.getBoundingClientRect().height;
76+
}, { width: timelineWidthPx, messageText: text, attachments: attachmentCount });
77+
}
78+
79+
function estimatedHeight(testCase: HeightCase): number {
80+
return estimateTimelineMessageHeight(
81+
{
82+
role: "user",
83+
text: testCase.text,
84+
attachments: Array.from({ length: testCase.attachmentCount }, (_value, index) => ({
85+
id: String(index),
86+
})),
87+
},
88+
{ timelineWidthPx: testCase.timelineWidthPx },
89+
);
90+
}
91+
92+
test.describe("timeline height estimator parity", () => {
93+
test("tracks long wrapped text growth at desktop width", async ({ page }) => {
94+
const baselineCase: HeightCase = { timelineWidthPx: 960, text: "", attachmentCount: 0 };
95+
const longCase: HeightCase = {
96+
timelineWidthPx: 960,
97+
text: "x".repeat(1200),
98+
attachmentCount: 0,
99+
};
100+
101+
const baselineMeasured = await measureUserRowHeight(page, baselineCase);
102+
const longMeasured = await measureUserRowHeight(page, longCase);
103+
const measuredDelta = longMeasured - baselineMeasured;
104+
105+
const estimatedDelta = estimatedHeight(longCase) - estimatedHeight(baselineCase);
106+
expect(Math.abs(measuredDelta - estimatedDelta)).toBeLessThanOrEqual(22);
107+
});
108+
109+
test("tracks additional wrapping when viewport narrows", async ({ page }) => {
110+
const desktopCase: HeightCase = {
111+
timelineWidthPx: 960,
112+
text: "x".repeat(1000),
113+
attachmentCount: 0,
114+
};
115+
const mobileCase: HeightCase = {
116+
timelineWidthPx: 360,
117+
text: desktopCase.text,
118+
attachmentCount: 0,
119+
};
120+
121+
const desktopMeasured = await measureUserRowHeight(page, desktopCase);
122+
const mobileMeasured = await measureUserRowHeight(page, mobileCase);
123+
const measuredDelta = mobileMeasured - desktopMeasured;
124+
125+
const estimatedDelta = estimatedHeight(mobileCase) - estimatedHeight(desktopCase);
126+
expect(Math.abs(measuredDelta - estimatedDelta)).toBeLessThanOrEqual(44);
127+
});
128+
129+
test("tracks attachment row growth", async ({ page }) => {
130+
const withoutAttachmentsCase: HeightCase = {
131+
timelineWidthPx: 960,
132+
text: "hello",
133+
attachmentCount: 0,
134+
};
135+
const withAttachmentsCase: HeightCase = {
136+
...withoutAttachmentsCase,
137+
attachmentCount: 3,
138+
};
139+
140+
const withoutMeasured = await measureUserRowHeight(page, withoutAttachmentsCase);
141+
const withMeasured = await measureUserRowHeight(page, withAttachmentsCase);
142+
const measuredDelta = withMeasured - withoutMeasured;
143+
144+
const estimatedDelta =
145+
estimatedHeight(withAttachmentsCase) - estimatedHeight(withoutAttachmentsCase);
146+
expect(Math.abs(measuredDelta - estimatedDelta)).toBeLessThanOrEqual(4);
147+
});
148+
});

apps/web/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"prepare": "effect-language-service patch",
1010
"preview": "vite preview",
1111
"typecheck": "tsc --noEmit",
12-
"test": "vitest run --passWithNoTests"
12+
"test": "vitest run --passWithNoTests",
13+
"test:browser": "playwright test -c playwright.config.ts",
14+
"test:browser:install": "playwright install chromium"
1315
},
1416
"dependencies": {
1517
"@base-ui/react": "^1.2.0",
@@ -36,6 +38,7 @@
3638
},
3739
"devDependencies": {
3840
"@effect/language-service": "catalog:",
41+
"@playwright/test": "^1.58.2",
3942
"@tailwindcss/vite": "^4.0.0",
4043
"@tanstack/router-plugin": "^1.161.0",
4144
"@types/react": "^19.0.0",

apps/web/playwright.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from "@playwright/test";
2+
3+
export default defineConfig({
4+
testDir: "./browser-tests",
5+
testMatch: "*.browser.e2e.ts",
6+
reporter: "list",
7+
fullyParallel: true,
8+
outputDir: "./.playwright/test-results",
9+
use: {
10+
headless: true,
11+
},
12+
});

bun.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)