635 lines
23 KiB
TypeScript
635 lines
23 KiB
TypeScript
import { test, expect } from "../fixtures/test-base";
|
||
import { AutomationsPage } from "../pages/automations-page";
|
||
|
||
/**
|
||
* E2E coverage for the github_pr_merged ("Pull request merged") trigger type.
|
||
*
|
||
* Spec: docs/specs/office/requirements/automations-pr-merged-trigger.md
|
||
* Decisions pinned here:
|
||
* - Picker shows "Pull request merged" under GitHub group, immediately after "New pull requests"
|
||
* - Config round-trip: all_repos, repos, base_branches survive save/reopen
|
||
* - {} config → all_repos=false, dead-config warning shown
|
||
* - Checking "All repositories" CLEARS repos; unchecking leaves list as-is
|
||
* - Toggle cycle: check → save → reopen → uncheck → empty repos + warning
|
||
* - workspaceId pass-through asserted on the outbound /api/v1/github/orgs request
|
||
* - List page badge label "GitHub PR Merged" in purple
|
||
* - Info tooltip mentions workspace and up-to-a-minute detection lag
|
||
* - Repository picker is ENABLED (unlike github_pr which disables it)
|
||
*/
|
||
test.describe("automations — Pull request merged trigger", () => {
|
||
test("picker shows 'Pull request merged' under GitHub group and is selectable", async ({
|
||
testPage,
|
||
seedData,
|
||
}) => {
|
||
const automations = new AutomationsPage(testPage, seedData.workspaceId);
|
||
await automations.gotoNew();
|
||
|
||
await automations.addConditionButton.click();
|
||
|
||
// "Pull request merged" should appear
|
||
await expect(testPage.getByRole("option", { name: /Pull request merged/i })).toBeVisible({
|
||
timeout: 5_000,
|
||
});
|
||
|
||
// It should be under the GitHub group (the GitHub heading is visible)
|
||
await expect(testPage.getByRole("group", { name: /GitHub/i })).toBeVisible();
|
||
|
||
// Select it
|
||
await testPage.getByRole("option", { name: /Pull request merged/i }).click();
|
||
|
||
// A trigger card should appear with the summary text
|
||
await expect(testPage.getByText(/PR merged/i).first()).toBeVisible({
|
||
timeout: 5_000,
|
||
});
|
||
});
|
||
|
||
test("'Pull request merged' appears immediately after 'New pull requests' in picker", async ({
|
||
testPage,
|
||
seedData,
|
||
}) => {
|
||
const automations = new AutomationsPage(testPage, seedData.workspaceId);
|
||
await automations.gotoNew();
|
||
|
||
await automations.addConditionButton.click();
|
||
await expect(testPage.getByRole("option", { name: /Pull request merged/i })).toBeVisible({
|
||
timeout: 5_000,
|
||
});
|
||
|
||
// Collect all option labels in the GitHub group (in DOM order)
|
||
const githubGroup = testPage.getByRole("group", { name: /GitHub/i });
|
||
const options = githubGroup.getByRole("option");
|
||
const labels = await options.allTextContents();
|
||
|
||
const prIdx = labels.findIndex((l) => /New pull requests/i.test(l));
|
||
const mergedIdx = labels.findIndex((l) => /Pull request merged/i.test(l));
|
||
|
||
expect(prIdx).toBeGreaterThanOrEqual(0);
|
||
expect(mergedIdx).toBe(prIdx + 1);
|
||
});
|
||
|
||
test("config round-trips: all_repos, repos and base_branches survive save/reopen", async ({
|
||
testPage,
|
||
seedData,
|
||
}) => {
|
||
const automations = new AutomationsPage(testPage, seedData.workspaceId);
|
||
await automations.gotoNew();
|
||
|
||
await automations.nameInput.fill("PR Merged Round-trip");
|
||
await automations.selectWorkflow("E2E Workflow");
|
||
await automations.selectWorkflowStep(seedData.steps[0].name);
|
||
|
||
// Add the condition
|
||
await automations.addConditionButton.click();
|
||
await testPage.getByRole("option", { name: /Pull request merged/i }).click();
|
||
|
||
// Expand the trigger card by clicking the summary button
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// The panel should show "All repositories" checked by default (all_repos:true from registry default)
|
||
const allReposSwitch = testPage.getByRole("switch", {
|
||
name: /All repositories allowed/i,
|
||
});
|
||
await expect(allReposSwitch).toBeChecked({ timeout: 5_000 });
|
||
|
||
// Uncheck "All repositories"
|
||
await allReposSwitch.click();
|
||
await expect(allReposSwitch).not.toBeChecked();
|
||
|
||
// Dead-config warning appears (repos is now empty)
|
||
await expect(testPage.getByText(/No repositories selected/i)).toBeVisible();
|
||
|
||
// Fill in a base branch
|
||
const branchInput = testPage.getByPlaceholder(/main, release/i);
|
||
await branchInput.fill("main");
|
||
await branchInput.blur();
|
||
|
||
// Save
|
||
await expect(automations.saveButton).toBeEnabled({ timeout: 5_000 });
|
||
await automations.saveButton.click();
|
||
await expect(testPage).toHaveURL(/automations$/, { timeout: 15_000 });
|
||
|
||
// Reopen
|
||
await expect(automations.table).toBeVisible({ timeout: 10_000 });
|
||
await automations.openByName("PR Merged Round-trip");
|
||
await expect(testPage).toHaveURL(/automations\/[a-f0-9-]+$/, { timeout: 10_000 });
|
||
await expect(automations.editor).toBeVisible();
|
||
|
||
// Expand trigger card again
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// all_repos should still be unchecked
|
||
await expect(
|
||
testPage.getByRole("switch", { name: /All repositories allowed/i }),
|
||
).not.toBeChecked({ timeout: 5_000 });
|
||
|
||
// Dead-config warning should still be visible (no repos selected)
|
||
await expect(testPage.getByText(/No repositories selected/i)).toBeVisible();
|
||
});
|
||
|
||
test("stored {} config renders with all_repos unchecked and dead-config warning", async ({
|
||
testPage,
|
||
seedData,
|
||
apiClient,
|
||
}) => {
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Empty Config Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
});
|
||
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: {},
|
||
enabled: true,
|
||
});
|
||
|
||
await testPage.goto(`/settings/workspace/${seedData.workspaceId}/automations/${automation.id}`);
|
||
await testPage.getByTestId("automation-editor").waitFor({ state: "visible", timeout: 15_000 });
|
||
// Expand the trigger card
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// all_repos should be unchecked (absent-field default is false, not true)
|
||
await expect(
|
||
testPage.getByRole("switch", { name: /All repositories allowed/i }),
|
||
).not.toBeChecked({ timeout: 5_000 });
|
||
|
||
// Dead-config warning must be shown
|
||
await expect(testPage.getByText(/No repositories selected/i)).toBeVisible({ timeout: 5_000 });
|
||
});
|
||
|
||
test("info tooltip mentions detection lag", async ({ testPage, seedData, apiClient }) => {
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Tooltip Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
await testPage.goto(`/settings/workspace/${seedData.workspaceId}/automations/${automation.id}`);
|
||
await testPage.getByTestId("automation-editor").waitFor({ state: "visible", timeout: 15_000 });
|
||
|
||
// Hover the info icon on the trigger card to reveal tooltip
|
||
await testPage.getByTestId("trigger-info-icon").hover();
|
||
|
||
// The tooltip should contain "minute" (detection lag) — per triggerInfoGithubPrMerged i18n key
|
||
const tooltip = testPage.getByTestId("trigger-info-tooltip");
|
||
await expect(tooltip).toBeVisible({ timeout: 5_000 });
|
||
await expect(tooltip).toContainText(/minute/i);
|
||
});
|
||
|
||
test("workspaceId is passed to the orgs request when the config panel mounts", async ({
|
||
testPage,
|
||
seedData,
|
||
}) => {
|
||
const automations = new AutomationsPage(testPage, seedData.workspaceId);
|
||
await automations.gotoNew();
|
||
|
||
await automations.addConditionButton.click();
|
||
await testPage.getByRole("option", { name: /Pull request merged/i }).click();
|
||
|
||
// Interceptor must be set up BEFORE expanding — the orgs fetch fires on
|
||
// RepoFilterSelector mount, which happens when the config panel becomes visible.
|
||
const orgRequestPromise = testPage.waitForRequest(
|
||
(req) =>
|
||
req.url().includes("/api/v1/github/orgs") &&
|
||
new URL(req.url()).searchParams.get("workspace_id") === seedData.workspaceId,
|
||
{ timeout: 10_000 },
|
||
);
|
||
|
||
// Expand the trigger card — this mounts RepoFilterSelector, which fires the orgs fetch
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// The key assertion: the outbound request carries this workspace's id
|
||
const orgRequest = await orgRequestPromise;
|
||
const url = new URL(orgRequest.url());
|
||
expect(url.searchParams.get("workspace_id")).toBe(seedData.workspaceId);
|
||
});
|
||
|
||
test("checking 'All repositories' clears repos; toggle cycle produces warning on reopen + uncheck", async ({
|
||
testPage,
|
||
seedData,
|
||
apiClient,
|
||
}) => {
|
||
// Seed an automation with a non-empty repos list and all_repos: false
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Toggle Cycle Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: {
|
||
all_repos: false,
|
||
repos: [{ owner: "acme", name: "api" }],
|
||
base_branches: [],
|
||
},
|
||
enabled: true,
|
||
});
|
||
|
||
await testPage.goto(`/settings/workspace/${seedData.workspaceId}/automations/${automation.id}`);
|
||
await testPage.getByTestId("automation-editor").waitFor({ state: "visible", timeout: 15_000 });
|
||
// Expand the trigger card
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// all_repos is false, no dead-config warning (repos has one entry)
|
||
await expect(
|
||
testPage.getByRole("switch", { name: /All repositories allowed/i }),
|
||
).not.toBeChecked({ timeout: 5_000 });
|
||
await expect(testPage.getByText(/No repositories selected/i)).not.toBeVisible();
|
||
|
||
// CHECK "All repositories" → should clear repos
|
||
const allReposSwitch = testPage.getByRole("switch", {
|
||
name: /All repositories allowed/i,
|
||
});
|
||
await allReposSwitch.click();
|
||
await expect(allReposSwitch).toBeChecked();
|
||
// Wait for the parent draft to render the cleared repository list before
|
||
// submitting. The switch updates first; saving in that intermediate render
|
||
// can otherwise race the config state update when the suite runs in full.
|
||
await expect(testPage.getByText("acme/api", { exact: true })).not.toBeVisible({
|
||
timeout: 5_000,
|
||
});
|
||
|
||
const automationsPage = new AutomationsPage(testPage, seedData.workspaceId);
|
||
|
||
// Save — updating a pre-existing automation stays on the editor page (no redirect)
|
||
await expect(automationsPage.saveButton).toBeEnabled({ timeout: 5_000 });
|
||
await automationsPage.saveButton.click();
|
||
|
||
// Wait for the shared save coordinator to finish. The button's accessible
|
||
// name changes from "Save changes" to "Saving changes" while the request
|
||
// is in flight, so waiting for that name to disappear can reload the page
|
||
// before trigger persistence completes.
|
||
await expect(testPage.getByTestId("settings-floating-save")).toHaveAttribute(
|
||
"data-status",
|
||
"saved",
|
||
{ timeout: 10_000 },
|
||
);
|
||
|
||
// Reload the page to verify the saved state is persisted
|
||
await testPage.reload();
|
||
await testPage.getByTestId("automation-editor").waitFor({ state: "visible", timeout: 15_000 });
|
||
|
||
// Expand trigger card again
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// all_repos is now true
|
||
const checkboxAfterSave = testPage.getByRole("switch", {
|
||
name: /All repositories allowed/i,
|
||
});
|
||
await expect(checkboxAfterSave).toBeChecked({ timeout: 5_000 });
|
||
|
||
// UNCHECK "All repositories" → repos was cleared, so dead-config warning appears
|
||
await checkboxAfterSave.click();
|
||
await expect(checkboxAfterSave).not.toBeChecked();
|
||
|
||
// repos is empty (was cleared when "All repositories" was checked), so warning shows
|
||
await expect(testPage.getByText(/No repositories selected/i)).toBeVisible({ timeout: 5_000 });
|
||
});
|
||
|
||
test("list page badge shows 'GitHub PR Merged' label", async ({
|
||
testPage,
|
||
seedData,
|
||
apiClient,
|
||
}) => {
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Badge Label Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const automations = new AutomationsPage(testPage, seedData.workspaceId);
|
||
await automations.goto();
|
||
|
||
await expect(automations.table).toBeVisible({ timeout: 10_000 });
|
||
|
||
// The badge for this automation should show "GitHub PR Merged"
|
||
const row = automations.table.locator("tr", { hasText: "Badge Label Test" });
|
||
await expect(row.getByText("GitHub PR Merged")).toBeVisible({ timeout: 10_000 });
|
||
});
|
||
|
||
test("repository picker is enabled for github_pr_merged (unlike github_pr)", async ({
|
||
testPage,
|
||
seedData,
|
||
apiClient,
|
||
}) => {
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Picker Enabled Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
await testPage.goto(`/settings/workspace/${seedData.workspaceId}/automations/${automation.id}`);
|
||
await testPage.getByTestId("automation-editor").waitFor({ state: "visible", timeout: 15_000 });
|
||
|
||
// Expand the trigger card
|
||
await testPage
|
||
.getByRole("button", { name: /PR merged/i })
|
||
.first()
|
||
.click();
|
||
|
||
// Uncheck "All repositories" to show the repo selector
|
||
const allReposSwitch = testPage.getByRole("switch", {
|
||
name: /All repositories allowed/i,
|
||
});
|
||
await expect(allReposSwitch).toBeChecked({ timeout: 5_000 });
|
||
await allReposSwitch.click();
|
||
|
||
// The "Add repository" button appears when all_repos=false and must NOT be disabled.
|
||
// (For github_pr the picker is disabled; for github_pr_merged it must be enabled.)
|
||
const addRepoButton = testPage.getByRole("button", { name: /Add repository/i });
|
||
await expect(addRepoButton).toBeVisible({ timeout: 5_000 });
|
||
await expect(addRepoButton).not.toBeDisabled();
|
||
});
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Agent behavior — spec lines 1487–1503
|
||
// These use the mock agent's inline script harness (e2e:mcp:... directives),
|
||
// NOT /e2e:<name> scenarios, because the inline script receives the already-
|
||
// interpolated {{data.task_id}} value whereas scenario handlers do not.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
test("agent behavior: scripted agent archives the correct task id from trigger data", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const target = await apiClient.createTask(seedData.workspaceId, "PR Merged Archive Target", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Archive on PR Merge",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
prompt:
|
||
'e2e:mcp:kandev:archive_task_kandev({"task_id":"{{data.task_id}}"})\ne2e:message("archived-done")',
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const { run_task_id } = await apiClient.firePRMerged({
|
||
taskId: target.id,
|
||
automationId: automation.id,
|
||
owner: "acme",
|
||
repo: "api",
|
||
});
|
||
|
||
await testPage.goto(`/t/${run_task_id}`);
|
||
await expect(testPage.getByText("archived-done", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
|
||
// Target task should no longer appear in the active task list
|
||
const { tasks } = await apiClient.listTasks(seedData.workspaceId);
|
||
expect(tasks.find((t) => t.id === target.id)).toBeUndefined();
|
||
});
|
||
|
||
test("agent behavior: a wrong same-owner archive target is rejected", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const target = await apiClient.createTask(seedData.workspaceId, "Bound Archive Target", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
const decoy = await apiClient.createTask(seedData.workspaceId, "Same Owner Decoy", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Reject Wrong Archive Target",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
prompt: `e2e:mcp:kandev:archive_task_kandev({"task_id":"${decoy.id}"})\ne2e:message("wrong-target-rejected")`,
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const { run_task_id } = await apiClient.firePRMerged({
|
||
taskId: target.id,
|
||
automationId: automation.id,
|
||
owner: "acme",
|
||
repo: "api",
|
||
});
|
||
|
||
await testPage.goto(`/t/${run_task_id}`);
|
||
await expect(testPage.getByText("wrong-target-rejected", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
|
||
const { tasks } = await apiClient.listTasks(seedData.workspaceId);
|
||
expect(tasks.find((task) => task.id === target.id)).toBeDefined();
|
||
expect(tasks.find((task) => task.id === decoy.id)).toBeDefined();
|
||
});
|
||
|
||
test("agent behavior: archive_task_kandev on an already-archived target succeeds and run completes", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const target = await apiClient.createTask(seedData.workspaceId, "Pre-archived Target", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
await apiClient.archiveTask(target.id);
|
||
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Archive Already Archived",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
prompt:
|
||
'e2e:mcp:kandev:archive_task_kandev({"task_id":"{{data.task_id}}"})\ne2e:message("already-archived-done")',
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const { run_task_id } = await apiClient.firePRMerged({
|
||
taskId: target.id,
|
||
automationId: automation.id,
|
||
owner: "acme",
|
||
repo: "api",
|
||
});
|
||
|
||
// Run must complete — the marker appearing proves the turn ended cleanly
|
||
await testPage.goto(`/t/${run_task_id}`);
|
||
await expect(testPage.getByText("already-archived-done", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
});
|
||
|
||
test("agent behavior: run succeeds even when archive_task_kandev fails for a deleted target", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const target = await apiClient.createTask(seedData.workspaceId, "Soon-deleted Target", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Archive Deleted Target",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
// e2e:delay gives the test time to delete the target before the archive call
|
||
prompt:
|
||
'e2e:delay(1000)\ne2e:mcp:kandev:archive_task_kandev({"task_id":"{{data.task_id}}"})\ne2e:message("deleted-done")',
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
// Wait until the event has created the automation run, then delete the
|
||
// source task while the delay keeps the agent before its archive call.
|
||
const { run_task_id } = await apiClient.firePRMerged({
|
||
taskId: target.id,
|
||
automationId: automation.id,
|
||
owner: "acme",
|
||
repo: "api",
|
||
});
|
||
await apiClient.deleteTask(target.id);
|
||
|
||
// Run must still complete cleanly — MCP error on a deleted task must not crash the turn
|
||
await testPage.goto(`/t/${run_task_id}`);
|
||
await expect(testPage.getByText("deleted-done", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
});
|
||
|
||
test("agent behavior: run succeeds and target is unarchived when agent makes no archive call", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const target = await apiClient.createTask(seedData.workspaceId, "Should Stay Unarchived", {
|
||
workflow_id: seedData.workflowId,
|
||
workflow_step_id: seedData.startStepId,
|
||
});
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "No Archive Automation",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
prompt: 'e2e:message("no-archive-done")',
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const { run_task_id } = await apiClient.firePRMerged({
|
||
taskId: target.id,
|
||
automationId: automation.id,
|
||
owner: "acme",
|
||
repo: "api",
|
||
});
|
||
|
||
await testPage.goto(`/t/${run_task_id}`);
|
||
await expect(testPage.getByText("no-archive-done", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
|
||
// Target must still appear in the active task list — no archive was called
|
||
const { tasks } = await apiClient.listTasks(seedData.workspaceId);
|
||
expect(tasks.find((t) => t.id === target.id)).toBeDefined();
|
||
});
|
||
|
||
test("agent behavior: manual run fires with no task_id and does not archive any task", async ({
|
||
testPage,
|
||
apiClient,
|
||
seedData,
|
||
}) => {
|
||
const automation = await apiClient.seedAutomation({
|
||
workspaceId: seedData.workspaceId,
|
||
name: "Manual Trigger Test",
|
||
workflowId: seedData.workflowId,
|
||
workflowStepId: seedData.startStepId,
|
||
prompt: 'e2e:message("manual-done")',
|
||
agentProfileId: seedData.agentProfileId,
|
||
});
|
||
await apiClient.seedTrigger({
|
||
automationId: automation.id,
|
||
type: "github_pr_merged",
|
||
config: { all_repos: true, repos: [], base_branches: [] },
|
||
enabled: true,
|
||
});
|
||
|
||
const result = await apiClient.triggerAutomationManual(automation.id);
|
||
expect(result.run_task_id).toBeTruthy();
|
||
|
||
await testPage.goto(`/t/${result.run_task_id!}`);
|
||
await expect(testPage.getByText("manual-done", { exact: true })).toBeVisible({
|
||
timeout: 30_000,
|
||
});
|
||
});
|
||
});
|