* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import { expect, test, type Page } from "../support/fixtures";
|
|
import { openAgentRoute, seedMockAgentWorkspace } from "../support/helpers/mock-agent";
|
|
import {
|
|
composerLocator,
|
|
expectComposerEditable,
|
|
expectComposerVisible,
|
|
fillComposerDraft,
|
|
submitMessage,
|
|
} from "../support/helpers/composer";
|
|
|
|
async function expectUserMessageCount(page: Page, expected: number): Promise<void> {
|
|
await expect(page.getByTestId("user-message")).toHaveCount(expected, { timeout: 15_000 });
|
|
}
|
|
|
|
async function expectIdleComposer(page: Page): Promise<void> {
|
|
await expectComposerEditable(page);
|
|
await expect(page.getByRole("button", { name: /stop|cancel/i })).toHaveCount(0, {
|
|
timeout: 15_000,
|
|
});
|
|
}
|
|
|
|
async function expectNoLoadingRegressionAfterIdle(page: Page): Promise<void> {
|
|
await expectIdleComposer(page);
|
|
await page.waitForTimeout(1_000);
|
|
await expectIdleComposer(page);
|
|
}
|
|
|
|
test.describe("User message UI contract", () => {
|
|
test("dedupes mock provider user_message echoes across multi-turn sends", async ({ page }) => {
|
|
const session = await seedMockAgentWorkspace({
|
|
repoPrefix: "user-message-contract-e2e-",
|
|
title: "User message contract e2e",
|
|
});
|
|
const prompts = [
|
|
"emit 1 coalesced agent stream updates for user message contract turn one.",
|
|
"emit 1 coalesced agent stream updates for user message contract turn two.",
|
|
"emit 1 coalesced agent stream updates for user message contract turn three.",
|
|
];
|
|
|
|
try {
|
|
await openAgentRoute(page, session);
|
|
await expectComposerVisible(page);
|
|
|
|
for (let index = 0; index < prompts.length; index += 1) {
|
|
const prompt = prompts[index]!;
|
|
await submitMessage(page, prompt);
|
|
await expect(page.getByText(prompt, { exact: true })).toBeVisible({ timeout: 15_000 });
|
|
await expect(page.getByText("stress-update-0", { exact: true }).first()).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
await expectUserMessageCount(page, index + 1);
|
|
await expectNoLoadingRegressionAfterIdle(page);
|
|
}
|
|
|
|
await fillComposerDraft(page, "append");
|
|
await composerLocator(page).evaluate((element) => element.blur());
|
|
await expectUserMessageCount(page, 3);
|
|
await expectIdleComposer(page);
|
|
} finally {
|
|
await session.cleanup();
|
|
}
|
|
});
|
|
});
|