Skip to content

QA Engineer Interview Questions & Answers

40 questions for QA Engineer interviews — 28 easy, 12 medium.

About the QA Engineer role

Ensures product quality through a mix of manual exploration and automated checks. A Playwright-focused QA engineer turns test cases into reliable automated tests, verifies APIs, reports clear bugs and keeps regression suites up to date. Interviews test testing fundamentals, writing simple Playwright tests, assertions, basic API checks and how you investigate and report failures. Clear communication with developers and attention to detail are tested through scenario questions about real defects.

Test case design Playwright basics Web-first assertions API checks Bug reporting
Solved for this roleStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

How do you validate an API response in Playwright?

Save to ▾
Show answer
Assert the status with expect(res.status()).toBe(200), parse res.json() and check fields, using schema validation for the structure.
02

What is the difference between getByText and getByLabel?

Save to ▾
Show answer
getByText matches visible text anywhere on the page, so it suits paragraphs, links and messages. getByLabel finds a form control through its associated label element, aria-label or aria-labelledby, so it is the right choice for inputs such as Email or Password. Using getByLabel for inputs also checks that labels are wired correctly, which getByText cannot do.
03

How do you work with iframes?

Save to ▾
Show answer
const frame = page.frameLocator('#payment-frame'); await frame.getByLabel('Card number').fill('4111 1111 1111 1111');
04

How do you assert an element's CSS class, attribute or value?

Save to ▾
Show answer
const btn = page.getByRole('button', { name: 'Submit' }); await expect(btn).toHaveClass(/disabled/); await expect(btn).toHaveAttribute('aria-busy', 'true'); await expect(page.getByLabel('City')).toHaveValue('Pune'); await expect(btn).toHaveCSS('cursor', 'not-allowed');
05

A locator matches three Delete buttons and the test fails with a strict mode violation. How do you fix it?

Save to ▾
Show answer
First scope the locator to the item you mean, for example the row that contains the user's name: page.getByRole('row', { name: 'Ravi Kumar' }).getByRole('button', { name: 'Delete' }). Do not reach for first() or nth() as a quick fix, because the order can change and the test may delete the wrong record. Scoping by meaningful context is stable and readable.
06

How do you change the default assertion timeout?

Save to ▾
Show answer
Web-first assertions wait 5 seconds by default. Change it globally in playwright.config with expect: { timeout: 10000 }, or for one assertion with await expect(locator).toBeVisible({ timeout: 15000 }). Raise it only for genuinely slow steps like report generation. Raising it everywhere hides performance problems and makes failing tests slower to report.
07

How do you add, read and clear cookies in a browser context?

Save to ▾
Show answer
await context.addCookies([ { name: 'consent', value: 'yes', url: 'https://shop.example.test' }, ]); const cookies = await context.cookies(); expect(cookies.find((c) => c.name === 'consent')?.value).toBe('yes'); await context.clearCookies({ name: 'consent' });
08

What are nth(), first() and last(), and why are they risky?

Save to ▾
Show answer
They pick one element by position from a locator that matches many. They are fine when order is truly meaningful, such as the first search result. They are risky when order depends on data, sorting or rendering speed, because the test may silently act on the wrong element. Prefer filtering by text or a child element that identifies the item.
09

In what order do hooks and fixtures run?

Save to ▾
Show answer
Worker fixtures and beforeAll run first for the worker. For each test, the test fixtures it needs are set up, then beforeEach hooks run, then the test body. Afterwards afterEach hooks run, then test fixtures are torn down in reverse order. afterAll and worker fixture teardown run when the worker finishes. Fixtures are lazy, so unused ones never run.
10

How do you send a POST request with a JSON body?

Save to ▾
Show answer
await request.post('/api/users', { data: { name: 'Asha' } }) automatically sets JSON headers and serialises the payload.
11

When should you use getByTestId?

Save to ▾
Show answer
Use getByTestId when no user-facing locator is stable or unique, for example icon-only buttons, dynamic grids or canvas wrappers. It matches the data-testid attribute by default, and you can change the attribute name with testIdAttribute in the config. Test ids are very stable but users never see them, so prefer role, label or text first and keep test ids as a fallback.
12

Write a test that updates a user with PUT and verifies the change.

Save to ▾
Show answer
test('update user', async ({ request }) => { const res = await request.put('/api/users/12', { data: { name: 'Neha', city: 'Jaipur' }, }); expect(res.status()).toBe(200); const get = await request.get('/api/users/12'); expect(await get.json()).toMatchObject({ name: 'Neha', city: 'Jaipur' }); });
13

