Files
paseo/packages/app/e2e/browser/new-workspace-composer-draft.spec.ts

186 lines
6.9 KiB
TypeScript

import {
composerLocator,
enterComposerFullscreen,
exitComposerFullscreen,
resizeComposerViewport,
} from "../support/helpers/composer";
import { expect, test } from "../support/fixtures";
import { gotoAppShell } from "../support/helpers/app";
import { getE2EDaemonPort } from "../support/helpers/daemon-port";
import {
delayBrowserWorkspaceCreatedResponse,
expectNewWorkspaceDraft,
expectNewWorkspaceProjectSelected,
fillNewWorkspaceDraft,
openGlobalNewWorkspaceComposer,
openNewWorkspaceComposer,
selectNewWorkspaceHost,
selectNewWorkspaceProject,
} from "../support/helpers/new-workspace";
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
import { getServerId } from "../support/helpers/server-id";
import { seedSavedSettingsHosts } from "../support/helpers/settings";
import { waitForSidebarHydration } from "../support/helpers/workspace-ui";
const DRAFT = `Please investigate the workspace startup failure.
Trace the request from the app through the daemon, preserve the existing behavior, and explain the root cause before making changes.`;
test.describe("New workspace composer draft", () => {
test.describe.configure({ timeout: 240_000 });
test("keeps the draft when the project changes", async ({ page }) => {
const firstProject: SeededWorkspace = await seedWorkspace({
repoPrefix: "new-workspace-draft-project-a-",
});
const secondProject: SeededWorkspace = await seedWorkspace({
repoPrefix: "new-workspace-draft-project-b-",
});
try {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await openNewWorkspaceComposer(page, {
projectKey: firstProject.projectKey,
projectDisplayName: firstProject.projectDisplayName,
});
await expectNewWorkspaceProjectSelected(page, firstProject.projectDisplayName);
await fillNewWorkspaceDraft(page, DRAFT);
await selectNewWorkspaceProject(page, {
projectKey: secondProject.projectKey,
projectDisplayName: secondProject.projectDisplayName,
});
await expectNewWorkspaceDraft(page, DRAFT);
} finally {
await secondProject.cleanup();
await firstProject.cleanup();
}
});
test("keeps the draft when the host changes", async ({ page }) => {
const project: SeededWorkspace = await seedWorkspace({
repoPrefix: "new-workspace-draft-host-",
});
const secondaryServerId = "new-workspace-draft-secondary-host";
try {
await seedSavedSettingsHosts(page, [
{
serverId: getServerId(),
label: "Primary host",
endpoint: `127.0.0.1:${getE2EDaemonPort()}`,
},
{
serverId: secondaryServerId,
label: "Secondary host",
endpoint: "127.0.0.1:9",
},
]);
await gotoAppShell(page);
await waitForSidebarHydration(page);
await openGlobalNewWorkspaceComposer(page);
await fillNewWorkspaceDraft(page, DRAFT);
await selectNewWorkspaceHost(page, "Secondary host");
await expectNewWorkspaceDraft(page, DRAFT);
} finally {
await project.cleanup();
}
});
test("does not restore a submitted draft after deferred publication", async ({ page }) => {
const project = await seedWorkspace({ repoPrefix: "new-workspace-submitted-draft-" });
try {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await openNewWorkspaceComposer(page, {
projectKey: project.projectKey,
projectDisplayName: project.projectDisplayName,
});
const composer = page.getByRole("textbox", { name: "Message agent..." });
const createButton = page.getByTestId("workspace-create-submit");
await expect(composer).toBeEditable({ timeout: 30_000 });
await expect(createButton).toBeEnabled({ timeout: 30_000 });
await composer.evaluate((element, draft) => {
if (!(element instanceof HTMLTextAreaElement)) {
throw new Error("Composer input is not a textarea");
}
const valueSetter = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
"value",
)?.set;
if (!valueSetter) throw new Error("Textarea value setter is unavailable");
valueSetter.call(element, draft);
element.dispatchEvent(
new InputEvent("input", {
bubbles: true,
data: draft,
inputType: "insertText",
}),
);
const button = document.querySelector('[data-testid="workspace-create-submit"]');
if (!(button instanceof HTMLElement)) {
throw new Error("Create button is unavailable");
}
button.click();
}, DRAFT);
await page.waitForURL((url) => url.pathname.includes("/workspace/"), { timeout: 30_000 });
await openGlobalNewWorkspaceComposer(page);
await expectNewWorkspaceDraft(page, "");
} finally {
await project.cleanup();
}
});
});
test("fullscreen creation returns inline and keeps submission locked when reopened", async ({
page,
}) => {
const project = await seedWorkspace({ repoPrefix: "fullscreen-submit-pending-" });
const delay = await delayBrowserWorkspaceCreatedResponse(page);
try {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await openNewWorkspaceComposer(page, project);
await resizeComposerViewport(page, "portrait");
await fillNewWorkspaceDraft(page, DRAFT);
await enterComposerFullscreen(page);
await page.getByRole("button", { name: "Create", exact: true }).click();
await delay.waitForCreateRequest();
await test.step("submission returns inline with the original draft and spinner", async () => {
await expect(page.getByTestId("composer-fullscreen")).toHaveCount(0);
await expect(composerLocator(page)).toHaveValue(DRAFT);
await expect(composerLocator(page)).not.toBeEditable();
await expect(page.getByRole("button", { name: "Create", exact: true })).toBeDisabled();
await expect(page.getByRole("progressbar")).toBeVisible();
});
await test.step("fullscreen can reopen while pending without unlocking or duplicating submission", async () => {
await enterComposerFullscreen(page);
await expect(page.getByTestId("composer-fullscreen")).toBeVisible();
await expect(composerLocator(page)).toHaveValue(DRAFT);
await expect(composerLocator(page)).not.toBeEditable();
await expect(page.getByRole("button", { name: "Create", exact: true })).toBeDisabled();
await expect(page.getByRole("progressbar")).toBeVisible();
await exitComposerFullscreen(page);
});
delay.release();
await page.waitForURL((url) => url.pathname.includes("/workspace/"));
await expect(composerLocator(page)).toHaveValue("");
await expect(composerLocator(page)).toBeEditable();
expect(delay.agentRequests).toHaveLength(1);
} finally {
delay.release();
await project.cleanup();
}
});