Interview Guide · 18 min read · Updated

Senior Frontend Interview Guide, Part 12: Testing with Jest, React Testing Library, and Playwright

Enterprise teams ship frequently, and they can only do so safely with an automated test suite they trust. Interviewers ask about testing to learn whether you write tests that catch real bugs without slowing the team down. This chapter covers testing strategy, Jest, React Testing Library, mocking…

Featured image: Part 12, Testing with Jest, React Testing Library, and Playwright

This article is Part 12 of 18 in The Senior Frontend Engineer Interview Guide, and includes 22 interview questions with model answers.

Enterprise teams ship frequently, and they can only do so safely with an automated test suite they trust. Interviewers ask about testing to learn whether you write tests that catch real bugs without slowing the team down. This chapter covers testing strategy, Jest, React Testing Library, mocking network calls with Mock Service Worker, testing hooks and stores, Playwright for end-to-end testing, visual and accessibility testing, and how to keep a test suite fast and reliable.

Testing Strategy

The Pyramid and the Trophy

The classic test pyramid recommends many unit tests, fewer integration tests, and few end-to-end tests. Kent C. Dodds’ testing trophy adapts it for frontend work: static analysis at the base, a modest number of unit tests, the largest share of integration tests (components rendered with their children and realistic data), and a smaller number of end-to-end tests at the top.

Layer Tools Catches Speed
Static TypeScript, ESLint, Prettier Typos, type errors, unsafe patterns Instant
Unit Jest/Vitest Logic bugs in pure functions, reducers, utilities Milliseconds
Integration (component) Jest + React Testing Library + MSW Behavior of components and features with mocked network Fast
End-to-end Playwright Real browser, real routing, real (or staged) backend, cross-page flows Seconds per test
Visual Playwright screenshots, Chromatic, Percy Unintended visual changes Moderate
Accessibility jest-axe, @axe-core/playwright Detectable WCAG violations Fast

The guiding principle: “The more your tests resemble the way your software is used, the more confidence they can give you.” Test behavior, not implementation details.

What to Test

  • Business rules: amount limits, fee calculations, validation, permission-based UI.
  • User flows: can a user complete a transfer, see an error, recover?
  • Edge cases: empty states, loading, errors, slow responses, large data, unusual locales.
  • Regressions: every bug fixed gets a test.

What not to test: third-party library internals, implementation details like internal state names or which hook was called, and trivial code with no logic.

Jest Fundamentals

Jest is a test runner, assertion library, and mocking framework. Vitest is a fast, Vite-native alternative with a largely Jest-compatible API; most of this section applies to both.

// fees.test.js
import { calculateWireFee } from './fees';

describe('calculateWireFee', () => {
  it('charges the domestic fee for domestic wires', () => {
    expect(calculateWireFee({ type: 'domestic', amountCents: 500_00 })).toBe(25_00);
  });

  it.each([
    ['international', 100_00, 45_00],
    ['international', 1_000_000_00, 45_00],
  ])('charges a flat %s fee', (type, amountCents, expected) => {
    expect(calculateWireFee({ type, amountCents })).toBe(expected);
  });

  it('rejects non-positive amounts', () => {
    expect(() => calculateWireFee({ type: 'domestic', amountCents: 0 })).toThrow(RangeError);
  });
});

Matchers You Should Know

Matcher Use
toBe Object.is equality for primitives and references
toEqual Recursive structural equality (ignores undefined properties)
toStrictEqual Structural equality including undefined properties and class types
toMatchObject Subset match
toThrow Function throws (optionally matching type or message)
toHaveBeenCalledWith, toHaveBeenCalledTimes Mock call assertions
resolves / rejects Promise assertions: await expect(p).rejects.toThrow()
expect.any(Number), expect.objectContaining() Asymmetric matchers
toMatchSnapshot, toMatchInlineSnapshot Snapshot comparisons

Setup and Teardown

beforeAll, beforeEach, afterEach, and afterAll run around tests within their describe block. Keep tests independent: each test should set up its own state and not rely on order.

Mocking

// Mock functions
const onSubmit = jest.fn();
onSubmit.mockResolvedValueOnce({ id: 'tx-1' });

// Spies on existing methods
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
// ...
spy.mockRestore();

