diff --git a/src/__tests__/data-cache.test.ts b/src/__tests__/data-cache.test.ts index f5d90db..78e1db5 100644 --- a/src/__tests__/data-cache.test.ts +++ b/src/__tests__/data-cache.test.ts @@ -155,15 +155,24 @@ describe("generateLocalValues", () => { // ─── getDynamicEmail ──────────────────────────────────────────────────────── describe("getDynamicEmail", () => { - it("prefers global dynamicEmail over local", () => { - const globalValues: GlobalPlaceholders = { - "{{global.shortid}}": "g1", - "{{global.fullName}}": "Global User", - "{{global.email}}": "g@test.com", - "{{global.dynamicEmail}}": "global-dyn@test.com", - "{{global.phoneNumber}}": "0000000000", - }; - expect(getDynamicEmail(localValues, globalValues)).toBe("global-dyn@test.com"); + const globalValues: GlobalPlaceholders = { + "{{global.shortid}}": "g1", + "{{global.fullName}}": "Global User", + "{{global.email}}": "g@test.com", + "{{global.dynamicEmail}}": "global-dyn@test.com", + "{{global.phoneNumber}}": "0000000000", + }; + + it("returns global dynamicEmail when preferGlobal=true", () => { + expect(getDynamicEmail(localValues, globalValues, true)).toBe("global-dyn@test.com"); + }); + + it("returns local dynamicEmail when preferGlobal=false even if global is set", () => { + expect(getDynamicEmail(localValues, globalValues, false)).toBe("dyn@test.com"); + }); + + it("returns local dynamicEmail when preferGlobal defaults to false", () => { + expect(getDynamicEmail(localValues, globalValues)).toBe("dyn@test.com"); }); it("falls back to local dynamicEmail when global is not provided", () => { diff --git a/src/data-cache.ts b/src/data-cache.ts index 216c24e..20fcf8a 100644 --- a/src/data-cache.ts +++ b/src/data-cache.ts @@ -67,6 +67,7 @@ export type ProcessPlaceholdersResult = { localValues: LocalPlaceholders; globalValues?: GlobalPlaceholders; projectDataValues?: ProjectDataPlaceholders; + hasGlobalPlaceholders: boolean; }; // ============================================================================= @@ -527,18 +528,25 @@ export async function processPlaceholders( localValues, globalValues, projectDataValues, + hasGlobalPlaceholders, }; } /** * Gets the dynamic email to use for email extraction. - * Prefers global email if available, otherwise falls back to local email. + * Only prefers global email when preferGlobal is true (i.e. the current step + * set actually references {{global.*}} placeholders). Otherwise always returns + * the run-scoped email so each runSteps call gets its own isolated address. */ export function getDynamicEmail( localValues: LocalPlaceholders, globalValues?: GlobalPlaceholders, + preferGlobal = false, ): string { - return globalValues?.["{{global.dynamicEmail}}"] || localValues["{{run.dynamicEmail}}"]; + if (preferGlobal && globalValues?.["{{global.dynamicEmail}}"]) { + return globalValues["{{global.dynamicEmail}}"]; + } + return localValues["{{run.dynamicEmail}}"]; } /** diff --git a/src/index.ts b/src/index.ts index 1e4d648..a2a67bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -137,7 +137,7 @@ export const runSteps = async ({ } // Process dynamic placeholders before running steps - const { processedSteps, processedAssertions, localValues, globalValues, projectDataValues } = + const { processedSteps, processedAssertions, localValues, globalValues, projectDataValues, hasGlobalPlaceholders } = await processPlaceholders(steps, assertions, executionId, projectId); logger.info(`Starting step-by-step execution of ${processedSteps.length} steps.`); @@ -163,12 +163,11 @@ export const runSteps = async ({ let errorInStepExecution, stepThatFailed: string = ""; for (let i = 0; i < processedSteps.length; i++) { - // Resolve email placeholders lazily just before step execution - // This ensures the email has arrived before we try to extract content - // Use global email if available, otherwise fall back to run email, and then use the supplied email from regex - - // ~~~ This logic needs to be fixed as global email will always be present if executionId is provided ~~~ - const dynamicEmail = getDynamicEmail(localValues, globalValues); + // Resolve email placeholders lazily just before step execution. + // Only use the shared global email when these steps actually reference + // {{global.*}} placeholders; otherwise each runSteps call gets its own + // run-scoped address so independent flows don't bleed into each other. + const dynamicEmail = getDynamicEmail(localValues, globalValues, hasGlobalPlaceholders); // Re-process step data and waitUntil with current localValues to pick up extracted values from previous steps let currentStep = processedSteps[i];