\" in Bio; Save.\nInput: Bio=\"\"\nExpected: Stored escaped or rejected; no script execution in UI; security sanitized."}},{"@type":"Question","name":"Describe how to automate file uploads and downloads in Selenium tests across different browsers and CI runners. Provide practical steps or code considerations for handling file dialogs, setting browser download directories, verifying downloaded files, and cleaning up after tests in Python.","acceptedAnswer":{"@type":"Answer","text":"Direct answer\nFor uploads, send the file path directly to the element with send_keys rather than trying to automate the native OS file-picker dialog; for downloads, configure the browser to save to a known, per-test directory with prompts disabled, then poll that directory until the expected file exists AND is no longer a partial in-progress download, before verifying its contents and cleaning it up.\n\nStructured elaboration\nUploads: WebDriver cannot interact with a native OS dialog at all, since it lives outside the browser's DOM entirely; the reliable approach sidesteps the dialog completely by finding the underlying element (even if it is visually hidden behind a styled button) and calling send_keys(absolute_file_path) on it directly, which the browser treats exactly as if a user had picked that file. If no such input exists (a genuinely native-only upload widget), that is one of the rare cases that legitimately needs OS-level tooling, and is worth flagging as an edge case rather than the default plan.\n\nDownloads, across browsers and CI runners, need three coordinated pieces: (1) a per-test or per-run download directory, configured via browser options (prefs in Chrome, browser.download.dir in Firefox) rather than the OS default Downloads folder, so parallel CI jobs never collide on the same files; (2) prompts disabled (profile.default_content_settings.popups: 0 and the \"always ask where to save\" preference turned off) so the download starts immediately without a dialog WebDriver cannot see; (3) verification that polls for the FINAL filename to exist while the browser's in-progress marker (Chrome's .crdownload, Firefox's .part) is gone, not just checking for the final filename's existence the instant the download starts, since large files spend real time in the partial state.\n\nHeadless mode needs one extra step in older Chrome versions: enabling downloads in headless mode required an explicit CDP command (Page.setDownloadBehavior) because headless Chrome historically disabled downloads by default for security reasons; modern Chrome/Selenium handles this more transparently, but it is worth knowing as a historical gotcha if an older pinned Chrome version is in play.\n\nWorked example\nimport os, time, tempfile, threading\n\ndef wait_for_download(download_dir, filename, timeout=10, poll=0.2):\n deadline = time.time() + timeout\n target = os.path.join(download_dir, filename)\n partial = target + \".crdownload\"\n while time.time() < deadline:\n if os.path.exists(target) and not os.path.exists(partial):\n return target\n time.sleep(poll)\n raise TimeoutError(f\"{filename} did not finish downloading within {timeout}s\")\n\ndownload_dir = tempfile.mkdtemp()\nfilename = \"report.csv\"\npartial_path = os.path.join(download_dir, filename + \".crdownload\")\nfinal_path = os.path.join(download_dir, filename)\n\nSimulate a browser: write the partial marker immediately, then finish the\ndownload after a short delay by writing the final file and removing the\npartial marker, on a background thread.\nwith open(partial_path, \"w\") as f:\n f.write(\"\")\n\ndef finish_download():\n time.sleep(0.5)\n with open(final_path, \"w\") as f:\n f.write(\"id,amount\\n1,42\\n\")\n os.remove(partial_path)\n\nthreading.Thread(target=finish_download).start()\n\nresult_path = wait_for_download(download_dir, filename, timeout=5, poll=0.1)\nprint(f\"download completed at: {result_path} | exists: {os.path.exists(result_path)} | partial gone: {not os.path.exists(partial_path)}\")\n\nRunning it:\ndownload completed at: /tmp/.../report.csv | exists: True | partial gone: True\n\nThe poll correctly waited past the partial-file window rather than returning the instant the (still-downloading) target check might have raced a browser that writes the final filename before the content is fully flushed.\n\nTrade-offs and pitfalls\nThe most common upload mistake is trying to automate the native file-picker dialog with OS-level tooling by default, when the send_keys-to-hidden-input approach works for the overwhelming majority of real upload widgets and is far more reliable in headless CI (native dialogs frequently cannot be driven at all in a headless container). The most common download mistake is checking only for the final filename's existence without also checking that the partial-download marker is gone, which produces a race where the test reads a file that is still being written and gets a truncated or empty result. Finally, per-test download directories (not a single shared Downloads folder) are essential for parallel CI; without that isolation, two tests downloading files with the same name can silently overwrite or read each other's downloads."}},{"@type":"Question","name":"Implement a class Sampler that wraps a fixed collection of items and provides a method sample(n, seed=None) returning n unique items without modifying the collection or any internal state. Given the same seed, two calls must return the same result. Explain why this kind of deterministic, seeded sampling matters for building reproducible test fixtures, and show a short unit test.","acceptedAnswer":{"@type":"Answer","text":"Direct answer\nBuild Sampler around Python's random.Random(seed) (a private, independent random-number-generator instance), and never mutate the wrapped collection: copy it once at construction, and use random.sample, which itself returns a new list without touching its input.\n\nStructured elaboration\nThree requirements have to hold simultaneously, and each maps to a specific implementation choice:\n\n1. Determinism given the same seed. A module-level random.random() call depends on global, mutable state that other code in the process can also perturb. Instantiating a fresh random.Random(seed) inside the call isolates this sampler's randomness completely: two calls with the same seed see an identical generator state and produce identical output, regardless of what any other code in the process has done to the global random module.\n2. No mutation of the wrapped collection. Store a private copy (list(items)) at construction time so a caller mutating their own original list afterward cannot silently change what the sampler draws from. random.sample itself also returns a new list and does not shuffle or consume its input in place.\n3. No internal state changes across calls. Each sample() call creates its own local random.Random(seed); nothing persists between calls, so calling sample() twice with the same arguments is safe and repeatable, and calling it with n larger than the population is handled by clamping rather than raising.\n\nWhy this matters for test fixtures specifically: a flaky test that occasionally samples a different subset of fixture data produces failures that look unrelated to the actual change under test, and are expensive to triage because they cannot be reproduced on demand. A deterministic, seeded sampler turns \"this test failed on CI but passes locally\" into a reproducible, debuggable case.\n\nWorked example\nVerified:\n\nimport random\nfrom typing import List, Optional, Sequence\n\nclass Sampler:\n \"\"\"Wraps a fixed collection and returns deterministic, seeded samples\n without ever mutating the wrapped collection or any internal state.\"\"\"\n\n def __init__(self, items: Sequence[str]) -> None:\n self._items: List[str] = list(items) # private copy\n\n def sample(self, n: int, seed: Optional[int] = None) -> List[str]:\n if n < 0:\n raise ValueError(\"n must be >= 0\")\n n = min(n, len(self._items))\n rng = random.Random(seed)\n return rng.sample(self._items, n)\n\ndef test_same_seed_same_result():\n s = Sampler([\"a\", \"b\", \"c\", \"d\", \"e\"])\n first = s.sample(3, seed=42)\n second = s.sample(3, seed=42)\n assert first == second\n print(\"same seed ->\", first)\n\ndef test_no_mutation_of_source():\n original = [\"a\", \"b\", \"c\"]\n s = Sampler(original)\n s.sample(2, seed=7)\n assert original == [\"a\", \"b\", \"c\"]\n print(\"source unmutated:\", original)\n\ndef test_n_larger_than_population():\n s = Sampler([\"x\", \"y\"])\n result = s.sample(10, seed=5)\n assert sorted(result) == [\"x\", \"y\"]\n print(\"n > population ->\", result)\n\ntest_same_seed_same_result()\ntest_no_mutation_of_source()\ntest_n_larger_than_population()\n\nOutput:\nsame seed -> ['a', 'e', 'c']\nsource unmutated: ['a', 'b', 'c']\nn > population -> ['y', 'x']\n\nTrade-offs and pitfalls\nrandom.Random is not cryptographically strong. That is the correct trade-off here (speed and reproducibility matter far more than unpredictability for test fixtures), but do not reuse this pattern for anything security-sensitive.\nVery large collections. Materializing a full copy at construction is fine for typical fixture sizes, but for a truly huge population, prefer reservoir sampling (single pass, O(1) extra memory) over copying the whole thing; that is a different, more complex algorithm with its own seeding subtleties.\nA common mistake is seeding the module-level random module once at the top of a test session instead of per-call: that makes test order affect results, since every other call to random in the process advances the same shared state. Isolating an independent random.Random(seed) per call, as done here, avoids that entirely."}},{"@type":"Question","name":"Explain the practice of writing two to three concrete test cases before or immediately after implementing a function. Why is this approach valuable for catching edge cases early? Provide a short example workflow for a simple algorithm (for instance, computing factorial or parsing CSV) showing the initial two to three example tests, expected outcomes, and how they guide implementing and validating corner cases.","acceptedAnswer":{"@type":"Answer","text":"Direct answer\nWriting two or three concrete example tests before, or immediately after, implementing a function forces you to state its contract in checkable form while you still remember the corner cases you were thinking about, rather than discovering them later once the code is already written around a narrower assumption.\n\nStructured elaboration\n\nThis is a lightweight, test-driven-development-adjacent discipline, not full test-driven development; it does not require a strict red-green-refactor cycle for every function, but writing a small number of examples up front, rather than zero, and rather than a large exhaustive suite before any code exists, captures most of the benefit at low cost.\n\nWhy it catches edge cases early: choosing two or three examples forces a decision about the function's corner cases at the point of least sunk cost. Before writing factorial, you have to decide what factorial(0) returns and what happens for negative input, before you have written a loop that silently assumes the input is at least 1. If the implementation is written first, with no separate statement of expected behavior at those inputs, they are easy to skip entirely and never notice.\n\nThe mechanism: each example is a concrete input-output pair. A \"typical\" case establishes the mainline contract; a case known to be a corner case for the function's domain (zero, an empty collection, a boundary value) forces an explicit decision on it; a third case, invalid input, forces an explicit decision on error handling rather than leaving it implicit. The examples then serve two purposes: they are a mini-specification that clarifies your own thinking before implementation, and they become permanent regression tests that keep the corner-case decisions from silently regressing later.\n\nWorked example (executed)\nimport pytest\n\ndef factorial(n: int) -> int:\n if n < 0:\n raise ValueError(\"factorial is undefined for negative numbers\")\n result = 1\n for i in range(2, n + 1):\n result *= i\n return result\n\ndef test_factorial_typical_case():\n assert factorial(5) == 120\n\ndef test_factorial_zero_case():\n assert factorial(0) == 1 # mathematical convention: 0! = 1\n\ndef test_factorial_negative_raises():\n with pytest.raises(ValueError):\n factorial(-3)\n\nAll three PASSED: factorial(5) == 120, factorial(0) == 1, factorial(-3) raises ValueError.\n\nNotice what the process caught: writing factorial(0) as an example before finishing the implementation is what surfaces the question \"does my loop naturally return 1 for n=0, or does it need a special case?\" A for i in range(2, n + 1) loop naturally returns the initial result = 1 unmodified when n = 0, since the range is empty, so no special case is needed here. That is exactly the kind of fact you only know you have to check because you wrote the example first, rather than something you would necessarily verify by writing only the mainline test.\n\nTrade-offs and pitfalls\nTwo or three examples establish the contract; they are not a substitute for the fuller systematic techniques (boundary value analysis, equivalence partitioning) that belong in the rest of the suite. This practice catches the most damaging early omissions cheaply, it does not achieve coverage on its own.\nPicking three examples that are all typical, factorial(3), factorial(4), factorial(6), defeats the purpose entirely. The value depends specifically on choosing at least one genuine corner case for the function's domain, which requires actually thinking about that domain, not picking arbitrary numbers.\nFor a function with a large or messy domain, parsing arbitrary CSV (comma-separated values) input for instance, two or three examples chosen at the start can anchor you to a too-narrow view of \"corner case.\" Treat the initial set as a living list you add to as more real-world messiness turns up, not a one-time exercise.\nThis practice front-loads the decision but not necessarily the implementation. A team can decide factorial(-3) should raise, write the test, and then never actually add the guard clause; the examples only help if they are run and enforced, not merely written down and forgotten."}},{"@type":"Question","name":"You have read enough about something new to believe you understand it, but you have not proven it and real work is about to depend on it being right. How do you set up something small to test whether your understanding actually holds, and how do you keep that from putting anything real at risk?","acceptedAnswer":{"@type":"Answer","text":"Direct answer\nI design the smallest test that could actually prove me wrong, write down what I expect to see before I run it, and keep the blast radius small enough that being wrong doesn't cost anything real while I find out.\n\nStructured elaboration\nChoosing the smallest falsifying experiment: not the smallest experiment that would confirm what I already believe, but the smallest one that could show my understanding is incomplete or wrong. Stating the expectation and acceptance criteria first: I write down what I expect to happen before running it, so I can't quietly reinterpret an ambiguous result afterward as agreeing with me.\n\nIsolating blast radius: a sandbox, a lab setup, or a separate account, with a cost or scope I've deliberately bounded in advance, so a wrong understanding is cheap to discover rather than expensive.\n\nRepresentative rather than toy data: using data or conditions close to the real failure pattern, not an artificially clean case that would pass regardless of whether my understanding is actually right.\n\nMaking the result reproducible: documenting the exact setup and outcome so it holds up to scrutiny, and so I can redo the check later if the underlying system changes, rather than relying on memory of what happened.\n\nReproducing claims instead of trusting them: if my understanding came from a vendor's or a blog's claim, I try to reproduce that specific claim myself rather than taking it as already proven.\n\nStaged progression before it matters: an isolated experiment first, then something closer to an integration test, then one small, low-risk, production-adjacent change, rather than jumping straight from a lab result to something that matters.\n\nWorked example\nI'd read that a specific retry and backoff configuration would fix a flaky downstream call, but hadn't verified it myself. I set up a throwaway environment and replayed real traffic that reproduced the actual failure pattern, rather than a clean synthetic case. Before running anything, I wrote down the falsifiable claim: the new configuration should reduce failures without increasing load on the downstream service, not just \"it'll work.\" I ran it isolated, checked both halves of that prediction, and both held. I rolled it out on one non-critical path first, watched it for a defined period, then extended it further once that held up too.\n\nTrade-offs and pitfalls\nThe most common failure mode is designing a gentle test that only confirms the claim rather than one that could genuinely falsify it, especially when the claim came from a source you already want to trust. The other is skipping the staged rollout because the lab result felt convincing enough, and jumping straight from an isolated test to full production."}}]}

