</>

Technology

Cucumber

Difficulty

Advanced

Interview Question

You have a Cucumber BDD suite that needs to run against both a web browser and a REST API for the same business flow. How do you structure this?

Answer

Dual-Channel BDD: Web UI + REST API from Same Feature File

The Business Need

CODE
Same business rule verified two ways:
  "A user with ADMIN role can access the user management module"

  Channel 1 (UI):  Navigate to /admin/users → page loads
  Channel 2 (API): GET /api/admin/users with ADMIN token → 200 OK
  Channel 3 (API): GET /api/admin/users with USER token → 403 Forbidden

Running both channels from same Gherkin = consistency testing

Feature File — Channel-Tagged

Gherkin
@regression
Feature: Role-Based Access Control

  @smoke @ui-channel
  Scenario: Admin can access user management via UI
    Given I am authenticated as role "ADMIN"
    When I navigate to the user management section
    Then I should have full access to the section

  @smoke @api-channel
  Scenario: Admin can access user management via API
    Given I am authenticated as role "ADMIN"
    When I request access to "user-management" via API
    Then the API should respond with status 200
    And the response should contain a list of users

  @regression @api-channel
  Scenario: Standard user cannot access admin APIs
    Given I am authenticated as role "USER"
    When I request access to "user-management" via API
    Then the API should respond with status 403
    And the response error should say "Access Denied"

Execution Context — Strategy Pattern

Java
// Channel interface
public interface ExecutionChannel {
    void navigateTo(String section);
    int checkAccess(String section);
    String getResponseBody();
}

// UI Channel Implementation
public class UIChannel implements ExecutionChannel {
    private final WebDriver driver;
    public UIChannel(WebDriver driver) { this.driver = driver; }

    @Override
    public void navigateTo(String section) {
        Map<String, String> urls = Map.of(
            "user-management", "/admin/users",
            "product-management", "/admin/products"
        );
        driver.get(ConfigReader.getBaseUrl() + urls.get(section));
    }

    @Override
    public int checkAccess(String section) {
        try {
            navigateTo(section);
            return new WebDriverWait(driver, Duration.ofSeconds(10))
                .until(d -> d.findElement(By.css("[data-page='" + section + "']")))
                != null ? 200 : 403;
        } catch (Exception e) { return 403; }
    }
}

// API Channel Implementation
public class APIChannel implements ExecutionChannel {
    private Response lastResponse;
    private String authToken;

    public void setToken(String token) { this.authToken = token; }

    @Override
    public void navigateTo(String section) {
        // No-op for API channel — navigation = API call
    }

    @Override
    public int checkAccess(String section) {
        Map<String, String> endpoints = Map.of(
            "user-management", "/api/admin/users",
            "product-management", "/api/admin/products"
        );
        lastResponse = given()
            .header("Authorization", "Bearer " + authToken)
            .get(ConfigReader.getApiUrl() + endpoints.get(section));
        return lastResponse.statusCode();
    }

    @Override
    public String getResponseBody() {
        return lastResponse.getBody().asString();
    }
}

ScenarioContext — Channel Aware

Java
public class ScenarioContext {
    public WebDriver driver;
    public ExecutionChannel channel;
    public String authToken;
    public int lastStatusCode;
    public SoftAssert softAssert = new SoftAssert();
}

Shared Step Definitions — Channel Agnostic

Java
public class AccessControlSteps {
    private final ScenarioContext ctx;

    @Before("@ui-channel")
    public void initUIChannel() {
        ctx.driver = DriverFactory.createDriver("chrome", true);
        ctx.channel = new UIChannel(ctx.driver);
    }

    @Before("@api-channel")
    public void initAPIChannel() {
        ctx.channel = new APIChannel();
    }

    @Given("I am authenticated as role {string}")
    public void authenticateAs(String role) {
        ctx.authToken = ApiAuth.getTokenForRole(role);
        if (ctx.channel instanceof APIChannel) {
            ((APIChannel) ctx.channel).setToken(ctx.authToken);
        } else {
            // UI: inject session cookie
            ctx.driver.get(ConfigReader.getBaseUrl());
            ctx.driver.manage().addCookie(
                new Cookie("auth_token", ctx.authToken));
        }
    }

    @When("I request access to {string} via API")
    public void requestViaApi(String section) {
        ctx.lastStatusCode = ctx.channel.checkAccess(section);
    }

    @Then("the API should respond with status {int}")
    public void verifyStatus(int expected) {
        ctx.softAssert.assertEquals(ctx.lastStatusCode, expected,
            "Wrong status code for section access");
    }

    @Then("I should have full access to the section")
    public void verifyUIAccess() {
        ctx.softAssert.assertFalse(
            ctx.driver.getPageSource().contains("403") ||
            ctx.driver.getPageSource().contains("Access Denied"),
            "User does not have UI access");
    }
}

Benefits of This Approach

CODE
1. Single Gherkin source of truth for both channels
2. API tests verify backend behavior
3. UI tests verify what users actually see
4. Inconsistency detected: API returns 200 but UI shows error → real bug
5. API tests run 10x faster → quick smoke before UI suite
6. Feature parity: any API endpoint that works must also work in UI

Follow AutomateQA

Related Topics