Files
kandev/apps/web/e2e/pages/workflow-settings-page.ts
T
José Almeida 68c94dfa3a feat: refine workbench chrome (#761)
* docs: define product and design direction

* style: establish restrained workbench primitives

* refactor: declutter board and task topbars

* style: align dockview panel chrome

* fix: align task topbar vcs control sizing

* refactor: surface integrations navigation

* fix: repair integrations and task creation workflow

* refactor: include linear in integrations navigation

* style: restore familiar workbench tones

* fix: add dockview tab breathing room

* fix: show configured integration shortcuts

* fix: hover open integrations menu

* fix: stabilize integrations hover menu

* fix: align home and dockview topbars

* fix: normalize home topbar controls

* fix: compact home topbar height

* fix: simplify home chrome and split buttons

* fix: move integrations to left chrome

* fix: restore home stats shortcut

* fix: add dockview tab breathing room

* fix: border home stats button

* fix: streamline dockview topbar actions

* fix: align task split button seam

* fix: address review feedback

* fix: restore topbar e2e affordances

* fix: stabilize workflow e2e isolation

* fix: add task topbar action overflow

* fix: address PR review feedback
2026-05-01 21:02:41 +01:00

141 lines
5.0 KiB
TypeScript

import { type Locator, type Page, expect } from "@playwright/test";
export class WorkflowSettingsPage {
readonly page: Page;
readonly addWorkflowButton: Locator;
readonly createDialog: Locator;
readonly workflowNameInput: Locator;
readonly confirmCreateButton: Locator;
constructor(page: Page) {
this.page = page;
this.addWorkflowButton = page.getByTestId("add-workflow-button");
this.createDialog = page.getByTestId("create-workflow-dialog");
this.workflowNameInput = page.getByTestId("workflow-name-input");
this.confirmCreateButton = page.getByTestId("confirm-create-workflow");
}
async goto(workspaceId: string) {
await this.page.goto(`/settings/workspace/${workspaceId}/workflows`);
// Wait for a client-rendered element to confirm hydration is complete
// (networkidle is unreliable with persistent WebSocket connections)
await expect(this.addWorkflowButton).toBeVisible();
}
/** Returns the card container for a workflow by matching text in the card's name input. */
workflowCard(workflowId: string): Locator {
return this.page.getByTestId(`workflow-card-${workflowId}`);
}
/** Find a workflow card by the name shown in its input field using its current value. */
async findWorkflowCard(name: string): Promise<Locator> {
const cards = this.page.locator('[data-testid^="workflow-card-"]');
await expect(cards.first()).toBeVisible();
const testIds = await cards.evaluateAll((elements) =>
elements
.map((element) => element.getAttribute("data-testid"))
.filter((testId): testId is string => Boolean(testId)),
);
for (const testId of testIds) {
const card = this.page.getByTestId(testId);
const input = card.locator("input").first();
const value = await input.inputValue({ timeout: 500 }).catch(() => null);
if (value === name) {
return card;
}
}
return this.page.getByTestId(`workflow-card-not-found-${name}`);
}
/** The pipeline step nodes within a specific workflow card. */
stepNodes(card: Locator): Locator {
return card.locator('[data-slot="alert-dialog-trigger"], .group.relative').filter({
has: this.page.locator(".rounded-full"),
});
}
/** Find a step node by its name text within a card. */
stepNodeByName(card: Locator, stepName: string): Locator {
return card.locator(".group.relative").filter({ hasText: stepName });
}
/** The add-step (+) button within a workflow card. */
addStepButton(card: Locator): Locator {
return card.getByTestId("add-step-button");
}
/** The save button within a workflow card (matches button text "Save"). */
saveButton(card: Locator): Locator {
return card.getByRole("button", { name: "Save" });
}
/** The delete workflow button within a card. */
deleteWorkflowButton(card: Locator): Locator {
return card.getByTestId("delete-workflow-button");
}
/** The step delete confirmation dialog. */
get stepDeleteDialog(): Locator {
return this.page.getByTestId("step-delete-confirm-dialog");
}
/** Returns the ordered names of all workflow cards on the page. */
async getWorkflowOrder(): Promise<string[]> {
const cards = this.page.locator('[data-testid^="workflow-card-"]');
const count = await cards.count();
const names: string[] = [];
for (let i = 0; i < count; i++) {
const input = cards.nth(i).locator("input").first();
names.push(await input.inputValue());
}
return names;
}
/** The drag handle for a specific workflow card. */
dragHandle(workflowId: string): Locator {
return this.page.getByTestId(`workflow-drag-handle-${workflowId}`);
}
/** Open the "Add Workflow" dialog and create a workflow. */
async createWorkflow(name: string, templateName?: string) {
await this.addWorkflowButton.click();
await expect(this.createDialog).toBeVisible();
if (name) {
await this.workflowNameInput.fill(name);
}
if (templateName === "Custom") {
await this.createDialog.locator('label[for="custom"]').click();
} else if (templateName) {
await this.createDialog.getByText(templateName, { exact: false }).first().click();
}
await this.confirmCreateButton.click();
await expect(this.createDialog).not.toBeVisible();
}
/** The workflow-level agent profile select trigger within a workflow card. */
workflowAgentProfileSelect(card: Locator): Locator {
return card.getByTestId("workflow-agent-profile-select");
}
/** The step agent profile override select trigger in the step config panel within a workflow card. */
stepAgentProfileSelect(card: Locator): Locator {
return card.getByTestId("step-agent-profile-select");
}
/** Hover over a step node to reveal the trash button, then click it. */
async clickDeleteStepButton(card: Locator, stepName: string) {
const node = this.stepNodeByName(card, stepName);
await node.hover();
await node
.locator("button")
.filter({ has: this.page.locator(".tabler-icon-trash") })
.click();
}
}