// Module mocks
jest.mock('./analytics', () => ({ track: jest.fn() }));
import { track } from './analytics';

// Partial module mocks
jest.mock('./featureFlags', () => ({
  ...jest.requireActual('./featureFlags'),
  isEnabled: jest.fn(() => true),
}));

jest.mock calls are hoisted above imports. Configure clearMocks or restoreMocks in the Jest config so mock state does not leak between tests.

Mock at the boundaries. Mock the network, time, randomness, and third-party SDKs; avoid mocking your own modules unless they are slow or nondeterministic. Over-mocking produces tests that pass while the real integration is broken.

Fake Timers

jest.useFakeTimers();
test('session warning appears after 13 minutes of inactivity', () => {
  render(<SessionManager timeoutMs={15 * 60_000} warningMs={2 * 60_000} />);
  act(() => { jest.advanceTimersByTime(13 * 60_000); });
  expect(screen.getByRole('dialog', { name: /session expiring/i })).toBeInTheDocument();
});

With React Testing Library’s userEvent and fake timers, pass advanceTimers: jest.advanceTimersByTime to userEvent.setup().

Snapshots

Snapshots serialize output and compare it with a stored version. Large component snapshots are brittle and rarely reviewed carefully, so they tend to be updated blindly. Use small, targeted inline snapshots for serialized data (an error object, generated CSV) rather than whole component trees.

Configuration Essentials

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
  moduleNameMapper: {
    '\\.(css|scss)$': 'identity-obj-proxy',          // CSS Modules return class names
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  transform: { '^.+\\.[jt]sx?$': ['@swc/jest'] },   // or babel-jest / ts-jest
  collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.stories.tsx'],
  coverageThreshold: { global: { branches: 75, lines: 80 } },
  clearMocks: true,
};

identity-obj-proxy makes styles.primary return "primary", so CSS Modules work in tests.

React Testing Library

React Testing Library (RTL) renders components into a jsdom document and encourages querying the DOM the way users perceive it: by role, label, and text.

Query Priority

  1. Accessible to everyone: getByRole (with name), getByLabelText, getByPlaceholderText, getByText, getByDisplayValue.
  2. Semantic queries: getByAltText, getByTitle.
  3. Test IDs: getByTestId as a last resort.

getByRole is preferred because it also verifies accessibility: if you cannot find a button by role and name, neither can a screen reader user.

Query Variants

Variant No match Multiple matches Async
getBy Throws Throws No
queryBy Returns null Throws No
findBy Rejects after timeout Rejects Yes (polls until found)
getAllBy / queryAllBy / findAllBy As above, returning arrays

Use queryBy only to assert that something is not present. Use findBy for elements that appear asynchronously.

A Realistic Component Test

import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('user can review and submit a transfer', async () => {
  const user = userEvent.setup();
  renderWithProviders(<TransferPage />);

  await user.selectOptions(await screen.findByLabelText(/from account/i), 'Checking ****4821');
  await user.selectOptions(screen.getByLabelText(/to/i), 'Savings ****1199');
  await user.type(screen.getByLabelText(/amount/i), '250.00');
  await user.click(screen.getByRole('button', { name: /review transfer/i }));

  const review = screen.getByRole('region', { name: /review/i });
  expect(within(review).getByText('$250.00')).toBeInTheDocument();

  await user.click(screen.getByRole('button', { name: /confirm/i }));
  expect(await screen.findByRole('status')).toHaveTextContent(/transfer scheduled/i);
});

Notes on good practice visible here:

  • userEvent (v14) simulates full interactions (pointer events, focus, keyboard) and is async; prefer it over fireEvent.
  • Use screen rather than destructuring render results.
  • Assertions use @testing-library/jest-dom matchers: toBeInTheDocument, toHaveTextContent, toBeDisabled, toHaveAccessibleName, toHaveAttribute.
  • No reliance on class names, component internals, or state.

A Custom Render with Providers

export function renderWithProviders(ui, { route = '/', queryClient, locale = 'en-US' } = {}) {
  const client = queryClient ?? new QueryClient({
    defaultOptions: { queries: { retry: false, gcTime: Infinity } },
  });
  window.history.pushState({}, '', route);
  return render(
    <QueryClientProvider client={client}>
      <I18nProvider locale={locale}>
        <MemoryRouter initialEntries={[route]}>{ui}</MemoryRouter>
      </I18nProvider>
    </QueryClientProvider>
  );
}