Write code to fill a login form using label-based locators.

Save to ▾
Show answer
test('login form', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('user@test.in'); await page.getByLabel('Password').fill('Pass@123'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL(/dashboard/); });
14

What is the difference between toHaveText and toContainText?

Save to ▾
Show answer
toHaveText checks that the element's full text matches the expected string or regex, after normalising whitespace. toContainText passes if the expected text appears anywhere inside it. Use toHaveText for exact labels like a heading, and toContainText for longer messages where only part matters. Both are web-first assertions, so they retry until the timeout before failing.
15

Write a beforeEach hook that opens the home page and accepts the cookie banner.

Save to ▾
Show answer
test.beforeEach(async ({ page }) => { await page.goto('/'); const accept = page.getByRole('button', { name: 'Accept cookies' }); if (await accept.isVisible()) { await accept.click(); } });
16

How is toBeVisible different from toBeAttached?

Save to ▾
Show answer
toBeAttached only checks that the element exists in the DOM. toBeVisible also needs it to have a non-empty bounding box and no visibility:hidden style. A hidden modal can be attached but not visible. Use toBeVisible for anything a user should see, and toBeAttached for hidden inputs or elements whose presence matters more than appearance.
17

What is the difference between page.goto and page.waitForURL?

Save to ▾
Show answer
page.goto starts a navigation to a URL and waits for the load state you choose. page.waitForURL does not navigate; it waits until the current page reaches a URL that matches a string, glob or regular expression, which is useful after a click that redirects, such as a login followed by a jump to the dashboard. Pair it with the click instead of adding fixed sleeps.
18

How do you send query parameters in an API request?

Save to ▾
Show answer
// GET /api/products?category=books&page=2 const res = await request.get('/api/products', { params: { category: 'books', page: 2 }, }); expect(res.ok()).toBeTruthy(); const body = await res.json(); expect(body.items.length).toBeGreaterThan(0);
19

How do you handle file uploads and downloads?

Save to ▾
Show answer
setInputFiles on the input for uploads; for downloads wait on page.waitForEvent('download') and save or inspect the file.
20

What does the waitUntil option of page.goto do?

Save to ▾
Show answer
waitUntil decides when goto considers navigation finished: 'load' waits for the load event and is the default, 'domcontentloaded' waits only for the HTML to parse, 'networkidle' waits for no network for 500 ms, and 'commit' returns when the response starts. Avoid networkidle, which is discouraged and flaky; use assertions on the content you need instead.
21

Tell me about a time you automated a difficult UI flow.

Save to ▾
Show answer
Situation: a booking page had a custom calendar, drag-to-select seats and a timer. Task: automate the full booking. Action: I used role locators for the calendar, mouse actions for seat selection and page.clock to control the timer, keeping each part in a page object method. Result: the flow ran reliably in CI and replaced a 20-minute manual check.
22

What does test.describe do?

Save to ▾
Show answer
test.describe groups related tests under one title, which shows up in reports. Hooks and test.use inside the group apply only to those tests. You can nest groups, for example Checkout then Guest user. Groups are only for organisation, they do not make tests share a page or state, so each test still gets fresh fixtures.
23

What does the channel option do?

Save to ▾
Show answer
By default Playwright uses its own bundled Chromium, Firefox and WebKit builds. Setting channel: 'chrome' or 'msedge' in a project runs tests in the installed branded Chrome or Edge browser instead. This is useful when you must test on the exact browser users run, for example for codecs or policies. Install with npx playwright install chrome.
24

How do you install and start a Playwright project?

Save to ▾
Show answer
Run npm init playwright@latest, which sets up config, example tests and browsers; execute with npx playwright test.
25

Why did you choose to build a career in Playwright automation?

Save to ▾
Show answer
I like that automation combines coding with product quality. I learned Selenium first, then moved to Playwright because auto-waiting, tracing and API support let me write stable tests faster. In my college project I automated twenty flows with TypeScript and a CI pipeline. I want a role where I can grow into framework design and CI ownership.
26

What are idempotent HTTP methods and why do they matter in testing?

Save to ▾
Show answer
An idempotent method gives the same result when repeated. GET, PUT and DELETE are idempotent, POST is not. This matters for retries: a retried PUT is safe, but a retried POST may create duplicates. Test that repeating a PUT leaves one updated record and that repeating DELETE returns a sensible status such as 404 or 204.
27

What happens before and after await use() in a fixture?

