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
01What is the Page Object Model and what problem does it solve?
middle
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:
- Locator encapsulation — selectors live inside the page object; the test never sees them.
- Readability — the test is expressed in business terms (
loginPage.login(user, pass)), not CSS/XPath. - PageFactory — on the Java stack,
@FindBy+ lazy element initialization viaPageFactory.initElements. - 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.
02What is data-driven testing and how do you implement it?
middle
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:
- pytest —
@pytest.mark.parametrizewith a list of sets. - TestNG —
@DataProviderreturningObject[][]. - External sources — CSV/JSON/Excel or a DB: data is edited without touching code.
- 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.
03What are pytest fixtures and what scopes do they have?
middle
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:
- DI by name — the test declares
def test_x(db):and pytest buildsdbfor it. - Teardown via yield — code after
yieldruns when the fixture is torn down. - 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.
04How does @pytest.mark.parametrize differ from fixture parametrization via params?
middle
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:
- parametrize — the data is bound to a single test function and touches nothing around it.
- fixture params — the fixture itself becomes multi-variant; any test that depends on it runs for each variant.
- 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.
05What is the difference between assert and verify (soft assert)?
junior
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:
- Hard assert — fails immediately, the remaining checks don't run (fail-fast).
- Soft assert — checks accumulate, the test fails at the end with the full list; in TestNG it's
SoftAssert+ a mandatoryassertAll(). - 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.
06What is the difference between a stub and a mock?
middle
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:
- Stub — state verification: feed a response, check the final result.
- Mock — behavior verification: check the fact and shape of the call (
assert_called_once_with). - 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.
07How are automated tests wired into a CI/CD pipeline? What happens when a test fails?
middle
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:
- Trigger — push, pull request, scheduled (nightly), or a manual run.
- Stages by cost — fast unit first, then API/smoke, heavy UI regression last; a failure on a cheap stage saves time.
- Reporting — Allure or JUnit XML as a run artifact, linked right in the PR.
- 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.
08What is Allure Report and why is it useful?
junior
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:
- @Step — breaks a test into human-readable steps.
- @Attachment — attaches screenshots, logs, request/response bodies to the failed step.
- History trend — pass/fail dynamics across runs, exposing flakes and degradation.
- 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.
09Why use Docker in test automation infrastructure?
middle
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:
- Identical versions — the image pins the browser + driver; locally and in CI it's the same.
- Clean state — a disposable DB container comes up empty on every run, so tests don't inherit junk from previous ones.
- Selenium Grid — hub and browser nodes in containers, easy to scale parallelism.
- Isolation — tests don't pollute the host;
docker compose downand 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.
10What is contract testing (Pact) and what does it replace in microservices?
senior
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:
- Consumer-driven — the consumer generates a pact file from its expectations of the API.
- Provider verification — the provider replays the contract against itself in its own CI.
- Pact Broker — stores contracts and versions;
can-i-deployallows or blocks a deploy. - 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.
11How do you run tests in parallel, and why do you need ThreadLocal<WebDriver>?
middle
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:
- TestNG —
<suite parallel="methods" thread-count="4">. - pytest —
pytest -n auto(xdist) distributes tests across workers. - Why ThreadLocal — it isolates the driver per thread; thread A doesn't open a URL in thread B's browser.
- Teardown —
driver.quit()andremove()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.
12Design a test automation framework from scratch: what layers do you create?
senior
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:
- Config + driver factory — environments, URLs, ThreadLocal driver.
- Page Objects — page locators and actions.
- Steps / business actions — scenario steps on top of pages.
- Tests — only "what we verify" and the asserts.
- Test data — a separate layer (factories/builders, external files).
- 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.