Resource library

QA Interview

TypeScript Coding Interview Questions for Playwright Testers (2026)

Practice TypeScript coding interview questions for Playwright testers with precise answers, runnable examples, debugging advice, and framework trade-offs.

25 min read | 4,522 words

TL;DR

Prepare TypeScript fundamentals and apply every answer to Playwright behavior. Interviewers look for sound typing, correct promise control, stable locators, isolated fixtures, useful assertions, and code that handles failure paths without hiding them.

Key Takeaways

  • Explain TypeScript rules through test reliability, not language trivia alone.
  • Model uncertain external data with unknown and narrow it before use.
  • Return and await Playwright promises so failures remain attached to the correct test step.
  • Prefer web-first assertions and Locator objects over sleeps and cached element handles.
  • Use typed fixtures, discriminated unions, and generics only where they make invalid states harder to express.
  • Keep each parallel test isolated by browser context, account, mutable data, and output path.
  • During live coding, state assumptions, test edge cases, and explain the smallest maintainable solution.

TypeScript coding interview questions for Playwright testers measure two skills at once: whether you understand the language and whether you can use it to build reliable browser tests. A strong candidate can explain a type rule, write a small correct function, and connect that choice to locators, fixtures, asynchronous actions, or parallel workers.

This interview hub gives you 48 distinct questions with practical model answers. Use the examples as rehearsal material, then rewrite them from memory and explain each trade-off aloud. For broader tool coverage, pair this guide with the Playwright interview question bank and practice timed answers in /practice.

TL;DR

Topic What to demonstrate Playwright consequence
Types Narrow uncertain data; avoid casual any Safer API payloads and environment config
Functions Explicit inputs, outputs, and side effects Reusable helpers that remain debuggable
Async code Return and await every meaningful promise Correct ordering and attributed failures
Collections Pick arrays, sets, or maps from behavior Clear test data and result processing
Page design Encapsulate user behavior, not selectors alone Smaller, maintainable page components
Locators Use semantic, lazy Locator queries Auto-waiting and resilient assertions
Fixtures Model scope and dependencies explicitly Worker-safe setup and deterministic cleanup
Debugging Preserve the first cause and artifacts Faster diagnosis without false passes

A persuasive response follows a compact pattern: state the rule, show the relevant code, name an edge case, and explain its effect on a test suite. Do not force all four parts when the interviewer asks for a one-line definition, but be ready for the probe.

1. TypeScript Coding Interview Questions for Playwright Testers: Type Safety

Q: What is the difference between any and unknown?

any disables meaningful checking for the value and lets unsafe operations spread through the program. unknown accepts any input but requires narrowing before property access, calls, or assignment to a specific type. For an API response or JSON fixture, use unknown at the boundary, validate its shape, and only then pass a typed value into a Playwright test. That design turns malformed test data into an early, descriptive error instead of a later Cannot read properties of undefined failure.

Q: When would you use a union type in a test framework?

A union represents a value with a known set of alternatives, such as 'admin' | 'viewer' or a successful versus failed API result. It is stronger than string because the compiler rejects misspelled roles and can require exhaustive handling. A discriminant field such as kind makes each branch easy to narrow. In Playwright setup code, this prevents a viewer account from accidentally entering an admin-only flow.

Q: What does never mean, and how can it catch missing cases?

never describes a value that cannot occur, often after every member of a union has been handled. Assigning the remaining value to never in a default branch makes the compiler report newly added variants that the switch ignores. It is useful in a reporter that formats passed, failed, timedOut, and skipped results. Unlike a silent fallback string, exhaustive checking forces the framework owner to decide what a new status means.

type Result =
  | { kind: 'passed'; durationMs: number }
  | { kind: 'failed'; error: string };

function summarize(result: Result): string {
  switch (result.kind) {
    case 'passed':
      return `passed in ${result.durationMs} ms`;
    case 'failed':
      return `failed: ${result.error}`;
    default: {
      const unreachable: never = result;
      return unreachable;
    }
  }
}

Q: What is structural typing in TypeScript?

