Skip to content

bug: getDynamicEmail always returns global email when executionId is provided #53

@gamandeepsingh

Description

@gamandeepsingh

Bug Report

Title: getDynamicEmail always returns global email when executionId is provided, silently ignoring run-scoped email


Description

When executionId is passed to runSteps, getDynamicEmail() always returns the global dynamic email — even for step sets that never use {{global.dynamicEmail}} or any {{global.*}} placeholder.

This means {{email.extract(...)}} resolves against the wrong email address in any independent runSteps call that shares an executionId but expects its own isolated run-scoped email.

The bug is self-acknowledged in the source:

// src/index.ts
// ~~~ This logic needs to be fixed as global email will always be present if executionId is provided ~~~
const dynamicEmail = getDynamicEmail(localValues, globalValues);

Root Cause

getDynamicEmail in src/data-cache.ts:

export function getDynamicEmail(
  localValues: LocalPlaceholders,
  globalValues?: GlobalPlaceholders,
): string {
  return globalValues?.["{{global.dynamicEmail}}"] || localValues["{{run.dynamicEmail}}"];
}

globalValues["{{global.dynamicEmail}}"] is always truthy when executionId is set because generateGlobalValues() unconditionally generates one:

// src/data-cache.ts
"{{global.dynamicEmail}}":
  existingValues?.["{{global.dynamicEmail}}"] ??
  (emailDomain ? `e2e-tester-${uuidv4()}@${emailDomain}` : ""),

So the || localValues["{{run.dynamicEmail}}"] fallback is never reached when an email domain is configured and executionId is present.


Steps to Reproduce

  1. Configure an email provider with a domain:

    configure({
      email: {
        domain: "yourdomain.com",
        extractContent: async ({ email, prompt }) => { /* ... */ },
      },
    });
  2. Run two independent runSteps calls with the same executionId but steps that only use {{run.dynamicEmail}}:

    await runSteps({ page, executionId: "exec-1", steps: [
      { action: "fill", description: "Email field", data: { value: "{{run.dynamicEmail}}" } },
      { action: "fill", description: "OTP", data: { value: "{{email.extract(get the OTP)}}" } },
    ]});
  3. Observe: {{email.extract(...)}} queries the global email (e2e-tester-<uuid>@yourdomain.com) instead of the run-scoped {{run.dynamicEmail}} address.


Expected Behavior

getDynamicEmail should return the global email only when the current step set actually references {{global.dynamicEmail}} or any {{global.*}} placeholder. For all other runSteps calls it should return the run-scoped {{run.dynamicEmail}} so each call is isolated.


Proposed Fix

processPlaceholders already computes hasGlobalPlaceholders (whether any step uses {{global.*}}). Expose it in the return value and thread it into getDynamicEmail as a preferGlobal flag:

src/data-cache.ts

export function getDynamicEmail(
  localValues: LocalPlaceholders,
  globalValues?: GlobalPlaceholders,
  preferGlobal = false,
): string {
  if (preferGlobal && globalValues?.["{{global.dynamicEmail}}"]) {
    return globalValues["{{global.dynamicEmail}}"];
  }
  return localValues["{{run.dynamicEmail}}"];
}

src/index.ts

const { ..., hasGlobalPlaceholders } = await processPlaceholders(...);
// later in the loop:
const dynamicEmail = getDynamicEmail(localValues, globalValues, hasGlobalPlaceholders);

Fixes #54

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions