Skip to content
Testing

12 QA Automation Patterns Interview Questions and Answers

This focused guide turns RecallDeck’s curated QA Automation Patterns material into 12 interview-ready questions. Answer each one before opening the explanation, then use the examples and edge cases to repair anything vague or incomplete.

11 min read12 detailed answersReviewed Aug 24, 2026
What to remember

Describe the risk first, then choose the smallest test layer that provides useful evidence. A tool name without an oracle or failure model is not a test strategy.

Question set

12 detailed answers

01

What is the Page Object Model and what problem does it solve?

Short answer: POM is a pattern where each page or screen of the app is described by its own class that encapsulates the locators and the actions on them. Tests call the class methods, not raw selectors, so when the markup changes a locator is fixed in one place instead of across a hundred tests.

In depth:

  1. Locator encapsulation — selectors live inside the page object; the test never sees them.
  2. Readability — the test is expressed in business terms (loginPage.login(user, pass)), not CSS/XPath.
  3. PageFactory — on the Java stack, @FindBy + lazy element initialization via PageFactory.initElements.
  4. A Steps layer on top of POM — business actions combining several pages; the test becomes a scenario.
┌──────────────┐
│    Test      │  scenario: what we verify
└──────┬───────┘

┌──────────────┐
│    Steps     │  business actions (login, checkout)
└──────┬───────┘

┌──────────────┐
│ Page Objects │  page locators + actions
└──────┬───────┘

┌──────────────┐
│  WebDriver   │  browser
└──────────────┘

⚠️ Common mistake: explaining POM as "it's the convention / it's trendy" without the why. The value is a single point of change when the UI shifts and the separation of locators from test logic.

02

What is data-driven testing and how do you implement it?

Short answer: DDT is an approach where the same test logic runs against multiple sets of input data pulled out of the test. The test data is separated from the test logic: adding a case means adding a data row, not copying the test.

In depth:

  1. pytest@pytest.mark.parametrize with a list of sets.
  2. TestNG@DataProvider returning Object[][].
  3. External sources — CSV/JSON/Excel or a DB: data is edited without touching code.
  4. Why — covering boundary values and equivalence classes without duplicating tests.
import pytest

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

⚠️ Common mistake: treating DDT as "just a loop inside the test." In a loop the first failure hides the remaining cases and the report shows one test; parametrization gives a separate case per data set.

03

What are pytest fixtures and what scopes do they have?

Short answer: A fixture is reusable setup/teardown that pytest injects into a test via dependency injection (by argument name). scope controls how often the fixture is recreated: function, class, module, package, session.

In depth:

  1. DI by name — the test declares def test_x(db): and pytest builds db for it.
  2. Teardown via yield — code after yield runs when the fixture is torn down.
  3. Scope — from the narrowest (function) to the widest (session); the wider it is, the longer the object lives.
import pytest

@pytest.fixture(scope="session")
def db():
    conn = connect()      # setup: once per run
    yield conn
    conn.close()          # teardown
scope Recreated
function per test (default)
class once per class
module once per file
package once per package
session once per whole run

⚠️ Common mistake: a session/module fixture handing out a mutable object (a list, dict, logged-in client). One test mutates the state, it leaks into the next, and tests start depending on run order.

04

How does @pytest.mark.parametrize differ from fixture parametrization via params?

Short answer: @pytest.mark.parametrize multiplies one specific test across data sets. A fixture with params=[...] multiplies every test that uses that fixture. The difference is the area of effect: the marker is local to the test, a parametrized fixture affects all of its consumers.

In depth:

  1. parametrize — the data is bound to a single test function and touches nothing around it.
  2. fixture params — the fixture itself becomes multi-variant; any test that depends on it runs for each variant.
  3. When to use which — parametrize for a specific test's inputs; a parametrized fixture for the environment (e.g. run the whole suite across several browsers).
# parametrize: multiplies ONLY test_add
@pytest.mark.parametrize("n", [1, 2, 3])
def test_add(n):
    ...

# fixture params: multiplies EVERY test that takes browser
@pytest.fixture(params=["chrome", "firefox"])
def browser(request):
    return start(request.param)

⚠️ Common mistake: confusing the scopes — putting something one test needs into a parametrized fixture and accidentally multiplying the whole module 2-3x.

05

What is the difference between assert and verify (soft assert)?

Short answer: A regular assert stops the test at the very first mismatch. A soft assert (verify) accumulates mismatches and reports them all at the end — handy when you need to check several independent fields of one form in a single run.

In depth:

  1. Hard assert — fails immediately, the remaining checks don't run (fail-fast).
  2. Soft assert — checks accumulate, the test fails at the end with the full list; in TestNG it's SoftAssert + a mandatory assertAll().
  3. When to go soft — independent checks (validating all form fields): one run gives the whole picture at once.
Hard assert Soft assert
On mismatch stops immediately keeps going
Report first error all errors
When dependent steps independent checks

⚠️ Common mistake: in TestNG, forgetting to call assertAll() — without it SoftAssert won't fail the test, all accumulated failures are silently lost, and a green run hides bugs.

06

What is the difference between a stub and a mock?

Short answer: A stub returns pre-canned responses and verifies nothing — it's a data substitute. A mock additionally verifies interactions: which methods were called, how many times, and with what arguments — it's substitute + behavior check.

In depth:

  1. Stub — state verification: feed a response, check the final result.
  2. Mock — behavior verification: check the fact and shape of the call (assert_called_once_with).
  3. Relatives — fake (a working simplified implementation, e.g. an in-memory DB) and spy (a wrapper that records calls).
from unittest.mock import Mock

stub = Mock(); stub.get_rate.return_value = 1.1   # stub: response only
rate = converter.convert(100, stub)

mock = Mock()                                     # mock: verify the call
notifier.notify(mock, "hi")
mock.send.assert_called_once_with("hi")
Stub Mock
Purpose provide data verify the call
Verifies result (state) interaction (behavior)
Test fails on result assert wrong call

⚠️ Common mistake: using "mock" and "stub" interchangeably. In an interview it matters that you show a mock verifies interaction while a stub only hands back data.

07

How are automated tests wired into a CI/CD pipeline? What happens when a test fails?

Short answer: Automated tests hang off a trigger (push/PR) and run in stages from fast to slow: unit → API/smoke → regression. The report (Allure) is published as an artifact. A red critical test is a gate: the merge or deploy is blocked.

In depth:

  1. Trigger — push, pull request, scheduled (nightly), or a manual run.
  2. Stages by cost — fast unit first, then API/smoke, heavy UI regression last; a failure on a cheap stage saves time.
  3. Reporting — Allure or JUnit XML as a run artifact, linked right in the PR.
  4. Reacting to a failure — a critical test red → pipeline red → gate on merge; known flakes go to quarantine rather than blocking everything.
push / PR


[ unit ] → [ API / smoke ] → [ UI-regression ]
   │             │                  │
   └── fail ─────┴────── fail ──────┘

     red pipeline → merge blocked

         Allure report (artifact)

⚠️ Common mistake: answering vaguely with "we run tests in CI" with no stages and no reaction to failures. The interviewer is waiting for a merge gate and a coherent flake strategy.

08

What is Allure Report and why is it useful?

Short answer: Allure is a reporting framework that turns run results into a readable HTML report with steps, attachments, and a history trend. It's published as a CI artifact so the team sees not just "it failed" but exactly where.

In depth:

  1. @Step — breaks a test into human-readable steps.
  2. @Attachment — attaches screenshots, logs, request/response bodies to the failed step.
  3. History trend — pass/fail dynamics across runs, exposing flakes and degradation.
  4. Categories and severity — grouping of defects and importance labels (critical/minor).
Annotation / feature Purpose
@Step steps inside a test
@Attachment screenshots / logs / HAR
@Severity case importance
@Epic / @Feature / @Story structure by functionality
history trend pass/fail across runs

⚠️ Common mistake: generating Allure only locally and viewing it alone. The value is publishing it as a CI artifact linked in the PR with accumulated run history.

09

Why use Docker in test automation infrastructure?

Short answer: Docker gives reproducible, isolated environments for tests: identical browser and driver versions, a clean disposable DB, Selenium Grid in containers. It kills "works on my machine" and a chunk of the flakes caused by environment differences.

In depth:

  1. Identical versions — the image pins the browser + driver; locally and in CI it's the same.
  2. Clean state — a disposable DB container comes up empty on every run, so tests don't inherit junk from previous ones.
  3. Selenium Grid — hub and browser nodes in containers, easy to scale parallelism.
  4. Isolation — tests don't pollute the host; docker compose down and it's all cleaned up.