Meta QA Engineer - Entry Level Interview Preparation Guide

QA Engineer
Meta
entry
6 rounds
Updated 6/13/2026

Meta's QA Engineer interview process for entry-level candidates typically consists of a recruiter screening, 1-2 technical phone screens, and 4-5 onsite rounds. The process evaluates foundational QA knowledge, manual testing ability, basic test automation skills, technical communication, and cultural alignment. Entry-level candidates are expected to demonstrate solid understanding of testing fundamentals, ability to write clear test cases, familiarity with basic automation frameworks, and eagerness to learn. The interview emphasizes systematic problem-solving in testing scenarios rather than deep automation expertise.

Interview Rounds

1

Recruiter Screening

2

Technical Phone Screen - Manual Testing and Test Case Design

3

Technical Phone Screen - Test Automation Fundamentals

4

Onsite - Manual Testing and Product Analysis

5

Onsite - Test Automation Implementation

6

Onsite - Behavioral and Collaboration

Frequently Asked QA Engineer Interview Questions

Explaining Technical Concepts to Non-Technical AudiencesMediumTechnical
49 practiced

Your team reduced the authentication endpoint's p95 latency from 500ms to 350ms, a 30% improvement. For three audiences: a non-technical CEO, external developer customers, and internal engineering managers, write a short tailored message explaining the business value and one key metric each audience should track.