TypeScript checks whether a value has the required shape, not whether it was created from a named class. An object with the required email and password properties can satisfy a Credentials interface even if it came from a factory. This makes test-data composition convenient, but extra runtime fields do not prove external JSON is valid. Structural compatibility is a compile-time rule, so validate network and file input separately.

2. Interfaces, Type Aliases, and Object Models

Q: Interface or type alias: which should you choose?

Both can describe object shapes, and either is suitable for many test models. Interfaces support declaration merging and communicate an extendable object contract, while type aliases express unions, intersections, tuples, primitives, and mapped types. I use an interface for a stable page-service contract and a type alias for closed result variants. The important interview answer is the reason for the choice, not a blanket claim that one is always better.

Q: How do optional properties differ from properties containing undefined?

With an optional property such as token?: string, the key may be absent. With token: string | undefined, the key is required even though its value may be undefined. That distinction matters when serializing request bodies because an omitted field and an explicit undefined can be treated differently by transformation code. Enable exactOptionalPropertyTypes when the project needs the compiler to preserve that intent more strictly.

Q: How would you model immutable test data?

Use readonly properties for the public contract and create new objects instead of mutating shared fixtures. For arrays, readonly User[] prevents push and element replacement through that reference. Remember that TypeScript readonly is compile-time and shallow, so nested mutable objects need their own readonly types or copying. Immutable data reduces order-dependent failures when Playwright tests execute in parallel.

Q: Why is an index signature often too broad for environment configuration?

A declaration such as { [key: string]: string } claims every key exists and returns a string, which is rarely true for process environment data. A narrow interface with specific optional keys documents the supported settings, but runtime validation is still required. Parse URLs, booleans, and numbers once in configuration code rather than inside every test. This produces one actionable startup error for a missing base URL instead of dozens of navigation failures.

3. Functions, Closures, and Reusable Helpers

Q: What is the difference between a function declaration and an arrow function?

A function declaration is hoisted with its implementation and has a dynamic this based on the call site. An arrow function is an expression and captures this lexically from its surrounding scope. Page object methods generally work well as normal methods when called through the instance. A callback passed to expect.poll or an array operation is often clearer as an arrow because lexical capture is intentional.

Q: Why should helper functions declare return types?

Type inference is reliable for small local functions, but exported framework helpers benefit from explicit return types as an API boundary. The annotation prevents an accidental refactor from changing Promise<User> into Promise<User | undefined> without review. It also makes missing returns visible near the implementation. Do not annotate every temporary variable just to add syntax; spend explicitness where other tests depend on the contract.

Q: What is a closure, and where does it appear in Playwright tests?

A closure retains access to variables from its defining scope after that surrounding function has returned. A factory can capture a base URL or role and return a configured request function. Closures are also present in callbacks supplied to test.step, expect.poll, and route handlers. Be careful when a callback captures mutable loop variables or shared state, because parallel execution can make the observed value surprising.

Q: Write a typed retry helper. Should it retry Playwright assertions?

A generic helper can retry an operation while preserving its resolved type, but it should only cover explicitly transient work. It must rethrow the last error and should not retry programming errors or destructive actions blindly. Playwright web-first assertions already retry until their timeout, so wrapping them in another generic retry can multiply wait time and hide the real synchronization problem. For polling an external condition, prefer expect.poll when its diagnostics fit the need.

async function retry<T>(
  operation: () => Promise<T>,
  attempts: number,
): Promise<T> {
  if (attempts < 1) throw new RangeError('attempts must be positive');
  let lastError: unknown;
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      return await operation();
    } catch (error: unknown) {
      lastError = error;
    }
  }
  throw lastError;
}

4. Async and Promise Coding Questions

Q: What does an async function return?

An async function always returns a Promise, even when its source code returns a plain value. A returned value becomes a fulfilled promise, while a thrown error becomes a rejected promise. This matters in test helpers because the caller must await the helper to preserve sequencing and failure attribution. An explicit Promise<void> return type also documents that a page action completes asynchronously without returning domain data.

Q: What happens when you forget await on a Playwright action?

The test continues before the action has necessarily completed, so the next assertion can race with navigation, filling, or clicking. A rejection may surface later with a confusing stack or after the test has ended. Type-aware lint rules such as no-floating-promises catch many of these defects. Returning the promise directly is valid when no local cleanup or error translation must happen, but silently discarding it is not.

