test(desktop): disambiguate workflow step text fills and settle inspector geometry

The "Message text" label exists in both the outgoing trigger pane and the
newly added send-message step pane; during the inspector's 150ms
AnimatePresence mode="wait" exit the unscoped dialog.getByLabel resolves
to the single mounted trigger input, so the step fill overwrote the trigger
condition and left the step text empty. On the slow CI runner this corrupted
the saved definition: workflows.spec.ts then waited forever for the "Turn on"
activation dialog that a filtered trigger correctly suppresses, and
workflow-local-controls.spec.ts reopened on the YAML tab with no Trigger
node button. Scope the five step-intended fills to the step textarea id
(wf-step-<i>-text) so the fill waits for the intended pane to mount;
trigger-intended "Message text" bindings and all assertions stay unchanged.

The reopened-inspector geometry sampled operator boundingBoxes up to
~160ms into the 240ms inspector column entry animation (waitForAnimations
ran only after the comparisons). Settle with the shared waitForAnimations
boundary before sampling; geometry thresholds are untouched.

Trace evidence: CI run 34188379868, job 101941389576 — the step fill resolved
to wf-trigger-trigger_text-value during the exit window.

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-08 02:22:43 -04:00
parent 639a45055b
commit 8b4f9d6d3f
2 changed files with 31 additions and 5 deletions

View File

@@ -63,7 +63,13 @@ async function addMessageStep(
) {
await dialog.getByRole("button", { name: "Add step", exact: true }).click();
await page.getByRole("menuitem", { name: "Send Message" }).click();
await dialog.getByLabel("Message text").fill("Workflow notification");
// "Message text" is also the outgoing trigger pane's condition label while
// the inspector exit transition (AnimatePresence mode="wait", 150ms) still
// mounts it. Scope to the step textarea id so the fill waits for the
// intended step pane instead of racing the outgoing trigger input.
await dialog
.locator('textarea[id^="wf-step-"][id$="-text"]')
.fill("Workflow notification");
}
async function createEnabled(
@@ -152,7 +158,9 @@ test("inserts template variables with keyboard control and restores the caret",
await dialog.getByRole("button", { name: "Add step", exact: true }).click();
await page.getByRole("menuitem", { name: "Send Message" }).click();
const textarea = dialog.getByLabel("Message text");
// Step-scoped (see addMessageStep): the label is ambiguous during the
// inspector exit transition.
const textarea = dialog.locator('textarea[id^="wf-step-"][id$="-text"]');
const listbox = page.getByRole("listbox");
await textarea.fill("Hello {{trig");
await expect(listbox).toBeVisible();
@@ -286,6 +294,10 @@ test("round-trips and reopens structured message-text conditions", async ({
await dialog.getByRole("tab", { name: "Form" }).click();
await openTriggerInspector(dialog);
// The inspector column enters through a 240ms opacity/width/x animation;
// settle it before sampling the operator geometry so each boundingBox is
// read from the final layout, not mid-animation positions.
await waitForAnimations(page);
const matchControls = dialog.getByRole("group", { name: "Match" });
const operatorButtons = matchControls.getByRole("button");
const firstOperatorBox = await operatorButtons.nth(0).boundingBox();

View File

@@ -96,7 +96,13 @@ async function createWorkflow(
await dialog.getByRole("button", { name: "Add step", exact: true }).click();
await page.getByRole("menuitem", { name: "Send Message" }).click();
await dialog.getByLabel("Message text").fill("Workflow notification");
// "Message text" is also the outgoing trigger pane's condition label while the
// inspector exit transition (AnimatePresence mode="wait", 150ms) still mounts
// it. Scope to the step textarea id so the fill waits for the intended step
// pane instead of racing the outgoing trigger input.
await dialog
.locator('textarea[id^="wf-step-"][id$="-text"]')
.fill("Workflow notification");
if (options?.stepName) {
await dialog.getByRole("button", { name: "Step details" }).click();
await dialog.getByLabel("Name (optional)").fill(options.stepName);
@@ -700,7 +706,11 @@ test("captures the built editor at desktop and narrow widths", async ({
await editWorkflowName(dialog, "editor_screenshot");
await dialog.getByRole("button", { name: "Add step", exact: true }).click();
await page.getByRole("menuitem", { name: "Send Message" }).click();
await dialog.getByLabel("Message text").fill("Notify the workflow channel");
// Step-scoped (see createWorkflow): the label is ambiguous during the
// inspector exit transition.
await dialog
.locator('textarea[id^="wf-step-"][id$="-text"]')
.fill("Notify the workflow channel");
const inspector = dialog.getByTestId("workflow-node-inspector");
for (const viewport of [
@@ -805,7 +815,11 @@ test("pane routes use stable IDs and Form/YAML changes stay synchronized", async
await dialog.getByRole("button", { name: "Add step", exact: true }).click();
await page.getByRole("menuitem", { name: "Send Message" }).click();
await dialog.getByLabel("Message text").fill("first message");
// Step-scoped (see createWorkflow): the label is ambiguous during the
// inspector exit transition.
await dialog
.locator('textarea[id^="wf-step-"][id$="-text"]')
.fill("first message");
await expect(page).toHaveURL(/pane=step%3Astep_1/);
await dialog.getByRole("button", { name: "Add after Step 1" }).click();