Defect Management and Bug LifecycleMediumTechnical
23 practiced

You're assigned to improve the bug triage process across multiple teams that currently have 200 open defects. Propose a triage workflow (roles, cadence), classification scheme (severity/priority/state), automation rules to reduce noise (auto-close duplicates, stale ticket rules), and metrics to track triage effectiveness over time.

Cross-Functional CollaborationMediumTechnical
33 practiced

What's your framework for deciding when a stalled cross-team dependency needs to go to leadership versus continuing to work it peer-to-peer?

Systematic Debugging and Root Cause AnalysisHardTechnical
24 practiced

Your service occasionally experiences process-level crashes with native segmentation faults (SIGSEGV). Explain the tools and steps you would use to collect and analyze core dumps, symbolicate native stack traces, map them back to source (C/C++/native extensions), and determine whether the root cause is a bug in your code, an external library, or OS/kernel interactions.

Clear Written and Verbal CommunicationEasyTechnical
130 practiced

At the end of a meeting, how do you confirm next steps out loud in the room, and then again in a short written follow-up, so nothing gets lost between the conversation and the written record?

Test Coverage Analysis and OptimizationEasyTechnical
66 practiced

You are writing test cases for a user profile update form. List eight specific test cases (including positive, negative, and edge cases) that validate functional behavior, validation errors, and UI constraints. For each test case include preconditions, test steps, input data and the expected result.

