Environment-Based Execution in Cucumber Automation

What Is Environment-Based Execution?

Environment-based execution is the ability of an automation framework to run the same test suite against different application environments without changing test code. A mature Cucumber framework should be able to execute against development, QA, UAT, staging, and production-like environments by changing configuration, not by editing feature files, step definitions, page objects, API services, or runner classes.

In practical automation projects, the application usually exists in more than one place. Developers may deploy early builds to a development environment. Testers may validate features in QA. Business users may verify acceptance behavior in UAT. Release teams may perform final checks in staging. Sometimes limited smoke checks are allowed against production. Each environment may have a different application URL, API base URL, database connection, user credentials, feature flags, browser needs, timeout behavior, and test data rules.

If those values are hardcoded inside the framework, every environment switch becomes a code change. That is slow, risky, and difficult to maintain. Environment-based execution solves this by separating environment-specific values from automation logic. The tests describe behavior. Configuration describes where and how the behavior should be executed.

Why Environment-Based Execution Is Important

Without environment-based execution, teams often duplicate code or modify existing code whenever they need to run automation in another environment. One tester may change the URL from QA to UAT in a Java class. Another tester may change credentials in a properties file but forget to revert it. A CI/CD job may run against the wrong environment because a value was committed accidentally. These mistakes waste time and can create serious release risk.

Change URL
  -> Modify Code
  -> Rebuild
  -> Execute
  -> Risk of Wrong Commit

With environment-based execution, the user selects an environment at runtime. The framework loads the correct configuration, initializes browser and API settings, runs the same tests, and generates reports that clearly identify the target environment. No source code changes are required.

Select Environment
  -> Load Configuration
  -> Initialize Framework
  -> Execute Tests
  -> Generate Environment-Specific Reports

This approach improves maintainability because configuration changes are centralized. It improves scalability because the same framework can support many environments. It improves security because credentials can be managed outside code. It improves CI/CD integration because Jenkins, GitHub Actions, Azure DevOps, or GitLab CI can pass the environment as a parameter.

Common Software Environments

Most software delivery pipelines use multiple environments. The names may differ by organization, but the purpose is usually similar. Development is used by developers while implementation is in progress. QA is used by testers for functional, regression, and automation validation. UAT is used by business users or product stakeholders for acceptance testing. Staging is used as a production-like environment before release. Production is the live environment used by real users.

Software Environments
  |-- Development
  |-- QA
  |-- UAT
  |-- Staging
  |-- Production

Automation is most commonly executed against DEV, QA, UAT, and staging. Production automation must be handled carefully. If production smoke checks exist, they should be read-only or designed with strict controls. Tests should not create fake orders, modify real accounts, or perform destructive operations in production unless the business has intentionally built safe test paths.

Purpose of Each Environment

Each environment serves a different purpose. Development environments are often unstable because developers are actively changing code. QA environments are usually more controlled and are the main place for automation runs. UAT environments allow business users to validate workflows before release. Staging environments should closely match production configuration so final release checks are realistic. Production environments serve real customers and must be protected from test side effects.

EnvironmentPurpose
DevelopmentUsed by developers during implementation and early integration
QAUsed for functional testing, automation, regression, and defect verification
UATUsed for business or user acceptance testing
StagingUsed for production-like validation before release
ProductionLive system used by real end users

The same Cucumber scenario can be useful across these environments, but the execution rules may differ. A login smoke scenario may run in QA, UAT, staging, and production. A scenario that creates test data may run in QA and UAT but not production. A database validation scenario may be allowed in QA but restricted in staging. Environment-based execution should support these differences through configuration and tagging strategy.

Environment-Based Execution Flow

The execution flow starts when a user or CI/CD tool selects the target environment. The framework reads that value, loads the corresponding configuration, initializes required services, opens the right application URL, points REST Assured to the correct API endpoint, prepares data, executes Cucumber scenarios, and writes reports that include the environment name.

Execution Starts
  -> Select Environment
  -> Read Configuration
  -> Initialize Framework
  -> Execute Tests
  -> Generate Reports