Disabling retries avoids slow tests waiting for retry backoff when testing error states. Create a new QueryClient per test to avoid cache leakage.

Async Utilities and act

  • findBy* and waitFor(() => expect(...)) wait for asynchronous updates.
  • Do not put side effects inside waitFor callbacks; only assertions.
  • waitForElementToBeRemoved waits for loading indicators to disappear.
  • RTL wraps renders and userEvent interactions in act() automatically. “Not wrapped in act” warnings usually mean an asynchronous state update happened after the test finished asserting; the fix is to wait for the resulting UI change (await screen.findBy...), not to wrap things manually in act.

Testing Hooks

import { renderHook, act } from '@testing-library/react';

test('useDebouncedValue updates after delay', () => {
  jest.useFakeTimers();
  const { result, rerender } = renderHook(({ v }) => useDebouncedValue(v, 300), {
    initialProps: { v: 'a' },
  });
  rerender({ v: 'ab' });
  expect(result.current).toBe('a');
  act(() => { jest.advanceTimersByTime(300); });
  expect(result.current).toBe('ab');
});

Test hooks directly only when they are reusable building blocks; otherwise test them through the components that use them.

Mock Service Worker (MSW)

MSW intercepts network requests at the network layer (a Service Worker in the browser, request interception in Node) and returns mocked responses. Components and Axios code run unchanged, which makes tests realistic and mocks reusable across unit tests, Storybook, and local development.

// test/handlers.js
import { http, HttpResponse, delay } from 'msw';

export const handlers = [
  http.get('/api/accounts', () =>
    HttpResponse.json([
      { id: 'a1', nickname: 'Checking', mask: '4821', balance: { value: '1520.33', currency: 'USD' } },
    ])
  ),
  http.post('/api/transfers', async ({ request }) => {
    const body = await request.json();
    if (Number(body.amount) > 10000) {
      return HttpResponse.json(
        { title: 'Limit exceeded', errors: [{ field: 'amount', code: 'DAILY_LIMIT' }] },
        { status: 422 }
      );
    }
    await delay(100);
    return HttpResponse.json({ id: 'tr-1', status: 'scheduled' }, { status: 201 });
  }),
];

// test/setup.ts
import { setupServer } from 'msw/node';
export const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Override per test to simulate errors:

test('shows an error when accounts fail to load', async () => {
  server.use(http.get('/api/accounts', () => HttpResponse.json({}, { status: 503 })));
  renderWithProviders(<AccountsPage />);
  expect(await screen.findByRole('alert')).toHaveTextContent(/temporarily unavailable/i);
});

onUnhandledRequest: 'error' ensures that a component calling an unmocked endpoint fails loudly instead of silently hanging.

Playwright for End-to-End Testing

Playwright drives real Chromium, Firefox, and WebKit browsers with a single API. Its test runner (@playwright/test) includes parallelism, fixtures, retries, tracing, screenshots, video, and HTML reports.

Core Concepts

  • Locators are lazy, auto-waiting references to elements: page.getByRole('button', { name: 'Confirm' }). They re-resolve on each action, which avoids stale-element errors.
  • Auto-waiting: actions wait for elements to be attached, visible, stable, enabled, and receiving events before acting.
  • Web-first assertions retry until they pass or time out: await expect(locator).toBeVisible(), toHaveText, toHaveURL, toHaveCount.
  • Browser contexts are isolated, incognito-like sessions; each test gets a fresh context, making tests independent and parallelizable.
  • Fixtures provide setup and teardown (page, context, request, and custom ones like authenticatedPage).

An End-to-End Test

import { test, expect } from '@playwright/test';