Q: When should you use Promise.all in a test?

Use it for independent asynchronous operations that are safe to start together. It is also useful when an action and an event listener must be coordinated, although Playwright often provides patterns where the waiting call is created before the action. Do not place sequential UI steps in Promise.all, because two actions against one page can interfere. For independent API setup, concurrent creation can reduce runtime if the backend and data ownership allow it.

const responsePromise = page.waitForResponse(
  response => response.url().endsWith('/api/orders') && response.status() === 201,
);
await page.getByRole('button', { name: 'Place order' }).click();
const response = await responsePromise;

Q: How do microtasks affect a Playwright test?

Promise continuations run as microtasks after the current synchronous stack, before later timer tasks. You rarely manipulate that queue directly, but understanding it explains why an unawaited promise does not block the next source line. Adding setTimeout or waitForTimeout does not repair missing synchronization; it merely changes timing. Wait for the observable browser or network condition that proves the application is ready. The async and await guide for QA engineers provides deeper event-loop practice.

5. Arrays, Maps, Sets, and Data Transformations

Q: How would you remove duplicate test IDs while preserving order?

Construct a Set from the array and spread it back into an array. Set preserves insertion order and ignores later occurrences of an equal primitive value. The operation is expected O(n) time with O(n) additional space for typical inputs. If IDs should be case-insensitive, normalize them deliberately and decide whether the output keeps the first original spelling.

function uniqueIds(ids: readonly string[]): string[] {
  return [...new Set(ids)];
}

console.log(uniqueIds(['login', 'search', 'login']));

Q: map, filter, or reduce: how do you choose?

Use map when every input becomes one output, filter when the result keeps selected inputs, and reduce when values accumulate into another shape. A chain of filter and map often communicates test-result processing better than a clever all-purpose reduction. reduce is appropriate for grouping counts or building an index when the accumulator type is explicit. Prefer a loop when branching, early exits, or diagnostic logging would otherwise become obscure.

Q: How do you compare two arrays in an assertion?

First establish whether order and duplicates are requirements. For exact order, compare the arrays directly with toEqual. For set-like comparison, deduplicate and sort copies, then compare, but note that this intentionally discards multiplicity and sequence. A robust answer states that JSON stringification is fragile because property order and unsupported values can distort equality.

Q: When is a Map better than a plain object?

Map accepts keys of any type, preserves insertion order, exposes size, and has explicit iteration and membership APIs. A plain object works well for JSON-shaped records with known string keys. In a framework, Map<string, BrowserContext> can represent dynamically created sessions, but lifecycle ownership still needs a cleanup strategy. Do not use either as a hidden global registry that survives between tests.

6. Generics and Utility Types for Test Frameworks

Q: Why use a generic function instead of returning any?

A generic preserves a relationship between input and output types. For example, a parser that accepts a runtime validator can return exactly the validator's result type rather than erasing it. This gives callers autocomplete and compile-time checking without unsafe assertions. A generic parameter adds value only when it links two or more positions or constrains behavior; an unused <T> is decoration.

Q: Explain keyof with a test-data example.

keyof User produces a union of the known property names of User. A helper using <K extends keyof User>(user: User, key: K): User[K] returns the matching property type, so an email key yields a string while an age key could yield a number. It also rejects misspelled fields at compile time. This is useful for typed table-driven checks, though direct property access remains clearer for a single fixed assertion.

Q: How would you use Pick, Omit, and Partial safely?

Pick selects named properties, Omit removes them, and Partial makes each property optional. Pick<User, 'email' | 'role'> is useful for login identity without exposing unrelated profile data. Partial<User> works for update patches, but it is too permissive for creating a valid user because all required fields disappear. Define a dedicated creation type when the business rules differ instead of stacking utility types until intent is lost.

Q: What is a generic constraint?

A constraint limits the types accepted by a generic while retaining their specific shape. A formatter constrained to { id: string } can safely read id and still return the caller's richer object type. Constraints are useful for shared test entities such as users, orders, and products. Avoid constraining to a large base interface just to reuse one property, because that couples unrelated fixtures.