This flow keeps test code independent of deployment details. Step definitions should not contain several environment-specific conditions. Page objects should not know whether the run is against QA or UAT. API client classes should receive the base URL from configuration. The framework's configuration layer should provide values to the rest of the system in a clean and predictable way.

Configuration Architecture

A clean architecture usually includes a configuration manager. The configuration manager reads the selected environment, loads the correct file or configuration source, validates required keys, and exposes values through a simple API. Other framework layers ask the configuration manager for values such as base URL, API URL, browser, timeout, username, password key, grid URL, and report settings.

Framework
  -> Configuration Manager
  -> Environment File
  -> Properties
  -> Framework Uses Values

This architecture prevents configuration logic from spreading everywhere. Without a configuration manager, teams often write file-reading logic inside driver factories, API clients, hooks, step definitions, and utility classes. That duplication makes the framework harder to debug. Centralizing configuration keeps environment handling consistent.

Configuration Files

One common approach is to keep one configuration file per environment. A project may have dev.properties, qa.properties, uat.properties, stage.properties, and prod.properties. Each file contains values specific to that environment. The framework chooses the right file based on a runtime parameter.

config
  |-- dev.properties
  |-- qa.properties
  |-- uat.properties
  |-- stage.properties
  |-- prod.properties

Separate files make it easy to understand environment-specific values. However, teams should avoid duplicating every value when many values are shared. A base configuration plus environment overrides can work well in larger systems. The exact structure matters less than the principle: environment data should be externalized, organized, and loaded consistently.

Example Configuration

A QA configuration file may contain application URL, API base URL, browser, timeout, and test user identifiers. Sensitive values may be referenced indirectly rather than stored directly. For example, the file may say which credential key to read from a secret store, while the actual password remains outside the repository.

baseUrl=https://qa.example.com
apiBaseUrl=https://api.qa.example.com
browser=chrome
explicitWait=20
pageLoadTimeout=60
testUserKey=qa.standard.user

The framework reads these values during startup. Selenium uses the configured base URL and browser. REST Assured uses the API base URL. Wait utilities use the timeout values. Authentication helpers resolve the correct credential. Reports include the environment name so failures are easier to analyze.

Configuration Manager

A configuration manager hides file loading and property lookup from the rest of the framework. Instead of every class opening property files, the framework can call a central method such as ConfigManager.get("baseUrl"). The configuration manager can also provide typed methods such as getInt, getBoolean, or getEnvironment to avoid repeated parsing logic.

ConfigManager.get("baseUrl");
ConfigManager.get("apiBaseUrl");
ConfigManager.getInt("explicitWait");

A good configuration manager also validates required keys early. If baseUrl is missing, the framework should fail fast with a clear message rather than failing later with a confusing browser or API error. Early validation saves debugging time and prevents incomplete configuration from producing misleading test failures.

Runtime Environment Selection

Runtime environment selection means choosing the environment when the test starts. With Maven, this is commonly done through a system property. A user may run mvn test -Denvironment=qa or mvn test -Denvironment=uat. The framework reads the environment property and loads the matching configuration file.

mvn test -Denvironment=qa
mvn test -Denvironment=uat

This pattern works well locally and in CI/CD. A developer can run a quick smoke test against DEV. A tester can run regression against QA. Jenkins can run nightly tests against UAT. GitHub Actions can allow manual workflow inputs for environment selection. The framework remains the same in every case.

Browser Configuration

Browser choice should also come from configuration. Hardcoding new ChromeDriver() everywhere limits flexibility. A better design uses a driver factory that reads the browser value and creates the correct WebDriver instance. This allows Chrome, Firefox, Edge, headless mode, Selenium Grid, or cloud execution without changing test logic.

Read Browser
  -> Create Driver
  -> Start Scenario

Browser configuration may differ by environment. QA may run Chrome locally. Staging may run through Selenium Grid. Release validation may run across Chrome, Firefox, and Edge. Headless execution may be used in CI. These differences should be controlled through configuration and pipeline parameters.

URL Configuration

Application URLs should never be hardcoded inside step definitions or page objects. A line such as driver.get("https://qa.example.com") makes the framework tied to QA. Instead, the framework should read baseUrl from configuration and use that value when opening the application.