test.describe('transfers', () => {
  test('customer schedules an internal transfer', async ({ page }) => {
    await page.goto('/transfers/new');
    await page.getByLabel('From account').selectOption({ label: 'Checking ****4821' });
    await page.getByLabel('To account').selectOption({ label: 'Savings ****1199' });
    await page.getByLabel('Amount').fill('250.00');
    await page.getByRole('button', { name: 'Review transfer' }).click();

    await expect(page.getByRole('heading', { name: 'Review your transfer' })).toBeVisible();
    await page.getByRole('button', { name: 'Confirm' }).click();

    await expect(page.getByRole('status')).toContainText('Transfer scheduled');
    await expect(page).toHaveURL(/\/transfers\/[a-z0-9-]+\/confirmation/);
  });
});

Authentication Setup

Logging in through the UI in every test is slow. Playwright’s recommended approach is a setup project that logs in once and saves storage state:

// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Username').fill(process.env.E2E_USER!);
  await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.getByLabel('Verification code').fill(await getTestOtp());
  await page.getByRole('button', { name: 'Verify' }).click();
  await expect(page.getByRole('heading', { name: 'Accounts' })).toBeVisible();
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

// playwright.config.ts
export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: [['html'], ['junit', { outputFile: 'results.xml' }]],
  use: { baseURL: process.env.BASE_URL, trace: 'on-first-retry', screenshot: 'only-on-failure' },
  projects: [
    { name: 'setup', testMatch: /auth\.setup\.ts/ },
    { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' }, dependencies: ['setup'] },
    { name: 'webkit', use: { ...devices['Desktop Safari'], storageState: 'playwright/.auth/user.json' }, dependencies: ['setup'] },
    { name: 'mobile', use: { ...devices['Pixel 7'], storageState: 'playwright/.auth/user.json' }, dependencies: ['setup'] },
  ],
});

For MFA in test environments, teams use test users with TOTP secrets (generate codes in the test with a TOTP library), a test-only OTP retrieval API, or an IdP configuration that bypasses MFA for designated test accounts in non-production environments only.

Network Mocking and API Testing

test('shows maintenance banner when payments API is down', async ({ page }) => {
  await page.route('**/api/payments/**', route =>
    route.fulfill({ status: 503, json: { title: 'Service unavailable' } }));
  await page.goto('/payments');
  await expect(page.getByRole('alert')).toContainText('temporarily unavailable');
});

test('API creates payee', async ({ request }) => {
  const res = await request.post('/api/payees', { data: { name: 'Electric Co', accountNumber: '123456789' } });
  expect(res.status()).toBe(201);
});

Use the request fixture to create test data through APIs before UI tests (faster and more reliable than creating it through the UI) and to clean up afterward.

Page Object Model

For larger suites, encapsulate page interactions in classes or fixtures:

export class TransferPage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/transfers/new'); }
  async fill({ from, to, amount }: { from: string; to: string; amount: string }) {
    await this.page.getByLabel('From account').selectOption({ label: from });
    await this.page.getByLabel('To account').selectOption({ label: to });
    await this.page.getByLabel('Amount').fill(amount);
  }
  async review() { await this.page.getByRole('button', { name: 'Review transfer' }).click(); }
}

Page objects keep tests readable and localize selector changes. Avoid putting assertions deep inside page objects; keep them in tests.

Debugging and Tracing

  • npx playwright test --ui opens UI mode with time-travel debugging.
  • --debug runs headed with the Playwright Inspector.
  • Trace Viewer (trace: 'on-first-retry') records actions, DOM snapshots, network, and console logs for failed tests in CI; open with npx playwright show-trace.
  • npx playwright codegen records interactions to generate a first draft of a test.

Visual Regression Testing

test('account summary card matches baseline', async ({ page }) => {
  await page.goto('/storybook/iframe.html?id=accounts-summarycard--default');
  await expect(page.getByTestId('summary-card')).toHaveScreenshot('summary-card.png', {
    maxDiffPixelRatio: 0.01,
    mask: [page.getByTestId('timestamp')],
  });
});

Run visual tests in a consistent environment (the official Playwright Docker image) because fonts and rendering differ across operating systems. Mask dynamic content like dates.

Accessibility in Playwright

Use @axe-core/playwright (Chapter 10) on key pages and states, and write keyboard-only tests for critical flows:

await page.keyboard.press('Tab');
await expect(page.getByRole('link', { name: 'Skip to main content' })).toBeFocused();

Flaky Tests

A flaky test passes and fails without code changes. Flakiness destroys trust in the suite. Causes and remedies:

