Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions src/__tests__/data-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
12 changes: 10 additions & 2 deletions src/data-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type ProcessPlaceholdersResult = {
localValues: LocalPlaceholders;
globalValues?: GlobalPlaceholders;
projectDataValues?: ProjectDataPlaceholders;
hasGlobalPlaceholders: boolean;
};

// =============================================================================
Expand Down Expand Up @@ -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}}"];
}

/**
Expand Down
13 changes: 6 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`);
Expand All @@ -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];
Expand Down