Read baseUrl
  -> Open Application
  -> Execute Scenario

This keeps scenarios portable. The same login scenario can run against QA, UAT, or staging. Reports should show which environment was used so a failure can be traced correctly. A defect in UAT may not exist in QA if deployments differ, so environment visibility matters.

API Endpoint Configuration

For REST Assured automation, API endpoints must be configurable. A framework may have apiBaseUrl for each environment. API service classes should build requests using this base URL rather than hardcoded endpoints. This is especially important when UI and API tests run together in the same Cucumber framework.

apiBaseUrl=https://api.qa.example.com

Different environments may expose different API domains, gateway paths, authentication services, rate limits, feature flags, and data states. Centralized endpoint configuration helps API tests remain reusable while still targeting the correct environment.

Credential Management

Credentials are environment-specific and sensitive. Usernames, passwords, tokens, API keys, database credentials, and cloud access keys should not be hardcoded in source code. For local execution, teams may use protected local configuration. For CI/CD execution, credentials should come from Jenkins credentials, GitHub Secrets, Azure Key Vault, HashiCorp Vault, AWS Secrets Manager, or another secure mechanism.

Feature files should not contain real passwords. Reports should not print tokens or credentials. Logs should avoid exposing authorization headers. A secure automation framework treats credential handling as part of design, not as an afterthought. Environment-based execution should make it easy to swap credentials safely when the target environment changes.

Database Configuration

Some automation frameworks use database utilities to prepare test data, verify records, or clean up after scenarios. Database configuration may include JDBC URL, username, password, schema, connection timeout, and read-only or write permissions. These values are strongly environment-specific.

dbUrl=jdbc:postgresql://qa-db.example.com:5432/app
dbUser=qa_reader
dbPasswordKey=qa.db.password

Database access should be used carefully. Direct database validation can be useful, but it can also make tests tightly coupled to implementation details. If database access is needed, configuration should be isolated and secure. Production database access from automation should be heavily restricted or avoided unless there is a controlled operational reason.

Timeout Configuration

Timeout values may differ across environments. A local or QA environment may respond quickly. UAT may be slower because of shared resources. Staging may have production-like caching and infrastructure. If all timeouts are hardcoded, tests may fail unnecessarily in slower environments or wait too long in faster environments.

implicitWait=0
explicitWait=20
pageLoadTimeout=60
apiTimeout=30

Timeouts should be configured thoughtfully. Increasing every timeout is not a proper fix for flaky tests. Use explicit waits for UI synchronization, sensible API timeouts, and clear failure messages. Environment-specific timeout configuration should support reality without hiding performance problems.

Environment Variables

Environment variables are commonly used in CI/CD platforms. Jenkins, GitHub Actions, GitLab CI, Azure DevOps, and other tools can pass values such as environment name, browser, tag expression, API URL, grid URL, and credential keys through environment variables. The framework can read these values at runtime.

Pipeline
  -> Environment Variable
  -> Framework
  -> Configuration

Environment variables are useful because they avoid editing files for every run. However, they should be documented clearly. If a workflow depends on TEST_ENV, BROWSER, and CUCUMBER_TAGS, new team members should know what those variables mean and what values are allowed.

Jenkins Integration

Jenkins supports environment selection through build parameters. A job may provide a dropdown for QA, UAT, stage, or production-like execution. When the user starts the build, Jenkins passes the selected value to Maven or Gradle. The framework reads that value and loads the correct environment configuration.

Build Parameter
  -> Environment=QA
  -> Maven Command
  -> QA Properties Loaded

A Jenkins command might look like mvn clean test -Denvironment=qa -Dbrowser=chrome. This keeps the job flexible. The same Jenkins job can run different environments without separate source code branches or manual edits.

GitHub Actions Integration

GitHub Actions can pass environment values through workflow inputs, repository variables, environment variables, and secrets. A manual workflow can ask the user to choose QA, UAT, or stage. A scheduled workflow can default to QA regression. A release workflow can use staging. The selected value is passed into the Maven or Gradle command.

Workflow Input
  -> Environment=QA
  -> Maven Command
  -> Framework Loads QA Configuration