Cause Remedy
Fixed sleeps (waitForTimeout(2000)) Web-first assertions and auto-waiting
Shared test data between parallel tests Unique data per test; API-based setup and teardown
Dependence on test order Independent tests with their own setup
Real third-party services Mock or stub them; contract-test separately
Animations and transitions Disable animations in test mode, reducedMotion: 'reduce'
Time and time zones Fake timers, fixed timezoneId and locale in Playwright config, page.clock API
Brittle selectors (CSS classes, nth-child) Role and label locators, test IDs as fallback
Environment instability Health checks before runs, retries with traces, quarantine with tickets

Retries hide flakiness; use them to keep CI green while tracking and fixing flaky tests, never as the fix itself.

Coverage and Quality Metrics

Code coverage tells you what code ran, not whether it was meaningfully tested. Set reasonable thresholds (often 70 to 85 percent) to catch untested areas, focus review on business-critical paths, and consider mutation testing (Stryker) for critical logic like fee calculations, which verifies that tests fail when code is changed.

Testing in CI

  • Run lint, type checks, and unit/component tests on every pull request; they should finish in a few minutes.
  • Shard large Jest and Playwright suites across CI machines (--shard=1/4).
  • Run a smoke subset of end-to-end tests on pull requests and the full suite on merges or nightly.
  • Publish JUnit reports, coverage, and Playwright HTML reports and traces as artifacts.
  • Run end-to-end tests against preview environments deployed per pull request when possible.

Interview Questions and Answers

Q1. What is the difference between unit, integration, and end-to-end tests?

Unit tests check small pieces of logic in isolation. Integration tests check several units working together, such as a component with its children, hooks, and a mocked network layer. End-to-end tests run the full application in a real browser against a real or staged backend, verifying complete user flows. They trade speed and isolation for realism.

Q2. What is the testing trophy?

A model that emphasizes static analysis at the base, some unit tests, the most effort on integration tests, and fewer end-to-end tests, because integration tests give high confidence at reasonable cost for frontend applications.

Q3. Why does React Testing Library discourage testing implementation details?

Tests coupled to internals (state variable names, component instance methods, class names) break when you refactor without changing behavior, and they can pass while user-visible behavior is broken. Querying by role and label and interacting like a user produces tests that fail only when behavior changes.

Q4. Explain getBy, queryBy, and findBy.

getBy returns the element or throws if missing or ambiguous. queryBy returns null when missing, for asserting absence. findBy returns a promise that resolves when the element appears, for asynchronous UI. Each has an All variant for multiple elements.

Q5. Why prefer userEvent over fireEvent?

userEvent simulates complete interactions as a browser would, including focus changes, pointer and keyboard event sequences, and input behavior, so tests catch bugs that a single synthetic event would miss. fireEvent dispatches one event and is suitable only for events userEvent does not support.

Q6. How do you mock API calls in component tests?

With Mock Service Worker, which intercepts requests at the network level so the real Axios client and data-fetching hooks run. Define default handlers in a shared setup and override per test for error and edge cases. Set onUnhandledRequest: 'error' to catch missing mocks.

Q7. How do you fix “not wrapped in act(…)” warnings?

They indicate a state update occurred outside of RTL’s awareness, typically after the test’s assertions finished. Wait for the expected UI change with findBy or waitFor, ensure async work completes or is cleaned up, and mock timers appropriately. Manually wrapping in act is rarely the right fix.

Q8. How do you test a component that uses a Zustand store or Context?

Render it within the real providers using a custom render function, set up initial state via the store’s setState or provider props, reset store state between tests, and assert on visible behavior. Avoid mocking the store unless it hides an expensive dependency.

Q9. What makes Playwright tests less flaky than older tools?

Auto-waiting for actionability before each action, web-first assertions that retry, locators that re-resolve, isolated browser contexts per test, and built-in tracing for diagnosing failures. Tests still become flaky with shared data, fixed sleeps, or unstable environments.

Q10. How do you handle authentication in Playwright tests?

Use a setup project that logs in once (including MFA with test TOTP secrets or a test-only mechanism) and saves the storage state to a file, then configure test projects to reuse that storage state. For different roles, create multiple storage state files.