7. Runtime Validation and API Data

Q: Why do TypeScript types not validate an API response?

Types are removed during compilation and cannot inspect bytes received at runtime. Writing response.json() as User only tells the compiler to trust you; it does not add missing properties or reject wrong ones. Validate boundary data with a schema library or a focused type guard, then use the narrowed value. In API-assisted Playwright setup, that prevents bad seed data from contaminating the UI scenario.

Q: Write a type guard for a login response.

A type guard returns a type predicate after checking runtime facts. Start from unknown, reject null and arrays, then inspect required property types. Keep the guard aligned with the smallest contract the test needs. For complex nested schemas, use a maintained validation library instead of hand-writing dozens of fragile checks.

type LoginResponse = { token: string; userId: string };

function isLoginResponse(value: unknown): value is LoginResponse {
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
    return false;
  }
  const record = value as Record<string, unknown>;
  return typeof record.token === 'string' &&
    typeof record.userId === 'string';
}

Q: How should response.json() errors be handled?

Check the HTTP status and content expectations before assuming the body matches a success model. Parse into unknown, validate it, and throw an error containing the endpoint purpose and relevant status, while avoiding credentials or personal data. Preserve the original error as cause when translating a parsing failure. The Playwright APIRequestContext examples show how browser and API setup can work together.

Q: What is unsafe about a non-null assertion?

The postfix ! removes null and undefined from the compiler's view without changing runtime behavior. It can be justified after a framework invariant that TypeScript cannot infer, but it should be rare and locally explained. On an environment variable, process.env.BASE_URL! converts a missing configuration into a later, less helpful error. An explicit startup check both narrows the type and gives the operator a useful message.

8. Playwright Locators and Web-First Assertions

Q: Why prefer Locator over ElementHandle?

A Locator describes how to find an element and resolves against the current DOM when an action or assertion runs. It integrates with Playwright actionability checks and web-first retrying, which helps with re-rendered interfaces. An ElementHandle points to a particular DOM node and can become stale in application terms even if the handle still exists. Use handles only for lower-level operations that genuinely require a specific node reference.

Q: What makes a locator resilient?

A resilient locator reflects user-visible semantics or a stable testing contract. getByRole('button', { name: 'Save' }) checks accessibility role and name, while a dedicated test ID can be appropriate when no good semantic locator exists. Long CSS chains and nth() often encode incidental layout that changes during harmless refactors. If multiple matches are valid, narrow by a meaningful container or text instead of suppressing strictness blindly.

Q: Why use await expect(locator).toBeVisible() instead of checking isVisible()?

The web-first assertion retries until the condition passes or its timeout expires and then reports assertion context. isVisible() returns the state at that instant, which is useful for branching but can race when used as a final oracle. A sleep before isVisible() guesses at application timing and slows every successful run. Select the assertion that expresses the business outcome, such as enabled, contains text, or has a URL, rather than visibility by habit.

Q: How would you locate a row by its cell content?

Start with a row role and filter it by a descendant locator or accessible text, then find the action inside that row. This keeps the query tied to the table's semantic unit rather than a global matching index. Verify uniqueness because duplicated customer names may require an ID or email column. The locator filter API is covered with more variations in Playwright locator filter examples.

const row = page.getByRole('row').filter({
  has: page.getByRole('cell', { name: 'order-1042', exact: true }),
});
await expect(row).toHaveCount(1);
await row.getByRole('button', { name: 'Refund' }).click();

9. Fixtures, Page Objects, and Dependency Design

Q: What belongs in a Playwright fixture?

A fixture should create and clean up a dependency with a clear test or worker scope, such as an authenticated page, API client, or seeded account. Its setup must call use(value) and place cleanup after that call so teardown runs when the dependent test finishes. Keep assertions about the scenario in the test, not hidden inside generic setup. Worker fixtures suit expensive resources that are safe to share within a worker, while mutable browser state usually belongs to each test.

Q: How do you type a custom fixture?

Define a fixture object type and pass it to base.extend<Fixtures>(). Each fixture callback receives its dependencies, the use function, and optional test information according to the API. Type dependencies as their real page or service objects rather than object or any. If a fixture needs a worker-scoped type parameter, model test and worker fixtures separately so scope mistakes surface at compile time.

