mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## Summary - add `just desktop-release-smoke`, a deterministic desktop correctness/reachability smoke against an ephemeral real local relay - preserve existing DM history when the first live DM enters a pageless query window, the desktop-v0.5.10 disappearing-DM regression - enforce foreground JS ordering: a frame and actionable sidebar input must dispatch before mounted stale queries begin resume refetches, while separately requiring the navigation to commit promptly - seed a 10,000-event dense-second fixture and verify exact event-ID reachability, SHA-256 identity, ordering, duplicate absence, bounded mounted rows, and drained render work - isolate Postgres per run, serialize the shared Redis DB, retain phase/relay/Playwright diagnostics, and gate desktop release manifest assembly on the smoke This is deliberately **not a performance-regression gate**. CDP and action timing fields are informational only. There is no candidate/baseline comparison or threshold. A future performance lane needs repeated equivalent fixtures, discrete interaction samples, and an explicit comparator/noise policy. The diagnostics record the fixture version, row count, wall-clock base timestamp (`fixtureSecond`), expected event-ID hash, observed state, and measurements. Because the created-at floor requires a current timestamp, paired comparison remains disabled. The release job runs on an isolated GitHub-hosted runner. The script also guards automatic local runs with a Redis allocation lock. Its remaining direct-PID cleanup and free-port selection race mean it should not be repurposed onto a persistent concurrent shared runner without first hardening process-group cleanup and port reservation. ### Related issue N/A ### Testing - `pnpm --dir desktop typecheck` - focused real-local-relay release smoke passed after adversarial review fixes - identical DM witness passed current and failed `desktop-v0.5.10` with the history-loss signature - identical foreground witness bytes (`2c1e97df04c9b8ca0304b66bbbe9bdb4d08924ad8ce0f68a9c490458fcc3aca8`) failed `desktop-v0.5.10` structurally: the first resume fetch was marker 1, before first frame/sidebar dispatch at marker 8 - with PR #5696 (`59f613c40`) merged, the witness showed focus at 951.3 ms, first frame at 951.6 ms, click dispatch at 952.1 ms, first resume fetch at 968.9 ms, and route commit at 992.4 ms - the gate therefore protects first paint and actionable input dispatch; route commit is a bounded responsiveness witness, not a prerequisite for resume work - the corrected focused foreground scenario passed at `6d9b5be40da58bbee92a856b04c3558946d0a950`; the prior merged-tree full run passed DM retention and 10k reachability before exposing this contract mismatch - pre-push passed on exact pushed head `6d9b5be40da58bbee92a856b04c3558946d0a950`, including desktop checks, typecheck, desktop tests, Rust tests, mobile tests, and Tauri checks - full 10,000-event scenario reached 10,000/10,000 exact IDs with matching SHA-256, 199 continuation requests, and 95 mounted rows in about 4.4 minutes - reduced-row review run passed in 18.4 seconds ### Foreground witness boundary The Chromium test is a deterministic JS policy gate. Headless Chromium does not expose an honest blur/focus transition in this fixture, so the test drives the production focus listener and `document.hasFocus()` predicate together and records that simulation explicitly. It proves refetch fan-out ordering, not AppKit activation, WKWebView paint, or an activating physical click. A packaged macOS native lane is still required before claiming the actual desktop activation experience is certified. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
243 lines
7.9 KiB
TypeScript
243 lines
7.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installRelayBridge } from "../helpers/bridge";
|
|
import { assertRelaySeeded } from "../helpers/seed";
|
|
|
|
const REPRESENTATIVE_FAMILIES = new Set([
|
|
"channels",
|
|
"home-feed",
|
|
"relay-agents",
|
|
"channel-templates",
|
|
"custom-emoji",
|
|
"user-status",
|
|
]);
|
|
|
|
type ForegroundProbe = {
|
|
marker: string;
|
|
at: number;
|
|
detail?: unknown;
|
|
};
|
|
|
|
test.beforeAll(async () => {
|
|
test.setTimeout(90_000);
|
|
await assertRelaySeeded();
|
|
});
|
|
|
|
test("foreground paints and handles a sidebar action before resume fetches", async ({
|
|
page,
|
|
}) => {
|
|
await page.addInitScript(() => {
|
|
type Probe = { marker: string; at: number; detail?: unknown };
|
|
const probes: Probe[] = [];
|
|
const record = (marker: string, detail?: unknown) =>
|
|
probes.push({ marker, at: performance.now(), detail });
|
|
(
|
|
window as typeof window & { __FOREGROUND_PROBES__?: Probe[] }
|
|
).__FOREGROUND_PROBES__ = probes;
|
|
|
|
let captured = false;
|
|
window.addEventListener(
|
|
"focus",
|
|
() => {
|
|
const armed = (
|
|
window as typeof window & { __FOREGROUND_PROBE_ARMED__?: boolean }
|
|
).__FOREGROUND_PROBE_ARMED__;
|
|
if (!armed || captured) return;
|
|
captured = true;
|
|
record("focus", {
|
|
hasFocus: document.hasFocus(),
|
|
visibilityState: document.visibilityState,
|
|
});
|
|
window.requestAnimationFrame(() => {
|
|
record("first-frame");
|
|
const target = document.querySelector<HTMLElement>(
|
|
'[data-testid="channel-alice-tyler"]',
|
|
);
|
|
if (!target)
|
|
throw new Error("Sidebar sentinel channel is unavailable.");
|
|
target.click();
|
|
record("interaction-dispatched");
|
|
|
|
const recordCommitWhenSelected = () => {
|
|
const selectedTitle = document
|
|
.querySelector<HTMLElement>('[data-testid="chat-title"]')
|
|
?.textContent?.trim();
|
|
if (selectedTitle === "alice-tyler") {
|
|
record("interaction-committed");
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(recordCommitWhenSelected);
|
|
};
|
|
recordCommitWhenSelected();
|
|
});
|
|
},
|
|
{ capture: true },
|
|
);
|
|
});
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("app-sidebar")).toBeVisible();
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
const seasonedKeys = await page.evaluate(
|
|
(representativeFamilies) => {
|
|
type QueryLike = {
|
|
queryKey: readonly unknown[];
|
|
options: { refetchOnWindowFocus?: unknown };
|
|
state: Record<string, unknown>;
|
|
getObserversCount: () => number;
|
|
setState: (state: Record<string, unknown>) => void;
|
|
};
|
|
type QueryCacheLike = {
|
|
findAll: () => QueryLike[];
|
|
subscribe: (
|
|
listener: (event: { query?: QueryLike }) => void,
|
|
) => () => void;
|
|
};
|
|
type QueryClientLike = { getQueryCache: () => QueryCacheLike };
|
|
const client =
|
|
window.__BUZZ_E2E_QUERY_CLIENT__ as unknown as QueryClientLike;
|
|
if (!client) throw new Error("E2E query client is unavailable.");
|
|
|
|
const families = new Set(representativeFamilies);
|
|
const active = client
|
|
.getQueryCache()
|
|
.findAll()
|
|
.filter(
|
|
(query) =>
|
|
query.getObserversCount() > 0 &&
|
|
families.has(String(query.queryKey[0] ?? "")),
|
|
);
|
|
for (const query of active) {
|
|
query.setState({
|
|
...query.state,
|
|
dataUpdatedAt: 1,
|
|
isInvalidated: true,
|
|
});
|
|
}
|
|
|
|
const probes = (
|
|
window as typeof window & { __FOREGROUND_PROBES__?: ForegroundProbe[] }
|
|
).__FOREGROUND_PROBES__;
|
|
if (!probes) throw new Error("Foreground probe was not installed.");
|
|
const record = (marker: string, detail?: unknown) =>
|
|
probes.push({ marker, at: performance.now(), detail });
|
|
|
|
const activeHashes = new Set(
|
|
active.map((query) => JSON.stringify(query.queryKey)),
|
|
);
|
|
const fetchingHashes = new Set<string>();
|
|
client.getQueryCache().subscribe((event) => {
|
|
const query = event.query;
|
|
if (!query) return;
|
|
const hash = JSON.stringify(query.queryKey);
|
|
if (
|
|
activeHashes.has(hash) &&
|
|
query.state.fetchStatus === "fetching" &&
|
|
!fetchingHashes.has(hash)
|
|
) {
|
|
fetchingHashes.add(hash);
|
|
record("resume-fetch-start", query.queryKey);
|
|
}
|
|
});
|
|
|
|
return active.map((query) => ({
|
|
key: query.queryKey,
|
|
refetchOnWindowFocus: query.options.refetchOnWindowFocus ?? null,
|
|
}));
|
|
},
|
|
[...REPRESENTATIVE_FAMILIES],
|
|
);
|
|
|
|
expect(seasonedKeys.length).toBeGreaterThanOrEqual(3);
|
|
|
|
const originalHasFocus = await page.evaluateHandle(() => document.hasFocus);
|
|
await page.evaluate(() => {
|
|
(
|
|
window as typeof window & { __FOREGROUND_PROBE_ARMED__?: boolean }
|
|
).__FOREGROUND_PROBE_ARMED__ = true;
|
|
Object.defineProperty(document, "hasFocus", {
|
|
configurable: true,
|
|
value: () => false,
|
|
});
|
|
window.dispatchEvent(new Event("blur"));
|
|
});
|
|
await expect.poll(() => page.evaluate(() => document.hasFocus())).toBe(false);
|
|
|
|
// Headless Chromium reports every page as focused, even after bringToFront on
|
|
// another target. Drive the production listener and isAppFocused predicate
|
|
// together rather than shipping a false-green no-op focus transition.
|
|
await page.evaluate(() => {
|
|
Object.defineProperty(document, "hasFocus", {
|
|
configurable: true,
|
|
value: () => true,
|
|
});
|
|
window.dispatchEvent(new Event("focus"));
|
|
});
|
|
await expect.poll(() => page.evaluate(() => document.hasFocus())).toBe(true);
|
|
await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler", {
|
|
timeout: 5_000,
|
|
});
|
|
|
|
const probes = await expect
|
|
.poll(
|
|
() =>
|
|
page.evaluate(
|
|
() =>
|
|
(
|
|
window as typeof window & {
|
|
__FOREGROUND_PROBES__?: ForegroundProbe[];
|
|
}
|
|
).__FOREGROUND_PROBES__ ?? [],
|
|
),
|
|
{ timeout: 5_000 },
|
|
)
|
|
.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ marker: "focus" }),
|
|
expect.objectContaining({ marker: "first-frame" }),
|
|
expect.objectContaining({ marker: "interaction-dispatched" }),
|
|
expect.objectContaining({ marker: "interaction-committed" }),
|
|
]),
|
|
);
|
|
|
|
// expect.poll returns void; read the settled marker stream for ordering.
|
|
void probes;
|
|
const settled = await page.evaluate(
|
|
() =>
|
|
(window as typeof window & { __FOREGROUND_PROBES__?: ForegroundProbe[] })
|
|
.__FOREGROUND_PROBES__ ?? [],
|
|
);
|
|
await test.info().attach("foreground-ordering", {
|
|
body: Buffer.from(
|
|
`${JSON.stringify({ seasonedKeys, probes: settled }, null, 2)}\n`,
|
|
),
|
|
contentType: "application/json",
|
|
});
|
|
|
|
const index = (marker: string) =>
|
|
settled.findIndex((probe) => probe.marker === marker);
|
|
const focus = settled.find((probe) => probe.marker === "focus");
|
|
expect(focus?.detail).toEqual({
|
|
hasFocus: true,
|
|
visibilityState: "visible",
|
|
});
|
|
expect(index("first-frame")).toBeGreaterThan(index("focus"));
|
|
expect(index("interaction-dispatched")).toBeGreaterThan(index("first-frame"));
|
|
expect(index("interaction-committed")).toBeGreaterThan(
|
|
index("interaction-dispatched"),
|
|
);
|
|
|
|
// The production scheduler promises a painted interaction boundary before
|
|
// resume work, not completion of an asynchronous route transition. Dispatch
|
|
// proves the sidebar target was actionable after the first frame; the commit
|
|
// marker remains a responsiveness witness and must still settle promptly.
|
|
const firstResumeFetch = index("resume-fetch-start");
|
|
if (firstResumeFetch !== -1) {
|
|
expect(firstResumeFetch).toBeGreaterThan(index("interaction-dispatched"));
|
|
}
|
|
|
|
await originalHasFocus.dispose();
|
|
});
|