Q11. What is the Page Object Model?

A pattern that wraps page-specific locators and actions in classes or fixtures, so tests express intent (“fill the transfer form”) and selector changes are fixed in one place. Assertions generally stay in tests.

Q12. How do you test accessibility automatically?

Run axe-core via jest-axe in component tests and @axe-core/playwright in end-to-end tests, use role-based queries that implicitly check accessible names, add eslint-plugin-jsx-a11y, and write keyboard navigation tests for critical flows. Automated checks complement manual testing.

Q13. When are snapshot tests useful?

For small, stable serialized outputs where any change should be reviewed, such as generated data structures or error messages, using inline snapshots. Large component snapshots are brittle, noisy, and often updated without review, so I avoid them.

Q14. How do you test time-dependent behavior?

Use Jest fake timers (or Vitest’s vi.useFakeTimers) to control setTimeout, intervals, and Date, and Playwright’s page.clock to control time in the browser. Configure time zones explicitly in the test environment and in Playwright (timezoneId) to catch time zone bugs.

Q15. How do you deal with flaky tests in CI?

Treat them as defects: capture traces and logs on retry, identify the cause (timing, data, order, environment), fix it, and if needed quarantine the test temporarily with a tracking ticket. Avoid fixed waits and shared data. Track flakiness rates over time.

Q16. What is code coverage, and is 100 percent a good goal?

Coverage measures which lines, branches, or functions executed during tests. It is a useful indicator of untested code but not of test quality, and chasing 100 percent leads to low-value tests. Set reasonable thresholds and focus on critical paths, with mutation testing for important logic.

Q17. How would you test a micro frontend?

Component and integration tests within the remote using a mock shell and MSW, contract tests for exposed modules and events, standalone end-to-end tests of the remote, and integration end-to-end tests in a composed environment that includes the host and other remotes at the versions being released.

Q18. How do you visually test components?

Render each component state (often Storybook stories) in a consistent browser environment, capture screenshots, and compare with baselines using Playwright toHaveScreenshot, Chromatic, or Percy. Mask dynamic content, run in Docker for consistency, and review diffs in pull requests.

Q19. How do you test error states like a 500 from the server?

Override the MSW handler for that test to return a 500, render the component, and assert that the error UI appears with a recovery action. In Playwright, use page.route to fulfill with an error status. Disable retries in the query client for faster tests.

Q20. What is the difference between Jest and Vitest?

Both are test runners with similar APIs. Vitest is built on Vite, reuses the app’s Vite configuration and transforms, supports ES modules natively, and is typically faster in Vite projects. Jest is mature with a large ecosystem and is common in webpack-based code bases. Migration is usually straightforward.

Q21. How do you test a custom hook?

Use renderHook from React Testing Library, providing a wrapper for any required providers, and interact with the result through act. For hooks that are only used by one component, test them through that component instead.

Q22. How would you structure testing for a payment submission flow?

Unit tests for amount parsing, validation, fee calculation, and the state machine; component tests with MSW for the form, review, and confirmation steps including validation, limit errors, step-up MFA, and network failures; an end-to-end test for the happy path and a critical error path in a staging environment; accessibility checks on each step; and contract tests for the payment API.

Key Takeaways

  • Invest most in integration tests that exercise real components with a mocked network.
  • Query by role and label, interact with userEvent, and assert on visible behavior.
  • Use MSW to mock the network realistically across tests, Storybook, and development.
  • Playwright provides reliable cross-browser end-to-end tests with auto-waiting, fixtures, and tracing.
  • Treat flaky tests as bugs; keep CI fast with sharding and tiered suites.

← Part 11: Internationalization and Localization · All chapters · Part 13: Git and CI/CD Pipelines →

Senior Frontend Interview Guide (full PDF)Every question and answer from this series in one printable guide.PDF · 12.5 MB
Free download: unlock it in one step

Share this page

or

Mailchimp opens in a new tab to confirm. Unsubscribe any time.

Filed underInterview Guide
Rupak Nepali
Written byRupak Nepali

Author of four Opencart book.
The recent are Opencart 4 developer book and Opencart 4 user manual

Leave a Reply

Your email address will not be published. Required fields are marked *