Test Automation & Engineering
Test-automation practice — unit and e2e tests, the Page Object pattern, flaky-test handling, locators, test data, CI/CD, and engineering judgment.
23 questions
JuniorTheoryVery commonWhat is an end-to-end (e2e) test?
What is an end-to-end (e2e) test?
An e2e test drives the whole system the way a real user would — through the UI and across all the layers (frontend, backend, database) — to verify a complete user flow works. It gives the highest confidence that the product works as a whole, but is the slowest, most brittle, and most expensive test type, so the pyramid keeps few of them.
Common mistakes
- ✗Confusing e2e with a unit test
- ✗Thinking e2e tests are fast and cheap to maintain
- ✗Believing the pyramid wants the most e2e tests
Follow-up questions
- →Why does the test pyramid keep e2e tests few?
- →What makes e2e tests more prone to flakiness?
MiddleTheoryVery commonWhat is an assertion, what kinds exist, and which tools provide them?
What is an assertion, what kinds exist, and which tools provide them?
An assertion is a check inside a test that fails it when the expected condition does not hold. Kinds include assertTrue/assertFalse, assertEquals/assertNotEquals, assertNull, and assertThrows, plus the soft-vs-hard distinction (soft asserts collect failures and continue; hard asserts stop at once). Tools: JUnit/TestNG, AssertJ, Hamcrest, pytest, and Chai.
Common mistakes
- ✗Thinking an assertion cannot fail a test
- ✗Confusing soft asserts (continue) with hard asserts (stop)
- ✗Believing frameworks offer no assertion methods
Follow-up questions
- →When is a soft assert preferable to a hard assert?
- →Why is one logical assertion per test a useful guideline?
MiddleTheoryVery commonWhat are flaky tests, and what do you do about them?
What are flaky tests, and what do you do about them?
A flaky test passes and fails non-deterministically on the same code. Common causes: timing/race conditions, implicit waits, test-order dependence, shared or dirty state, and network instability. Fix by replacing sleeps with explicit waits, isolating state, removing order coupling — then quarantine the test and either stabilize it or delete it, since a flaky test erodes trust in the whole suite.
Common mistakes
- ✗Adding sleeps instead of explicit waits
- ✗Leaving a flaky test in the suite instead of quarantining it
- ✗Blaming the code when the cause is shared state or order
Follow-up questions
- →Why is a flaky test sometimes worse than no test?
- →How does test-order dependence cause flakiness?
MiddleTheoryVery commonWhat is the Page Object pattern, and where is it applied?
What is the Page Object pattern, and where is it applied?
Page Object Model is a design pattern for UI automation: each page or screen is a class that exposes its elements and actions as methods, so tests call page methods instead of raw locators. It centralizes locator maintenance in one place, removes duplication, and keeps tests readable. It is used in Selenium, Appium, and Playwright suites.
Common mistakes
- ✗Putting raw locators in tests instead of page classes
- ✗Thinking it applies to unit tests rather than UI automation
- ✗Believing it adds duplication rather than removing it
Follow-up questions
- →How does the pattern reduce locator maintenance when the UI changes?
- →What belongs in a page object versus in the test itself?
MiddleTheoryCommonHow do the cross-platform tool Appium and native automation instruments compare?
How do the cross-platform tool Appium and native automation instruments compare?
Appium gives one codebase that drives both iOS and Android, so you write tests once — but it is slower and flakier and can lag in supporting new OS features. Native instruments (Espresso for Android, XCUITest for iOS) are faster and more stable and live in the app's code so developers can write them, but require two codebases and roughly double the maintenance.
Common mistakes
- ✗Swapping which option is cross-platform vs native
- ✗Thinking native tools support both platforms from one codebase
- ✗Believing Appium is the fastest and most stable
Follow-up questions
- →When would the double maintenance of native tools still be worth it?
- →Why does Appium tend to lag in supporting new OS features?
MiddleTheoryCommonWhat is CI/CD, and what problem does it solve?
What is CI/CD, and what problem does it solve?
CI (Continuous Integration) automatically builds and tests every change as it lands, catching integration problems early. CD (Continuous Delivery/Deployment) automates delivering validated builds toward or into production. Together they solve integration hell and manual-release errors, giving fast feedback and a repeatable, auditable path from commit to running software.
Common mistakes
- ✗Saying CI deploys without building or testing
- ✗Thinking CI/CD is manual rather than automated
- ✗Confusing the integration step with the delivery step
Follow-up questions
- →Where do automated tests fit into a CI pipeline?
- →How does CI catch integration problems earlier than a big merge?
MiddleTheoryCommonWhat makes a good automated test from a technical standpoint?
What makes a good automated test from a technical standpoint?
A good autotest is independent and atomic, deterministic (not flaky), and readable. It has clear setup/teardown (fixtures, preconditions, postconditions), produces a clear failure message, and ideally checks one logical thing. These map to the FIRST principles — Fast, Independent, Repeatable, Self-validating, Timely — making the suite trustworthy and cheap to maintain.
Common mistakes
- ✗Allowing tests to depend on each other's order or state
- ✗Tolerating flakiness instead of stabilizing the test
- ✗Writing vague failure messages or no assertion
Follow-up questions
- →Why is test independence more important than test count?
- →How does a clear failure message speed up debugging?
MiddleTheoryCommonWhat kinds of locators exist, and how do they differ?
What kinds of locators exist, and how do they differ?
Web locators include id, name, class, tag, CSS selector, XPath, and link text. id is the fastest and most stable; XPath is the most flexible but brittle and slow, so CSS is generally preferred over it. Mobile adds accessibility id and Android's resource-id. The trade-off is always stability versus flexibility — pick the most stable selector the markup allows.
Common mistakes
- ✗Reaching for XPath when a stable id or CSS would do
- ✗Thinking all locators are equally stable
- ✗Forgetting mobile-specific locators (accessibility id, resource-id)
Follow-up questions
- →Why is an XPath locator more brittle than a stable id?
- →What makes a locator resilient to UI refactoring?
MiddleTheoryCommonWhat is mocking, and why is it used in automated tests?
What is mocking, and why is it used in automated tests?
Mocking replaces a real dependency — a service, database, clock, or network call — with a controlled stand-in that returns predefined responses. It keeps a test isolated, fast, and deterministic, lets you simulate hard-to-reproduce conditions (errors, timeouts, a closed shop at night), and removes reliance on external state that you cannot control during the run.
Common mistakes
- ✗Thinking mocking uses the real dependency
- ✗Forgetting mocks let you simulate error and timeout conditions
- ✗Confusing mocking with copying tests
Follow-up questions
- →How would you mock a clock to test a time-dependent feature?
- →When does over-mocking make a test less valuable?
JuniorTheoryOccasionalWhat is a linter, and what does it do?
What is a linter, and what does it do?
A linter is a static analyzer that reads source code without running it and flags style violations and likely errors — unused variables, undefined names, suspicious patterns, formatting drift. It enforces a consistent code style and catches a class of bugs early, before the code is ever executed, often as part of the CI pipeline.
Common mistakes
- ✗Thinking a linter runs the code rather than reading it statically
- ✗Limiting it to formatting only, ignoring error detection
- ✗Confusing it with a runtime profiler
Follow-up questions
- →How does running a linter in CI help a team?
- →What kinds of bugs can a linter not catch?
JuniorTheoryOccasionalWhat is load testing, and what is it for?
What is load testing, and what is it for?
Load testing measures how a system behaves under expected and peak load — checking throughput, response time, and capacity, and finding bottlenecks before users hit them. Tools include JMeter, k6, Gatling, and Locust. Related subtypes are stress (beyond peak), spike, soak/endurance (long duration), and scalability testing.
Common mistakes
- ✗Confusing load testing with functional or UI testing
- ✗Treating load and stress testing as the same thing
- ✗Forgetting it aims at throughput, response time, and capacity
Follow-up questions
- →How does load testing differ from stress testing?
- →What metrics tell you a system has hit its capacity limit?
JuniorTheoryOccasionalWhat is a unit test, and why is it valuable?
What is a unit test, and why is it valuable?
A unit test checks the smallest isolated piece of code — a single function or module — independently of the rest of the system, usually with its dependencies mocked. It is valuable because it runs fast, pinpoints exactly where a fault is, minimizes regression by catching breakages early, and gives developers quick feedback while they change code.
Common mistakes
- ✗Confusing a unit test with an integration or e2e test
- ✗Thinking unit tests need a real database or network
- ✗Believing they cannot be automated
Follow-up questions
- →How does mocking keep a unit test isolated?
- →Where do unit tests sit in the test pyramid?
MiddleTheoryOccasionalWhat is an anti-pattern, with examples from test automation?
What is an anti-pattern, with examples from test automation?
An anti-pattern is a common but counterproductive solution that looks helpful yet causes problems. In test automation: hard-coded waits (Thread.sleep), interdependent tests that must run in order, copy-pasted test code, leaving flaky tests in the suite, and the ice-cream-cone pyramid (too many slow UI tests, too few unit tests).
Common mistakes
- ✗Treating an anti-pattern as a best practice
- ✗Not recognizing
Thread.sleepwaits as an anti-pattern - ✗Thinking the ice-cream-cone pyramid is desirable
Follow-up questions
- →Why is the ice-cream-cone pyramid an anti-pattern?
- →What should replace a hard-coded
Thread.sleepwait?
MiddleTheoryOccasionalWhat are Git basics for a tester, and what is Git Flow?
What are Git basics for a tester, and what is Git Flow?
A branch is an independent line of development. Core commands: git clone copies a repo, git pull fetches updates, git commit records changes, git push uploads them. Git Flow is a branching model with long-lived main and develop branches plus short-lived feature/*, release/*, and hotfix/* branches, giving a structured path from work to release.
Common mistakes
- ✗Reversing what
git pushandgit pulldo - ✗Thinking Git has no branches or only one
- ✗Confusing Git Flow's branch roles (feature vs hotfix)
Follow-up questions
- →Why would a tester check out a feature branch before testing it?
- →What is the purpose of a hotfix branch in Git Flow?
MiddleTheoryOccasionalIn a unit-test framework, how do per-test and per-class setup hooks differ?
In a unit-test framework, how do per-test and per-class setup hooks differ?
A per-test setup hook (JUnit's @Before / @BeforeEach) runs before every single test method, giving each test a fresh, isolated state — used for cheap, mutable fixtures. A per-class hook (@BeforeClass / @BeforeAll) runs once before all tests in the class and must be static, used for expensive shared setup like opening a DB connection. The rule of thumb: per-test for isolation, per-class for costly resources you can safely share read-only across tests.
Common mistakes
- ✗Swapping which hook is per-test and which is per-class
- ✗Forgetting the per-class hook must be static
- ✗Sharing mutable state in a per-class hook, breaking isolation
Follow-up questions
- →Why must the per-class hook be static in JUnit 4?
- →When would sharing a resource per-class cause flaky tests?
MiddleTheoryOccasionalWhat is mutation testing, and what does it measure?
What is mutation testing, and what does it measure?
Mutation testing introduces small deliberate faults (mutants) into the code — flipping an operator, changing a constant — then reruns the test suite. If a test fails, the mutant is killed; if all tests still pass, the mutant survived, exposing a gap. It measures the quality of the tests themselves (do they actually detect bugs), going beyond line coverage, which only shows code was executed.
Common mistakes
- ✗Thinking it mutates test data rather than the code
- ✗Believing a surviving mutant means the tests are good
- ✗Equating it with line coverage
Follow-up questions
- →Why is line coverage a weaker quality signal than a killed-mutant rate?
- →What does a surviving mutant tell you to add to your tests?
MiddleTheoryOccasionalHow do you speed up a test run, and how does parallel differ from distributed?
How do you speed up a test run, and how does parallel differ from distributed?
Speed up by running tests concurrently, pruning redundant ones, mocking slow dependencies, and pushing checks to lower (faster) test levels. Parallel execution runs the same suite concurrently, often across several devices or browsers; distributed execution shards the suite across nodes so different tests run on different machines, cutting total wall-clock time.
Common mistakes
- ✗Swapping the definitions of parallel and distributed
- ✗Thinking adding sleeps speeds up a run
- ✗Forgetting that pushing checks to lower levels is faster
Follow-up questions
- →What state problems can parallel execution surface?
- →When does sharding (distributed) help more than parallelism?
MiddleTheoryOccasionalHow do load testing, stress testing, and synthetic monitoring differ?
How do load testing, stress testing, and synthetic monitoring differ?
Load testing checks behavior under expected, realistic load to confirm the system meets its targets (response time, throughput) at normal and peak traffic. Stress testing pushes beyond those limits to find the breaking point and observe how the system fails and recovers. Synthetic monitoring runs scripted transactions on a schedule against production to watch availability and performance over time. Load verifies capacity, stress finds limits, synthetic observes — not a pre-release gate.
Common mistakes
- ✗Swapping the goals of load and stress testing
- ✗Thinking synthetic monitoring is a one-time pre-release test
- ✗Ignoring response time and throughput as the measured metrics
Follow-up questions
- →What metrics confirm a load test passed?
- →Why run synthetic monitoring against production specifically?
SeniorDesignOccasionalYou must find which version introduced a bug. A known-good build works and a later build has the bug; many builds sit between them. Design how you would localize the offending version efficiently. Explain why you would not test each version one by one, what search strategy you would use instead and its cost, and what extra information (for example whether the fault is in code or in data) would let you narrow the search faster.
You must find which version introduced a bug. A known-good build works and a later build has the bug; many builds sit between them. Design how you would localize the offending version efficiently. Explain why you would not test each version one by one, what search strategy you would use instead and its cost, and what extra information (for example whether the fault is in code or in data) would let you narrow the search faster.
Use binary search (git bisect): repeatedly test the build in the middle of the suspect range, then keep only the half that still contains the transition from good to bad. That is O(log n) checks versus O(n) for testing every version in sequence. Narrow further by first asking whether the fault is in code or in data, which can eliminate a whole class of builds before bisecting.
Common mistakes
- ✗Testing versions linearly instead of bisecting
- ✗Calling binary search O(n) instead of O(log n)
- ✗Ignoring whether the fault is in code or in data
Follow-up questions
- →How many checks does bisecting 1024 builds take versus a linear scan?
- →Why does knowing the fault is data-related shrink the search?
SeniorDesignOccasionalYour automated tests run on a clean database, and one test orders a pizza — but it needs the pizzeria to be open, and the suite may run at night when it is closed. The test depends on external real-world state you do not control. How would you make this test reliable? Cover the options for providing the required precondition, which you would prefer and why, and how this generalizes to any test that depends on time or external state.
Your automated tests run on a clean database, and one test orders a pizza — but it needs the pizzeria to be open, and the suite may run at night when it is closed. The test depends on external real-world state you do not control. How would you make this test reliable? Cover the options for providing the required precondition, which you would prefer and why, and how this generalizes to any test that depends on time or external state.
Never depend on uncontrolled real-world state. Best: mock the clock or the open/closed flag so the precondition is always satisfied deterministically. Otherwise seed the database to a known open state, or call an API in the test's setup to prepare data. The general rule: a test must own its preconditions — control time and external state through mocks, seeding, or setup, never the live environment.
Common mistakes
- ✗Letting a test depend on uncontrolled real-world state
- ✗Adding sleeps or reruns instead of controlling the precondition
- ✗Deleting the check instead of owning the precondition
Follow-up questions
- →Why is mocking the clock preferable to seeding the database here?
- →How does this principle apply to a test that depends on a third-party API?
SeniorDebuggingRareA messenger shows "Failed to send" on a file — bug or not?
A messenger shows "Failed to send" on a file — bug or not?
It is not a bug if the failure is correct handling of a real condition — no network, file too large or an unsupported type, server down, expired session — and the user is clearly told why and can retry. It is a bug if it fails under valid conditions, gives no reason, offers no retry, or the message is wrong or misleading. The test is expected-versus-actual plus graceful degradation.
Open full question →Common mistakes
- ✗Filing any error message as a bug without checking the condition
- ✗Ignoring whether a reason and a retry are offered
- ✗Skipping the expected-versus-actual judgment
Follow-up questions
- →What would turn a correct failure into a reportable bug?
- →Why is a clear reason plus retry the line between bug and not-bug here?
SeniorDebuggingRareA support-ticket app shows "Save error" — diagnose across the chain
A support-ticket app shows "Save error" — diagnose across the chain
First gather evidence: open DevTools to inspect the request/response, enable a proxy, read the app logs, and debug the backend if you have access. Then reason across the full chain: a duplicate ticket number, a Cyrillic character the generator emitted, server-side validation rejecting the number or text, the DB being down, the network failing, a slow/timed-out response, or a malformed request from the frontend.
Open full question →Common mistakes
- ✗Jumping to one cause without gathering request/response evidence
- ✗Ignoring whole links of the chain (network, DB, validation)
- ✗Assuming the generator's number is always valid
Follow-up questions
- →How would the response status code narrow down which link failed?
- →Which causes would a proxy let you confirm that DevTools alone cannot?
SeniorDebuggingRareSame URL serves different file versions to different devices — why?
Same URL serves different file versions to different devices — why?
Likely causes are CDN edge-cache staleness (one node still holds the old version), geo or locale-based content negotiation (the Accept-Language or geo headers select a version), a stale local browser cache, or a VPN changing the exit node. Investigate by inspecting request/response headers with DevTools or a proxy and comparing ETag, Last-Modified, and Cache-Control across the devices.
Common mistakes
- ✗Assuming one URL must always return the same bytes
- ✗Ignoring CDN cache and geo/locale content negotiation
- ✗Not comparing
ETag/Last-Modified/Cache-Controlheaders
Follow-up questions
- →Which response header would reveal a stale CDN edge node?
- →How could
Accept-Languagecause one device to get an older build?