advanced20 min

Repo Structure, Configuration Management, and Secret Handling

Organizing a framework's folders so its layers stay honest, designing configuration that adapts across environments without code changes, and handling secrets in test configuration safely.

What you'll learn

  • Explain why a framework's folder structure should reflect its architectural layers, not just group files by file type
  • Design an environment-aware configuration strategy that avoids hardcoding a single target environment
  • Explain why real secrets must never appear in a test framework's source or version-controlled config

Prerequisites

Explanation

No real environment, secret, or CI system is used by this lesson's exercises -- they model configuration decisions as data, using genuine JavaScript/TypeScript execution.

A framework's folder structure is itself an architectural decision, not just organizational tidiness — structuring folders around what a file's layer/role is (config/, pages/, fixtures/, data/, services/) rather than only its file type keeps the layering from Lesson 1 honest and visible: a new contributor can tell, just from where a file lives, roughly what it's allowed to depend on and what depends on it. A flat folder of hundreds of similarly named .spec.ts files with no deeper structure makes this layering invisible, and invisible architecture tends to erode over time as more people touch the codebase without a clear convention to follow.

Environment-aware configuration means the exact same test suite can run against a local dev server, a staging environment, or (carefully, deliberately) production-adjacent environments, without editing code — only by changing which configuration values are supplied, typically via environment variables read at startup. This matters because a framework that hardcodes a single base URL or set of credentials directly in source code cannot be safely or easily pointed at a different environment, and worse, actively invites exactly the secret-handling problem below if those hardcoded values happen to be real credentials.

Secrets never belong in source code or version-controlled configuration files — not because of an abstract rule, but because a git repository's history is effectively permanent and often far more widely readable than the live system the secret protects: a committed secret remains recoverable from history even after being "removed" in a later commit, and a public or semi-public repository can expose it to far more people than were ever meant to have it. The correct pattern for test configuration mirrors general application secret handling: secrets are supplied to the test run via environment variables (typically injected by the CI system from a secrets manager, never checked into files) or a git-ignored local .env file for individual local development — and test code and configuration are written to consume them at runtime, never to contain a real secret value directly.

Example

Modeling why layered folder structure matters and a safe vs. unsafe way to obtain a config value, as data.

function layerFromPath(filePath) {
  // Models inferring a file's architectural layer from where it lives, not just its extension.
  if (filePath.startsWith("src/pages/")) return "page-object";
  if (filePath.startsWith("src/fixtures/")) return "fixture";
  if (filePath.startsWith("src/data/")) return "test-data";
  if (filePath.startsWith("src/config/")) return "config";
  return "unknown";
}
console.log(layerFromPath("src/pages/login-page.ts"));   // "page-object" -- clear from location alone
console.log(layerFromPath("tests/random-file-42.ts"));   // "unknown" -- a flat, undifferentiated structure gives no such signal

function isSafeConfigSource(source) {
  // Models the safe-vs-unsafe secret-sourcing distinction.
  const safeSources = ["environment-variable", "gitignored-local-env-file", "ci-secrets-manager"];
  return safeSources.includes(source);
}
console.log(isSafeConfigSource("environment-variable"));       // true
console.log(isSafeConfigSource("hardcoded-in-source-file"));   // false -- a real, committed secret

Try it yourself

Call isSafeConfigSource with 'committed-config-json', and confirm a secret baked into a version-controlled JSON file is correctly rejected as unsafe.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

This models inferring an architectural layer from a file path only -- no real project is scanned. Write layerFromPath(filePath): 'src/pages/' -> 'page-object', 'src/fixtures/' -> 'fixture', 'src/data/' -> 'test-data', 'src/config/' -> 'config', anything else -> 'unknown'. Use startsWith checks.

Checks: correctly identifies a page-object file by its folder · correctly identifies a config file by its folder · correctly falls back to unknown for an unrecognized location

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

This models resolving a config value with an environment-variable-first, safe-default-fallback strategy only -- no real environment variable is read. Write resolveBaseUrl(envVars, environment): if envVars.BASE_URL is set, return it. Else return a built-in default based on environment: 'staging' -> 'https://staging.example.test', anything else -> 'http://localhost:3000'.

Checks: correctly prioritizes an explicit override over any default · correctly falls back to a staging-specific default · correctly falls back to a local-specific default

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Organizing test files only by file type (all .spec.ts files in one flat folder) instead of by architectural layer -- this makes the layering decisions from Lesson 1 invisible and easy to erode over time.
  • Hardcoding a single base URL or credential set directly in source code -- this makes the suite unable to safely target a different environment, and risks accidentally committing a real secret.
  • Committing a secret to version control and assuming deleting it in a later commit removes it -- git history is effectively permanent; a committed secret should be treated as compromised and rotated, not just 'removed.'

Knowledge check

Knowledge check

1. Why structure a framework's folders around architectural LAYER (config/, pages/, fixtures/, data/) rather than only by file type?
2. What does environment-aware configuration allow a test suite to do?
3. Why is a secret committed to version control considered compromised, even if it's removed in a later commit?

Takeaway

Organize a framework's folders around architectural layer, not just file type, to keep its design visible and durable. Design configuration to read from the environment with sensible defaults, so the same suite can target multiple environments without code changes. Never commit a real secret -- source it from environment variables, a git-ignored local file, or a CI secrets manager, and rotate anything that was ever committed.

Summary

A layer-based folder structure (config/, pages/, fixtures/, data/) keeps a framework's architecture visible and resistant to erosion, unlike a flat, file-type-only structure. Environment-aware configuration, read from environment variables with sensible defaults, lets the same test suite target multiple environments without editing code. Secrets must never appear in source or version-controlled config -- they belong in environment variables, a git-ignored local file, or a CI secrets manager, and a committed secret should be treated as compromised and rotated.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.