113 lines
5.3 KiB
TypeScript
113 lines
5.3 KiB
TypeScript
import { expect } from "vitest";
|
|
import { control, createAndSelectWorkspace, evalIn, seedSessions, waitFor } from "@openwork/behaviors";
|
|
import { desktop } from "@openwork/hosts";
|
|
import { needs, test } from "@openwork/testkit";
|
|
|
|
const enabled = process.env.OPENWORK_EVAL_E2E_TESTS === "1";
|
|
const title = enabled
|
|
? "chat working and command activity use quiet shimmer without spinners"
|
|
: "chat loading shimmer skipped — needs: set OPENWORK_EVAL_E2E_TESTS=1";
|
|
|
|
test.skipIf(!enabled)(title, async ({ evidence }) => {
|
|
needs({ optIn: ["OPENWORK_EVAL_E2E_TESTS"] });
|
|
|
|
await using app = await desktop({ name: "chat-loading-shimmer" });
|
|
await createAndSelectWorkspace(app, {
|
|
path: `/tmp/openwork-chat-loading-shimmer-${Date.now()}`,
|
|
});
|
|
await seedSessions(app, ["Shimmer proof"]);
|
|
await waitFor(
|
|
app,
|
|
`window.__openworkControl.listActions().some((action) => action.id === "eval.chat_loading.seed" && !action.disabled)`,
|
|
{ timeoutMs: 30_000, label: "chat loading proof control ready" },
|
|
);
|
|
await control(app, "eval.chat_loading.seed");
|
|
await waitFor(app, `Boolean(document.querySelector('[data-loading-message="working"] .ow-text-shimmer'))`, {
|
|
timeoutMs: 15_000,
|
|
label: "main Working shimmer",
|
|
});
|
|
const working = await evalIn(app, `(() => {
|
|
const row = document.querySelector('[data-loading-message="working"]');
|
|
return {
|
|
text: row instanceof HTMLElement ? row.innerText.trim() : "",
|
|
hasSpinner: Boolean(row?.querySelector('.animate-spin')),
|
|
hasShimmer: Boolean(row?.querySelector('.ow-text-shimmer')),
|
|
};
|
|
})()`);
|
|
expect(working).toMatchObject({ hasSpinner: false, hasShimmer: true });
|
|
if (!working || typeof working !== "object" || !("text" in working) || typeof working.text !== "string") {
|
|
throw new Error(`Working row was not readable: ${JSON.stringify(working)}`);
|
|
}
|
|
expect(working.text).toContain("Working");
|
|
evidence.recordAssertionEvidence(
|
|
"The main chat Working state uses shimmer instead of a circular spinner",
|
|
`The live row remained readable as “${working.text}” and contained no animate-spin indicator.`,
|
|
true,
|
|
);
|
|
|
|
await control(app, "eval.session_lifecycle.seed_unfinished_tools", { lifecycle: "active" });
|
|
await waitFor(app, `Boolean(document.querySelector('[data-tool-aggregate-now] .ow-text-shimmer'))`, {
|
|
timeoutMs: 15_000,
|
|
label: "aggregate Now shimmer",
|
|
});
|
|
const aggregate = await evalIn(app, `(() => {
|
|
const row = document.querySelector('[data-tool-aggregate-now]');
|
|
const summary = [...document.querySelectorAll('[data-tool-aggregate] > button')]
|
|
.find((button) => (button.textContent ?? "").includes("Running command"));
|
|
return {
|
|
text: row instanceof HTMLElement ? row.innerText.replace(/\\s+/g, " ").trim() : "",
|
|
hasSpinner: Boolean(row?.querySelector('.animate-spin')),
|
|
hasShimmer: Boolean(row?.querySelector('.ow-text-shimmer')),
|
|
singularSummary: summary instanceof HTMLElement ? summary.innerText.replace(/\\s+/g, " ").trim() : "",
|
|
};
|
|
})()`);
|
|
expect(aggregate).toMatchObject({ hasSpinner: false, hasShimmer: true });
|
|
if (!aggregate || typeof aggregate !== "object" || !("text" in aggregate) || typeof aggregate.text !== "string") {
|
|
throw new Error(`Aggregate activity row was not readable: ${JSON.stringify(aggregate)}`);
|
|
}
|
|
expect(aggregate.text).toContain("Now:");
|
|
if (!("singularSummary" in aggregate) || typeof aggregate.singularSummary !== "string") {
|
|
throw new Error(`Aggregate summary was not readable: ${JSON.stringify(aggregate)}`);
|
|
}
|
|
expect(aggregate.singularSummary).toContain("Running command");
|
|
expect(aggregate.singularSummary).not.toContain("Running 1 command");
|
|
evidence.recordAssertionEvidence(
|
|
"The aggregate Now state uses shimmer instead of a circular spinner",
|
|
`The live aggregate row remained readable as “${aggregate.text}” and contained no animate-spin indicator.`,
|
|
true,
|
|
);
|
|
|
|
const expanded = await evalIn(app, `(() => {
|
|
const trigger = document.querySelector('[data-tool-aggregate] > button');
|
|
if (!(trigger instanceof HTMLButtonElement)) return false;
|
|
trigger.click();
|
|
return true;
|
|
})()`);
|
|
expect(expanded).toBe(true);
|
|
await waitFor(app, `Boolean(document.querySelector('[data-tool-aggregate-command]'))`, {
|
|
timeoutMs: 15_000,
|
|
label: "expanded aggregate command block",
|
|
});
|
|
const command = await evalIn(app, `(() => {
|
|
const block = document.querySelector('[data-tool-aggregate-command]');
|
|
const aggregate = block?.closest('[data-tool-aggregate]');
|
|
return {
|
|
text: block instanceof HTMLElement ? block.innerText.replace(/\\s+/g, " ").trim() : "",
|
|
commandSummaryCount: aggregate instanceof HTMLElement
|
|
? (aggregate.innerText.match(/(?:Ran|Running) command/g) ?? []).length
|
|
: 0,
|
|
};
|
|
})()`);
|
|
expect(command).toMatchObject({ commandSummaryCount: 1 });
|
|
if (!command || typeof command !== "object" || !("text" in command) || typeof command.text !== "string") {
|
|
throw new Error(`Expanded command block was not readable: ${JSON.stringify(command)}`);
|
|
}
|
|
expect(command.text).toContain("$");
|
|
expect(command.text).toContain("git status --short --branch");
|
|
evidence.recordAssertionEvidence(
|
|
"Expanded command history uses a readable rounded shell block",
|
|
"The live expanded aggregate rendered the command with a shell prompt inside its dedicated command block.",
|
|
true,
|
|
);
|
|
});
|