This approach works well for teams using GitHub repositories. It keeps execution configuration close to the code while still protecting sensitive values through GitHub Secrets and environment protection rules.

Multiple Environment Flow

In some organizations, the same suite may run against multiple environments. A smoke suite may run against QA after every deployment, UAT before business testing, and staging before release. The scenario text remains the same, but configuration changes for each run.

Framework
  -> DEV / QA / UAT / STAGE
  -> Same Tests
  -> Different Configurations

Multiple environment execution helps compare behavior across deployment stages. If a scenario passes in QA but fails in UAT, the issue may be deployment difference, data difference, configuration difference, or environment instability. Reports should include environment details to support this analysis.

Environment Validation

Before executing a large suite, the framework or pipeline should validate that the target environment is available. This may include checking whether the application URL is reachable, API health endpoint responds, database is available, required services are running, test accounts exist, and feature flags are in the expected state.

Failing fast is better than running hundreds of scenarios against a broken environment. If the environment is down, the pipeline should report an environment failure clearly. This prevents misleading test failures and saves execution time.

Parallel Multi-Environment Execution

Large organizations may execute the same suite across multiple environments simultaneously. For example, one CI/CD pipeline may run smoke tests against QA, UAT, and staging in parallel. This can provide fast confidence across environments, but it requires careful infrastructure and data isolation.

Thread 1 -> QA
Thread 2 -> UAT
Thread 3 -> Stage

Parallel multi-environment execution should use separate configuration contexts. One run should not overwrite another run's environment values. Reports and artifacts should be named by environment so results do not mix. Test data should be environment-specific and isolated.

Hardcoded URL Mistake

Hardcoded URLs are one of the most common environment execution mistakes. They may appear in hooks, page objects, API clients, step definitions, or utility classes. At first, hardcoding feels convenient. Later, it becomes painful because every environment switch requires code changes.

The fix is simple: all URLs should come from configuration. The framework should have one clear way to retrieve the application base URL and API base URL. Code should use those values consistently.

Hardcoded Credential Mistake

Hardcoded credentials are more dangerous than hardcoded URLs because they create security risk. Passwords, tokens, API keys, and database credentials should never be committed to the repository. Even test credentials can be misused if exposed.

Use secret-management solutions. For CI/CD, use Jenkins credentials, GitHub Secrets, GitLab variables, Azure variable groups, or a dedicated vault. For local execution, use ignored local files or secure developer setup. Keep real secrets out of source control and reports.

Single Large Configuration File Mistake

Some teams store every environment in one huge file. This can work for small projects, but it often becomes difficult to read as environments grow. Developers may update the wrong section. Testers may miss duplicated keys. Merge conflicts become more likely.

One file per environment often improves clarity. A base file plus overrides can also work well. The best structure depends on project size, but the configuration should remain easy to read, validate, and maintain.

Mixing Environment Logic Mistake

Another mistake is scattering environment logic throughout the framework. Code such as if(environment.equals("QA")) appears in page objects, step definitions, API clients, and hooks. This makes the framework brittle because every new environment requires many code changes.

Environment logic should be centralized. The configuration manager decides which values apply. Other classes consume values without knowing how they were selected. This separation keeps automation logic cleaner and easier to extend.

Manual Code Change Mistake

Changing source code to switch environments is a sign that the framework is not CI/CD-ready. It increases the chance of accidental commits, broken branches, and inconsistent execution. It also makes it harder for non-technical users to run automation because they must know which file to edit.

Runtime parameters are the better model. A tester, developer, or pipeline should choose the environment from a command line, dropdown, workflow input, or scheduled configuration. The framework should handle the rest.

Best Practices

Externalize all environment-specific values. Use one configuration file per environment or an equivalent centralized configuration strategy. Select environments using runtime parameters. Keep automation code environment-independent. Secure credentials through secret management. Validate environment availability before execution. Support browser and API endpoint configuration externally. Use CI/CD parameters for environment selection. Document supported environments and allowed values.

Also include environment details in reports. A failed scenario report should show the target environment, browser, base URL or masked endpoint, tag expression, build number, and execution time. This context helps teams diagnose whether a failure is related to the application, test script, data, or environment.