Q: What should a page object expose?

Expose user or domain actions such as submitOrder and meaningful state such as confirmationNumber, not a public catalog of every selector. Keep locators private or readonly when tests should not manipulate internals. Return data when callers need it, and let Playwright failures propagate with their useful call logs. The Playwright TypeScript framework tutorial provides a larger structure for pages, fixtures, and configuration.

Q: Composition or inheritance for page objects?

Composition usually maps better to interfaces made of headers, dialogs, tables, and services. A CheckoutPage can own an AddressForm and CartSummary without inheriting unrelated helpers from a giant BasePage. A small base class can still be reasonable for a genuine shared invariant, but deep hierarchies make behavior and initialization harder to trace. Explain the dependency graph in terms of change boundaries, not a rule that inheritance is forbidden.

10. Errors, Debugging, and Test Reliability

Q: How should you catch errors in a Playwright helper?

Catch an error only when the helper can recover, add specific context, or perform required local cleanup. Narrow the caught value because TypeScript can treat it as unknown, then preserve it with new Error(message, { cause: error }). Do not replace a detailed Playwright timeout with a generic Step failed message. Often the best implementation has no catch block and lets the runner retain the original stack, trace, and call log.

Q: Why is waitForTimeout a poor synchronization strategy?

A fixed delay waits too long when the app is fast and may still be too short when it is slow. It also says nothing about which condition the test requires. Use a locator assertion, URL assertion, response wait, or application-specific state instead. Keep fixed waits for deliberate demonstrations or rare timing experiments, not production correctness.

Q: How do you debug a test that passes locally but fails in CI?

First compare environment facts: browser project, viewport, workers, data, secrets, time zone, locale, network access, and application build. Inspect the first failing action using the trace, screenshot, video if enabled, and Playwright call log. Reproduce with the same command and configuration before changing timeouts. The guide to debugging Playwright in VS Code helps with local stepping, while CI artifacts reveal remote state.

Q: What is wrong with catching an assertion and continuing?

It can convert a real regression into a false pass and make later steps operate on invalid state. If several independent checks must be collected, use soft assertions deliberately and call expect(test.info().errors).toHaveLength(0) or rely on the runner's final soft-failure behavior as designed. Log-only verification is not an assertion. A test should make its pass criterion machine-enforceable and leave the first relevant evidence intact.

11. Parallelism, Isolation, and Configuration

Q: Are Playwright tests executed in parallel safely by default?

Playwright gives each test an isolated browser context under the standard test fixtures, which separates cookies and local storage. That does not isolate shared accounts, database records, file names, mailboxes, or external rate limits. A test remains parallel-safe only when every mutable resource has clear ownership. Generate unique data from stable identifiers such as worker index and test metadata, then clean it through supported APIs.

Q: What is the difference between test-scoped and worker-scoped fixtures?

A test-scoped fixture is set up for each test that uses it and is suited to mutable scenario state. A worker-scoped fixture is created once per worker process and can amortize expensive setup. Worker scope must not leak one test's mutations into another, and its identity may change when the runner restarts a worker after failure. Choose scope from isolation requirements first, then consider speed.

Q: How should environment variables be parsed?

Read them in one configuration module, validate required values, and convert strings into domain types explicitly. The string 'false' is truthy in JavaScript, so Boolean(process.env.HEADLESS) is a common bug. Parse known spellings and reject anything else rather than guessing. Never print tokens in configuration errors or attach secret-bearing objects to reports.

Q: What causes order-dependent tests?

Typical causes include reused accounts, global mutable variables, unreset feature flags, shared downloads, and tests that assume another test created data. Each test should arrange the state it needs or consume an immutable worker-owned resource. Serial mode can model one intentional end-to-end journey, but it should not conceal accidental coupling. Randomizing data and repeating tests with multiple workers can expose dependencies before CI does.

12. Live TypeScript Coding Interview Questions for Playwright Testers

Q: Write a function that groups failed test titles by project.

