Skip to main content

Testing

One of the most important principles in postman-app is this: a green CI pipeline means you can merge and ship with confidence. If tests pass, the code is good to go. If they fail, you fix the code — not the tests.

That sounds obvious, but it requires a testing strategy where tests are actually trustworthy. Flaky tests — tests that sometimes pass and sometimes fail for no reason — are not tolerated. They are disabled immediately, because a flaky test is worse than no test: it trains engineers to ignore failures.

This page explains how testing works in postman-app, layer by layer.


Three levels of testing

Think of testing as a pyramid. At the bottom is the biggest layer — fast, cheap unit tests that run on every change. At the top is the small, expensive layer — slow E2E tests that simulate a real user.

/\
/ \
/ E2E \ ← Playwright: real browser, full user flows
/--------\
/ Component \ ← React Testing Library: what users see and do
/ tests \
/----------------\
/ Unit tests \ ← Vitest/Jest: individual functions and hooks
/____________________\

Most of your tests will be unit and component tests. E2E tests are for critical user flows that would be catastrophic to break.


Level 1: Unit tests

Unit tests test a single piece of code in isolation — one function, one hook, one store — without involving the rest of the application.

Where they live: Co-located with the code they test, inside each package.

data/workspace-data/
├── src/
│ ├── store.ts
│ ├── store.test.ts ← tests for store.ts, right next to it
│ └── __tests__/
│ └── controller.test.ts

Tool: Vitest for new packages (faster, better TypeScript support). Jest for older packages still being migrated.

How to run:

# Test a specific package
nx test @postman-app/workspace-data

# Test with coverage
nx test @postman-app/workspace-data --coverage

# Watch mode during development
nx test @postman-app/workspace-data --watch

Why decoupled packages have faster tests

This is subtle but important. When you test a coupled package (one that imports from src/renderer/), the test runner has to load an enormous number of auto-mock files from the monolith setup. We have thousands of these.

When you test a decoupled package, those auto-mocks are completely skipped. Tests are faster, cleaner, and easier to understand. This is one of the hidden benefits of the decoupling migration — it's not just about architecture, it directly makes tests better.


Level 2: Component tests

Component tests test React components as a user would experience them — they render a component in a simulated browser environment and interact with it.

// workspace-overview-tab/src/__tests__/WorkspaceOverviewTab.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { WorkspaceOverviewTab } from '../components/WorkspaceOverviewTab';

it('shows the workspace description when loaded', async () => {
render(<WorkspaceOverviewTab workspaceId="ws-123" />);

await screen.findByText('My workspace description');
expect(screen.getByText('My workspace description')).toBeInTheDocument();
});

These live in the same co-located test directories as unit tests. The difference is in what they test: not "does this function return the right value" but "does the user see the right thing when they open this tab".

Tool: React Testing Library + Vitest.

Best practice: Test user behavior, not implementation details. "The user clicks Save and sees a success message" — not "ComponentStore.save() was called once with the right arguments."


Level 3: E2E tests

E2E tests launch the actual Postman app in a browser and click through it like a real user. They are the most confidence-giving tests — if they pass, you know the real product works — but they are also the slowest and most fragile.

Where they live: tests-v2/e2e-tests/ — the current standard, using Playwright.

tests-v2/e2e-tests/
└── specs/
└── workspace/
└── workspace-overview.spec.ts ← tests for the workspace overview page

The old system: tests/e2e-tests/ uses @postman/teleport, an internal wrapper around WebdriverIO (WDIO). This still exists but is being replaced by Playwright. Write all new E2E tests in tests-v2/.

How Playwright tests work:

// tests-v2/e2e-tests/specs/workspace/workspace-overview.spec.ts
import { test, expect } from '@playwright/test';

test('user can see workspace description on overview tab', async ({ page }) => {
await page.goto('/workspace/ws-123');
await page.click('[data-testid="overview-tab"]');
await expect(page.getByText('My workspace description')).toBeVisible();
});

When E2E tests run: Not on every PR — they are too slow. They run selectively based on what changed, and in full on the nightly build and before production releases.


How CI decides what to test

This is one of the most important ideas in the testing strategy: only test what changed.

The monolith approach (old): every PR runs the entire test suite. Takes 30+ minutes.

The Nx approach (new): CI looks at what files changed, traces the dependency graph, and only tests the packages that are actually affected.

Example: You change a utility function in libs/dock-constants. CI traces which packages import from it — maybe platform-libs/sidebar and ui-features/collections-sidebar — and only runs tests for those packages. Everything else is skipped.

libs/dock-constants (changed)

platform-libs/sidebar (affected → test it)

ui-features/collections-sidebar (affected → test it)

ui-features/ai-chat (not affected → skip ✓)
data/workspace-data (not affected → skip ✓)

This is called nx affected and it's why CI can be fast even on a 250+ package monorepo.

Currently, the pre-integration check (the main CI gate for every PR) takes 7–11 minutes. The team's goal is to get it under 5 minutes by December 2026 through Rspack v2, TypeScript v7, and completing the Vitest migration.


What CI checks on every PR

When you open a PR, this is what runs automatically:

CheckToolWhat it does
Code formattingPrettierChecks only the files you changed
LintingESLintRuns only on affected packages
TypeScripttscTypechecks only affected packages
Unit testsVitest / JestRuns only for affected packages (nx affected)
Bundle sizeStatoscopeFails if the critical path bundle grows by more than 5kB
Circular dependency checkCustom scriptEnsures you haven't created new circular imports

All of these must be green before your PR can merge.


Writing a good test: the philosophy

From the team's experience, the most important rule is this: the problem is usually not the code, it's the testing.

Teams that ship confidently have good test coverage. Teams that are afraid to refactor usually don't. Here's what makes tests good in postman-app:

Test behavior, not implementation. If you refactor the internals but the behavior stays the same, the tests should still pass. Tests that break on refactors are testing the wrong thing.

Make tests deterministic. A test that sometimes passes and sometimes fails is worse than no test. It trains engineers to ignore CI failures.

Keep unit tests close to the code. Co-location (store.ts next to store.test.ts) means tests are less likely to go stale. When you delete a file, the test goes with it.

E2E tests for critical paths only. The workspace overview loading. The collection runner working. The request sending and getting a response. Not every edge case — that's what unit tests are for.


Testing the Nx layers

Different layers have different testing approaches because their responsibilities are different:

LayerWhat to testHow
libs/Pure functions and utilitiesUnit tests — no mocking needed, just inputs and outputs
data/Store state transitions, API call shapesUnit tests with mocked API responses
platform-libs/Service initialization, event handlingUnit tests with mocked browser APIs
ui-features/Component rendering, user interactionsReact Testing Library component tests
views/Tab navigation, permission-gated visibilityComponent tests + E2E for critical paths
E2EFull user journeys (sign in, create collection, run request)Playwright in tests-v2/

References