Save to ▾
Show answer
Code before await use(value) is setup and runs before the test. use hands the value to the test and waits while the test runs. Code after it is teardown and runs when the test finishes, even if it failed. This keeps setup and cleanup for one resource in one place, which is cleaner than a separate beforeEach and afterEach pair.
28

Tell me about a time you replaced brittle locators in an existing suite.

Save to ▾
Show answer
Situation: a suite I joined used long XPath selectors and about ten tests broke after every UI release. Task: make them stable. Action: I rewrote the locators with getByRole and getByLabel, added test ids for two custom grids and moved everything into page objects. Result: locator-related failures dropped to almost zero over the next three releases.
29

How do you select an option from a native select dropdown?

Save to ▾
Show answer
const country = page.getByLabel('Country'); await country.selectOption('India'); // by value or label await country.selectOption({ label: 'India' }); await expect(country).toHaveValue('IN'); // multi-select: selectOption(['a', 'b'])
30

How does Playwright compare with Selenium and Cypress?

Save to ▾
Show answer
Playwright supports multiple browsers, tabs, languages and has auto-waiting and tracing; Selenium has the widest ecosystem; Cypress runs in-browser with a great UI but has more limits across tabs.
31

How do you handle dialogs such as alerts?

Save to ▾
Show answer
Register page.on('dialog', d => d.accept()) before the action triggers the dialog.
32

How do you assert the number of rows in a table?

Save to ▾
Show answer
// waits until exactly 5 data rows are rendered const rows = page.getByRole('table').getByRole('row'); await expect(rows).toHaveCount(6); // 1 header + 5 rows // avoid: expect(await rows.count()).toBe(6) // that reads once and does not retry
33

What does the mount fixture return?

Save to ▾
Show answer
mount renders the component into the test page and returns a locator pointing at the component root. You can call getByRole, click, and use web-first assertions on it like any locator. It also has helpers: update to re-render with new props or children, and unmount to remove it. Because it is a locator, the auto-waiting and retry behaviour of end-to-end tests applies here too.
34

What is locator strictness in Playwright?

Save to ▾
Show answer
Locators are strict: an action such as click or fill throws a strict mode violation if the locator matches more than one element. This prevents a test from silently clicking the wrong item. Fix it by making the locator more specific with a name, filter or chaining. Methods like count(), all() or first() are allowed to work with many matches on purpose.
35

How do you handle a new tab?

Save to ▾
Show answer
const [newPage] = await Promise.all([ context.waitForEvent('page'), page.getByText('Open').click(), ]); await newPage.waitForLoadState();
36

Write a test that logs in and checks the dashboard heading.

Save to ▾
Show answer
test('login', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('a@b.com'); await page.getByLabel('Password').fill('secret'); await page.getByRole('button', { name: 'Login' }).click(); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); });
37

Tell me about a time you learned a new testing tool under a deadline.

Save to ▾
Show answer
Situation: my team moved from another tool to Playwright two weeks before a release. Task: I had to migrate the smoke tests. Action: I spent two evenings on the official docs and codegen, rewrote one test end to end, then asked a senior to review it before converting the rest with the same pattern. Result: all 25 smoke tests ran in Playwright before the release and ran faster than before.
38

How do you make an API request in Playwright?

Save to ▾
Show answer
Use the request fixture: const res = await request.get('/api/users'); expect(res.ok()).toBeTruthy(); const body = await res.json();
39

What does the open option of the HTML reporter control?

Save to ▾
Show answer
open decides whether the report opens in a browser after the run: 'always', 'never' or 'on-failure', which is the default. In CI you set it to 'never' so the process does not try to start a browser or hang waiting. Locally on-failure is convenient. You can also set the PLAYWRIGHT_HTML_OPEN environment variable instead of editing config.
40

What is Playwright and which browsers does it support?

Save to ▾
Show answer
An open-source end-to-end testing framework from Microsoft that automates Chromium, Firefox and WebKit through one API, in multiple languages.

Frequently asked questions

What does a QA Engineer do?

Ensures product quality through a mix of manual exploration and automated checks. A Playwright-focused QA engineer turns test cases into reliable automated tests, verifies APIs, reports clear bugs and keeps regression suites up to date. Interviews test testing fundamentals, writing simple Playwright tests, assertions, basic API checks and how you investigate and report failures. Clear communication with developers and attention to detail are tested through scenario questions about real defects.

Which skills are tested in a QA Engineer interview?

Expect questions on Test case design, Playwright basics, Web-first assertions, API checks, Bug reporting. Most rounds also ask you to explain a project and solve one practical problem live.

How do I prepare for a QA Engineer interview?

Revise Playwright Core, Locators and Assertions, API Testing first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.