Skip to content

Architecture

This page explains how the Experiment E2E Generator works internally: the pipeline, template system, file mappings, and package merging.

Generator Pipeline

The CLI follows a sequential pipeline, orchestrated by src/generator.js:

bin/cli.js
  └─> src/generator.js (orchestrator)
        ├─> 1. Pre-flight validation
        ├─> 2. Interactive prompts
        ├─> 3. File generation
        ├─> 4. Package.json update
        ├─> 5. Dependency install
        └─> 6. Optional test run

1. Pre-flight Validation

validateProjectDirectory() in utils.js checks that:

  • A package.json exists in the current directory
  • The generator is running inside a valid project root

If validation fails, the CLI exits with a descriptive error message.

2. Interactive Prompts

prompts.js uses the prompts library for interactive input:

  • Experiment name - auto-detected from package.json name or first component in src/components/ via detectExperimentName()
  • Base URL - free text input
  • Market selection - autocomplete powered by getMarketChoices() from markets.js
  • ESLint preference - yes/no toggle

3. File Generation

file-operations.js manages the core generation:

  1. Defines fileMappings - an array of [source, destination] pairs
  2. Iterates over each mapping
  3. Reads the template file from templates/
  4. Calls replaceTemplateVars() to substitute placeholders
  5. Writes the processed file to the target project

Additionally:

  • addTestsToEslintIgnore() - appends tests/ to .eslintignore if the user opted in
  • addTestOutputDirsToGitignore() - adds playwright-report/, coverage/, test-results/ to .gitignore

4. Package.json Update

package-updater.js merges Playwright dependencies and scripts into the target project's package.json:

  • Uses mergePackageJson() from utils.js for a safe merge
  • Adds devDependencies (Playwright, @playwright/test)
  • Adds scripts (test:e2e, test:e2e:experiment)
  • Existing entries are preserved (not overwritten)

5. Dependency Install

The generator auto-detects the package manager by checking for yarn.lock vs package-lock.json, then runs the appropriate install command via spawnSync.

6. Optional Test Run

If the user opts in, the generator:

  1. Runs yarn build (or npm run build) to compile the experiment
  2. Runs yarn test:e2e:experiment (bundle injection test) as a smoke test
  3. Falls back to yarn test:e2e if the experiment-specific script doesn't exist

Template System

Templates live in the templates/ directory and use placeholders.

Placeholder Syntax

{{EXPERIMENT_NAME}}       -> Original name (e.g., "My Promo Banner")
{{EXPERIMENT_NAME_KEBAB}} -> Kebab-case (e.g., "my-promo-banner")
{{BASE_URL}}              -> Application URL
{{MARKET_GROUP}}          -> Market group code (e.g., "SEBN")
{{MARKETS_JSON}}          -> JSON array of market objects

How Substitution Works

The replaceTemplateVars() function in utils.js:

js
function replaceTemplateVars(content, variables) {
  let result = content;
  Object.entries(variables).forEach(([key, value]) => {
    const placeholder = new RegExp(`{{${key}}}`, 'g');
    result = result.replace(placeholder, value);
  });
  return result;
}

Each template file is read as a string, all occurrences are replaced globally, and the result is written to the destination.

Static Templates

experiment-test.spec.js is a static template with no variable substitution. It tests the built bundle by loading it into a blank page.

File Mappings

The fileMappings array in file-operations.js defines the source-to-destination mapping:

Source TemplateDestination
playwright.config.jsplaywright.config.js
tests/config/index.jstests/config/index.js
tests/config/experiment.config.jstests/config/experiment.config.js
tests/config/qa-links.config.jstests/config/qa-links.config.js
tests/fixtures/test-fixtures.jstests/fixtures/test-fixtures.js
tests/utils/test-helpers.jstests/utils/test-helpers.js
tests/e2e/experiment-name/experiment.spec.jstests/e2e/<kebab-name>/<kebab-name>.spec.js
tests/e2e/experiment-name/experiment-test.spec.jstests/e2e/<kebab-name>/experiment-test.spec.js

The experiment spec files use a dynamic folder name derived from toKebabCase(experimentName).

Source Modules

ModuleResponsibility
generator.jsOrchestration, step sequencing, user-facing output
prompts.jsInteractive CLI via prompts library, market autocomplete
file-operations.jsTemplate copy with variable replacement, .eslintignore/.gitignore updates
package-updater.jspackage.json merging, spawnSync for install/build/test
utils.jstoKebabCase, replaceTemplateVars, mergePackageJson, detectExperimentName, validateProjectDirectory, copyTemplateFile
markets.jsMARKET_GROUPS constant, resolveMarkets(), getMarketChoices(), formatMarketCodes()

Adding New Templates

To add a new generated file:

  1. Create the template file in templates/ using placeholders
  2. Add a [source, destination] entry to the fileMappings array in file-operations.js
  3. Add the destination path to getGeneratedFilesList() so it appears in the CLI output

Experiment Framework

The generated tests are designed to work with @sogody/experiment-framework, which provides:

  • runScript() - entry point for experiment execution
  • waitFor() - waits for target DOM elements before mounting the experiment
  • trackAAEvent() - sends Adobe Analytics tracking events

The bundle injection test (experiment-test.spec.js) loads the compiled output of this framework into a blank page to verify the experiment renders correctly.

What's Next