Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ jobs:
- name: Browser regression (UI suites)
if: needs.changes.outputs.draft != 'true'
env:
BROWSER_SCRIPTS: scripts/ui-regression.ts,scripts/ui-contexts-regression.ts
BROWSER_SCRIPTS: scripts/ui-regression.ts,scripts/ui-contexts-regression.ts,scripts/ui-contexts-layout-regression.ts
BROWSER_WORKERS: 2
run: bun run check run -- bun scripts/ci-browser-parallel.ts
- name: Preserve browser logs and evidence
Expand Down Expand Up @@ -450,8 +450,8 @@ jobs:
- name: Browser regression (parallel)
if: needs.changes.outputs.draft != 'true'
env:
# Every script of scripts/ci-browser.sh except the two browser-main runs.
BROWSER_EXCLUDE: scripts/ui-regression.ts,scripts/ui-contexts-regression.ts
# Every script of scripts/ci-browser.sh except the browser-main runs.
BROWSER_EXCLUDE: scripts/ui-regression.ts,scripts/ui-contexts-regression.ts,scripts/ui-contexts-layout-regression.ts
BROWSER_SHARD: ${{ matrix.shard }}/${{ strategy.job-total }}
BROWSER_WORKERS: ${{ runner.environment == 'self-hosted' && '6' || '3' }}
run: bun run check run -- bun scripts/ci-browser-parallel.ts
Expand Down
14 changes: 8 additions & 6 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -1055,12 +1055,14 @@ The [CI workflow](../.github/workflows/ci.yml) runs on every PR and `main` push:
Missing herdr fails the run. Its herdr servers are stopped even on failure.
Integration tests have a 15-second default timeout so their bounded process-startup
probes can finish; individual tests can still specify a longer timeout.
- **Browser regression (ui-regression)** runs `scripts/ui-regression.ts` and
`scripts/ui-contexts-regression.ts` concurrently. The core's stateful flow stays serial;
the extracted feature checks own a separate temporary herdr config/session, Chromium
process, server and fixture panes. Those helpers stay sequential within their own fixture.
The context suite prints each helper's elapsed time and stops its owned daemon on exit.
`bun run test:ui` also runs both suites.
- **Browser regression (ui-regression)** runs `scripts/ui-regression.ts`,
`scripts/ui-contexts-regression.ts` and `scripts/ui-contexts-layout-regression.ts`, two at a
time. The core's stateful flow stays serial; the extracted feature checks own a separate
temporary herdr config/session, Chromium process, server and fixture panes. Those helpers stay
sequential within their own fixture. The two slowest (split view, a background tab's terminal
size) run in the layout script, on a fixture of their own, so neither context script nears
the per-script cap. Each context script prints each helper's elapsed time and stops its owned
daemon on exit. `bun run test:ui` also runs all three.
- **Browser regression (other scripts)** runs every other browser script: six at a time in
one job on self-hosted CI, and on hosted CI split over four jobs (`BROWSER_SHARD=k/4`,
balanced by `scripts/ci-browser-timings.json`) that run three at a time each.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"test": "bun test",
"test:unit": "bun scripts/ci-tests.ts unit",
"test:integration": "bun scripts/ci-tests.ts integration",
"test:ui": "bun run build && bun scripts/ui-regression.ts && bun scripts/ui-contexts-regression.ts && bun scripts/sticky-modifiers-regression.ts",
"test:ui": "bun run build && bun scripts/ui-regression.ts && bun scripts/ui-contexts-regression.ts && bun scripts/ui-contexts-layout-regression.ts && bun scripts/sticky-modifiers-regression.ts",
"generate:types": "bun run scripts/generate-protocol-types.ts",
"generate:themes": "bun run scripts/generate-themes.ts",
"generate:case-folding": "bun run scripts/generate-case-folding.ts",
Expand Down
1 change: 1 addition & 0 deletions scripts/ci-browser-parallel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ async function main(): Promise<void> {
const shouldDisableEvidence = baseEvidenceDir && (
script === "scripts/ui-regression.ts" ||
script === "scripts/ui-contexts-regression.ts" ||
script === "scripts/ui-contexts-layout-regression.ts" ||
script === "scripts/key-bar-customization-demo-regression.ts"
);

Expand Down
3 changes: 2 additions & 1 deletion scripts/ci-browser-timings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"scripts/ui-contexts-regression.ts": 395,
"scripts/ui-contexts-regression.ts": 300,
"scripts/ui-contexts-layout-regression.ts": 120,
"scripts/ui-regression.ts": 290,
"scripts/background-agent-detail-demo-regression.ts": 273,
"scripts/security-summary-demo-regression.ts": 219,
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci-browser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ run_script() {
if [ -n "${CI:-}${CHECK_DIR:-}" ]; then
# These evidence branches add viewport changes, assertions or font waits, not just captures.
case "$1" in
scripts/ui-regression.ts|scripts/ui-contexts-regression.ts|scripts/key-bar-customization-demo-regression.ts) evidence="";;
scripts/ui-regression.ts|scripts/ui-contexts-regression.ts|scripts/ui-contexts-layout-regression.ts|scripts/key-bar-customization-demo-regression.ts) evidence="";;
esac
fi
UI_EVIDENCE_DIR="$evidence" bun "$@" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
Expand Down Expand Up @@ -60,6 +60,7 @@ export HERDR_DEMO_BUILD
run_script scripts/demo-build.ts "$HERDR_DEMO_BUILD"
run_script scripts/ui-regression.ts
run_script scripts/ui-contexts-regression.ts
run_script scripts/ui-contexts-layout-regression.ts
run_script scripts/machine-events-browser-regression.ts
run_script scripts/workspace-rows-regression.ts
run_script scripts/pending-pane-switch-regression.ts
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci-browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ describe("browser lane", () => {
{ script: "scripts/demo-build.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/ui-regression.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/ui-contexts-regression.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/ui-contexts-layout-regression.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/machine-events-browser-regression.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/workspace-rows-regression.ts", seconds: expect.any(Number), code: 0 },
{ script: "scripts/pending-pane-switch-regression.ts", seconds: expect.any(Number), code: 0 },
Expand Down Expand Up @@ -179,7 +180,7 @@ describe("browser lane", () => {
for (const script of ["sticky-modifiers-regression.ts", "file-viewer-regression.ts"]) {
expect(text).toMatch(new RegExp(`^\\[[^\\]]+\\] STEP=scripts/${script} EVIDENCE=.+/browser-evidence$`, "m"));
}
for (const script of ["ui-regression.ts", "ui-contexts-regression.ts", "key-bar-customization-demo-regression.ts"]) {
for (const script of ["ui-regression.ts", "ui-contexts-regression.ts", "ui-contexts-layout-regression.ts", "key-bar-customization-demo-regression.ts"]) {
expect(text).toMatch(new RegExp(`^\\[[^\\]]+\\] STEP=scripts/${script} EVIDENCE=$`, "m"));
}
}, LANE_TEST_MS);
Expand Down
18 changes: 10 additions & 8 deletions scripts/pages-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ describe("Browser lane script lists", () => {
expect(serial).toEqual(runs);
expect(serial).toContain("scripts/ui-regression.ts");
expect(serial).toContain("scripts/ui-contexts-regression.ts");
expect(serial).toContain("scripts/ui-contexts-layout-regression.ts");
expect(serial).toContain("scripts/sidebar-spacing-demo-regression.ts");
expect(new Set(serial).size).toBe(serial.length);
});
Expand Down Expand Up @@ -244,11 +245,11 @@ describe("Browser lane script lists", () => {
it("runs both isolated UI flows in browser-main and keeps the local UI command complete", () => {
const main = ciJob("browser-main");
expect(selectBrowserScripts(serial, env(main, "BROWSER_SCRIPTS"))).toEqual([
"scripts/ui-regression.ts", "scripts/ui-contexts-regression.ts",
"scripts/ui-regression.ts", "scripts/ui-contexts-regression.ts", "scripts/ui-contexts-layout-regression.ts",
]);
expect(env(main, "BROWSER_WORKERS")).toBe("2");
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
for (const script of ["ui-regression.ts", "ui-contexts-regression.ts", "sticky-modifiers-regression.ts"]) {
for (const script of ["ui-regression.ts", "ui-contexts-regression.ts", "ui-contexts-layout-regression.ts", "sticky-modifiers-regression.ts"]) {
expect(pkg.scripts["test:ui"]).toContain(`bun scripts/${script}`);
}
});
Expand Down Expand Up @@ -276,13 +277,14 @@ describe("Extracted UI checks", () => {
expect(result.stderr).toContain("HERDR_TEST_MODE=unit");
});

// the layout group (ui-contexts-layout-regression.ts) comes first in ui-contexts-checks.ts
const moved = [
"checkSplitView", "checkBackgroundTabKeepsTerminalSize",
"checkPushSettings", "checkWakeLock", "checkSecretInput", "checkTerminalCopy", "checkPaneFind",
"checkUsageMeters", "checkNotificationStartup", "checkNotificationView", "checkMobileViewport",
"checkTerminalPinch", "checkMobileTabs", "checkTabMenu", "checkDefaultView",
"checkComposerReconnect", "checkDroplet", "checkSplitView", "checkTakeOver", "checkAlertSound",
"checkChatKeepsTerminalSize", "checkPaneSwitchKeepsTerminalSize",
"checkBackgroundTabKeepsTerminalSize", "checkFocusedWindowReclaimsTerminalSize",
"checkComposerReconnect", "checkDroplet", "checkTakeOver", "checkAlertSound",
"checkChatKeepsTerminalSize", "checkPaneSwitchKeepsTerminalSize", "checkFocusedWindowReclaimsTerminalSize",
"checkUpdateNotice", "checkTelemetrySettings",
];
const calls = (source: string) => [...source.matchAll(/\b(check[A-Z]\w*)\(/g)].map((match) => match[1]!);
Expand All @@ -298,14 +300,14 @@ describe("Extracted UI checks", () => {
}
});

for (const mode of ["unit", "live"] as const) {
it(`refuses ${mode} execution before touching a herdr daemon`, () => {
for (const [script, mode] of ["ui-contexts-regression.ts", "ui-contexts-layout-regression.ts"].flatMap((script) => (["unit", "live"] as const).map((mode) => [script, mode] as const))) {
it(`${script} refuses ${mode} execution before touching a herdr daemon`, () => {
const root = mkdtempSync(join(tmpdir(), "ui-context-guard-"));
try {
const invoked = join(root, "invoked");
const herdr = join(root, "herdr");
writeFileSync(herdr, '#!/bin/sh\nprintf called > "$HERDR_GUARD_LOG"\nexit 1\n', { mode: 0o700 });
const result = spawnSync(process.execPath, [fileURLToPath(new URL("./ui-contexts-regression.ts", import.meta.url))], {
const result = spawnSync(process.execPath, [fileURLToPath(new URL(`./${script}`, import.meta.url))], {
encoding: "utf8", timeout: 5_000,
env: {
...process.env, TMPDIR: root, XDG_CONFIG_HOME: root,
Expand Down
78 changes: 42 additions & 36 deletions scripts/ui-contexts-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ function isWorkspaceMissing(error: unknown): boolean {
return typeof error === "object" && error !== null && "code" in error && error.code === "workspace_not_found";
}

export async function runUiContextChecks(): Promise<void> {
/** main: every check but the two slowest; layout: those two (split view, a background tab's size). */
export type UiContextGroup = "main" | "layout";

export async function runUiContextChecks(group: UiContextGroup): Promise<void> {
const tempBase = existsSync("/tmp") ? "/tmp" : tmpdir();
const root = realpathSync(mkdtempSync(join(tempBase, "hwc-")));
const workspaces: string[] = [];
Expand Down Expand Up @@ -94,41 +97,44 @@ export async function runUiContextChecks(): Promise<void> {
args: ["--no-sandbox", "--accept-lang=en-US"],
});

await timed("checkPushSettings", () => checkPushSettings(browser!, origin));
await timed("checkWakeLock", () => checkWakeLock(browser!, origin, paneA));
await timed("checkSecretInput", () => checkSecretInput(browser!, origin));
await timed("checkTerminalCopy", () => checkTerminalCopy(browser!, origin));
await timed("checkTerminalCopyOff", () => checkTerminalCopyOff(browser!));
await timed("checkPaneFind", () => checkPaneFind(browser!, origin));
await timed("checkUsageMeters", () => checkUsageMeters(browser!, origin));
await timed("checkUsageSignInAgain", () => checkUsageSignInAgain(browser!, origin));
await timed("checkNotificationStartup", () => checkNotificationStartup(browser!, origin, paneA, paneB));
await timed("checkNotificationView", () => checkNotificationView(browser!, origin));
await timed("checkMobileViewport", () => checkMobileViewport(browser!, origin, paneB));
await timed("checkTerminalPinch", () => checkTerminalPinch(browser!, origin, paneB));
await timed("checkKeyBarKeepsKeyboardDown", () => checkKeyBarKeepsKeyboardDown(browser!, origin));
await timed("checkMobileTabs", () => checkMobileTabs(browser!, origin));
await timed("checkHeaderLensSwitch", () => checkHeaderLensSwitch(browser!, origin));
await timed("checkTerminalComposition", () => checkTerminalComposition(browser!, origin));
await timed("checkTabMenu", () => checkTabMenu(browser!, origin));
await timed("checkNewTabTypeAhead", () => checkNewTabTypeAhead(browser!, origin));
await timed("checkDefaultView", () => checkDefaultView(browser!, origin));
await timed("checkSettingSurvivesReload", () => checkSettingSurvivesReload(browser!, origin));
await timed("checkSettingsAcrossTabs", () => checkSettingsAcrossTabs(browser!, origin));
await timed("checkTerminalInputSpacing", () => checkTerminalInputSpacing(browser!, origin));
await timed("checkComposerReconnect", () => checkComposerReconnect(browser!, origin, paneB));
await timed("checkDroplet", () => checkDroplet(browser!, origin));
await timed("checkTerminalKeyboardSuggestions", () => checkTerminalKeyboardSuggestions(browser!, origin));
await timed("checkSplitView", () => checkSplitView(browser!, origin));
await timed("checkTakeOver", () => checkTakeOver(browser!, origin));
await timed("checkTabOverflow", () => checkTabOverflow(browser!, origin));
await timed("checkAlertSound", () => checkAlertSound(browser!, origin));
await timed("checkChatKeepsTerminalSize", () => checkChatKeepsTerminalSize(browser!, origin));
await timed("checkPaneSwitchKeepsTerminalSize", () => checkPaneSwitchKeepsTerminalSize(browser!, origin));
await timed("checkBackgroundTabKeepsTerminalSize", () => checkBackgroundTabKeepsTerminalSize(browser!, origin));
await timed("checkFocusedWindowReclaimsTerminalSize", () => checkFocusedWindowReclaimsTerminalSize(browser!, origin));
await timed("checkUpdateNotice", () => checkUpdateNotice(browser!, origin));
await timed("checkTelemetrySettings", () => checkTelemetrySettings(browser!));
if (group === "layout") {
await timed("checkSplitView", () => checkSplitView(browser!, origin));
await timed("checkBackgroundTabKeepsTerminalSize", () => checkBackgroundTabKeepsTerminalSize(browser!, origin));
} else {
await timed("checkPushSettings", () => checkPushSettings(browser!, origin));
await timed("checkWakeLock", () => checkWakeLock(browser!, origin, paneA));
await timed("checkSecretInput", () => checkSecretInput(browser!, origin));
await timed("checkTerminalCopy", () => checkTerminalCopy(browser!, origin));
await timed("checkTerminalCopyOff", () => checkTerminalCopyOff(browser!));
await timed("checkPaneFind", () => checkPaneFind(browser!, origin));
await timed("checkUsageMeters", () => checkUsageMeters(browser!, origin));
await timed("checkUsageSignInAgain", () => checkUsageSignInAgain(browser!, origin));
await timed("checkNotificationStartup", () => checkNotificationStartup(browser!, origin, paneA, paneB));
await timed("checkNotificationView", () => checkNotificationView(browser!, origin));
await timed("checkMobileViewport", () => checkMobileViewport(browser!, origin, paneB));
await timed("checkTerminalPinch", () => checkTerminalPinch(browser!, origin, paneB));
await timed("checkKeyBarKeepsKeyboardDown", () => checkKeyBarKeepsKeyboardDown(browser!, origin));
await timed("checkMobileTabs", () => checkMobileTabs(browser!, origin));
await timed("checkHeaderLensSwitch", () => checkHeaderLensSwitch(browser!, origin));
await timed("checkTerminalComposition", () => checkTerminalComposition(browser!, origin));
await timed("checkTabMenu", () => checkTabMenu(browser!, origin));
await timed("checkNewTabTypeAhead", () => checkNewTabTypeAhead(browser!, origin));
await timed("checkDefaultView", () => checkDefaultView(browser!, origin));
await timed("checkSettingSurvivesReload", () => checkSettingSurvivesReload(browser!, origin));
await timed("checkSettingsAcrossTabs", () => checkSettingsAcrossTabs(browser!, origin));
await timed("checkTerminalInputSpacing", () => checkTerminalInputSpacing(browser!, origin));
await timed("checkComposerReconnect", () => checkComposerReconnect(browser!, origin, paneB));
await timed("checkDroplet", () => checkDroplet(browser!, origin));
await timed("checkTerminalKeyboardSuggestions", () => checkTerminalKeyboardSuggestions(browser!, origin));
await timed("checkTakeOver", () => checkTakeOver(browser!, origin));
await timed("checkTabOverflow", () => checkTabOverflow(browser!, origin));
await timed("checkAlertSound", () => checkAlertSound(browser!, origin));
await timed("checkChatKeepsTerminalSize", () => checkChatKeepsTerminalSize(browser!, origin));
await timed("checkPaneSwitchKeepsTerminalSize", () => checkPaneSwitchKeepsTerminalSize(browser!, origin));
await timed("checkFocusedWindowReclaimsTerminalSize", () => checkFocusedWindowReclaimsTerminalSize(browser!, origin));
await timed("checkUpdateNotice", () => checkUpdateNotice(browser!, origin));
await timed("checkTelemetrySettings", () => checkTelemetrySettings(browser!));
}
} catch (error) {
failed = true;
failure = error;
Expand Down
5 changes: 5 additions & 0 deletions scripts/ui-contexts-layout-regression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// The two slowest UI context checks (split view, a background tab's terminal size), apart from
// ui-contexts-regression.ts so that neither script comes near the browser lane's per-script cap.
import { runIsolatedUiContextChecks } from "./ui-contexts-run.ts";

await runIsolatedUiContextChecks("layout");
Loading
Loading