Scenario-Based SDET Interview¶
API + Automation Scenarios, Managerial & HR Rounds
Round 2 (hands-on API + automation scenarios), Round 3 (Managerial), Round 4 (HR / Salary). Each scenario: approach -> code -> spoken answer.
0. How to Use This Guide¶
This covers a real interview loop: Round 2 hands-on API + automation scenarios (8 questions), Round 3 the Managerial round, and Round 4 the HR / salary discussion. Every scenario has a step-by-step approach, a 'How to say it' spoken answer, and code where it helps.
- Scenario answers use a diagnose -> approach -> code -> prevent shape.
- 'How to say it' is the interview-length version you speak out loud.
- TIP = shows seniority. AVOID = what hurts you.
1. Scenario: 15 tests fail only in Jenkins, pass locally¶
How will you debug this step by step?¶
The theme is environment parity: the test is fine, the environment differs. I never edit the test first. I diagnose why CI is different.
Step 1 - Triage and find the pattern
- Are the same 15 failing every run (deterministic) or different each run (flaky)? Re-run the Jenkins job 2-3 times to tell them apart.
- Group the 15: same page/module? same suite position? same data? all run late in the suite? A pattern points straight at the cause.
- Read the Jenkins artifacts BEFORE guessing: console log, stack trace, screenshot-on-failure, video, Playwright trace / Selenium logs.
Step 2 - Check the usual CI-only root causes
- Headless vs headed: CI runs headless - rendering, hover, element size, and animations behave differently. Run headless LOCALLY to reproduce.
- Viewport / screen resolution: CI has a different or tiny default window -> elements off-screen or not clickable. Set a fixed viewport.
- Timing / speed: CI agents are slower -> race conditions. Replace Thread.sleep and hard waits with explicit / web-first waits.
- Test data & isolation: locally you run a subset; CI runs all 200 in parallel -> data collisions, order dependency, shared state.
- Browser/driver mismatch: local Chrome vs CI chromedriver version.
- Environment config: base URL, feature flags, or secrets differ; the QA env CI points to may have different data than your local.
- Locale / timezone: CI often UTC -> date, currency, number formatting assertions break.
- Resource limits: low CPU/RAM on the agent -> timeouts and slow renders.
- Network: firewall blocks a third-party (maps, payment sandbox, CDN).
Step 3 - Reproduce, then isolate
- Reproduce locally by matching CI: run headless, same viewport, same browser version, same env URL, TZ=UTC.
- Run the 15 in isolation, then inside the full 200, to expose order/parallel data collisions.
- Best fix for parity: run the SAME Docker image locally that Jenkins uses - then 'works on my machine' disappears.
Step 4 - Fix and prevent
- Make each test create its own unique data (UUID/timestamp) and clean up.
- Explicit waits on conditions, never fixed sleeps.
- Pin browser/driver versions; containerize the test runtime.
- Quarantine genuinely flaky tests and track them - do NOT paper over them with blind retries.
How to say it: "First I check whether the same 15 fail every run or randomly, and I group them to find a pattern. Then I read the Jenkins logs, screenshots and trace instead of guessing. Ninety percent of CI-only failures come down to environment differences: headless rendering, smaller viewport, slower agents causing race conditions, parallel test-data collisions, or a different timezone and locale. I reproduce by running headless locally with the same viewport and browser version - ideally the same Docker image Jenkins uses - fix the real cause like adding explicit waits or unique test data, and quarantine anything truly flaky rather than hiding it with retries."
AVOID - Do not just add retries: Retrying until green hides real product bugs and race conditions. Retries are a safety net that must be reported, not a fix.
2. Scenario: Daily-changing UI price vs API¶
Automate validating a price that changes daily on the UI against the API response.¶
The key idea: never hardcode the expected price. The API (or DB) is the source of truth. I read the expected value at runtime and assert the UI matches it.
Approach
- In test setup, call the product API (GET /products/{id}) and extract the current price - this is my dynamic expected value.
- Open the UI product page and read the displayed price.
- Normalize both sides before comparing: strip currency symbol, fix decimals/rounding, handle locale (1,299.00 vs 1299).
- Assert UI price == API price. Make it data-driven across many products.
Edge cases a senior mentions
- Propagation delay: UI may be cached and lag the API by seconds/minutes - poll/retry with a timeout instead of asserting instantly.
- Transforms: UI may show price + tax or after discount; compare the right field, or replicate the transform from the API value.
- The 'daily change' boundary: know WHEN it flips (midnight which TZ?) and add a test around that boundary.
- Also contract-test the API field itself: type is number, non-null, >= 0, correct currency code.
// Rest Assured + Selenium (Java) - dynamic expected value
double apiPrice = given().auth().oauth2(token)
.when().get('/products/' + id)
.then().statusCode(200)
.extract().jsonPath().getDouble('price');
driver.get(baseUrl + '/product/' + id);
String uiText = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector('[data-testid=price]'))).getText();
double uiPrice = Double.parseDouble(uiText.replaceAll('[^0-9.]', ''));
Assert.assertEquals(uiPrice, apiPrice, 0.001,
'UI price must match API price for product ' + id);
How to say it: "I treat the API as the source of truth and never hardcode the price. In setup I hit the product API and pull the current price as my expected value, then read the price shown on the UI and compare after normalizing currency symbol, decimals and locale. I make it data-driven across products. I also handle real-world issues: caching delay between API and UI, so I poll with a timeout, and any tax or discount transform the UI applies. That way the test keeps passing every day even though the value changes."
3. Scenario: File upload (PDF) + verify download¶
Automate PDF upload and verify the download succeeds in Selenium + Java.¶
Upload - use sendKeys, not the OS dialog
If it is a real , send the absolute path with sendKeys. That skips the native OS file picker entirely, which Selenium cannot drive. Robot/AutoIt are only a last resort for custom widgets that hide the input.
WebElement upload = driver.findElement(By.id('fileInput'));
upload.sendKeys('/abs/path/tests/data/sample.pdf');
// verify upload success in the UI (filename / success toast):
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.cssSelector('.upload-status'), 'sample.pdf'));
Download - configure Chrome to auto-save PDFs
By default Chrome OPENS a PDF in its viewer instead of downloading. Set prefs to force a download to a known folder and disable the prompt, so the test can find the file.
Map<String,Object> prefs = new HashMap<>();
prefs.put('download.default_directory', downloadDir);
prefs.put('download.prompt_for_download', false);
prefs.put('plugins.always_open_pdf_externally', true); // download, not view
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption('prefs', prefs);
// trigger download, then wait for the file to finish:
driver.findElement(By.id('downloadBtn')).click();
File file = new WebDriverWait(driver, Duration.ofSeconds(30))
.until(d -> {
File f = new File(downloadDir, 'sample.pdf');
File part = new File(downloadDir, 'sample.pdf.crdownload');
return (f.exists() && !part.exists()) ? f : null;
});
Verify the download is really successful
- File exists AND the .crdownload temp file is gone (download finished).
- File size > 0 and extension is .pdf.
- Open with Apache PDFBox to assert page count / text - proves it is a valid, non-corrupt PDF, not just a 0-byte file.
- Optional: compare checksum of downloaded vs uploaded file.
AVOID - Headless / Grid caveat: Old headless Chrome blocked downloads - use new headless or set Page.setDownloadBehavior via CDP. On Selenium Grid the file lands on the NODE, not your machine - use the node's download API or CDP.
How to say it: "For upload I send the absolute file path with sendKeys straight to the input element, which avoids the OS dialog Selenium can't control, and I confirm the UI shows the filename or a success message. For download I configure ChromeOptions to save PDFs to a known directory, disable the viewer with always_open_pdf_externally, click download, then wait until the file exists and the .crdownload temp file is gone. Finally I assert it is non-empty and open it with PDFBox to confirm it is a valid PDF. On Grid I remember the file lands on the node, so I pull it via CDP."
4. Scenario: API 1000 records vs UI 50 with pagination¶
How will you validate data consistency between UI and API?¶
AVOID - The trap: Do NOT compare 50 UI rows on page 1 against all 1000 API records - they will never match. You must reconcile like-for-like.
Approach - three levels
- Counts / metadata: API total (1000) -> expected pages = ceil(1000/50) = 20. Assert the UI shows '1000 results' and 20 pages, last page count correct.
- Page-by-page mapping: for UI page N, call the API with the SAME page, size and sort params, then compare the 50 keys/values in the same order. Sort order must match or everything looks 'wrong'.
- Full reconciliation: walk all UI pages, collect rows into a set keyed by unique id; get all API records; assert no missing and no extra, and field values match on a sample.
What senior engineers call out
- Sorting: UI and API must use the same default sort, else sets match but page-by-page comparison fails.
- Data changing mid-run: 1000 can become 1001 during the test. Freeze data, snapshot a timestamp, or filter to a stable subset.
- Boundary / off-by-one: last page partial rows, empty last page when total is an exact multiple.
- Field-level check, not just IDs: verify name/price/status on sampled rows so a wrong value isn't missed.
- Strategy: use the API to verify the DATA (fast, exact); use the UI to verify PRESENTATION and pagination controls (next/prev/jump, disabled on first/last).
How to say it: "I never compare one UI page to the whole API set. I reconcile at three levels. First metadata: 1000 records at 50 per page means 20 pages, so I assert the total count and page count on the UI. Second, page by page: for UI page N I call the API with the same page, size and sort and compare the 50 records in order. Third, full reconciliation: I collect every row across all UI pages into a set keyed by id and diff it against the full API list to catch any missing or extra record, checking field values on a sample. I watch for sort mismatch, data changing during the run, and boundary pages. I let the API verify the data and the UI verify pagination behavior."
5. Scenario: Auth token expires every 30 min¶
How will you handle an auth token in Rest Assured when it expires mid-execution?¶
Centralize token handling in one place and refresh automatically. Tests should never fetch or think about tokens themselves.
Design
- A TokenManager caches the token plus its expiry time. Before each use it checks: if expired or within a small buffer (say 2 min), refresh.
- Prefer a refresh_token call over a full re-login when the API supports it - faster and closer to real clients.
- Reactive safety net: if a request still returns 401, refresh once and retry that request (via a Rest Assured Filter).
- Thread-safety for parallel runs: synchronize the refresh with a double-check so 8 threads don't all re-login at once.
- Never hardcode credentials or tokens - read from env / secrets.
public class TokenManager {
private static String token;
private static Instant expiresAt = Instant.EPOCH;
public static synchronized String get() {
if (token == null || Instant.now().isAfter(expiresAt.minusSeconds(120))) {
Response r = given().contentType(JSON).body(creds)
.post('/auth/login').then().statusCode(200).extract().response();
token = r.path('access_token');
expiresAt = Instant.now().plusSeconds(r.path('expires_in')); // ~1800
}
return token;
}
}
// usage - every request just asks the manager:
given().auth().oauth2(TokenManager.get()).get('/orders');
How to say it: "I never scatter login calls across tests. I put a TokenManager in charge that caches the token and its expiry, and before every request it checks whether the token is expired or about to expire within a two-minute buffer and refreshes if so - using the refresh token when available. I also add a Rest Assured filter that, if a call still comes back 401, refreshes once and retries. For parallel runs the refresh is synchronized so all threads don't re-login at the same time. Credentials come from environment secrets, never hardcoded."
6. Scenario: Rest Assured - validate response¶
Write Rest Assured code to validate nested JSON values, status code, and response time.¶
given/when/then with inline Hamcrest matchers covers status, nested fields, arrays and response time in one readable chain.
given()
.auth().oauth2(TokenManager.get())
.pathParam('id', 101)
.when()
.get('/users/{id}')
.then()
.statusCode(200) // status code
.time(lessThan(2000L)) // response time < 2s
.contentType(ContentType.JSON)
.body('data.user.name', equalTo('Rohan')) // nested value
.body('data.user.address.city', equalTo('Pune')) // deep nested
.body('data.roles', hasItems('admin', 'qa')) // array contains
.body('data.orders[0].amount', greaterThan(0)) // array index
.body('data.user.email', notNullValue());
Extract-and-assert style (for reuse / complex logic)
Response res = given().auth().oauth2(TokenManager.get())
.get('/users/101').then().extract().response();
assertEquals(res.statusCode(), 200);
assertTrue(res.time() < 2000);
assertEquals(res.jsonPath().getString('data.user.address.city'), 'Pune');
// contract-level safety net:
res.then().body(matchesJsonSchemaInClasspath('schemas/user.json'));
- data.user.address.city - dot path walks nested objects.
- roles has Items(...) - Hamcrest matcher asserts the array contains values regardless of order.
- time(lessThan(2000L)) - the L makes it a long; guards against perf regressions.
- matchesJsonSchemaInClasspath - validates the whole structure/types, catching contract drift a field-by-field check would miss.
How to say it: "I use the given-when-then chain. In then I assert the status code, then response time with time and lessThan, then drill into nested fields using dot paths like data.user.address.city, and for arrays I use matchers like hasItems or index into orders zero. For anything reusable I extract the Response and assert in Java, and I add a JSON schema validation as a contract safety net so a renamed or wrong-typed field fails immediately."
7. Scenario: Retry for API failures (500 / 429)¶
How will you handle retry for API failures in your framework?¶
First, classify the failure - not everything should retry
- 429 Too Many Requests: rate limited - retry, and RESPECT the Retry-After header if present.
- 500 / 502 / 503 / 504: transient server/upstream issues - retry with backoff.
- 4xx like 400/401/403/404: client errors - do NOT retry, the request itself is wrong (except 401 -> refresh token once, then retry).
- Non-idempotent POST that may double-create: only retry if the API supports an idempotency key, else you risk duplicate orders.
Strategy
- Exponential backoff with jitter: wait 1s, 2s, 4s (+ random) to avoid thundering-herd; cap at a max number of attempts.
- Honor Retry-After for 429 instead of guessing.
- Log every retry with status and attempt number - visible, never silent.
- Track retry rate as a metric; a spike means a real backend problem, not just flakiness.
int max = 4; long delay = 1000;
for (int attempt = 1; attempt <= max; attempt++) {
Response r = given().auth().oauth2(TokenManager.get()).get('/orders');
int sc = r.statusCode();
if (sc < 500 && sc != 429) return r; // success or real client error
long wait = (sc == 429 && r.header('Retry-After') != null)
? Long.parseLong(r.header('Retry-After')) * 1000
: delay * (1L << (attempt - 1)); // 1s,2s,4s...
log.warn('Retry {}/{} after HTTP {} in {}ms', attempt, max, sc, wait);
Thread.sleep(wait);
}
throw new AssertionError('API failed after ' + max + ' retries');
Where to put it: a Rest Assured Filter or a resilience4j Retry wraps all calls transparently. TestNG's IRetryAnalyzer can retry a whole test, but that hides flakiness - use it sparingly and always report it, never to mask a real failure.
How to say it: "I don't blindly retry everything. I classify: 429 I retry and honor the Retry-After header, 5xx I retry with exponential backoff and jitter, and 4xx client errors I do not retry because the request is wrong - except a 401 where I refresh the token once and retry. I'm careful with non-idempotent POSTs so I don't double-create; I only retry those if there's an idempotency key. I implement it as a filter so it's transparent, cap the attempts, and log every retry as a metric so a spike surfaces a real backend issue instead of being hidden."
AVOID - Retries are not a fix: Test-level retries that turn red to green hide product bugs. Report retry counts; investigate anything that needs them regularly.
8. Scenario: Critical production defect in checkout¶
How will you write the RCA and what immediate tests will you add?¶
Step 1 - Contain first (before RCA)
- Assess impact and severity: how many users, is money affected, is checkout fully down or partial?
- Stop the bleeding: roll back the release or disable via feature flag / hotfix. Recovery before investigation.
- Communicate: raise a Sev-1/incident, notify stakeholders, start an incident timeline.
Step 2 - Write the RCA (blameless)
- Summary: one-line what happened and business impact (users, revenue, duration).
- Timeline: introduced -> deployed -> first failure -> detected -> mitigated -> resolved.
- Root cause via 5 Whys: e.g. checkout failed -> payment call threw -> response schema changed -> API contract updated without notice -> no contract test in CI.
- Detection gap: why did QA/monitoring miss it? (no negative test, no prod synthetic check).
- Corrective (fix now) + Preventive (stop recurrence) actions, each with an owner and date.
Step 3 - Immediate tests to add
- Regression test that reproduces the EXACT bug - it must fail on the buggy build and pass on the fix (red-to-green).
- Checkout happy path across every payment method (card, UPI, wallet, COD).
- Negative: declined card, expired card, insufficient funds, payment gateway timeout, network drop mid-payment.
- Double-submit / idempotency: rapid double-click must not double-charge or create two orders.
- Boundary: empty cart, max quantity, coupon/discount edges, currency and tax rounding.
- Contract test on the payment API so a schema change fails CI, not production.
- Cross-browser + mobile checkout.
- Production synthetic monitor: a scripted checkout every few minutes with alerting, so next time we detect it in minutes, not from users.
How to say it: "My first move is not the RCA - it's containment: assess impact, then roll back or flag off to stop the bleeding, and raise an incident. Then I write a blameless RCA with a timeline and a 5-Whys root cause - for a checkout case it often traces to a payment API contract change with no contract test in CI. I capture both the detection gap and corrective plus preventive actions with owners. For tests, the first one reproduces the exact defect so it goes red on the bad build and green on the fix. Then I broaden checkout coverage: all payment methods, declines and gateway timeouts, double-submit idempotency so we never double-charge, boundary cases, a payment contract test, and a production synthetic monitor with alerting so we catch it early next time."
AVOID - Never do this: Don't quietly patch prod and close it. Every critical needs a written RCA and a regression test, or it comes back.
9. Round 3: Managerial Round¶
This round is not about syntax. The manager checks: ownership, communication, how you handle conflict and pressure, prioritization, and whether you'll fit and grow. Answer with short STAR stories from real projects (situation, task, action, result).
What they're really assessing
- Ownership: do you drive quality or just run scripts?
- Conflict handling: dev disagrees a bug is a bug; you push a release date.
- Prioritization: limited time, 300 tests, a release tomorrow - what runs?
- Communication: can you explain a risk to a non-technical stakeholder?
- Growth & culture fit: why leaving, what you want next.
Common questions + how to answer
Q: Tell me about a conflict with a developer.
A: Use STAR and stay factual, not personal: 'A dev marked my bug as won't-fix. I reproduced it with a video and the exact API response, showed the user impact and data, and we agreed it was a real edge case. It shipped as a fix.' Show data-driven, low-ego resolution.
Q: You have a release tomorrow and can't run everything - what do you do?
A: Risk-based prioritization: run smoke + critical-path (checkout, login, payment) first, cover changed areas, defer low-risk stable modules, and clearly communicate what was and wasn't tested so the go/no-go is an informed decision - not silent.
Q: How do you handle pressure / a tight deadline?
A: Prioritize by risk, automate the repetitive, communicate early and honestly about what's realistic. Give one concrete example where you did this.
Q: Why are you leaving / why this role?
A: Stay positive - never bad-mouth your current employer. Frame it as growth: 'I want deeper AI/LLM quality-engineering work and bigger scale', which matches where the market and this role are going.
Q: How do you ensure quality beyond writing tests?
A: Shift-left: review requirements for testability, add contract tests, push observability and quality gates in CI, track flaky tests and escaped defects - quality as a process, not a phase.
How to say it: "I lead with ownership. I don't just execute test cases - I look at risk, push quality earlier with contract tests and CI gates, and I handle disagreements with data rather than opinion. When time is short I prioritize the critical business paths, automate the repetitive parts, and I'm always transparent about what is and isn't covered so leadership can make an informed go or no-go call."
TIP - Prepare 4-5 STAR stories: Have ready: a conflict, a tough deadline, a bug you're proud of catching, a failure you learned from, and a time you improved a process. Reuse them across behavioral questions.
10. Round 4: HR / Salary Negotiation¶
This is important and very learnable. The goal is a fair number without souring the relationship. Preparation and calm beat aggression.
Before the call - prepare
- Research the market band for your role, stack and city (Glassdoor, AmbitionBox, levels, peers). Know a realistic range.
- Know your number: your minimum (walk-away), target, and an anchor slightly above target.
- Think TOTAL compensation, not just base: bonus, ESOPs, notice period, WFH, learning budget, growth path.
- Have your value ready: your AI/LLM evaluation niche, VAPT, full automation stack - that justifies a premium.
During the negotiation
- Let them put a number first if you can. If pushed, give a RANGE anchored at/above your target ('based on my research and experience, I'm looking at X to Y').
- Justify with value and market data, not personal need ('given my AI evaluation and automation experience and the market band...').
- Stay warm and collaborative: 'I'm excited about the role - can we close the gap on the base?' Negotiation is not a fight.
- If base is capped, negotiate the rest: joining bonus, ESOPs, earlier review, title, notice-period buyout.
- Silence is a tool - after you state your number, stop talking.
- Get the final offer IN WRITING before you resign anywhere.
Scripts you can say
Q: What is your expected CTC?
A: 'Based on my experience in automation plus AI/LLM evaluation and the current market for this role, I'm looking in the range of X to Y. I'm flexible for the right overall package and growth.'
Q: That's above our budget / band.
A: 'I understand. The role and team really interest me. If the base is capped, can we look at a joining bonus, ESOPs, or an early review at six months to bridge the gap?'
Q: What's your current salary?
A: If comfortable, share; if not, deflect to expectations: 'I'd rather focus on the value I bring and the market rate for this role - I'm targeting X to Y.' (Check local norms - in some regions you needn't disclose.)
AVOID - Avoid these: Don't accept on the spot out of excitement, don't lie about competing offers, don't negotiate from personal need ('I have EMIs'), and don't resign before you have the written offer.
TIP - The one-line mindset: Anchor high but realistic, justify with value and data, negotiate the whole package, stay positive, and get it in writing.
How to say it: "I go in prepared with the market band and a clear minimum, target and anchor. I let them share first, and if asked I give a range anchored at my target, justified by my automation plus AI-evaluation experience rather than personal need. I keep it collaborative - if the base is capped I pivot to joining bonus, ESOPs or an early review. I stay positive throughout and I never resign until the final number is in writing."