Enterprise Architecture

An enterprise environment-based execution architecture separates test logic from configuration. Feature files describe behavior. Step definitions coordinate actions. Page objects and API services interact with the application. Driver factories create browsers. API clients send requests. Configuration managers provide environment-specific values. Secret managers provide sensitive values. CI/CD tools pass runtime inputs. Reports capture the execution context.

Framework
  -> Configuration Manager
  -> Environment Selection
  -> Properties / Secrets
  -> Browser
  -> URL
  -> API Endpoint
  -> Database
  -> Execution
  -> Reports

This architecture supports scale because adding a new environment usually means adding configuration, not rewriting automation logic. If the business creates a new staging environment, the framework can support it by adding a new configuration file and CI/CD option. The scenarios remain unchanged.

Environment-Based vs Hardcoded Execution

Environment-based execution is more maintainable than hardcoded execution because it treats environment details as configuration. Hardcoded execution ties tests to one environment. It may work for a demonstration, but it does not scale well for real projects.

Hardcoded ExecutionEnvironment-Based Execution
URLs are written in codeURLs come from configuration
Browser is fixedBrowser is configurable
Credentials may be exposedCredentials are externalized and secured
Code changes are requiredRuntime configuration changes are enough
Difficult to maintainEasier to maintain and scale
Poor CI/CD fitEnterprise-ready CI/CD fit

The difference becomes obvious as the project grows. Hardcoded execution creates duplicated logic, manual changes, and deployment risk. Environment-based execution creates flexibility and repeatability.

Reporting and Audit Value

Reports should always show which environment was used. This is important because the same scenario can produce different results in QA, UAT, and staging. A failed payment scenario in UAT may be caused by an unavailable payment stub, while the same scenario in QA may pass. Without environment information, the report is incomplete.

For audit and release review, environment details matter. A release manager may need to know whether smoke tests passed in staging, whether regression passed in QA, and whether UAT checks were completed. Environment-based reporting provides evidence that the correct environment was tested.

Handling Environment Drift

Environment drift happens when two environments that are expected to behave similarly slowly become different. QA may have one version of a service, UAT may have another version, staging may have a different feature flag, and production may have a different data setup. When this happens, automation failures become difficult to interpret because the team is no longer sure whether a scenario failed due to a product defect, an environment mismatch, or a configuration problem.

Environment-based execution helps expose drift because the same Cucumber scenarios can run against multiple environments using controlled configuration. If a scenario passes in QA and fails in staging, the report gives the team a clear comparison point. The investigation can then focus on deployment version, service availability, feature flags, database migrations, test accounts, API gateway rules, third-party integrations, and environment-specific data.

Teams should not treat every environment difference as a test failure. Some differences are expected. DEV may be unstable. QA may contain test-only stubs. UAT may contain business acceptance data. Staging may be production-like. The important practice is to document expected differences and make the framework configurable enough to handle them intentionally. Unexpected differences should be visible, investigated, and corrected before they become release surprises.

Test Data Across Environments

Test data is one of the hardest parts of environment-based execution. The same scenario may require a valid user, an active product, an available account, a payment method, a test order, or a specific API state. If QA has the data but UAT does not, the scenario fails even though the application behavior may be correct. This is why environment configuration should include data strategy, not only URLs and browsers.

There are several practical approaches. Some teams use fixed reserved accounts for smoke scenarios. Some generate data through API setup before each scenario. Some use database scripts to prepare known records. Some maintain environment-specific data pools. Some use service virtualization or stubs for external dependencies. The right choice depends on the application, environment access, and risk of side effects.

Whatever approach is chosen, test data should be isolated as much as possible. Parallel execution and repeated CI/CD runs can corrupt shared data if scenarios update the same records. A stable environment-based framework should either create unique data for each run or clearly control shared data usage. Reports should include enough data identifiers to support debugging, while still avoiding sensitive information exposure.

Feature Flags and Environment Differences

Modern applications often use feature flags to enable or disable functionality by environment, user group, release branch, or experiment. This affects automation because a scenario may be valid only when a feature flag is enabled. If the framework ignores feature flags, it may report false failures in environments where a feature is intentionally disabled.

