Interview Prep · 2025 Edition
Top 100 SDET
Interview Questions
Every question top companies ask — with expert answers. Selenium, Java, API Testing, Framework Design, CI/CD, BDD and more. Filterable. Trackable. Free.
100Questions
10Topics
32Hard Level
9+Companies
FreeAlways
01
findElement() returns a single
WebElement — it throws NoSuchElementException if nothing is found. findElements() returns a List<WebElement> — it returns an empty list (never throws) if nothing matches. Use findElements().isEmpty() to safely check presence without try-catch.02
Implicit Wait sets a global timeout for every
findElement call — simple but can cause unpredictable slowdowns when combined with explicit waits. Explicit Wait (WebDriverWait + ExpectedConditions) waits only for a specific condition on a specific element — preferred for most cases. Fluent Wait extends explicit wait with configurable polling interval and exception ignoring — best for elements that appear/disappear intermittently. Best practice: use explicit wait with explicit expected conditions and avoid mixing implicit + explicit waits.03
Stale element occurs when a DOM node is replaced after you located it. Solutions: 1) Re-locate the element inside a retry loop. 2) Use
ExpectedConditions.refreshed() with WebDriverWait. 3) Use Page Object Model — relocate inside methods rather than storing elements as fields. 4) If using POM with @FindBy, use PageFactory with lazy init. Root cause is usually an Angular/React re-render — add a wait for page stability before interacting.04
Structure: Base page class with driver, common methods (waitFor, scroll, screenshot). Each page extends BasePage. Separation: Locators as private static finals; no test logic inside pages — pages expose business actions like
login(email, pwd) not clickEmailField(). Fluent interface: return this or the next page object for chaining. Factory: PageFactory with @FindBy and initElements(). Components: Extract re-used UI pieces (header, modal, datepicker) as separate component objects. Thread-safe: use ThreadLocal<WebDriver> for parallel execution. Never put assertions inside page objects — assertions belong in tests.05
driver.close() closes only the currently focused window/tab but keeps the WebDriver session alive. driver.quit() closes all windows and kills the entire WebDriver process, releasing all resources. Always call quit() in your @AfterSuite teardown to prevent zombie chromedriver processes, especially in CI environments.06
Three main approaches: 1) TestNG parallel="tests" in XML + ThreadLocal WebDriver — each thread gets its own driver instance. 2) Selenium Grid 4 — hub/node architecture; set
RemoteWebDriver with DesiredCapabilities pointing to Grid URL. 3) Docker + Grid — docker-compose with selenium/hub and selenium/node-chrome/firefox images; scale nodes dynamically. Key rule: never share WebDriver instance across threads — use ThreadLocal<WebDriver> with init in @BeforeMethod and remove() in @AfterMethod.07
Strategies: 1) Use stable attributes —
data-testid, name, aria-label. 2) Text-based XPath: //button[contains(text(),'Submit')]. 3) Partial attribute: //input[contains(@id,'username')] or starts-with(@id,'user_'). 4) Positional XPath relative to stable parent: //div[@class='login-form']//input[@type='text']. 5) CSS attribute selectors: [data-cy='submit-btn']. Advocate for data-testid attributes with developers — this is the best long-term solution.08
Actions class provides advanced user interactions beyond simple clicks and typing. Use it for: Hover (moveToElement), right-click (contextClick), double-click (doubleClick), drag-and-drop (dragAndDrop), keyboard combos (keyDown(Keys.CONTROL).sendKeys("a")). Always call .build().perform() to execute the action chain. In Selenium 4, perform() alone also works.09
Encapsulation: BasePage hides WebDriver — tests access it through public methods only. Inheritance: Every page class extends BasePage — gets driver, wait, navigation for free. Polymorphism: A
LoginStrategy interface with AdminLogin and UserLogin implementations — test code calls strategy.login() without knowing the type. Abstraction: WebDriver is an interface — test code doesn't care whether it's Chrome or Firefox underneath.10
S — Single Responsibility: Each page object handles one page only. Each test method tests one scenario. O — Open/Closed: Add new test data via new DataProviders — don't modify existing tests. L — Liskov: Subclasses of BasePage can replace BasePage without breaking tests. I — Interface Segregation: Separate interfaces for Navigation, Assertions, APIClient — don't force test classes to implement unneeded methods. D — Dependency Inversion: Tests depend on WebDriver interface, not ChromeDriver directly — inject driver via constructor. Most impactful for QA: SRP and DIP — they keep frameworks maintainable.
11
ArrayList uses a dynamic array — O(1) random access, O(n) insert/delete in middle. LinkedList uses doubly-linked nodes — O(1) insert/delete at ends, O(n) random access. In test automation: use
ArrayList for storing test data, locators, results — you read by index frequently. Use LinkedList as a queue for processing test steps sequentially (rare). Most QA code uses ArrayList or just List interface for abstraction.12
Generics provide compile-time type safety without casting. In test frameworks:
PageFactory<T extends BasePage> — a factory method T getPage(Class<T> clazz) creates any page type safely. DataProvider<T> — typed test data containers. List<WebElement> — type-safe element collections. Bounded generics: T extends Comparable<T> lets you write reusable sort/filter utilities for test data. Wildcards: List<? extends TestResult> for methods that read any result subtype.13
Lambda is an anonymous function — syntax:
(params) -> expression. In test automation: 1) Fluent Wait conditions: wait.until(driver -> driver.findElement(By.id("btn")).isDisplayed()). 2) Filtering results: elements.stream().filter(e -> e.getText().contains("Error")).collect(toList()). 3) Sorting test data: testData.sort((a,b) -> a.getName().compareTo(b.getName())). 4) Thread-safe operations with Runnable/Callable for parallel test setup.14
== compares reference equality — whether two variables point to the same memory object. .equals() compares content/value equality — defined by the class. Critical for test automation: "text" == "text" may return true due to string interning, but element.getText() == "Login" will almost always return false — always use .equals() or assertEquals() for string comparisons in assertions.15
Each thread in Java has its own stack (local variables) but shares the heap (objects). ThreadLocal provides a per-thread value stored in a map keyed by thread identity. Pattern for parallel Selenium:
private static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>(). In @BeforeMethod: driverThread.set(new ChromeDriver()). Getter: static WebDriver getDriver() { return driverThread.get(); }. In @AfterMethod: driverThread.get().quit(); driverThread.remove(). Always call remove() to prevent memory leaks in thread pools.16
@BeforeClass runs once before the first test method in a class — ideal for creating a single WebDriver instance reused across all tests (faster). @BeforeMethod runs before each test method — ideal when each test needs a fresh browser state (more isolated, more reliable). Best practice: prefer @BeforeMethod for UI tests to avoid state leakage; use @BeforeClass for read-only API tests or expensive setup operations.17
A
@DataProvider method returns Object[][] — each inner array is one test run. From array: return new Object[][]{{ "user1","pass1" },{ "user2","pass2" }}. From Excel: use Apache POI to read an XLSX sheet and build the 2D array. From JSON: use Gson/Jackson to parse a JSON array. Cross-class: @Test(dataProvider="users", dataProviderClass=TestData.class). Parallel data: @DataProvider(parallel=true) runs each data row in its own thread — pair with ThreadLocal driver.18
SoftAssert collects assertion failures without stopping the test — the test continues until all assertions are checked, then sa.assertAll() reports all failures at once. Use it when: you need to verify multiple fields on the same page (form validation, dashboard data) and want to see ALL failures in one run. Don't use it when: each assertion is a prerequisite for the next step — a login failure shouldn't let the test keep going. Important: create a new SoftAssert per test method — never share across tests.19
Implement
ITestListener interface: override onTestStart, onTestSuccess, onTestFailure, onTestSkipped. In onTestFailure: capture screenshot, log to Extent Reports, attach to report. Register it: via @Listeners(MyListener.class) on test class, or globally in testng.xml under <listeners>. Also implement ISuiteListener for suite-level events like emailing the report in onFinish. Use ITestResult.getThrowable() to get the exception and format error messages.20
@Test(groups={"smoke","login"}) tags a test with logical group names. In testng.xml use <groups><run><include name="smoke"/></run></groups> to run only smoke tests. In Maven: -Dgroups=smoke. Common group strategy: smoke (10-15 critical tests, run on every build), regression (full suite, run nightly), flaky (tag known-flaky tests and exclude from CI until fixed).21
PUT is idempotent and replaces the entire resource — if you omit fields, they get nullified. PATCH does a partial update — only the fields you send are changed. Testing implications: For PUT, send the full payload in positive tests and verify missing fields are reset. For PATCH, test that only specified fields change and others remain unchanged. Idempotency test: call PUT/PATCH twice with the same data — result should be identical both times.
22
Authentication tests: valid credentials → 200; wrong password → 401; expired token → 401; missing token → 401. Authorization tests: user role accessing admin endpoint → 403; user A accessing user B's data → 403 (IDOR test). Token tests: tampered JWT → 401; JWT with wrong signature → 401. REST Assured:
given().header("Authorization","Bearer "+token). OAuth2 flow test: exchange auth code for token, verify token scopes, use refresh token. Always test negative cases — auth bugs have critical security impact.23
Extract values from one response and inject into the next request. Pattern:
String userId = given().body(payload).when().post("/users").then().statusCode(201).extract().path("id"); then use userId in subsequent calls. For tokens: POST to /login, extract token from response, set as a RequestSpecification header using RestAssured.requestSpecification. For setup: Create test data via API in @BeforeMethod and delete in @AfterMethod — this is faster and more reliable than UI-based test data setup.24
Contract testing verifies that consumer and provider agree on the API contract without needing both running simultaneously. Flow: Consumer writes a Pact test defining the expected request/response. Pact framework generates a JSON contract file. Provider runs a verification test against the Pact file using a mock — no consumer needed. Why it matters: catches API breaking changes before deployment. Better than E2E integration tests because it's fast and isolated. Implementation: Consumer uses
PactDslWithProvider to define interactions; Provider uses @PactVerification with PactBroker URL. Deploy to Pact Broker for sharing contracts.25
A stub returns a pre-configured fixed response regardless of input — it replaces a dependency passively. Example: WireMock stub always returns
200 { "status":"ok" } for any GET to /health. A mock has expectations set upfront — it verifies that specific calls were made with specific parameters, and fails the test if they weren't. Example: a mock verifies that the payment service was called exactly once with the correct amount. Rule of thumb: use stubs for isolation; use mocks when call verification matters.26
Schema validation ensures API responses always return the correct structure, types and required fields. JSON Schema approach in REST Assured:
given()...then().assertThat().body(matchesJsonSchemaInClasspath("user-schema.json")). The JSON schema file defines type, required fields, properties with types. Why it matters: catches when a developer renames a field, changes a string to an integer, or removes a required field — changes that break consumers silently. Run schema validation on every test response, not just happy-path tests.27
Layers: Core (WebDriver factory, config, retry, base page), Pages (POM with components), Tests (TestNG tests with DataProviders), Utilities (Excel reader, screenshot, email), Reports (Extent Reports/Allure). Config: properties file + environment variables, read by a singleton Config class. Driver management: WebDriverManager + ThreadLocal for parallel. Test data: JSON fixtures or DB seeding, never hardcode. CI: Maven profiles for smoke/regression; Dockerfile for consistent execution. Reporting: Allure with screenshots on failure, history trend, flaky test detection. Trade-offs: POM adds boilerplate but pays off at scale; screenplay pattern is more expressive but steeper learning curve.
28
Screenplay models tests as Actors performing Tasks using Abilities and asking Questions. Example:
james.attemptsTo(Login.withCredentials(email, pass)); assertThat(james.asksAbout(CurrentPage.url()), containsString("/dashboard")). vs POM: Screenplay is more expressive for complex workflows, better at reuse (tasks compose), and scales better for multi-persona tests. POM is simpler, widely understood, easier for junior engineers. Use POM for standard CRUD UI testing. Use Screenplay when tests involve multiple user roles or complex multi-step workflows (e-commerce checkout, banking flows).29
Strategies by layer: Static data — JSON/YAML fixtures in test resources, committed to repo, used for schema/validation tests. Generated data — use Faker library for unique names/emails per run; avoids collisions in parallel. API-seeded data — create via API in
@BeforeMethod, clean up in @AfterMethod using stored IDs. Database seeding — SQL scripts in test profile; rollback transactions after each test. Principles: tests must be order-independent (own their data), never depend on data created by another test, never use production data.30
Detection: Track pass/fail history in CI — flag tests failing >10% of runs without code changes. Use
@flaky tags. Run test suite 5x on the same commit to surface flakiness. Root causes: timing issues (fix with explicit waits), test order dependency (fix with independent setup/teardown), shared state (fix with fresh data per test), network variability (fix with WireMock stubs). Fix strategy: isolate by running in a retry group, add detailed logging, fix root cause, remove retry hacks. Never mask flakiness with blanket retries — it hides real issues and bloats run times.31
Create
.github/workflows/tests.yml. Key steps: 1) actions/checkout@v4. 2) actions/setup-java@v4 with Java 17. 3) mvn test -Dheadless=true -Dgroups=smoke. 4) actions/upload-artifact for Surefire reports with if: always(). Triggers: on push to main and pull_request. Parallel jobs: use strategy.matrix for browser matrix testing. Secrets: store credentials in GitHub Secrets, inject as env vars. Chrome: Ubuntu runners have Chrome pre-installed; use -Dheadless=true for no display.32
Shift-left means testing as early as possible in the SDLC — catching defects when they're cheapest to fix. Implementation: Unit tests in developer PRs (Jest, JUnit) — block merge if coverage drops below threshold. Contract tests (Pact) run before integration. Static analysis (SonarQube, Checkstyle) as a PR check. API tests before UI tests — verify contracts before building UI tests on top. Three amigos sessions — QA joins sprint planning to review acceptance criteria before dev starts. BDD — write feature files during refinement, before implementation. Goal: zero bugs reaching production, not zero testing.
33
Problem: "works on my machine" syndrome — different OS, Chrome versions, Java versions break tests. Solution: Dockerize everything.
FROM maven:3.9-eclipse-temurin-17 base image. Copy pom.xml, download dependencies layer (Docker cache-friendly), then copy src/. CMD ["mvn","test","-Dheadless=true"]. Selenium Grid in Docker: docker-compose with selenium/hub + selenium/node-chrome — set RemoteWebDriver to http://hub:4444. Benefits: same image in local + CI, no machine setup, easy scaling of nodes, version pinning.34
Scenario is a single test case with fixed values in the steps. Scenario Outline is a template for data-driven scenarios — use
<placeholder> syntax and supply multiple rows in an Examples table. Cucumber runs one scenario per row. Example: a login Scenario Outline with rows for valid user, locked user, and invalid credentials — three tests from one template. When to use: Scenario for happy path; Scenario Outline when the same flow needs testing with multiple data sets.35
Cucumber supports PicoContainer (simplest), Spring, and Guice for DI. With PicoContainer: create a shared context class (e.g.,
TestContext) with fields for WebDriver, test data, etc. Inject it via constructor in all step definition classes — Cucumber creates one instance per scenario. Never use static fields for sharing state — they break parallel execution. Pattern: LoginSteps(TestContext ctx) and DashboardSteps(TestContext ctx) share the same context object within one scenario.36
BVA tests at the exact boundaries of valid input ranges, plus just inside and just outside. For a field accepting age 18–60: test 17 (invalid, below), 18 (valid, lower bound), 19 (valid, just inside), 59 (valid, just inside), 60 (valid, upper bound), 61 (invalid, above). Why: most bugs cluster at boundaries — off-by-one errors in conditions like
> vs >=. BVA is most effective combined with EP — test one representative per class plus all boundaries.37
Good bug report has: Clear title (what is wrong + where), Steps to reproduce (numbered, minimal, exact), Expected result, Actual result, Environment (OS, browser, build version), Severity + Priority, Screenshots/video/logs. Bad bug report: "Login is broken" — no steps, no expected/actual, no environment. Tips: reduce steps to minimum that still reproduces the bug; verify it's reproducible before filing; search for duplicates; include relevant console errors; add a workaround if known. A good bug report allows any developer to reproduce without asking follow-up questions.
38
Severity measures the technical impact on the system. Priority measures the urgency of the fix from a business perspective. Examples: High severity, Low priority — crash in an obscure admin feature used once a year. Low severity, High priority — CEO's name misspelled on the homepage. High severity, High priority — payment gateway failing — fix NOW. Low severity, Low priority — tooltip text typo on settings page — fix in next sprint. Who decides: QA sets severity; Product/Business sets priority. Conflicts are resolved in triage meetings.
39
Load testing: simulate expected concurrent users — verify system meets SLA under normal conditions. Example: 1000 concurrent users, response time < 2s. Stress testing: push beyond capacity to find the breaking point — what happens at 5000 users? Does it degrade gracefully or crash? Spike testing: sudden massive increase — Black Friday scenario: 100 → 10,000 users in 30 seconds. Does auto-scaling kick in fast enough? Soak/Endurance testing: 500 users for 48 hours — does memory leak cause degradation over time? Tools: JMeter (Java-based, GUI), k6 (JS-based, script-as-code), Gatling (Scala DSL, excellent reports).
40
1. Injection (SQL/XSS): inject
' OR 1=1 -- into inputs; inject <script>alert(1)</script> into fields. 2. Broken Auth: test session fixation, concurrent sessions, password reset flaws. 3. IDOR: change /users/123 to /users/124 — can you access another user's data? 4. Security Misconfiguration: check for exposed /actuator, default credentials, verbose error messages. 5. Sensitive Data Exposure: is PII encrypted in transit (HTTPS)? In logs? 6. Broken Access Control: role escalation — can a regular user call admin APIs? Tools: OWASP ZAP, Burp Suite for proxy-based testing.41
Implement
ITestListener.onTestFailure(ITestResult result). Get driver from your driver manager: WebDriver driver = DriverManager.getDriver(). Cast to TakesScreenshot: File src = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE). Copy to report folder: FileUtils.copyFile(src, new File("screenshots/" + result.getName() + ".png")). Attach to Extent Report: test.addScreenCaptureFromPath(path). Also useful: OutputType.BASE64 for embedding directly in HTML reports without file I/O.42
Upload: find the file input element and call
element.sendKeys("/absolute/path/to/file.pdf") — this works without clicking the native OS dialog. Download verification: set Chrome download path via capabilities: prefs.put("download.default_directory", downloadPath), then wait for the file to appear using a polling mechanism (Fluent Wait checking file existence). Headless download: add prefs.put("download.prompt_for_download", false) and configure download behavior via Chrome DevTools Protocol in Selenium 4. Avoid AutoIT/Robot class for file dialogs — they break on headless and CI.43
Write a loop sending N+1 requests where N is the documented rate limit. After hitting the limit, verify: 1) status code is 429 (Too Many Requests). 2)
Retry-After header is present with a valid value. 3) Response body contains meaningful error message. 4) After waiting Retry-After seconds, the API accepts requests again. 5) Rate limit applies per user (not globally) — test with two different tokens simultaneously. Use REST Assured + parameterized loop, or k6 for high-volume rate limit testing.44
REST: multiple endpoints, fixed response shapes, test each endpoint separately, status codes are meaningful (200/404/401). GraphQL: single endpoint (
/graphql), client specifies exact fields, always returns 200 even on error — check errors array in body. GraphQL testing challenges: harder to test with Postman (need to send query in body), errors are in response body not status code, need to test resolver-level errors. GraphQL testing tools: Apollo Studio, REST Assured with contentType("application/graphql"), Insomnia. Test query complexity and N+1 queries under load.45
Page Object (structural): encapsulates page UI; most universal pattern. Factory (creational):
WebDriverFactory.getDriver("chrome") — create drivers without exposing instantiation. Singleton: one Config instance, one driver per thread. Builder: constructing complex test data objects: new User.Builder().email("x").role("admin").build(). Strategy: pluggable algorithms — LoginStrategy for OAuth, email/password, SSO. Observer: TestNG Listeners observe test events. Template Method: BasePage defines test flow skeleton; subclasses override specific steps.46
Streams provide declarative data processing. Filter elements with text:
elements.stream().filter(e -> e.getText().startsWith("Error")).collect(Collectors.toList()). Map to texts: elements.stream().map(WebElement::getText).collect(toList()). Check any element matches: elements.stream().anyMatch(e -> e.getText().equals("Success")). Find test result by name: results.stream().filter(r -> r.getName().equals("login")).findFirst().orElseThrow(). Count failures: results.stream().filter(r -> r.getStatus() == FAILED).count(). Streams replace verbose for-loops with readable, composable pipelines.47
Implement
IRetryAnalyzer: public boolean retry(ITestResult result) { return retryCount++ < MAX_RETRIES; }. Attach to test: @Test(retryAnalyzer=RetryAnalyzer.class) or globally via a IAnnotationTransformer listener that applies retry to all tests. Important: only retry tests that truly have infrastructure-related flakiness (network timeouts, browser crashes). Never retry tests with application bugs — you'll hide real failures. Log retries with reason. Set MAX_RETRIES = 1 or 2, not more.48
On every PR/commit: smoke tests (10-15 critical paths, <10 min) — fast feedback, catch showstoppers. On merge to main: sanity tests (30-50 tests, <30 min) — verify key features. Nightly: full regression (complete suite, 1-4 hours). Weekly: performance, security, cross-browser, accessibility. Trigger logic: use changed file paths to trigger only relevant test modules (e.g., payment changes → run payment tests). Goal: developer gets test feedback within 10 minutes of pushing code.
49
Exploratory testing is simultaneous learning, design, and execution — no pre-written scripts. Structure with charters: "Explore [feature] with [resources] to discover [information type]." Example: "Explore the payment flow with valid/invalid cards to discover data validation gaps." Session-Based Testing (SBTM): time-box sessions (60-90 min), use a charter, take notes in real-time, debrief with a bug/issue/observation count. Tools: screen recording (Loom), mind mapping (XMind) for coverage notes. When to use: new features before scripted tests are written, after a major code change, to supplement scripted regression.
50
Abstract class: can have concrete methods, fields, constructors; single inheritance only. Use when subclasses share implementation. Interface: (pre-Java 8) only abstract methods; (Java 8+) can have
default and static methods; a class can implement multiple interfaces. In test frameworks: BasePage is typically an abstract class with concrete utility methods (wait, scroll). Navigable or Verifiable are interfaces that page classes implement. Use interfaces for capabilities, abstract classes for shared behavior.51
Switch using index:
driver.switchTo().frame(0), name/ID: driver.switchTo().frame("frameName"), or WebElement: driver.switchTo().frame(iframeElement). Always return to main document: driver.switchTo().defaultContent(). For nested frames: switch to outer frame, then inner. Best practice: wrap in a utility method with explicit wait for frame availability using ExpectedConditions.frameToBeAvailableAndSwitchToIt().52
An operation is idempotent if calling it multiple times produces the same result as calling it once. Idempotent: GET, PUT, DELETE, HEAD, OPTIONS. Not idempotent: POST (each call creates a new resource). Test implication: verify that calling PUT twice with the same payload results in the same resource state; verify DELETE on an already-deleted resource returns 404 (not 500); verify POST creates a new record each time.
53
Use a properties file per environment:
config-dev.properties, config-staging.properties, config-prod.properties. Read environment from system property: System.getProperty("env", "dev"). Load the matching file in a ConfigManager singleton. Sensitive values (passwords, API keys) come from environment variables, never properties files. Maven profiles: mvn test -Pstaging sets -Denv=staging. In CI: GitHub Secrets → env vars → picked up by ConfigManager. Never commit credentials to repo.54
dependsOnMethods creates hard dependency between specific test methods in the same class. If dependency fails, dependent test is skipped. dependsOnGroups creates dependency on all tests in a group — more flexible across classes. Warning: over-using dependencies creates brittle, hard-to-maintain test chains. Prefer independent tests where each creates its own prerequisites. Use dependencies only for inherently sequential workflows (login → order → payment).55
Hooks are blocks that run before/after each scenario or step.
@Before — launch browser, initialize context. @After — quit browser, capture screenshot on failure, clean up test data. @Before(order=1) — control execution order. @Before("@smoke") — conditional hooks for tagged scenarios only. Best practices: put browser initialization in hooks, not step definitions. Use Scenario scenario parameter in @After to check scenario.isFailed() for conditional screenshot capture.56
Reflection lets you inspect and manipulate classes, methods, and fields at runtime. In test frameworks: TestNG uses reflection to discover
@Test methods and invoke them. Selenium's PageFactory.initElements() uses reflection to initialize @FindBy fields. Custom uses: dynamic test execution — read method names from a config file, invoke them via Method.invoke(). Test data injection: map JSON keys to POJO fields without explicit setters. Caution: reflection bypasses compile-time checks — use sparingly and document clearly.57
The test pyramid (Mike Cohn) advocates: many unit tests at the base (70%), some integration/API tests in the middle (20%), few UI/E2E tests at the top (10%). UI tests are slow, brittle, and expensive. Practical strategy: push validation logic to unit tests (business rules, calculations). Use API tests for integration (user creation, ordering flow). Use UI tests only for end-to-end smoke scenarios. Anti-pattern (ice cream cone): many manual and UI tests, few unit tests — slow feedback, high maintenance.
58
SPAs change content without full page reloads — standard waits fail. Key strategies: 1) Wait for Angular to stabilize using
executeScript with Angular's getAllAngularTestabilities. 2) Use ExpectedConditions.invisibilityOf(loadingSpinner) after actions. 3) Wait for network idle: poll document.readyState === "complete" via JS. 4) Use data-testid attributes — React/Angular components should expose stable test IDs. 5) Consider Playwright/Cypress for SPAs — they auto-wait for network requests and re-renders.59
Smoke testing is a broad, shallow test of critical functionalities after a new build — "does the app start and do the basics work?" Run after every new build deployment. Fails early if core is broken. Sanity testing is narrow, deep testing of a specific area after a bug fix or small change — "does THIS fix work correctly?" More focused than smoke. Analogy: smoke = turn on the power and check nothing smokes; sanity = check the specific component that was repaired.
60
Unit level: test each service in isolation with mocked dependencies (WireMock for external HTTP calls). Contract level: Pact consumer-driven contracts verify interface compatibility without needing all services running. Component level: test one service end-to-end with its real database (using TestContainers for Docker DB). Integration level: test service interactions with a subset of real services + stubs for the rest. E2E: test complete business flows through all services — run in staging, not CI. Key principle: test each layer independently first, then test interactions.
61
List (ArrayList): ordered, duplicates allowed — store WebElements, test results, CSV rows. Set (HashSet/LinkedHashSet): unique values — verify no duplicate results in a list, track visited URLs. Map (HashMap/LinkedHashMap): key-value — store test data (username→password), expected vs actual values, locale strings. Queue (LinkedList/ArrayDeque): process steps sequentially. In practice:
Map<String,String> for dynamic locators, Set<String> for de-duplication checks, List<Map<String,String>> for tabular test data from Excel.62
Allure Reports: annotations (
@Step, @Attachment) + JSON results → beautiful HTML with history, trends, flaky test tracking. Integrate with CI via Allure Plugin for Jenkins/GitHub Actions. Extent Reports: programmatic API, no agent needed — good for Selenium-specific reports. TestNG Surefire: built-in XML reports readable by CI dashboards. Pipeline integration: publish HTML artifacts with upload-artifact in GitHub Actions; send failure notifications to Slack via webhook in post.failure block; maintain history by persisting allure-results across builds in a shared storage.63
Basic payloads: Enter
' OR '1'='1 in login field — if it logs you in without a password, the app is vulnerable. Try '; DROP TABLE users; -- in search fields. Error-based detection: single quote ' in inputs — a SQL error in the response reveals the database type. Automated scanning: use OWASP ZAP or SQLMap pointed at the application. API testing: inject payloads in all query parameters, request body fields, and headers. Verify fix: after patching, confirm parameterized queries are used — re-run all payloads and verify they return proper 400 errors without data exposure.64
Response time: how long a single request takes end-to-end (from send to complete response). Measured in milliseconds/seconds. User-facing metric — directly impacts perceived performance. Throughput: how many requests the system processes per second (TPS/RPS). Server capacity metric. Relationship: as concurrent users increase, throughput increases until saturation, then response time increases sharply. The "knee" of the performance curve is your optimal load point. SLAs to define before testing: "95th percentile response time < 2s under 500 concurrent users" — specific, measurable performance requirements.
65
Blue-green maintains two identical production environments. Traffic switches from blue (current) to green (new) after validation. QA impact: run full test suite against green before traffic switch — zero-downtime deployment with rollback safety net. Smoke tests run automatically after each deployment. Canary releases (related): route 5% of traffic to new version, monitor error rates, gradually increase. QA role: validate green environment, define go/no-go criteria for traffic switch, monitor production metrics post-deploy with alerting thresholds.
66
In
testng.xml: <suite name="Suite" parallel="methods" thread-count="5">. All @Test methods run concurrently up to thread-count. Requirements for safe parallel execution: 1) ThreadLocal WebDriver (never static). 2) Each test creates its own test data — no shared state. 3) No test order dependency. 4) Thread-safe logging (use Log4j2 with context map). 5) Separate report file per thread or synchronized report access. Troubleshooting: run with thread-count=1 first to eliminate test isolation issues from parallel issues.67
JavascriptExecutor lets you run JavaScript in the browser context from Selenium. Common uses: click an element hidden behind another element (last resort): js.executeScript("arguments[0].click()", el); scroll to element: js.executeScript("arguments[0].scrollIntoView(true)", el); get hidden attribute: js.executeScript("return arguments[0].getAttribute('data-value')", el); wait for page ready: js.executeScript("return document.readyState").equals("complete"). Caution: JS click bypasses real user interaction — use only when standard Selenium can't reach the element.68
Upload: use
multipart/form-data in REST Assured: given().multiPart("file", new File("test.pdf")).when().post("/upload"). Test with: valid file, oversized file, wrong format, empty file, malicious filename. Download: given()...when().get("/download/123").then().statusCode(200).header("Content-Type","application/pdf"). Extract binary: extract().response().asByteArray(). Verify file integrity with MD5 checksum. Test authorization — can user A download user B's file? Test large file performance and partial content (HTTP 206 with Range header).69
Challenges and solutions: Step reuse — too many similar step definitions; fix by writing generic, parameterized steps and composing tasks from them. Living documentation drift — scenarios not updated when requirements change; fix by making Cucumber runs part of CI gates. Slow execution — hundreds of Cucumber scenarios are slow; run in parallel with
@Cucumber(plugin="parallel") and distribute by tag. Feature file ownership — non-technical stakeholders stop reading; hold regular three-amigos sessions. Ambiguous step matching — long regex patterns that match wrong steps; use specific Cucumber Expressions ({string}, {int}) over regex.70
Step 1: talk to the developer and product owner — understand the intent (30 min beats hours of guessing). Step 2: explore the feature manually to understand the current behavior. Step 3: test similar features in competitor products for mental model. Step 4: apply heuristics — SFDPOT (Structure, Function, Data, Platform, Operations, Time). Step 5: use charters for exploratory sessions. Step 6: document what you discover as acceptance criteria — this becomes the living spec. Step 7: report ambiguities as questions (not bugs) to product.
71
Extent Reports generates HTML test execution reports with pass/fail/skip stats, timelines, screenshots, and metadata. Integration: 1) Add ExtentReports Maven dependency. 2) Create
ExtentReports instance in @BeforeSuite, attach ExtentHtmlReporter. 3) In @BeforeMethod: test = extent.createTest(method.getName()). 4) In @AfterMethod: check ITestResult.status and call test.pass/fail/skip. 5) On failure: attach screenshot via test.addScreenCaptureFromBase64String(). 6) In @AfterSuite: extent.flush().72
Comparable: the class defines its own natural ordering via
compareTo(T o) — a TestResult that implements Comparable<TestResult> can define ordering by timestamp. Comparator: external comparison strategy via compare(T a, T b) — allows multiple orderings without modifying the class. In automation: sort test results by severity: results.sort(Comparator.comparing(r -> r.getSeverity())). Chain comparators: Comparator.comparing(TestResult::getSeverity).thenComparing(TestResult::getName).73
REST Assured has built-in logging filters:
given().log().all() logs full request; .then().log().ifValidationFails() logs response only on failure. Better approach: custom RequestSpecification with a ResponseSpecification attached globally via RestAssured.requestSpecification. For reports: use Allure with @Step annotation on API methods + Allure.attachment("Request", requestJson) and Allure.attachment("Response", responseJson). This gives you a clickable request/response in each Allure test step — invaluable for debugging CI failures.74
Stored XSS: inject
<script>alert(document.cookie)</script> in text inputs that get persisted and displayed (comments, usernames, product descriptions). If alert fires on viewing the page, it's vulnerable. Reflected XSS: inject in URL params: https://site.com/search?q=<script>alert(1)</script>. DOM XSS: check JS code that writes untrusted data to innerHTML. Bypass attempts: try <img src=x onerror=alert(1)>, <svg/onload=alert(1)>, encoding variants. Verify fix: re-test with same payloads; confirm output encoding is applied.75
Approach: Selenium Grid on Kubernetes using official Helm chart or
selenium-grid K8s manifests. Hub as a Deployment, nodes as auto-scaling Deployments with HPA. Test distribution: split TestNG XML by groups or use JUnit Platform Suite sharding. Dynamic nodes: Selenium 4 supports dynamic Grid — nodes register themselves, Hub distributes sessions. Alternative: GitHub Actions matrix — spin up N parallel job runners, each running a subset of tests, combine results in final step. Use actions/cache for Maven dependencies to speed up startup.76
Options: 1) Mailinator/Guerrilla Mail: use disposable email addresses with public inboxes accessible via API — fetch inbox, parse email, extract link/OTP. 2) MailHog/MailCatcher: deploy a fake SMTP server in test env, access emails via its REST API — no external service needed. 3) Gmail API: dedicated test Gmail account with OAuth2 — reliable but more setup. 4) Direct DB/queue access: query the database or message queue directly for the OTP/token — fastest but bypasses the email flow. Never test with real production email accounts.
77
Techniques: Analogy-based: compare to similar past features. Three-point estimation: Optimistic + (4×Most Likely) + Pessimistic / 6. Planning Poker: team consensus. Factors to consider: feature complexity and risk, number of integration points, test data setup complexity, automation coverage goal, device/browser matrix, whether APIs are documented, regulatory compliance requirements. Breakdown: requirements review (10%), test case design (30%), test execution (40%), defect retesting (15%), reporting (5%). Key principle: always include time for exploratory testing — it finds bugs scripted tests miss.
78
Locate all rows:
List<WebElement> rows = driver.findElements(By.cssSelector("table tbody tr")). For each row, find cells: List<WebElement> cells = row.findElements(By.tagName("td")). Search by column value: iterate rows, check if cell at column index contains target text. Store as Map: use header row to map column names to cell values for readable assertions: Map<String,String> rowData = Map.of("Name", cells.get(0).getText(), "Price", cells.get(1).getText()). For pagination: loop through pages extracting all rows, check "Next" button is enabled/disabled.79
RequestSpecification captures reusable request configuration — base URI, headers, auth tokens, content type. Pattern: create a RequestSpecBuilder in a base test class, set common headers (Authorization, Content-Type, Correlation-ID), build into a RequestSpecification and assign to RestAssured.requestSpecification. All tests inherit this automatically. Benefits: no repetition, change auth header in one place, consistent logging setup across all tests. Create separate specs per environment (dev, staging) and select based on config.80
Given-When-Then (Gherkin syntax) structures scenarios as: Given — the initial state/context (user is logged in, cart has 2 items). When — the action performed (user clicks "Checkout"). Then — the expected outcome (order confirmation page is shown, email is sent). Why it matters: separates setup from action from assertion; makes scenarios readable by non-technical stakeholders; maps naturally to Arrange-Act-Assert in code; prevents scenarios from becoming long unstructured narrative. Use And/But for multiple steps in one part.
81
Key components: Thread Group — defines users (threads), ramp-up period, loop count. HTTP Request Sampler — defines the API call (method, URL, body, headers). Config Elements — HTTP Header Manager (auth), HTTP Cookie Manager. Pre-Processors — CSV Data Set Config for data-driven load testing. Assertions — Response Assertion (status code), JSON Assertion (field value), Duration Assertion (response time). Listeners — Summary Report, Aggregate Report, Response Time Graph. Best practice: run JMeter in non-GUI mode in CI:
jmeter -n -t test.jmx -l results.jtl -e -o report/.82
Workbook wb = WorkbookFactory.create(new File("testdata.xlsx")). Get sheet: Sheet sheet = wb.getSheetAt(0). Iterate rows: for(Row row : sheet) { for(Cell cell : row) { ... } }. Handle cell types: cell.getCellType() == CellType.STRING/NUMERIC/BOOLEAN. DataProvider pattern: build Object[][] where each row is test parameters. Utility class: ExcelReader.getData(sheet, row, col) with all cell types handled. Modern alternative: Jackson for JSON test data — less fragile than Excel, version-control friendly, no cell type complications.83
Approach 1 — Direct input (preferred): clear the date input and send keys:
element.clear(); element.sendKeys("12/25/2025"). Much faster than calendar navigation. Approach 2 — JS injection: js.executeScript("arguments[0].value='2025-12-25'", element) then trigger change event. Approach 3 — Navigate calendar: click "next month" arrow until target month is visible, then click target day. Fragile — use only when input is truly read-only. Test cases: valid date, past date (if disabled), future date, date range (start > end), edge: Feb 28/29, timezone handling.84
Maven: XML-based (
pom.xml), convention-over-configuration, well-established in Java enterprise and Selenium world, verbose but predictable. Gradle: Groovy/Kotlin DSL, flexible, incremental builds (faster for large projects), better for multi-module Android/Kotlin. In automation: most Selenium/TestNG projects use Maven — large ecosystem, Surefire/Failsafe plugins, well-documented CI integration. mvn test -Dgroups=smoke -Dbrowser=chrome. Choose Gradle for new projects needing custom build logic or multi-language builds.85
TestContainers: spin up Docker containers programmatically in
@BeforeAll. Example: @Container static PostgreSQLContainer db = new PostgreSQLContainer("postgres:15"). Automatically maps ports, provides JDBC URL. Test against real DB in isolation. WireMock Server: start in @BeforeAll, define stubs per test in @BeforeEach, reset in @AfterEach. Spring Boot Test: @SpringBootTest(webEnvironment=RANDOM_PORT) starts the service with a real port. Compose: docker-compose.test.yml defines the full service dependency graph — start in CI before running tests.86
Automated scanning: axe-core (browser extension or library), Lighthouse in Chrome DevTools, WAVE. These catch ~30% of accessibility issues automatically. Keyboard navigation: tab through entire page — every interactive element must be focusable, visible focus ring must be present. Screen reader testing: NVDA+Firefox or VoiceOver on Mac — verify meaningful announcements for buttons, images, form errors. Color contrast: WCAG 2.1 requires 4.5:1 for normal text, 3:1 for large text — use Colour Contrast Analyser. Standards: WCAG 2.1 Level AA is the common compliance target.
87
WebSocket is a persistent full-duplex connection — different from REST. Testing tools: Postman supports WebSocket testing; k6 has
ws.connect(); write Java client with javax.websocket or OkHttp WebSocket. Test cases: connection establishment (101 Switching Protocols), sending messages and verifying echo, subscribe/publish patterns (chat, live updates), connection drop handling (server sends close frame), heartbeat/ping-pong, authentication on connection upgrade, concurrent connections under load, message ordering guarantees. Common frameworks: Socket.io (JS), Spring WebSocket (Java) — test against real server, not mocked.88
HashMap: O(1) get/put, no ordering guarantee — best for pure lookups (locator maps, config values). LinkedHashMap: maintains insertion order — use when order matters (test step logs, ordered test data). TreeMap: sorted by key (natural or Comparator), O(log n) — use for sorted output (test report ordered by test name). In automation:
HashMap<String,String> for test data (fastest lookup), LinkedHashMap for ordered form fields to fill sequentially, TreeMap for alphabetically ordered test summaries.89
Selenium 4 key additions: W3C WebDriver protocol (no more JSON Wire Protocol) — more stable, predictable cross-browser behavior. Chrome DevTools Protocol (CDP) access — mock network, intercept requests, capture console logs. Relative locators —
with(By.tagName("input")).toRightOf(label). New window/tab — driver.switchTo().newWindow(WindowType.TAB). Grid 4 — fully revised with Selenium Manager (auto-downloads driver binaries). Migration: replace DesiredCapabilities with ChromeOptions, remove Selenium 3 explicit driver download code.90
Old approach (painful): manually download ChromeDriver, match versions, store in project, break when Chrome updates. Modern approach: WebDriverManager (by Boni Garcia):
WebDriverManager.chromedriver().setup() — auto-downloads the correct driver version matching the installed browser. Selenium 4 Manager: Selenium 4.6+ ships with Selenium Manager built-in — new ChromeDriver() automatically manages the driver. No setup needed. CI: GitHub Actions Ubuntu runners have Chrome pre-installed; WebDriverManager detects the version and downloads matching driver automatically.91
Cucumber JUnit Platform Engine: with JUnit 5 + Maven Surefire 3: set
<forkCount>4</forkCount> and <reuseForks>false</reuseForks> in Surefire config. Cucumber-JVM parallel plugin: @CucumberOptions(features="src/test/resources", plugin={"parallel"}). Tag-based distribution: split features by @smoke, @regression1, @regression2 into separate CI jobs. Thread-safe requirements: PicoContainer DI for shared state, ThreadLocal WebDriver, independent test data per scenario, stateless step definitions.92
Key metrics: Samples — total requests sent. Average — mean response time (misleading if outliers exist). Median (50th percentile) — half of requests completed faster than this. 90th/95th/99th percentile — most important for SLAs (e.g., "99% of requests < 2s"). Throughput — requests per second (higher is better). Error% — should be 0 for functional tests; any error needs investigation. Red flags: error% > 0, p99 > your SLA, throughput plateauing (bottleneck found), response times climbing over soak test duration (memory leak).
93
Exception handling uses try-catch-finally blocks. Common Selenium exceptions:
NoSuchElementException — element not found with given locator. TimeoutException — explicit wait timed out. StaleElementReferenceException — DOM changed after element was located. ElementNotInteractableException — element exists but can't be clicked (hidden, disabled). WebDriverException — driver crashed or browser closed. Best practice: catch specific exceptions, not generic Exception. Log meaningful messages. In test frameworks, convert exceptions to test failures with context info.94
Environments: Dev — developer's local/shared env, unstable, not for automation. Integration/SIT — services integrated, run API + contract tests. QA/Staging — production-like, run full regression. Pre-prod/UAT — business validation, performance tests. Production — smoke tests only, alert-based monitoring. Management: environment-specific config files, never hardcode URLs. Infrastructure-as-Code (Terraform, Helm) for consistent env provisioning. Feature flags to enable/disable features per environment. Test data isolation — separate DBs, seeded per environment.
95
Differences: Input — touch, swipe, pinch, tap vs mouse. Connectivity — test on 3G, airplane mode, Wi-Fi switch. Device fragmentation — hundreds of Android/iOS device+OS combos. Orientation — portrait/landscape rotation. Interruptions — incoming call, push notification, app switch. Mobile-specific tests: install/update/uninstall flow, deep links, biometric auth, offline mode, battery/data usage, push notifications, permission flows, back button behavior. Tools: Appium for automation, Firebase Test Lab / BrowserStack for real device cloud, Charles Proxy for network testing.
96
Selenium 4 provides CDP access via
HasDevTools interface. Network mocking: devTools.send(Network.enable()) then devTools.addListener(Network.responseReceived(), response -> logResponse(response)). Block requests: block ads or third-party scripts by URL patterns. Set geolocation: devTools.send(Emulation.setGeolocationOverride(lat, lng, accuracy)). Console log capture: listen for Log.entryAdded() events to capture JavaScript errors. Network throttling: simulate slow 3G connections for performance testing.97
Test cases: first page (page=1) returns correct number of items. Last page returns remaining items (fewer than page size). Page beyond last returns empty array (not 404). Metadata validation: verify
total, page, pageSize, hasNext, hasPrev fields match expected values. Consistency test: fetch all pages, aggregate items, compare total count to total field. Edge cases: page=0 or negative (expect 400), pageSize=0, pageSize > max allowed, sort parameter with pagination. Cursor-based pagination: test that using cursor from response produces correct next page.98
Use SLF4J with Log4j2 (not System.out.println). Log levels: DEBUG for locator searches, wait attempts; INFO for test steps, API calls; WARN for retries, non-critical failures; ERROR for exceptions, test failures. MDC (Mapped Diagnostic Context):
MDC.put("testName", testName); MDC.put("thread", threadId) — automatically prefixes every log line with test context in parallel runs. File appender: separate log file per test run with rolling. Screenshot on ERROR. Attach logs to Allure/Extent Reports — log file contents embedded in report for each failed test.99
Risk-based testing focuses effort on areas most likely to fail and most impactful if they do. Risk = Likelihood × Impact. Process: identify all features/components, rate each for probability of defect (based on code complexity, change frequency, past bug count) and impact (revenue, user count, legal/compliance). Multiply to get risk score. Test high-risk areas first and most thoroughly. Practical prioritization: payment flow (high impact, critical) > authentication (high impact) > search (medium impact) > user preferences (low impact). Update risk assessment each sprint as code changes.
100
Week 1 — Foundation: understand business risk (what failures cost most money/users). Set up a simple CI pipeline with GitHub Actions. Write 10-15 smoke tests for the most critical user flows (signup, core feature, payment). Month 1 — API layer: API tests for all endpoints — they're 10x faster than UI tests and catch 80% of bugs. Set up contract tests if microservices. Month 2 — Framework: POM structure, config management, Allure reports, Slack notifications on failure. Add data-driven tests for business logic. Month 3+: expand regression suite, add performance baseline tests, explore mutation testing for unit test quality. Philosophy: perfect is the enemy of good — 20 reliable automated tests beat 200 flaky ones.