Files
kandev/apps/web/e2e/tests/pr/pr-merge-queue.spec.ts
T
2026-08-29 07:47:46 +00:00

193 lines
7.3 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
import type { ApiClient } from "../../helpers/api-client";
import { SessionPage } from "../../pages/session-page";
const OWNER = "northstar-labs";
const REPO = "relay-console";
const PR_NUMBER = 842;
test("adds an eligible GitHub PR to its merge queue", async ({ testPage, apiClient, seedData }) => {
test.setTimeout(120_000);
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("maya-chen");
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Ship resilient deployment controls",
seedData.agentProfileId,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
await seedEligiblePR(apiClient, task.id);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
await testPage.reload();
await session.waitForLoad();
await seedEligiblePR(apiClient, task.id);
await session.hoverPRTopbar();
const popover = session.prTopbarPopover();
const compactMerge = popover.getByRole("button", { name: "Merge PR" });
await expect(compactMerge).toBeVisible({ timeout: 15_000 });
// The persisted layout can reopen the detail panel as a dock tab, but a
// reload is also allowed to leave it closed. The topbar button is the
// canonical fallback for opening the same panel.
const detailTab = session.prDetailTab();
if (await detailTab.isVisible()) {
await detailTab.click();
} else {
await session.prTopbarButton().click();
}
await seedEligiblePR(apiClient, task.id);
const detail = testPage.getByTestId("change-request-detail");
const merge = detail.getByRole("button", { name: "Merge PR" });
await expect(merge).toBeVisible({ timeout: 15_000 });
const [response] = await Promise.all([
testPage.waitForResponse((candidate) => candidate.url().includes(`/${PR_NUMBER}/merge`)),
merge.click(),
]);
const responseBody = await response.json();
expect(response.ok(), JSON.stringify(responseBody)).toBe(true);
expect(responseBody).toMatchObject({ status: "queued" });
await expect(testPage.getByText("PR added to merge queue", { exact: true })).toBeVisible();
await expect(detail.getByTestId("pr-merge-button")).toHaveCount(0);
});
test("surfaces queued PR metadata across desktop status surfaces", async ({
testPage,
apiClient,
seedData,
prCapture,
}) => {
test.setTimeout(120_000);
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("maya-chen");
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Ship resilient deployment controls",
seedData.agentProfileId,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
await seedQueuedPR(apiClient, task.id);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
await testPage.reload();
await session.waitForLoad();
await seedQueuedPR(apiClient, task.id);
const taskIcon = testPage.getByTestId("pr-task-icon-" + task.id);
await expect(taskIcon).toBeVisible({ timeout: 15_000 });
await expect(taskIcon).toHaveClass(/text-\[#966600\]/);
await taskIcon.hover();
const taskSummary = testPage
.locator('[data-slot="tooltip-content"]:not([data-state="closed"])')
.getByTestId("pr-task-status-summary")
.first();
await expect(taskSummary).toBeVisible({ timeout: 5_000 });
await expect(taskSummary.getByTestId("pr-task-status-merge")).toContainText("Queued");
await expect(taskSummary.getByTestId("pr-task-status-merge-detail")).toContainText("Position 2");
await expect(taskSummary.getByTestId("pr-task-status-merge-detail")).toContainText("2 minutes");
await prCapture.screenshot("desktop-pr-merge-queue-status", {
caption: "Desktop pull request queue status with position and estimate",
});
await session.hoverPRTopbar();
const popover = session.prTopbarPopover();
await expect(popover.getByTestId("pr-merge-queue-status")).toContainText("Queued");
await expect(popover.getByTestId("pr-merge-queue-status")).toContainText("Position 2");
await expect(popover.getByTestId("pr-merge-queue-status")).toContainText("2 minutes");
await session.hoverPRChip();
const compactPopover = session.prChipPopover();
await expect(compactPopover.getByTestId("pr-merge-queue-status")).toContainText("Queued");
await expect(compactPopover.getByTestId("pr-merge-queue-status")).toContainText("Position 2");
await expect(compactPopover.getByTestId("pr-merge-queue-status")).toContainText("2 minutes");
// The default layout can reopen PR details as a dock tab or through the
// existing topbar affordance. Keep the display scenario independent from
// the preceding merge-action test's accepted-state layout.
const detailTab = session.prDetailTab();
if (await detailTab.isVisible()) {
await detailTab.click();
} else {
await session.prTopbarButton().click();
}
await seedQueuedPR(apiClient, task.id);
const detail = testPage.getByTestId("change-request-detail");
await expect(detail).toBeVisible({ timeout: 15_000 });
await expect(detail.getByTestId("pr-merge-queue-status")).toContainText("Queued");
await expect(detail.getByTestId("pr-merge-queue-status")).toContainText("Position 2");
await expect(detail.getByTestId("pr-merge-queue-status")).toContainText("2 minutes");
});
async function seedQueuedPR(apiClient: ApiClient, taskId: string) {
await apiClient.mockGitHubAddRepos(OWNER, [
{ full_name: `${OWNER}/${REPO}`, owner: OWNER, name: REPO },
]);
await apiClient.mockGitHubAssociateTaskPR({
task_id: taskId,
owner: OWNER,
repo: REPO,
pr_number: PR_NUMBER,
pr_url: `https://github.com/${OWNER}/${REPO}/pull/${PR_NUMBER}`,
pr_title: "Make deployment rollbacks deterministic",
head_branch: "feat/resilient-rollbacks",
base_branch: "main",
author_login: "maya-chen",
state: "open",
review_state: "approved",
checks_state: "success",
mergeable_state: "blocked",
merge_queue_state: "queued",
merge_queue_position: 2,
merge_queue_estimated_time_to_merge_seconds: 61,
review_count: 2,
pending_review_count: 0,
required_reviews: 2,
checks_total: 3,
checks_passing: 3,
});
await apiClient.mockGitHubSetMergeOutcome(OWNER, REPO, PR_NUMBER, "queued");
}
async function seedEligiblePR(apiClient: ApiClient, taskId: string) {
await apiClient.mockGitHubAddRepos(OWNER, [
{ full_name: `${OWNER}/${REPO}`, owner: OWNER, name: REPO },
]);
await apiClient.mockGitHubAssociateTaskPR({
task_id: taskId,
owner: OWNER,
repo: REPO,
pr_number: PR_NUMBER,
pr_url: `https://github.com/${OWNER}/${REPO}/pull/${PR_NUMBER}`,
pr_title: "Make deployment rollbacks deterministic",
head_branch: "feat/resilient-rollbacks",
base_branch: "main",
author_login: "maya-chen",
state: "open",
review_state: "approved",
checks_state: "success",
mergeable_state: "blocked",
review_count: 2,
pending_review_count: 0,
required_reviews: 2,
checks_total: 3,
checks_passing: 3,
});
await apiClient.mockGitHubSetMergeOutcome(OWNER, REPO, PR_NUMBER, "queued");
}