Clarify whether the input includes only final attempts and whether project order matters. A Map<string, string[]> represents dynamic project keys and preserves first-seen order. The function below is O(n) time and stores one output entry per failed result. It does not mutate the caller's array, which makes it safe to reuse in reporting code.

type TestResult = { project: string; title: string; status: 'passed' | 'failed' };

function failuresByProject(results: readonly TestResult[]): Map<string, string[]> {
  const grouped = new Map<string, string[]>();
  for (const result of results) {
    if (result.status !== 'failed') continue;
    const titles = grouped.get(result.project) ?? [];
    titles.push(result.title);
    grouped.set(result.project, titles);
  }
  return grouped;
}

Q: How would you find the first duplicate in a list of test names?

Walk the array once while recording names in a Set. Return the first name whose insertion reports that it already exists, or return undefined when all are unique. State whether matching is case-sensitive and whether surrounding whitespace is significant before coding. This solution uses expected O(n) time and O(n) space, while a nested-loop solution uses constant extra space but quadratic time.

Q: Implement a timeout wrapper around a promise. What caveat matters?

Race the operation against a timer promise and clear the timer in finally so the timer does not remain scheduled. The critical caveat is that rejecting the wrapper does not cancel the underlying operation. Real cancellation requires an API that accepts an AbortSignal or another cancellation mechanism. For Playwright actions, prefer their native timeout options because those integrate with runner diagnostics and operation cleanup.

Q: How would you review a candidate's locator helper?

I would check whether the helper preserves Locator laziness, accepts a meaningful domain input, and avoids encoding layout indexes. I would also ask how it behaves with zero or multiple matches and whether the accessible name is exact enough. A helper that returns a Locator is often more composable than one that immediately clicks and hides every assertion opportunity. Finally, I would test it against a re-render and a duplicate-label case, not just the happy path.

How Interviewers Grade Your Answers

Interviewers usually grade correctness first, then reasoning, communication, and maintainability. A syntactically perfect solution that ignores duplicate policy, failure behavior, or asynchronous ordering is weaker than a simple solution whose assumptions are explicit. They also watch how you respond to a correction: verify the point, adjust the code, and continue without defending a broken premise.

Signal Strong evidence Weak evidence
Language accuracy Distinguishes compile-time types from runtime validation Says an assertion validates JSON
Async control Awaits actions and explains concurrency boundaries Adds sleeps to make races disappear
Playwright knowledge Uses Locator and web-first assertions Caches handles and checks instantaneous state
Design judgment Names scope, ownership, and trade-offs Adds abstractions without a concrete need
Coding method Clarifies inputs and tests edge cases Types immediately and never verifies
Debugging Preserves original evidence and narrows the cause Catches every error or raises all timeouts

During live coding, narrate only decisions that help the reviewer follow your work. Run through empty input, one ordinary case, and one adversarial case. If you do not remember an exact Playwright method, say what behavior you need and verify the API rather than inventing a name. Use the /dashboard?tab=upload to align your resume evidence with the framework skills you can actually demonstrate.

Common Mistakes

  • Using any at every difficult boundary: Start with unknown and validate what arrives from files, APIs, and environment settings.
  • Claiming TypeScript validates runtime data: Compilation erases types, so external values still need checks.
  • Forgetting an await: Enable promise-aware linting and return asynchronous helpers explicitly.
  • Parallelizing steps on one page: Concurrency is appropriate only when operations are independent and ownership is clear.
  • Using sleeps as readiness checks: Wait for an observable state tied to the requirement.
  • Choosing CSS chains before accessible locators: Prefer role, label, text, or a stable test ID contract.
  • Making every page inherit BasePage: Compose focused components and keep dependencies visible.
  • Sharing mutable accounts across workers: Allocate unique users or reset server state through controlled setup.
  • Catching and replacing Playwright errors: Add context only when it preserves the original cause and diagnostics.
  • Overusing non-null assertions: Prove configuration and data invariants with runtime checks.
  • Writing a generic with no type relationship: Use a concrete type when flexibility has no caller benefit.
  • Ignoring cleanup in fixtures: Put teardown after use and design it to run even when the test fails.
  • Optimizing before clarifying behavior: Confirm ordering, duplicates, nullability, and error policy first.
  • Reciting definitions without test impact: Connect the language feature to reliability, diagnosis, or maintenance.