Test Automation ScriptingMediumTechnical
58 practiced

Describe how to automate file uploads and downloads in Selenium tests across different browsers and CI runners. Provide practical steps or code considerations for handling file dialogs, setting browser download directories, verifying downloaded files, and cleaning up after tests in Python.

Programming for Test AutomationMediumTechnical
75 practiced

Implement a class Sampler that wraps a fixed collection of items and provides a method sample(n, seed=None) returning n unique items without modifying the collection or any internal state. Given the same seed, two calls must return the same result. Explain why this kind of deterministic, seeded sampling matters for building reproducible test fixtures, and show a short unit test.

Test Case Design and Edge Case AnalysisEasyTechnical
78 practiced

Explain the practice of writing two to three concrete test cases before or immediately after implementing a function. Why is this approach valuable for catching edge cases early? Provide a short example workflow for a simple algorithm (for instance, computing factorial or parsing CSV) showing the initial two to three example tests, expected outcomes, and how they guide implementing and validating corner cases.

Growth Mindset and Learning AgilityMediumTechnical
59 practiced

You have read enough about something new to believe you understand it, but you have not proven it and real work is about to depend on it being right. How do you set up something small to test whether your understanding actually holds, and how do you keep that from putting anything real at risk?

Want to create your own tailored preparation guide using our deep research?

Get Started for Free

Interview-Ready Courses

Visual-first, interactive, structured learning paths

Browse QA Engineer jobs

AI-enriched listings across hundreds of company career pages

Explore Jobs