Environment-based execution should account for feature flags through configuration or pre-run validation. A scenario that requires a new checkout flow should run only in environments where that flow is enabled. A smoke suite should avoid unstable experimental features unless the purpose is to validate those features. Tagging can help here. For example, scenarios can be tagged by feature area, release, or risk category, and CI/CD jobs can select the right scenarios for the environment.

The important principle is clarity. If a scenario is skipped because a feature is not enabled in UAT, that should be visible. If a scenario fails because a feature flag is unexpectedly off in staging, that is useful release information. Environment-based execution is not only about loading URLs; it is about understanding the conditions under which behavior should exist.

Release Pipeline Usage

Environment-based execution becomes especially valuable in release pipelines. A common release flow may deploy code to QA, run smoke tests, promote the build to UAT, run acceptance tests, deploy to staging, run production-like validation, and then approve production release. The same Cucumber framework can support each step by receiving a different environment parameter from the pipeline.

Deploy to QA
  -> Run QA Smoke
  -> Promote to UAT
  -> Run UAT Acceptance
  -> Deploy to Staging
  -> Run Release Validation
  -> Approve Production

This creates a repeatable path from development to release. The team can compare reports across environments and confirm that critical behavior continues to work as the build moves forward. If a scenario fails in staging, the release can pause before production users are affected. If the same suite passes across required environments, the release team has stronger evidence for go or no-go decisions.

Designing Environment-Independent Scenarios

Feature files should remain environment-independent. A Gherkin scenario should not say that the user opens the QA URL or logs in with a UAT-specific account. It should describe business behavior such as successful login, order placement, profile update, or API authentication. The environment details belong in configuration and step implementation, not in the scenario language.

This keeps scenarios readable for business users and reusable across environments. If a scenario mentions environment-specific infrastructure, it becomes less like living documentation and more like a technical script. Good Cucumber design separates business intent from execution context. Environment-based execution depends on that separation.

Maintaining Environment Configuration

Configuration files require maintenance. URLs change, credentials rotate, test accounts expire, API gateways are updated, database hosts move, and timeout needs evolve. If configuration is not reviewed, the framework may fail because of stale values rather than real product defects. This is frustrating for teams and weakens trust in automation.

Configuration should be treated as part of the framework. Keep naming consistent. Remove unused keys. Validate required values at startup. Protect secrets. Document supported environments. Review configuration when new environments are added or old ones are retired. In CI/CD, make sure job parameters and workflow inputs match the environment names used by the framework.

A simple naming mismatch can break execution. For example, Jenkins may pass UAT, while the framework expects uat. The configuration manager should either normalize accepted values or fail with a clear error explaining the allowed environment names. Clear failures are much better than silent defaults that accidentally run tests against the wrong environment.

Interview-Ready Summary

Environment-based execution enables the same Cucumber automation framework to run against multiple environments without modifying test code. Environment-specific values such as application URLs, API endpoints, browsers, credentials, database connections, timeout settings, feature flags, and report settings are externalized into configuration files, runtime parameters, environment variables, or secret-management systems.

A configuration manager loads the correct settings based on the selected environment and provides those values to driver factories, API clients, hooks, utilities, and reporting components. CI/CD tools such as Jenkins and GitHub Actions commonly pass environment parameters to Maven or Gradle during execution. This keeps the automation framework flexible, secure, maintainable, and scalable.

The key interview point is that environment-based execution separates configuration from automation logic. Feature files and step definitions should remain environment-independent. Only configuration changes when the framework runs against DEV, QA, UAT, staging, or production-like systems.

Golden Rules

Never hardcode environment-specific values such as URLs, browsers, credentials, API endpoints, or database details. Externalize configuration and select the target environment at runtime. Keep the automation framework environment-independent by separating configuration from test logic. Store sensitive credentials securely using secrets management rather than source code.

Integrate environment selection with CI/CD pipelines to support consistent and repeatable automated execution. Validate environment availability before running large suites. Include environment details in reports. The practical takeaway is simple: one framework should run across many environments through configuration, not code changes.