Interview Solved #7 : HikeQA — Lead SDET Interview Decoded: What They Ask, What They Expect & How to Answer
26 October 2026
Experience category: 6–10 years (Senior SDET / Lead SDET / Automation Lead)
Programming & Automation covers the basics you’d expect at 3+ years. At lead level, though, interviewers listen for why you made a choice, not only how you did it. Two of the ten questions are about managing work, not code.
Framework & Automation is where they find out whether you’ve run a framework under parallel load, across environments, with mocked dependencies. If you’ve only ever added tests to someone else’s framework, it shows here.
Enterprise Test Architecture sorts candidates into “strong senior engineer” and “someone we can trust with the whole quality platform.”
If you’re interviewing for a QA Architect or Head of Automation role, treat this as the minimum. Expect to be asked about cost, team adoption and roadmap as well.
𝗛𝗶𝗸𝗲𝗤𝗔 | 𝗟𝗲𝗮𝗱 𝗦𝗗𝗘𝗧 | 𝗜𝗻𝘁𝗲𝗿𝘃𝗶𝗲𝘄 𝗤𝘂𝗲𝘀𝘁𝗶𝗼𝗻𝘀
🔹 𝗣𝗿𝗼𝗴𝗿𝗮𝗺𝗺𝗶𝗻𝗴 & 𝗔𝘂𝘁𝗼𝗺𝗮𝘁𝗶𝗼𝗻
1. Self intro & SDET contributions.
2. Daily agile squad deliverables & QA scope.
3. Common chars in two strings e.g. 'apple', 'pear' -> 'p', 'e', 'a'.
4. Alerts & popups handling in Selenium?
5. Dynamic elements locator strategy with changing DOM IDs?
6. Playwright auto-wait mechanism vs explicit wait?
7. OAuth 2.0 Bearer token flow in REST Assured?
8. REST Assured JsonSchemaValidator setup?
9. Auto-rerun failed Cucumber scenarios in TestNG?
10. Handling 500 test cases with 2 days left in sprint?
🔹 𝗙𝗿𝗮𝗺𝗲𝘄𝗼𝗿𝗸 & 𝗔𝘂𝘁𝗼𝗺𝗮𝘁𝗶𝗼𝗻
1. Find index of target in array e.g. [10, 20, 30], target=20 -> 1.
2. Canvas drag & drop using Actions class in Selenium?
3. Multi-tenant SaaS automation architecture?
4. ThreadLocal WebDriver thread safety in parallel runs?
5. Multi-env config handling via CLI (QA/UAT/Prod)?
6. WireMock setup for backend API mocking?
7. Kubernetes Selenium Grid test execution?
8. Thread-safe Log4j2 setup for parallel builds?
9. DB row updates verification post API batch run?
🔹 𝗘𝗻𝘁𝗲𝗿𝗽𝗿𝗶𝘀𝗲 𝗧𝗲𝘀𝘁 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲
1. Unified framework across Web, Mobile & API?
2. Cutting suite time from 2hrs to 15mins via parallel grid?
3. Synthetic test data strategy for compliance apps?
4. Maintaining locators during core app UI redesigns?
Section 1 – Programming & Automation
Q1. Self intro & SDET contributions
What they’re really checking: Whether you can describe your career as a story about impact, not a list of tools. For a lead role, the first 90 seconds tell them how you think about your work.
A strong answer: Use four parts, and keep the whole thing under two minutes:
Who you are: “I’m an SDET with 8 years in quality engineering, the last 3 leading automation for a payments platform.”
What you own: the framework, the CI quality gates, the test strategy, the people you mentor.
Two contributions with numbers: “I cut regression from 4 hours to 35 minutes by moving setup to APIs and sharding on a Grid,” or “I introduced contract tests that caught 11 breaking API changes before integration.”
Why this role: one sentence that links your direction to what they need.
Where people go wrong:
Going through your résumé job by job.
Listing tools (”Selenium, Java, TestNG, Jenkins, Jira…”) as if the tools were the achievement.
Saying “we” for everything, so the interviewer can’t tell what you did.
Using numbers you can’t defend when they follow up.
Expect the follow-up: “Which of those contributions are you proudest of, and what would you do differently?”
Q2. Daily agile squad deliverables & QA scope
What they’re really checking: Whether QA is built into your sprint or tacked on at the end. A lead should be talking about shift-left, not test execution.
A strong answer: Go through the sprint, not just your day:
Refinement: challenge acceptance criteria and identify testability gaps early. Agree on
data-testidattributes and API contracts before development starts.During the sprint: write API tests in parallel with development, and automate the story’s acceptance criteria before or alongside the code (in-sprint automation).
Daily: triage the nightly run, raise blockers in stand-up, pair with developers on defects.
Definition of Done: a story isn’t done until its automated checks are merged and green in CI.
Lead scope: review test PRs, track flakiness and escape rate, decide what goes into regression.
Where people go wrong:
“I execute test cases and log bugs.” That’s a junior answer.
Describing automation that runs one sprint behind, without acknowledging it as a problem.
Never mentioning refinement or acceptance criteria.
Expect the follow-up: “How do you handle a story that arrives for testing on the last day of the sprint?”
Q3. Common characters in two strings: ‘apple’, ‘pear’ → ‘p’, ‘e’, ‘a’
What they’re really checking: Whether you choose the right data structure, handle duplicates, and think about output order before you start typing.
A strong answer: Ask first: “Should the result be unique? Whose order should it follow?” The expected output p, e, a follows the order of the second string, with no duplicates.
public static List<Character> commonChars(String s1, String s2) {
Set<Character> first = new HashSet<>();
for (char c : s1.toCharArray()) first.add(c);
Set<Character> result = new LinkedHashSet<>(); // keeps order, removes duplicates
for (char c : s2.toCharArray()) {
if (first.contains(c)) result.add(c);
}
return new ArrayList<>(result);
}
// commonChars("apple", "pear") -> [p, e, a]Complexity: O(n + m) time and O(n) space.
Where people go wrong:
Nested loops (O(n×m)) without mentioning that a faster approach exists.
Returning
p, p, e, a, with duplicates, because “apple” has two p’s.Using a
HashSetfor the result and getting a random order.Not asking about case sensitivity (”Apple” vs “pear”).
Expect the follow-up: “What if duplicates should count? For ‘apple’ and ‘ppear’, return ‘p, p, e, a’.” Use a frequency map (Map<Character, Integer>) and decrement the count on each match.
Q4. Alerts & popups handling in Selenium
What they’re really checking: Whether you know that “popup” covers several different things, each handled differently.
A strong answer: Classify the popup first:
Alert alert = new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.alertIsPresent());
String msg = alert.getText();
alert.accept(); // or dismiss(), or sendKeys("text") for a promptWhere people go wrong:
Using
switchTo().alert()on an HTML modal. It throwsNoAlertPresentException.Not waiting for the alert before switching.
Using the Robot class or AutoIt for uploads. That breaks on Grid and headless runs.
Forgetting that an unhandled alert blocks every later command (
UnhandledAlertException).
Expect the follow-up: “How do you handle an unexpected alert that appears randomly?” Set unhandledPromptBehavior in browser capabilities, and treat a random alert as a possible defect rather than noise to suppress.
Q5. Locator strategy for dynamic elements with changing DOM IDs
What they’re really checking: Locator discipline, and whether you fix the cause (missing test IDs) or keep patching the symptom.
A strong answer: Give an order of preference:
Test-specific attributes:
[data-testid='checkout-btn']. Agree on this with developers as a team convention.Stable attributes:
name,aria-label,role. These also push the team toward accessibility.Partial matching on the stable part:
[id^='order_']or//div[contains(@class,'card')]Anchoring through a stable neighbour:
//td[text()='INV-204']/following-sibling::td//buttonText-based locators, used carefully when the app supports multiple languages.
At lead level, add the process: “I add locator review to test PRs, and new UI stories include test IDs in the Definition of Done.”
Where people go wrong:
Absolute XPath, or index-based locators like
(//div)[7].Matching on framework-generated class names (
css-1x2y3z) that change on every build.Treating it as purely a QA problem and never involving developers.
Expect the follow-up: “How do you handle elements inside Shadow DOM?” In Selenium 4, use element.getShadowRoot().findElement(By.cssSelector(...)).
Q6. Playwright auto-wait vs explicit wait
What they’re really checking: Whether you understand what Playwright waits for, or assume it waits for everything.
A strong answer:
Auto-wait (actionability checks): Before an action such as click(), Playwright checks that the element is attached, visible, stable (not animating), able to receive events (not covered by another element) and enabled. If those checks don’t pass within the timeout, the action fails with a clear reason.
Web-first assertions retry automatically:
await page.getByTestId('submit').click(); // auto-waits for actionability
await expect(page.getByText('Order placed')).toBeVisible(); // retries until timeoutExplicit waits are still needed for things that aren’t element actions:
const [response] = await Promise.all([
page.waitForResponse(r => r.url().includes('/api/orders') && r.status() === 201),
page.getByTestId('submit').click(),
]);
await page.waitForURL('**/confirmation');The point that sets you apart: “Auto-wait covers actionability, not business state. If I need data to be saved on the backend, or a specific network call to finish, I wait on that event explicitly. I never use waitForTimeout().”
Where people go wrong:
“Playwright doesn’t need waits.” That’s half true.
Using
expect(await locator.isVisible()).toBe(true). This checks once and doesn’t retry.Using
waitForTimeout(3000), which isThread.sleep()under another name.
Expect the follow-up: “How is a Playwright locator different from a Selenium WebElement?” A Playwright locator is lazy and re-resolved on every action, so there’s no equivalent of a stale element reference.
Q7. OAuth 2.0 Bearer token flow in REST Assured
What they’re really checking: Whether you generate tokens properly or paste them in from Postman.
A strong answer: Name the grant type first. For service-to-service testing, it’s usually client credentials.
public class TokenManager {
private static String token;
private static Instant expiry = Instant.MIN;
public static synchronized String getToken() {
if (Instant.now().isAfter(expiry.minusSeconds(60))) {
Response res = given()
.auth().preemptive().basic(Config.clientId(), Config.clientSecret())
.formParam("grant_type", "client_credentials")
.formParam("scope", "orders.read orders.write")
.post(Config.tokenUrl());
token = res.jsonPath().getString("access_token");
expiry = Instant.now().plusSeconds(res.jsonPath().getLong("expires_in"));
}
return token;
}
}
given().auth().oauth2(TokenManager.getToken()) // sends "Authorization: Bearer <token>"
.get("/v1/orders")
.then().statusCode(200);Points to cover: cache the token and refresh it before it expires; make access thread-safe for parallel runs; take secrets from a vault or CI credentials; for user-context flows (authorization code), use a test identity or a dedicated test client.
Where people go wrong:
Hardcoding a token that expires mid-run.
Fetching a new token before every request, which slows the suite and can trip rate limits.
Testing only the success path. Also test with an expired token (401), a missing token (401) and the wrong scope (403).
Expect the follow-up: “What’s the difference between 401 and 403?” 401 means “I don’t know who you are.” 403 means “I know who you are, and you’re not allowed to do this.”
Q8. REST Assured JsonSchemaValidator setup
What they’re really checking: Whether you validate the contract of a response, not just a few of its fields.
A strong answer:
Dependency:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${restassured.version}</version>
<scope>test</scope>
</dependency>Schema at src/test/resources/schemas/order.json:
{
"type": "object",
"required": ["id", "status", "amount"],
"properties": {
"id": { "type": "string" },
"status": { "type": "string", "enum": ["CREATED", "PAID", "CANCELLED"] },
"amount": { "type": "number", "minimum": 0 }
},
"additionalProperties": false
}Usage:
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
given().spec(spec).get("/v1/orders/123")
.then().statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/order.json"));The point that sets you apart: “Schema validation catches structural drift: a renamed field, a type change, a field that’s suddenly nullable. It doesn’t replace value assertions. I use both.”
Where people go wrong:
Writing schemas so loose (no
required, noadditionalProperties) that they pass everything.Keeping schemas by hand when the OpenAPI spec could generate them.
Not knowing where the schema file lives on the classpath.
Expect the follow-up: “How is schema validation different from contract testing?” Schema validation checks the provider’s response. Contract testing (e.g., Pact) checks that what the consumer expects matches what the provider delivers, and runs in both pipelines.
Q9. Auto-rerun failed Cucumber scenarios in TestNG
What they’re really checking: Whether you know the mechanics and whether you have a policy on reruns.
A strong answer: There are two common approaches.
Approach 1: the rerun plugin plus a second runner
@CucumberOptions(features = "src/test/resources/features",
glue = "steps",
plugin = {"pretty", "rerun:target/failed_scenarios.txt"})
public class MainRunner extends AbstractTestNGCucumberTests { }
@CucumberOptions(features = "@target/failed_scenarios.txt",
glue = "steps")
public class FailedRerunner extends AbstractTestNGCucumberTests { }Run MainRunner first, then FailedRerunner as a separate CI step.
Approach 2: TestNG IRetryAnalyzer attached through an IAnnotationTransformer listener, capped at 1 retry.
The policy matters more than the code: “A scenario that passes on rerun gets tagged as flaky in the report. It doesn’t quietly count as a pass.”
Where people go wrong:
Unlimited retries.
Merging rerun results so the flakiness disappears from the report.
Not knowing that the rerun file needs the
@prefix infeatures.
Expect the follow-up: “How do you run scenarios in parallel with TestNG?” Override scenarios() with @DataProvider(parallel = true) and set dataproviderthreadcount.
Q10. 500 test cases with 2 days left in the sprint
What they’re really checking: Prioritisation under pressure, honesty and stakeholder management. This is the first lead-level judgment question.
A strong answer:
Don’t try to run all 500. Be clear from the start that full coverage in 2 days isn’t realistic.
Prioritise by risk: P1 is business-critical flows plus areas changed this sprint. P2 is high-use flows nearby. P3 is everything else.
Use what you already have: run the automated suite in parallel overnight, and spend manual effort only on what isn’t automated.
Remove duplicates: large test sets usually overlap heavily.
Share the load: pair with developers on targeted checks, and let them unit-test the edge cases.
Communicate early and in writing: “Here’s what we’ll cover, here’s what we won’t, here’s the risk.” The product owner decides whether to ship.
Afterwards: raise in the retro why 500 cases landed at the end of the sprint. That’s the real problem.
Where people go wrong:
“We’ll work overtime and finish everything.”
Quietly skipping tests without telling anyone.
Signing off on incomplete coverage under pressure.
Expect the follow-up: “The PO insists on releasing anyway. What do you do?” Record the risk, get the decision in writing, and plan monitoring after release.
Section 2 – Framework & Automation
Q1. Find the index of a target in an array: [10, 20, 30], target = 20 → 1
What they’re really checking: Whether you notice the array is sorted and ask about it.
A strong answer: Ask: “Is the array always sorted?”
Unsorted means linear search, O(n):
for (int i = 0; i < arr.length; i++) if (arr[i] == target) return i;
return -1;Sorted means binary search, O(log n):
int lo = 0, hi = arr.length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2; // avoids integer overflow
if (arr[mid] == target) return mid;
if (arr[mid] < target) lo = mid + 1; else hi = mid - 1;
}
return -1;Where people go wrong:
Writing
(lo + hi) / 2without knowing about overflow.Returning
0or throwing an exception when the target isn’t found, instead of-1.Getting the loop condition wrong (
<vs<=) and missing single-element arrays.
Expect the follow-up: “What if there are duplicates and I need the first occurrence?” When you find a match, store the index and keep searching left (hi = mid - 1).
Q2. Canvas drag & drop using the Actions class in Selenium
What they’re really checking: Whether you understand that a canvas has no DOM elements inside it, so you work with coordinates, and that verification needs a different approach.
A strong answer:
WebElement canvas = driver.findElement(By.id("board"));
new Actions(driver)
.moveToElement(canvas, -100, -50) // Selenium 4: offset from the element's CENTRE
.clickAndHold()
.moveByOffset(150, 80) // move in steps if the app needs mousemove events
.moveByOffset(10, 10)
.release()
.perform();Points to cover:
Offsets in Selenium 4 are from the element’s centre. In Selenium 3 they were from the top-left. This catches people out during migrations.
Some canvas apps only respond to several intermediate
mousemoveevents, so a single big jump may not register.Verification is the hard part. There’s nothing in the DOM to assert on. Use the app’s state via a JS hook or API, visual comparison against a baseline, or canvas pixel data through
toDataURL().For HTML5 native drag & drop (
draggable="true"),dragAndDrop()often fails in some drivers. Simulating it with JS events is a known workaround.
Where people go wrong:
Using
dragAndDrop(source, target)on a canvas. There’s no target element.Asserting only that “no exception was thrown.”
Hardcoding pixel offsets that break on a different screen resolution. Calculate them from canvas dimensions.
Expect the follow-up: “How would you make canvas tests stable across screen resolutions?” Fix the window size in config, and calculate offsets as a proportion of the canvas size.
Q3. Multi-tenant SaaS automation architecture
What they’re really checking: Whether you can design for many customers sharing one codebase but each with different configuration, features and data.
A strong answer:
1. Make the tenant a first-class concept in the framework:
# tenants/acme.yaml
baseUrl: https://acme.app.com
features: { sso: true, bulkExport: false }
locale: en-GB
users: { admin: vault:acme/admin, viewer: vault:acme/viewer }At runtime: -Dtenant=acme -Denv=uat.
2. Feature-aware tests: skip or adapt based on the tenant’s feature flags (@RequiresFeature("bulkExport")), so the same test doesn’t fail because a feature is switched off.
3. Tenant isolation tests. This is the most important point to raise: a user in tenant A must never see tenant B’s data. Test it at the API layer by trying to access B’s resource IDs with A’s token, and expect 403 or 404.
4. Execution matrix: run the core suite on a few representative tenant configurations (default, heavily customised, high-volume), not on all of them.
5. Data: create test data per tenant through APIs, namespaced so parallel tenant runs don’t collide.
Where people go wrong:
Copying the framework once per tenant.
if (tenant.equals("acme"))scattered through test code.Never testing cross-tenant data leakage, which is the most serious multi-tenant risk.
Expect the follow-up: “You have 200 tenants. How do you choose which to test?” Group them by configuration, and pick one representative from each group plus the highest-revenue tenants.
Q4. ThreadLocal WebDriver for thread safety in parallel runs
What they’re really checking: Whether you understand why a static driver fails, and the cleanup step most people forget.
A strong answer:
public final class DriverManager {
private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();
public static WebDriver get() { return DRIVER.get(); }
public static void init(String browser) {
DRIVER.set(DriverFactory.create(browser));
}
public static void quit() {
WebDriver d = DRIVER.get();
if (d != null) { d.quit(); }
DRIVER.remove(); // essential with thread pools
}
}Call init() in @BeforeMethod and quit() in @AfterMethod(alwaysRun = true).
Why remove() matters: TestNG reuses threads from a pool. If you don’t call remove(), the next test on that thread may pick up a dead driver, and you get a memory leak.
Where people go wrong:
Using
static WebDriver driverand wondering why tests click in each other’s browsers.Forgetting
DRIVER.remove().Making the driver thread-safe but leaving page objects, reports or test data as shared static state.
Expect the follow-up: “Is ThreadLocal enough to make parallel tests safe?” No. Every piece of shared mutable state needs the same treatment.
Q5. Multi-environment config via the CLI (QA/UAT/Prod)
What they’re really checking: Clean configuration design, and whether you treat Prod differently.
A strong answer:
mvn test -Denv=uat -Dbrowser=chrome -Dgroups=smokepublic final class Config {
private static final Properties props = load(System.getProperty("env", "qa"));
public static String get(String key) {
return Optional.ofNullable(System.getProperty(key)) // 1. CLI
.or(() -> Optional.ofNullable(System.getenv(key.toUpperCase().replace('.', '_')))) // 2. env var
.orElse(props.getProperty(key)); // 3. file default
}
}Points to cover:
Precedence: CLI > environment variable > file. State it clearly.
Secrets never go in files. They come from a vault or CI credentials.
Fail fast: if
envis invalid or a required key is missing, stop immediately with a clear message.Prod is special: only read-only smoke tests and synthetic monitoring. No data creation. Have a guard that refuses to run destructive tests when
env=prod.
Where people go wrong:
Hardcoding URLs in test classes.
One config file full of
ifstatements.Running the full regression suite against production.
Expect the follow-up: “How do you pass the environment from a Jenkins pipeline?” Use a parameterised build: mvn test -Denv=${ENV}.
Q6. WireMock setup for backend API mocking
What they’re really checking: Whether you can isolate the system under test from unstable or unavailable dependencies, and whether you know where mocking stops being useful.
A strong answer:
WireMockServer wm = new WireMockServer(options().dynamicPort());
wm.start();
wm.stubFor(get(urlPathEqualTo("/payments/status"))
.withQueryParam("id", equalTo("P-101"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBodyFile("payment_success.json")));
// Failure simulation, which is the real value
wm.stubFor(post("/payments").willReturn(aResponse().withStatus(503).withFixedDelay(5000)));Point the application’s dependency URL at http://localhost:<port> through config.
Use it for: third-party APIs (payment gateways, SMS), services that aren’t built yet, and failure scenarios you can’t trigger for real: timeouts, 500s, malformed responses.
Also verify the calls your app made:
wm.verify(postRequestedFor(urlEqualTo("/payments"))
.withRequestBody(matchingJsonPath("$.amount", equalTo("500"))));Where people go wrong:
Mocking everything, so the tests only prove the mocks work.
Letting mocks drift from the real API. Pair mocks with contract tests.
Using fixed ports that clash in parallel runs.
Expect the follow-up: “Mock vs stub vs fake?” A stub returns canned data. A mock also verifies interactions. A fake is a lightweight working implementation, such as an in-memory database.
Q7. Selenium Grid test execution on Kubernetes
What they’re really checking: Whether you understand scalable test infrastructure, not just how to run Grid on your laptop.
A strong answer:
Architecture: Selenium Grid 4 runs as separate components (Router, Distributor, Session Map, Session Queue, Event Bus) plus browser Node pods. On Kubernetes it’s usually deployed with the official Helm chart.
Autoscaling: node pods scale with the session queue (commonly via KEDA). When tests arrive, Chrome pods start. When the queue empties, they scale back down. You pay for browsers only while tests are running.
Test side: nothing changes except the URL:
WebDriver driver = new RemoteWebDriver(new URL(Config.get("grid.url")), new ChromeOptions());Operational points that show real experience:
Set resource limits (CPU and memory) on node pods. Starved browsers cause flaky timeouts.
Each node pod should handle one session at a time for isolation.
Use video recording and logs for debugging.
Tune session and queue timeouts to match pod startup time.
Where people go wrong:
“We put Grid in Docker” with no idea how it scales.
Too many sessions per node, which leads to random failures.
Not accounting for pod startup time in the overall run time.
Expect the follow-up: “When would you choose BrowserStack/LambdaTest over your own Grid?” When you need real devices, wide OS/browser coverage, or don’t have a platform team to run the infrastructure. Your own Grid wins on cost at high volume and on data control.
Q8. Thread-safe Log4j2 setup for parallel builds
What they’re really checking: Whether you know the real problem: Log4j2 is already thread-safe, but parallel logs interleave and become unreadable.
A strong answer:
Tag each log line with its test using ThreadContext (Log4j2’s MDC):
@BeforeMethod
public void tag(Method m) { ThreadContext.put("testName", m.getName()); }
@AfterMethod(alwaysRun = true)
public void untag() { ThreadContext.clearMap(); }<PatternLayout pattern=”%d [%t] [%X{testName}] %-5level %c{1} - %msg%n”/>One file per test using RoutingAppender:
<Routing name="PerTest">
<Routes pattern="$${ctx:testName}">
<Route>
<File name="f-${ctx:testName}" fileName="logs/${ctx:testName}.log">
<PatternLayout pattern="%d %-5level - %msg%n"/>
</File>
</Route>
</Routes>
</Routing>Performance: async loggers keep logging from slowing down parallel threads.
Where people go wrong:
Saying “Log4j2 isn’t thread-safe.” It is.
Never clearing
ThreadContext, so the next test on a pooled thread inherits the wrong name.Using
System.out.printlnin a parallel framework.Logging secrets or tokens.
Expect the follow-up: “How do you attach the per-test log to the failure report?” In the onTestFailure listener, read that test’s log file and attach it to Allure or Extent.
Q9. Verifying DB row updates after an API batch run
What they’re really checking: Cross-layer validation, and whether you handle asynchronous processing.
A strong answer:
Baseline: record the relevant state before the run (row count, or the specific IDs).
Trigger the batch through the API and capture the
batchIdorjobId.Wait for completion properly: poll the job status API, not
sleep:
await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(5))
.until(() -> jobApi.status(jobId).equals("COMPLETED"));Verify in the DB with parameterised queries:
String sql = "SELECT COUNT(*) FROM orders WHERE batch_id = ? AND status = 'PROCESSED'";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, batchId);
ResultSet rs = ps.executeQuery(); rs.next();
assertEquals(rs.getInt(1), expectedCount, "Processed row count mismatch");
}Check more than counts: field values for sampled rows, rejected rows written to the error table with the right reason, no duplicates (idempotency), and audit columns (
updated_at,updated_by).Partial failure: send a batch with some invalid records, and check the valid ones commit while the invalid ones are reported. That’s the scenario most teams skip.
Where people go wrong:
Thread.sleep(60000), then query.Only checking the count.
String-concatenated SQL.
Leaving DB connections open (no try-with-resources).
Running against a shared database where other tests change the same rows. Filter by your own
batchId.
Expect the follow-up: “What if you have no DB access in higher environments?” Verify through read APIs, or through events and audit logs.
Section 3 – Enterprise Test Architecture
Q1. Unified framework across Web, Mobile & API
What they’re really checking: Whether you can design shared foundations without forcing everything into one giant framework.
A strong answer: Share the core, separate the drivers.
One config, data and reporting model across all layers, so one dashboard shows quality for every channel.
The API layer serves everyone: web and mobile tests use it for setup and for backend verification.
Business-level actions:
placeOrder()exists for each channel, so the same scenario can run on web or mobile with the right implementation.Version it as libraries (Maven modules), so a mobile change doesn’t break the web build.
The trade-off to name: “Unified doesn’t mean identical. Mobile has gestures, device state and app installation that web doesn’t. Forcing one abstraction over both creates leaky code.”
Where people go wrong:
One huge module with
if (platform == MOBILE)everywhere.Copying utilities into three separate frameworks.
Not putting the API layer at the centre.
Expect the follow-up: “How would you get three separate teams to adopt it?” Start with one pilot team, show the measured gain, write good docs, and set up an internal contributor model. Don’t force a big-bang migration.
Q2. Cutting suite time from 2 hours to 15 minutes with a parallel grid
What they’re really checking: Whether you know that parallelisation alone rarely gets you there, and whether you can show the maths.
A strong answer: Do the arithmetic. 120 minutes down to 15 is an 8× speed-up. Even perfect parallelism needs around 10 nodes once you allow for overhead, and parallelism is rarely perfect. So combine several levers:
Remove UI setup: create users and orders through the API. This often takes 30–50% off each test.
Push checks down the pyramid: move business-rule validations from UI to API tests, which run in seconds.
Make tests independent: no shared accounts and no order dependencies. Without this, parallel runs fail.
Scale out: Grid on Kubernetes with autoscaling nodes, and thread count matched to node capacity.
Shard by duration, not by test count: balance shards using historical run times so one slow shard doesn’t hold up the build.
Remove waste: remove duplicate tests, delete low-value tests, and remove remaining sleeps.
Run smarter: run a smoke suite on every PR, impact-based selection on merge, and the full suite nightly.
The trap to point out: the environment under test has to cope with 10× the concurrent load. Otherwise you’ve moved the bottleneck to the application.
Where people go wrong:
“Increase thread-count to 20.” No independence, no data strategy.
Ignoring the slowest single test. The suite can’t finish faster than its longest test.
Measuring only time, not flakiness. A fast suite that fails 15% of the time isn’t progress.
Expect the follow-up: “What’s your stopping point?” When adding nodes stops reducing time, which is usually because of a long test, environment capacity, or setup that has to run in sequence.
Q3. Synthetic test data strategy for compliance-heavy apps
What they’re really checking: Whether you understand that, in regulated domains, test data is a compliance risk, not just a convenience.
A strong answer:
Principle: no real personal data in non-production environments. Data-protection rules (GDPR in the EU, the DPDP Act in India, and sector-specific rules) make this a legal issue, not a preference.
Strategy, in layers:
Generate synthetic data that follows business rules: valid formats (PAN, IBAN, phone), realistic distributions, correct relationships (customer → account → transactions). Use libraries such as Datafaker plus your own generators for domain rules.
Masking for cases that need production-shaped data, such as migration testing. Mask in a way that can’t be reversed and keeps formats and relationships consistent.
Edge-case data on purpose: Unicode names, maximum-length fields, boundary amounts, expired documents. Real data rarely contains these.
Provide data through APIs or a test-data service, so tests ask for “a KYC-verified customer with an overdue loan” instead of hunting for one.
Governance: document the data’s origin, set retention and cleanup jobs, keep access controls on test environments, and keep audit trails.
Where people go wrong:
“We copy prod and mask a few columns.” Free-text fields, logs and attachments still leak personal data.
Random data that breaks business rules, so tests fail for the wrong reasons.
Forgetting that logs and screenshots can contain sensitive data too.
Expect the follow-up: “How do you test a feature that depends on years of transaction history?” Use a generator that creates time-series history, so you don’t need production snapshots.
Q4. Maintaining locators during a core app UI redesign
What they’re really checking: Whether you can manage a large, predictable disruption without the suite collapsing.
A strong answer:
Before the redesign:
Test IDs as a contract: agree with front-end developers that
data-testidattributes survive the redesign. If the IDs stay the same, most locators don’t change.POM and component objects: a redesigned header means one component class changes, not 300 tests.
Get involved early: QA joins the redesign planning and gets the component list and the release order.
During the redesign:
Migrate in phases, behind feature flags: run old and new UI paths in parallel, with page objects chosen by flag (
LoginPageV1/LoginPageV2behind an interface).Keep API-level coverage stable. The business logic didn’t change, so your API suite stays your safety net while UI tests are reworked.
Track progress openly: tests migrated vs remaining, per module.
After the redesign:
Retire the V1 page objects, and add visual regression baselines for the new design.
Where people go wrong:
Discovering the redesign when 300 tests fail in CI.
Rewriting the whole suite from scratch.
Turning to self-healing locator tools as the main strategy. They help at the edges but can hide real UI changes.
Expect the follow-up: “What’s your opinion on AI self-healing locators?” They’re useful for reducing noise, but every heal should be logged and reviewed, because a “healed” locator may now be pointing at the wrong element.
Quick Revision Sheet (read this the night before)
Self intro → role, ownership, two contributions with numbers, why this role, under 2 minutes
Agile QA → shift-left: refinement, in-sprint automation, automation in the Definition of Done
Common chars → HashSet for lookup, LinkedHashSet for ordered unique output, O(n + m)
Popups → classify first: JS alert, HTML modal, window, permission or auth
Dynamic locators → data-testid > aria/name > partial match > neighbour anchors; fix it with developers
Playwright → auto-wait covers actionability; explicitly wait for network and business state
OAuth → client credentials, cache plus refresh, thread-safe, test 401 vs 403
Schema validation →
requiredplusadditionalProperties; schema checks structure, not valuesCucumber rerun → rerun plugin plus
@rerun.txt, max 1 retry, flag retry-passes as flaky500 tests / 2 days → risk-based, communicate in writing, PO decides, fix the cause in the retro
Binary search → ask if sorted;
lo + (hi - lo) / 2Canvas → offsets from the centre in Selenium 4; verify through state or visual comparison, not the DOM
Multi-tenant → tenant as config, feature-aware tests, cross-tenant isolation tests
ThreadLocal → always
remove(); remove other shared state tooConfig → CLI > env > file; secrets from a vault; Prod is read-only
WireMock → mock third parties and failures; verify calls; pair with contract tests
K8s Grid → Helm, queue-based autoscaling, one session per pod, resource limits
Log4j2 → already thread-safe; ThreadContext plus RoutingAppender for readable logs
DB verification → poll job status, parameterised SQL, verify values, duplicates and partial failures
Unified framework → shared core, separate drivers, API layer in the middle
2 hrs → 15 min → 8× needs API setup, pyramid, independence, scaling and duration-based sharding
Synthetic data → no real personal data; rule-valid generators; governance
UI redesign → test-ID contract, POM, phased migration behind flags, API suite as the safety net
Share this article
Found this useful? Share it with another QA engineer.
Keep learning


