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 run1. Pre-flight Validation
validateProjectDirectory() in utils.js checks that:
- A
package.jsonexists 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.jsonname or first component insrc/components/viadetectExperimentName() - Base URL - free text input
- Market selection - autocomplete powered by
getMarketChoices()frommarkets.js - ESLint preference - yes/no toggle
3. File Generation
file-operations.js manages the core generation:
- Defines
fileMappings- an array of[source, destination]pairs - Iterates over each mapping
- Reads the template file from
templates/ - Calls
replaceTemplateVars()to substituteplaceholders - Writes the processed file to the target project
Additionally:
addTestsToEslintIgnore()- appendstests/to.eslintignoreif the user opted inaddTestOutputDirsToGitignore()- addsplaywright-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()fromutils.jsfor 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:
- Runs
yarn build(ornpm run build) to compile the experiment - Runs
yarn test:e2e:experiment(bundle injection test) as a smoke test - Falls back to
yarn test:e2eif 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 objectsHow Substitution Works
The replaceTemplateVars() function in utils.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 Template | Destination |
|---|---|
playwright.config.js | playwright.config.js |
tests/config/index.js | tests/config/index.js |
tests/config/experiment.config.js | tests/config/experiment.config.js |
tests/config/qa-links.config.js | tests/config/qa-links.config.js |
tests/fixtures/test-fixtures.js | tests/fixtures/test-fixtures.js |
tests/utils/test-helpers.js | tests/utils/test-helpers.js |
tests/e2e/experiment-name/experiment.spec.js | tests/e2e/<kebab-name>/<kebab-name>.spec.js |
tests/e2e/experiment-name/experiment-test.spec.js | tests/e2e/<kebab-name>/experiment-test.spec.js |
The experiment spec files use a dynamic folder name derived from toKebabCase(experimentName).
Source Modules
| Module | Responsibility |
|---|---|
generator.js | Orchestration, step sequencing, user-facing output |
prompts.js | Interactive CLI via prompts library, market autocomplete |
file-operations.js | Template copy with variable replacement, .eslintignore/.gitignore updates |
package-updater.js | package.json merging, spawnSync for install/build/test |
utils.js | toKebabCase, replaceTemplateVars, mergePackageJson, detectExperimentName, validateProjectDirectory, copyTemplateFile |
markets.js | MARKET_GROUPS constant, resolveMarkets(), getMarketChoices(), formatMarketCodes() |
Adding New Templates
To add a new generated file:
- Create the template file in
templates/usingplaceholders - Add a
[source, destination]entry to thefileMappingsarray infile-operations.js - 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 executionwaitFor()- waits for target DOM elements before mounting the experimenttrackAAEvent()- 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
- Configuration - reference for all config files
- Test Structure - generated file tree explained
- Getting Started - run the generator yourself