Conclusion

The best way to prepare for TypeScript coding interview questions for Playwright testers is to practice language concepts inside realistic automation constraints. Type a boundary, await the action, assert an observable outcome, and explain how the code behaves under failure and parallel execution.

Rebuild the examples without looking, then answer each question aloud in under two minutes. When your explanation includes the rule, one edge case, and a Playwright consequence, you demonstrate the practical judgment an SDET interview is designed to find.

Interview Questions and Answers

What is the difference between any and unknown in TypeScript?

Any disables type safety for the value and lets unsafe operations propagate. Unknown accepts any value but requires narrowing before use. I use unknown at API and JSON boundaries, validate it, and then expose a typed domain object to tests.

Why do TypeScript types not validate a Playwright API response?

Types are erased when code is compiled, so they cannot inspect runtime JSON. A type assertion only changes what the compiler believes. I parse into unknown and use a schema or type guard before the response enters the test flow.

What happens if you forget await on a Playwright action?

The next line can run before the action finishes, creating a race. A later rejection may be attached to the wrong step or occur after test completion. Promise-aware lint rules and explicit async return types help prevent this.

When would you use Promise.all in Playwright tests?

I use Promise.all for independent work that is safe to start concurrently, often API setup. I do not use it for sequential actions on the same page. When coordinating an event with an action, I register the wait before triggering the action.

Why is Locator preferred over ElementHandle?

A Locator resolves against the current DOM and integrates with actionability and retrying assertions. An ElementHandle references a particular node and is easier to misuse across re-renders. I reserve handles for low-level cases that truly need a node reference.

How do you make a Playwright test safe for parallel execution?

Give each test ownership of its browser context, mutable records, accounts, and files. Avoid module-level mutable state and generate unique data from stable worker or test identifiers. Clean up external resources through a fixture or API even when the test fails.

What belongs in a custom Playwright fixture?

A fixture owns setup and teardown for a dependency with a deliberate test or worker scope. Examples include authenticated pages, seeded accounts, and API clients. Scenario assertions stay visible in the test rather than being hidden in generic fixture setup.

How do you choose between interface and type in TypeScript?

I use either for ordinary object shapes, choosing an interface when I want an extendable object contract. A type alias is required for unions and is convenient for intersections, tuples, and mapped types. I explain the local design reason instead of declaring one universally superior.

Why is waitForTimeout usually a poor choice?

It guesses how long readiness might take and provides no proof that the required state occurred. Fast runs waste time, while slow runs can still fail. I wait for a locator, URL, response, or domain state that expresses the requirement.

How should errors be handled in a Playwright helper?

I catch only when I can recover, perform local cleanup, or add specific context. When translating, I preserve the original error as the cause so the Playwright diagnostics remain available. Otherwise I let the runner report the original failure.

Frequently Asked Questions

How much TypeScript is needed for a Playwright interview?

Know object and union types, narrowing, functions, arrays, maps, generics, promises, error handling, and runtime validation. You should also connect these concepts to locators, fixtures, page objects, configuration, and parallel tests.

Are TypeScript coding questions common for Playwright testers?

Yes. Interviewers often use short data-transformation or asynchronous exercises to see how you reason, then ask how the same code would fit a Playwright framework. Expect both language fundamentals and browser-testing judgment.

Should Playwright testers use any or unknown for API data?

Use unknown for untrusted API data and narrow or validate it before access. Any disables checks and can allow a malformed response to fail much later in an unrelated UI step.

What TypeScript coding exercises should an SDET practice?

Practice deduplication, grouping results, array comparison, typed parsing, promise coordination, retry boundaries, and discriminated unions. For each solution, state input rules, complexity, failure behavior, and an automation use case.

What is the most important async rule in Playwright?

Return or await every meaningful Playwright promise. Missing await creates races, misleading failure locations, and work that may continue after the test has ended.

How should I practice Playwright TypeScript live coding?

Use a plain editor, clarify requirements aloud, write the simplest typed solution, and test empty and adversarial inputs. Then explain how the function would behave inside a parallel Playwright suite.

Related Guides