AI SDET L1 — Python + Playwright Interview Q&A (Advanced)
Top 50 advanced interview questions & answers for 5–15 years experienced SDETs
Focus: Python · Playwright · Framework Design · CI/CD · GenAI-in-Testing✍️ Written by Himanshu Agarwal · himanshuai.com
📚 Full 500-question bank → himanshuai.gumroad.com/l/500-SDET-GenAI-Interview-Questions
How to use this bank
These 50 questions are calibrated for senior / lead SDET interviews where the bar is not "can you write a test" but "can you design, scale, and defend a test strategy." Each answer includes the why, not just the what, plus a curated resource so you can go deeper. Skim the question, answer it out loud, then read the model answer and close any gaps.
Legend: 🐍 Python · 🎭 Playwright · 🏗️ Architecture · ⚙️ CI/CD · 🤖 GenAI · 🔌 API
Section 1 — Python Core for SDET 🐍
Q1. How do you decide between pytest fixtures with function, class, module, and session scope in a large suite?
Scope controls setup cost vs. isolation. function scope gives maximum isolation (fresh state per test) but is expensive for things like DB connections or a browser. session scope is created once per test run — ideal for read-only expensive resources (a config object, an auth token, a base API client). module/class sit in between for grouping related tests that can share state safely.
The senior insight: never share mutable state across tests via a broad scope. A common bug is a session-scoped logged-in page object where one test's navigation leaks into the next. Use session for the token, function for the page. Combine with yield for teardown and mark expensive fixtures with clear naming.
📎 Resource: pytest fixtures — scope docs
Q2. Explain the GIL and how it affects your test parallelization strategy.
The Global Interpreter Lock allows only one thread to execute Python bytecode at a time, so threads do not give true CPU parallelism. For SDET work this matters less than people fear because test execution is overwhelmingly I/O-bound (network calls, browser waits, DB round-trips), and the GIL is released during I/O. So threads do help for I/O concurrency.
For real parallel test execution you use process-level parallelism: pytest-xdist spawns worker processes (-n auto), each with its own interpreter and GIL. The tradeoff is that state must be process-safe — no shared in-memory fixtures, unique test data per worker, and DB/namespace isolation. Note: Python 3.13+ ships an experimental free-threaded (no-GIL) build, worth mentioning to show currency.
📎 Resource: pytest-xdist docs
Q3. What is the difference between @staticmethod, @classmethod, and instance methods in the context of a Page Object base class?
Instance methods (self) operate on a specific object — e.g. login_page.enter_username(). @classmethod (cls) is bound to the class and is perfect for factory constructors — BasePage.from_context(context) that returns a configured page object without hardcoding the subclass. @staticmethod belongs to the class namespace but takes no implicit first arg — good for pure helpers like URL builders or selector formatters that logically live on the page but don't need state.
The design win: use @classmethod factories so subclasses inherit construction logic, keeping your page object hierarchy DRY.
📎 Resource: Python data model — methods
Q4. How do you handle flaky test data setup — describe an idempotent data strategy.
Flaky data comes from shared, order-dependent, or leftover state. The senior approach has three layers: (1) Isolation — each test creates its own uniquely-namespaced data (UUID/timestamp suffixes) so parallel workers never collide. (2) Idempotent setup — setup logic that can run repeatedly and converge to the same state (create-or-get, not blind create). (3) Deterministic teardown — teardown via API/DB, not UI, in a finally/fixture yield so it runs even on failure.
Prefer API-based seeding over UI-based setup: it's faster, more stable, and decouples "arrange" from the thing under test. Never rely on data seeded by a previous test.
📎 Resource: Test Data Management patterns — Martin Fowler
Q5. Explain context managers and why they matter for resource-heavy tests.
A context manager (with + __enter__/__exit__, or @contextmanager) guarantees deterministic cleanup even on exception. For SDET this is how you avoid leaked browser contexts, open file handles, un-rolled-back DB transactions, and dangling network sessions that cause slow, mysterious flakiness.
Example: wrapping a temporary feature-flag override in a context manager ensures the flag resets no matter how the test exits. In Playwright-Python, with sync_playwright() as p: and browser.new_context() used as context managers ensure teardown. Building your own with contextlib.contextmanager lets you express "set up X, guarantee we undo X" as a reusable, testable unit.
📎 Resource: contextlib docs
Q6. How would you design a custom pytest plugin or hook for your org?
pytest exposes hooks at every lifecycle point (pytest_collection_modifyitems, pytest_runtest_makereport, pytest_configure). A senior SDET builds a conftest.py or installable plugin to centralize cross-cutting concerns: auto-attaching screenshots/traces on failure (pytest_runtest_makereport), injecting a correlation ID per test into logs, dynamically skipping tests by environment marker, or re-ordering/quarantining flaky tests.
The value is consistency across teams — one plugin means every suite reports, retries, and traces the same way. Package it, version it, and distribute via your internal index.
📎 Resource: Writing pytest plugins
Q7. Describe how you'd use asyncio in test automation and its pitfalls.
asyncio lets you run many I/O-bound operations concurrently on a single thread via an event loop. In testing it shines for fan-out API calls (validating 100 endpoints, seeding data in parallel) and for Playwright's async API. Use asyncio.gather() to await many coroutines concurrently.
Pitfalls: mixing sync and async code blocks the loop (a single blocking time.sleep stalls everything — use await asyncio.sleep); shared mutable state still races; and exceptions in gather can be swallowed unless you set return_exceptions=True and inspect results. For Playwright, pick one API style (sync or async) per project — don't mix.
📎 Resource: asyncio docs
Q8. How do you enforce type safety and catch bugs before runtime in a test framework?
Use type hints plus a static checker (mypy or pyright) in CI. Types on page objects, fixtures, and API clients catch whole classes of bugs — passing a str where a Locator is expected, forgetting a return, misusing an Optional. Add TypedDict/dataclass/pydantic models for API payloads so response shape drift fails fast and loudly.
Layer this with ruff/flake8 for lint and black for format, all gated in pre-commit hooks. The point for a senior role: a test framework is production code and deserves the same static-analysis rigor as the app under test.
📎 Resource: mypy documentation
Q9. Explain mocking vs. stubbing vs. faking, with a testing example each.
All three are test doubles but differ in behavior. A stub returns canned answers (a fake get_user() that always returns a fixed dict) — used to control indirect inputs. A mock additionally records interactions and lets you assert them (verify payment_api.charge() was called once with the right amount) — used to verify indirect outputs. A fake is a working but lightweight implementation (an in-memory DB replacing Postgres) — used when you need real-ish behavior without the real dependency.
Senior nuance: over-mocking couples tests to implementation and hides integration bugs. Mock at architectural seams (external services), prefer real components inside your boundary.
📎 Resource: Mocks Aren't Stubs — Martin Fowler
Q10. How do you structure a Python test framework repo for 200+ tests across teams?
Separate framework from tests. A typical layout: core/ (base clients, config, driver factory), pages/ or components/ (page objects), api/ (API clients + models), tests/ (grouped by domain), data/ (fixtures/factories), conftest.py at the right levels, and utils/. Ship the framework as an installable package so multiple test repos depend on a versioned core instead of copy-pasting.
Enforce structure with lint rules, keep config environment-driven (12-factor: env vars, not hardcoded URLs), and layer conftests so shared fixtures live high and team-specific ones live low. This scales because teams extend, not fork.
📎 Resource: Good Integration Practices — pytest
Section 2 — Playwright Deep Dive 🎭
Q11. Explain Playwright's auto-waiting and why it reduces flakiness compared to Selenium.
Before every action, Playwright performs actionability checks: the element must be attached, visible, stable (not animating), enabled, and able to receive events. It retries these checks until they pass or the timeout hits — so you rarely need explicit sleeps or manual WebDriverWait. Web-first assertions like expect(locator).to_be_visible() also auto-retry.
This eliminates the #1 source of Selenium flakiness: acting on an element before it's ready. The senior framing: Playwright shifts waiting from your imperative code into the engine, so tests describe intent ("click Submit") not mechanics ("wait 2s then click").
📎 Resource: Auto-waiting — Playwright
Q12. What is the difference between a Locator and an ElementHandle, and why does it matter?
An ElementHandle points to a specific DOM node captured at a moment in time — if the DOM re-renders, it goes stale. A Locator is a lazy, re-evaluated description of how to find an element; it re-queries the DOM every time you use it. Because SPAs constantly re-render, Locator is strongly preferred — it sidesteps stale-element errors by design.
Locators also enable strictness (they fail if a selector matches multiple elements, catching ambiguous tests) and chaining/filtering. Reserve ElementHandle for rare cases needing a raw node reference. In modern Playwright, you almost never touch handles.
📎 Resource: Locators — Playwright
Q13. Walk through Playwright's tracing and how you use it in CI.
The trace is a complete recording of a run: DOM snapshots before/after each action, screenshots, network calls, console logs, source, and timings — viewable in the Trace Viewer as a timeline. You enable it via context.tracing.start(screenshots=True, snapshots=True, sources=True) and stop/save on completion.
The senior pattern is trace-on-failure-or-retry: capture traces only when a test fails or on first retry, attach the .zip as a CI artifact, and open it in the hosted Trace Viewer. This turns "it failed on the pipeline but passes locally" into a fully reproducible, click-through post-mortem — no re-running required.
📎 Resource: Trace Viewer — Playwright
Q14. How do you handle authentication efficiently across hundreds of tests?
Log in once, save the authenticated state, reuse it. context.storage_state(path="auth.json") serializes cookies + localStorage; new contexts start with new_context(storage_state="auth.json") already logged in. This avoids paying the login cost per test and keeps the UI login flow tested exactly once (in its own dedicated test).
For multiple roles, generate one state file per role in a session-scoped fixture, then map tests to roles. Refresh state when tokens expire. This pattern cuts suite runtime dramatically and removes login as a flakiness source for everything downstream.
📎 Resource: Authentication — Playwright
Q15. Explain network interception and three testing scenarios where it's essential.
page.route() lets you intercept, inspect, modify, mock, or block requests. Three high-value scenarios: (1) Mock third-party/unstable APIs — return deterministic responses so a flaky payment gateway or ad service doesn't break your UI test. (2) Simulate error/edge states — force a 500, a timeout, or an empty list to test error UI you can't easily trigger organically. (3) Assert outgoing requests — verify the frontend sends the correct payload/headers on submit.
Bonus: block heavy assets (images, analytics) to speed runs. The senior point: interception lets you test the frontend in isolation from backend variability, and test failure paths that are otherwise near-impossible to reproduce.
📎 Resource: Network — Playwright
Q16. How does Playwright handle multiple tabs, popups, and iframes?
Tabs/popups: each is a Page; you capture popups by awaiting the page.expect_popup() event (or listen on the context's page event) rather than guessing. iframes: you can't reach into them with normal selectors — you scope via page.frame_locator("iframe#x").locator(...), which handles the frame boundary and still auto-waits. New windows belong to the same BrowserContext, so they share auth/storage.
The trap is racing the popup — always set up the expectation before the action that triggers it. FrameLocator is the modern, retry-friendly way to work across frames (payment widgets, embedded editors, ad slots).
📎 Resource: Frames — Playwright
Q17. What's your strategy for cross-browser and mobile emulation testing in Playwright?
Playwright ships Chromium, Firefox, and WebKit from one API, so cross-browser is a parametrized fixture (browser_name) rather than separate stacks. Mobile is emulated via device descriptors (playwright.devices["iPhone 13"]) that set viewport, user-agent, touch, and device-scale-factor — real DOM/JS runs, only the environment is emulated.
Senior nuance: emulation catches layout/responsive/touch bugs but not true device GPU/engine quirks — for that you still need a real-device cloud. So the strategy is: emulate broadly and cheaply in CI for coverage, reserve real-device runs for release-critical smoke.
📎 Resource: Emulation — Playwright
Q18. How do you write resilient selectors, and what's your locator priority order?
Prefer user-facing, semantic locators that survive refactors: get_by_role (accessibility role + name) first, then get_by_label, get_by_placeholder, get_by_text, and get_by_test_id for app-controlled hooks. Avoid brittle CSS/XPath tied to DOM structure or auto-generated classes — they break on every re-style.
The reasoning is twofold: role/label locators test what the user actually perceives (so they double as accessibility checks), and data-testid gives a stable contract between dev and QA that survives visual redesigns. Codify this priority in team guidelines and lint against raw XPath.
📎 Resource: Locators best practices — Playwright
Q19. Explain visual regression testing in Playwright and how you handle false positives.
expect(page).to_have_screenshot() captures a baseline and pixel-compares future runs, flagging visual diffs. To control false positives: pin a consistent rendering environment (run baselines in the same OS/browser as CI — usually via the official Docker image, since font rendering differs by platform), mask dynamic regions (timestamps, avatars, ads) with the mask option, and set a sensible max_diff_pixels/threshold to absorb sub-pixel antialiasing noise.
Senior discipline: baselines are reviewed artifacts, updated deliberately via a PR, never blindly --update-snapshots. Freeze animations and network state before snapping.
📎 Resource: Visual comparisons — Playwright
Q20. When would you NOT use Playwright, and what would you use instead?
Playwright is a browser tool — the wrong hammer for many jobs. For pure API/contract testing use requests/httpx + pytest (or Pact for contracts) — driving a browser to test an API is slow and indirect. For unit/component logic use the app's own unit framework or component testing, not full E2E. For load/performance use k6/Locust/JMeter. For native mobile apps use Appium/Espresso/XCUITest. For accessibility audits, pair with axe-core.
The senior message: match the tool to the test pyramid layer. Over-relying on E2E (the "ice-cream cone" anti-pattern) yields slow, flaky suites; push tests down the pyramid wherever possible.
📎 Resource: Test Pyramid — Martin Fowler
Section 3 — Framework Architecture & Design 🏗️
Q21. Compare Page Object Model, Screenplay pattern, and component-based design.
POM encapsulates a page's selectors + actions behind a class — simple, familiar, but pages can bloat into god-objects and it couples tests to page structure. Screenplay models actors performing tasks via abilities ("Actor.attempts_to(Login)") — highly composable and readable for complex user journeys, but more upfront ceremony. Component-based mirrors modern SPAs: model reusable UI components (a DataGrid, a DatePicker) rather than whole pages, so a shared widget is defined once.
Senior take: components + task-level abstractions age best for component-driven frontends; pure POM strains when the same widget appears on 20 pages. Choose by app architecture, not fashion.
📎 Resource: Screenplay Pattern — SerenityJS docs
Q22. How do you design a framework to run against multiple environments (dev/stage/prod)?
Externalize all environment-specific config — base URLs, credentials, feature flags, timeouts — out of code, following 12-factor. Load via env vars or per-env config files selected by a single ENV switch, with secrets pulled from a vault/CI secret store, never committed. A typed config object (pydantic) validates presence and shape at startup so a missing var fails loudly, not mid-test.
Add environment markers so prod-unsafe tests (those that mutate data) are auto-skipped in prod, and smoke subsets are tagged for post-deploy verification. One codebase, many targets, zero hardcoded hosts.
📎 Resource: The Twelve-Factor App — Config
Q23. What metrics prove your automation framework is healthy?
Beyond pass rate: flakiness rate (tests that pass on retry / total — the single most important quality signal), mean time to detect/diagnose (are failures actionable?), suite duration & trend (is it slowing releases?), coverage of critical user journeys (not line coverage — risk coverage), false-positive rate (failures caused by the test, not the product), and escaped-defect rate (bugs that reached prod despite green suites).
The senior insight: a 100% pass rate can hide a useless suite. Track flakiness and escaped defects to prove the framework actually protects the product, and quarantine flaky tests instead of letting them erode trust.
📎 Resource: Test flakiness — Google Testing Blog
Q24. How do you handle test retries without hiding real bugs?
Retries are a double-edged sword: they mask both infra flakiness (good to absorb) and genuine intermittent product bugs (bad to hide). The disciplined approach: allow limited retries (usually 1–2) but record every retry, tag tests that only pass on retry, and surface a flakiness dashboard. A test that needs retries is a bug ticket, not a solved problem.
Never retry infinitely, never retry data-mutating tests blindly, and quarantine persistently flaky tests out of the blocking gate (so they stop failing the pipeline) while a ticket tracks the fix. Retry is a shock absorber, not a cure.
📎 Resource: pytest-rerunfailures
Q25. Design a reporting strategy for a suite consumed by devs, QA, and management.
Different audiences, different views from one source of truth. Devs need deep failure detail — stack trace, Playwright trace, screenshot, network log, correlation ID — attached per test (Allure or similar). QA needs run-over-run trends, flaky lists, and coverage by feature. Management needs a rolled-up health signal: pass trend, release-readiness, and risk areas — a dashboard, not a log.
Emit structured results (JUnit XML / Allure results) from the run, then let tooling render each view. Integrate with Slack/Teams for instant failure alerts on the main branch. The principle: capture rich data once, present it appropriately per stakeholder.
📎 Resource: Allure Report
Q26. How do you keep a large test suite fast as it grows?
Attack it on multiple axes: parallelize (pytest-xdist -n auto, sharded across CI runners), push tests down the pyramid (replace slow E2E with API/unit where the risk allows), seed via API not UI, reuse auth state, block non-essential network (images/analytics), and run the right subset at the right time — smoke on every commit, full regression nightly, impacted-tests on PRs via test selection.
Continuously prune: delete redundant tests, merge overlapping ones, and profile the slowest tests each sprint. A suite that takes an hour won't be run; speed is a feature that determines whether the suite gets used at all.
📎 Resource: Test selection / impact analysis — Martin Fowler CI
Q27. Explain how you'd introduce contract testing between microservices.
Contract testing (e.g. Pact) verifies that a consumer and provider agree on the API shape without spinning up both services together. The consumer's tests generate a contract (expected requests/responses); the provider verifies it can satisfy that contract in its own pipeline. Contracts are shared via a broker.
This catches breaking API changes at build time instead of in a slow, flaky end-to-end environment, and lets teams deploy independently with confidence. Senior framing: contract tests fill the gap between fast-but-blind unit tests and slow-but-real E2E — they give integration confidence at unit-test speed.
📎 Resource: Pact — Introduction
Q28. How do you test event-driven / async systems (queues, webhooks)?
Async systems have no immediate response to assert on, so you replace "check return value" with polling with timeout (await the eventual state — e.g. poll the DB or an API until the record appears, with a bounded retry) rather than fixed sleeps. For webhooks, stand up a test receiver (or use a service like a request-bin) to capture and assert the callback payload.
Key challenges to name: eventual consistency (assert the end state, not intermediate timing), ordering (don't assume FIFO unless guaranteed), idempotency (verify duplicate events don't double-process), and poison messages (test dead-letter handling). Deterministic correlation IDs let you trace one event end-to-end.
📎 Resource: Testing event-driven systems — AWS
Q29. What's your approach to test data factories vs. fixtures vs. builders?
Fixtures provide ready-made context (a logged-in user, a DB connection). Factories (factory_boy, faker) generate fresh, valid, randomized-but-controlled data on demand — great for volume and avoiding hardcoded IDs. The Builder pattern constructs complex objects step-by-step with sensible defaults and only the fields the test cares about overridden (OrderBuilder().with_status("shipped").build()), which keeps tests readable and resilient to model changes.
Senior guidance: default everything, override only what's under test. This makes intent obvious ("this test is about shipped orders") and means adding a required field to the model doesn't break 200 tests — you fix the builder once.
📎 Resource: factory_boy docs
Q30. How do you approach testing in a CI pipeline with zero-downtime deployments?
Layer tests by deploy stage: pre-merge run fast unit/API/component + a small E2E smoke; pre-deploy run full regression against staging; post-deploy run a smoke/synthetic subset against the just-deployed environment (canary or blue-green) to validate the live release; and continuously run synthetic monitoring in prod. Prod tests must be read-only or use disposable test accounts.
For zero-downtime, integrate tests with the rollout: a failing post-deploy smoke triggers an automatic rollback (canary abort). The senior point is that testing doesn't stop at merge — it becomes part of the deployment control loop, gating traffic promotion.
📎 Resource: Progressive delivery / canary — Martin Fowler
Section 4 — CI/CD, Docker & DevOps ⚙️
Q31. How do you containerize Playwright tests, and why use the official image?
Use the official mcr.microsoft.com/playwright/python image — it bundles the exact browser binaries, matching OS libraries, and fonts that Playwright's version needs. This solves the two biggest "works on my machine" problems: missing system dependencies and font-rendering differences that wreck visual snapshots. Pin the image tag to your Playwright version so browser and library versions never drift apart.
In CI, run tests in this container so local, CI, and baseline-generation environments are byte-identical. Mount results/traces to a volume for artifact upload. Containerization turns environment parity from a hope into a guarantee.
📎 Resource: Docker — Playwright
Q32. Describe a robust CI pipeline for a Playwright + pytest suite.
Stages: (1) Lint/type-check (ruff, mypy) — fail fast on cheap checks. (2) Build/install in the pinned Playwright container, cache dependencies. (3) Test sharded across parallel runners (--shard or xdist), with retries limited and traces captured on failure. (4) Report — publish JUnit/Allure results, upload traces/screenshots as artifacts, post a summary to the PR/Slack. (5) Gate — block merge on failures, surface flaky results separately.
Add matrix builds for cross-browser, cache browser binaries, and run the full regression nightly while PRs run a fast subset. The senior signal: fast feedback on PRs, thorough coverage on a schedule, everything reproducible.
📎 Resource: CI — Playwright
Q33. How do you manage secrets and credentials in test pipelines securely?
Never hardcode or commit secrets. Store them in a secrets manager or CI secret store (GitHub Actions secrets, Vault, cloud KMS), inject as environment variables at runtime, and scope them least-privilege (test accounts, not admin). Rotate regularly and use short-lived tokens where possible. Mask them in logs (most CI auto-masks registered secrets) and scrub them from traces/screenshots — a Playwright trace can capture a token in a network call, so review what artifacts expose.
For local dev, use .env files that are gitignored with a committed .env.example template. The mindset: test infra is a real attack surface — credential leaks from CI are a common breach vector.
📎 Resource: OWASP Secrets Management Cheat Sheet
Q34. What's your strategy for sharding and parallelizing across CI runners?
Two levels: within a runner, pytest-xdist -n auto uses all cores; across runners, split the suite into shards (--shard=1/4 … 4/4 or xdist's distribution) so N machines each run a slice, cutting wall-clock time near-linearly. Balance shards by historical test duration, not count, so no runner is the long pole.
Requirements that make this safe: full test isolation (unique data per worker), no shared mutable state, and idempotent setup/teardown. Then aggregate all shards' JUnit/Allure results into one report. Senior nuance: parallelism exposes hidden ordering dependencies — flaky-under-parallel tests reveal real isolation bugs worth fixing.
📎 Resource: Sharding — Playwright
Q35. How do you decide what runs on every commit vs. nightly vs. pre-release?
Optimize for fast feedback where it matters. On every PR/commit: lint, type-check, unit, API, and a thin critical-path E2E smoke — target a few minutes. Nightly/scheduled: full regression, cross-browser matrix, visual suite, long-running and data-heavy tests. Pre-release: full regression against a production-like environment plus manual exploratory on high-risk areas. Post-deploy: synthetic smoke against live.
The principle is risk-vs-cost: developers need sub-10-minute PR feedback or they context-switch, so heavy suites move to schedules. Use test impact analysis to run only tests affected by a change on PRs where tooling allows.
📎 Resource: Continuous Integration — Martin Fowler
Section 5 — API & Integration Testing 🔌
Q36. How do you combine UI and API testing in one Playwright framework?
Playwright ships an APIRequestContext (page.request / playwright.request) that shares cookies and auth state with the browser context — so you can call APIs and drive the UI in the same test with the same session. Use API calls for arrange (seed data fast) and assert (verify backend state after a UI action), keeping the UI focused on the actual user interaction under test.
Example: create an order via API, verify it renders correctly in the UI, then check via API that a UI "cancel" actually updated the backend. This hybrid keeps tests fast and lets you assert both what the user sees and what truly happened server-side.
📎 Resource: API testing — Playwright
Q37. What layers of API testing exist and where does each add value?
Unit (individual functions/handlers, mocked deps) — fast, catch logic bugs. Contract (consumer/provider agreement) — catch breaking interface changes cheaply. Integration (service + real DB/dependencies) — catch wiring and serialization bugs. End-to-end API (full request across the real stack) — catch environment/config issues. Schema/validation (assert responses match an OpenAPI/JSON-schema) — catch drift automatically. Plus negative (4xx/5xx paths), auth/authz, and performance/load.
Senior framing: most API confidence should come from contract + integration layers (fast, targeted), with full E2E kept thin. Auto-validate every response against the published schema to catch undocumented changes.
📎 Resource: OpenAPI Specification
Q38. How do you test API idempotency, rate limiting, and pagination?
Idempotency: send the same request (with an idempotency key) twice and assert the side effect happens once and both responses match — critical for payments/retries. Rate limiting: burst past the limit and assert you get 429 with correct Retry-After/rate headers, then confirm the limit resets on schedule. Pagination: assert page size, next/prev cursors, total counts, no duplicates across pages, stable ordering, and correct behavior at boundaries (empty last page, single item, out-of-range page).
Senior nuance for pagination: test consistency under concurrent writes (does inserting a row shift offset-based pages and cause skips/dupes?) — cursor-based pagination avoids this; offset-based doesn't.
📎 Resource: REST API design — idempotency (Stripe docs)
Q39. Explain your approach to testing authentication and authorization flows.
Authentication = who you are; authorization = what you're allowed to do — test both separately. For authn: valid login, invalid creds, expired/tampered tokens, refresh flow, logout invalidation, and MFA paths. For authz, the high-value work is negative and cross-tenant testing: verify a low-privilege user is denied admin endpoints, and that user A cannot access user B's resources by ID manipulation (IDOR / broken object-level authorization — a top API security risk).
Automate a role-vs-endpoint matrix so every protected route is tested against every role. Senior emphasis: the bugs that hurt aren't "can the right user get in" but "can the wrong user get in" — test the denials hardest.
📎 Resource: OWASP API Security Top 10
Q40. How do you validate data integrity across a UI action that spans multiple services?
Trace the action end-to-end with a correlation ID and assert state at each hop. Example for "place order": UI submits → assert the order API returns 201 → assert the DB row exists with correct fields → assert an event was published to the queue → assert the downstream service (inventory/email) consumed it and updated its own state. Because these are async, poll with timeout at each async boundary rather than asserting instantly.
Senior framing: don't just trust the happy UI toast — verify the system of record and every side effect, because a green UI can hide a dropped event or a half-committed transaction. Correlation IDs make the whole chain observable and debuggable.
📎 Resource: Distributed tracing — OpenTelemetry
Section 6 — GenAI & AI in Testing 🤖
Q41. How can LLMs assist test-case generation, and what are the risks?
LLMs accelerate the arrange and ideation work: draft test cases from a user story or acceptance criteria, generate boundary/edge-case ideas a human might miss, produce test data variations, scaffold page objects from a DOM, and translate manual test steps into code. They're excellent at breadth and boilerplate.
The risks a senior must name: hallucinated selectors/APIs that don't exist, plausible-but-wrong assertions, over-trust (generated tests that pass without actually verifying anything meaningful — "assert True"), missing domain/business context, and non-determinism. The rule: LLM output is a draft reviewed by a human, never merged unread. Use it to expand coverage ideas, then validate every generated test actually fails when the behavior breaks.
📎 Resource: Anthropic — Claude for coding/testing
Q42. What is self-healing test automation and what are its real limitations?
Self-healing frameworks detect when a locator breaks and automatically try alternative strategies (nearby attributes, text, visual position, ML-scored candidates) to re-find the element, then optionally suggest a fix. The upside is fewer failures from trivial selector churn (a renamed class) and lower maintenance on volatile UIs.
The senior critique: self-healing can mask real regressions — if a button moved or changed because of an actual bug, "healing" past it hides the defect. It can also heal onto the wrong element and produce false passes, which is worse than a false failure. Treat healing as a maintenance-reduction signal with human review, gate it behind confidence thresholds, and always log/flag heals for audit — never let it silently rewrite intent.
📎 Resource: Healenium (open-source self-healing)
Q43. How would you test an LLM-powered feature in your own product (e.g. a chatbot)?
You can't assert exact string equality on non-deterministic output, so you shift to property/behavior-based evaluation: check that responses satisfy criteria (contains required facts, correct format/JSON schema, stays on-topic, cites sources, respects length limits) rather than matching a golden string. Use an evaluation set of representative inputs with graded expectations, and consider LLM-as-judge (a model scores another model's output against a rubric) for subjective quality — validated against human labels.
Critically, test safety and failure modes: prompt injection, jailbreaks, PII leakage, hallucination on out-of-scope questions, and refusal behavior. Add regression evals that run on every prompt/model change. Senior framing: you're testing a distribution of acceptable outputs, not a fixed answer, plus a hard safety boundary.
📎 Resource: Anthropic — building evals
Q44. What are non-determinism, hallucination, and prompt injection — and how do you test for each?
Non-determinism: same input → different outputs (temperature, sampling). Test with property-based assertions and run multiple samples to check consistency within tolerance; lower temperature for testable paths. Hallucination: confident but false/fabricated output. Test with grounded eval sets where ground truth is known, and check factuality/citation against a source of truth. Prompt injection: malicious input that hijacks the model's instructions ("ignore previous instructions and..."). Test with an adversarial suite that attempts to override system prompts, exfiltrate hidden context, or trigger unauthorized tool calls, and assert the system refuses/sanitizes.
Senior framing: these are the three defining test challenges of GenAI features — deterministic test assumptions break, so you test statistically for quality and adversarially for safety.
📎 Resource: OWASP Top 10 for LLM Applications
Q45. How do you evaluate AI test tools before adopting them (build vs. buy)?
Assess against concrete criteria: accuracy (false-positive/negative rate on your app, measured in a pilot, not the vendor demo), explainability (can you see why it healed/flagged something?), integration (does it fit your CI, language, reporting?), maintainability & lock-in (can you export tests/data if you leave?), cost at scale, security/data handling (does your app data leave your boundary — a dealbreaker for regulated domains?), and team skill fit.
Run a time-boxed pilot on a real, representative slice and compare against your current baseline with hard numbers (maintenance hours saved, flakiness delta, escaped defects). Senior discipline: resist hype — many AI-testing tools demo beautifully and underperform on messy real apps. Buy for commoditized pain, build where it's your differentiator.
📎 Resource: ThoughtWorks Technology Radar
Q46. How would you use AI to reduce test flakiness and maintenance?
Several concrete applications: flaky-test detection — ML clusters failures by pattern to auto-identify and quarantine flaky tests instead of manual triage. Root-cause suggestion — feed a failure (trace, logs, screenshot, diff) to an LLM to draft a probable cause and fix, speeding diagnosis. Locator resilience — AI-scored locators/self-healing to survive cosmetic UI churn (with the review caveats from Q42). Test-impact selection — ML predicts which tests a code change is likely to affect, so PRs run a smart subset. Log summarization — condense noisy CI output into an actionable summary.
Senior framing: AI is best as a triage accelerator and signal-ranker, not an autonomous fixer. It reduces the human time per failure; humans still own the decision. Measure whether it actually cuts maintenance hours before trusting it.
📎 Resource: Google Testing Blog — flaky tests
Section 7 — Senior / Lead Behavioral & Strategy 🎯
Q47. How do you decide what to automate and what to leave manual?
Automate what is stable, repetitive, high-value, and high-risk: regression of critical user journeys, data-driven scenarios, smoke/sanity, and anything run frequently. Leave manual (or exploratory) what is volatile, subjective, or one-off: brand-new features still changing daily (automating churns waste), visual/UX aesthetics and usability judgment, exploratory testing to find unknown unknowns, and low-value edge cases where automation cost exceeds risk reduced.
The framing to give: automation is an investment with maintenance cost, so ROI = (risk reduced × run frequency) − (build + maintenance cost). Automate the boring, stable, business-critical middle; keep human brains on discovery and judgment. Exploratory testing remains irreplaceable.
📎 Resource: Exploratory testing — Ministry of Testing
Q48. How do you drive a quality culture and shift-left in an org that treats QA as a gate?
Move quality upstream and everywhere rather than being the last gate. Concrete levers: get SDETs into refactoring and design reviews so testability is built in; write acceptance criteria and test scenarios during story grooming (BDD/three-amigos), not after dev; give developers fast local test tooling so they own unit/component coverage; make the pipeline the gate (automated quality signals block merges) instead of a human QA sign-off; and publish quality metrics so the whole team owns them.
The senior message: reframe QA from "we catch bugs at the end" to "we build the systems and culture that prevent bugs and give everyone fast feedback." Quality is a whole-team property; the SDET is an enabler and coach, not a bottleneck.
📎 Resource: Shift-left testing — Martin Fowler / Continuous Delivery
Q49. A critical release is tomorrow and the regression suite has 30 new failures. Walk me through your triage.
First, stabilize the signal: are failures product bugs, test bugs, or environment issues? Bucket fast — re-run once to strip obvious flakiness, then group by pattern (a single broken shared component or auth change often causes a cluster, so 30 failures may be 2 root causes). Use traces/screenshots to diagnose without re-running.
Then prioritize by risk: map each real failure to user/business impact and the release's critical paths. Communicate a clear go/no-go recommendation with data — "these 3 are genuine P1 blockers on checkout, these 27 are a test-side selector break from a known refactor." Propose mitigations (hotfix, feature-flag off, scoped rollback, or ship-with-known-issue) rather than a binary panic. Senior behavior: calm, evidence-based communication and a decision the release owner can act on — not "everything's red, we can't ship."
📎 Resource: Risk-based testing — ISTQB glossary
Q50. How do you keep your testing skills current in a fast-moving AI/tooling landscape?
Show a system, not vibes. Follow primary sources (Playwright/pytest release notes, tool changelogs, official AI-lab docs) so you know what actually changed vs. hype; build small spikes/POCs with new tools on real problems rather than reading about them; contribute to or read open-source test frameworks to see idiomatic patterns; engage a community (Ministry of Testing, conferences, focused newsletters); and teach — writing/mentoring forces depth.
For AI specifically: run your own evals on new models/tools against your actual suite instead of trusting benchmarks. The senior signal: you have a repeatable learning loop and a filter for signal-vs-noise, so you adopt what genuinely helps and skip what doesn't — and you can justify each choice with evidence.
📎 Resource: Ministry of Testing community
🚀 Go deeper — the full 500-question bank
This is 50 of 500. The complete bank covers advanced Python, Playwright internals, framework architecture, CI/CD, performance, security, and the full GenAI-in-testing track — with model answers calibrated for L1–L3 SDET interviews.
👉 Get all 500 questions: himanshuai.gumroad.com/l/500-SDET-GenAI-Interview-Questions
Written by **Himanshu Agarwal* — himanshuai.com*
Share this with someone preparing for their next SDET interview.
Top comments (0)