services:
  chrome:
    image: selenium/standalone-chrome:148.0
    shm_size: 2gb
    ports: ["4444:4444"]
  tests:
    build: .
    depends_on: [chrome]
    environment:
      REMOTE_URL: http://chrome:4444/wd/hub

⚠️ Common mistake: forgetting shm_size for the Chrome container — the browser crashes with "session deleted / tab crashed" because /dev/shm is too small.

10

What is contract testing (Pact) and what does it replace in microservices?

Short answer: Contract testing verifies service compatibility against a contract rather than through a live cross-service environment. In the consumer-driven approach (Pact) the consumer describes the expected request and response, the provider is obliged to fulfill that contract; the checks run in CI on both sides and catch breaking changes without standing up all the services.

In depth:

  1. Consumer-driven — the consumer generates a pact file from its expectations of the API.
  2. Provider verification — the provider replays the contract against itself in its own CI.
  3. Pact Broker — stores contracts and versions; can-i-deploy allows or blocks a deploy.
  4. What it replaces — heavy end-to-end tests between services: faster, more stable, and it catches API drift early.
┌──────────┐  expectations ┌──────────┐  verify     ┌──────────┐
│ Consumer │ ────────────► │ Contract │ ◄────────── │ Provider │
│ (client) │  pact.json    │  (Pact)  │ verify in CI│ (service)│
└──────────┘               └────┬─────┘             └──────────┘

                          Pact Broker
                      can-i-deploy? → gate

⚠️ Common mistake: treating contract testing as a replacement for all tests. It verifies the shape and compatibility of the API, not the business logic inside the service — unit and functional tests aren't going anywhere.

11

How do you run tests in parallel, and why do you need ThreadLocal<WebDriver>?

Short answer: Parallelism is enabled at the runner level: in TestNG the parallel and thread-count attributes in the suite XML, in pytest the pytest-xdist plugin (-n auto). ThreadLocal<WebDriver> is needed so each thread has its own driver instance: a shared static driver gets clobbered by threads and tests fail intermittently.

In depth:

  1. TestNG<suite parallel="methods" thread-count="4">.
  2. pytestpytest -n auto (xdist) distributes tests across workers.
  3. Why ThreadLocal — it isolates the driver per thread; thread A doesn't open a URL in thread B's browser.
  4. Teardowndriver.quit() and remove() from ThreadLocal, otherwise memory leaks and browsers hang around.
public class DriverFactory {
  private static final ThreadLocal<WebDriver> TL = new ThreadLocal<>();

  public static WebDriver get() {
    if (TL.get() == null) TL.set(new ChromeDriver());
    return TL.get();                 // own driver per thread
  }
  public static void quit() {
    TL.get().quit();
    TL.remove();                     // else it leaks with a thread pool
  }
}

⚠️ Common mistake: keeping one static WebDriver for all threads. Under parallelism threads fight over a single browser — random failures you can never reproduce locally on a single thread.

12

Design a test automation framework from scratch: what layers do you create?

Short answer: The framework breaks into layers with a single responsibility each: config and driver factory → page objects → steps/business actions → tests, with test data and reporting as separate layers and CI integration. The key is SOLID: the page doesn't know about the test (SRP), the driver is passed through the constructor (DI) rather than pulled from a global.

In depth:

  1. Config + driver factory — environments, URLs, ThreadLocal driver.
  2. Page Objects — page locators and actions.
  3. Steps / business actions — scenario steps on top of pages.
  4. Tests — only "what we verify" and the asserts.
  5. Test data — a separate layer (factories/builders, external files).
  6. Reporting + CI — Allure and running in the pipeline.
┌──────────────────────────┐
│ CI / runner (pipeline)   │
├──────────────────────────┤
│ Reporting (Allure)       │
├──────────────────────────┤
│ Tests (assert, "what")   │
├──────────────────────────┤
│ Steps / business actions │
├──────────────────────────┤
│ Page Objects (locators)  │
├──────────────────────────┤
│ Config + DriverFactory   │
└──────────────────────────┘
   Test data — cross-cutting layer

⚠️ Common mistake: a monolithic test class with locators, driver, and asserts all in one heap. The markup changes and you fix dozens of tests; it violates SRP and kills maintainability.

Source notes

References and review policy

RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going

RecallDeck Interview Library

Detailed answers from the same curated interview deck, organized for search, study, and durable recall.

RSS