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:
| Check | Tool | What it does |
|---|---|---|
| Code formatting | Prettier | Checks only the files you changed |
| Linting | ESLint | Runs only on affected packages |
| TypeScript | tsc | Typechecks only affected packages |
| Unit tests | Vitest / Jest | Runs only for affected packages (nx affected) |
| Bundle size | Statoscope | Fails if the critical path bundle grows by more than 5kB |
| Circular dependency check | Custom script | Ensures 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:
| Layer | What to test | How |
|---|---|---|
libs/ | Pure functions and utilities | Unit tests — no mocking needed, just inputs and outputs |
data/ | Store state transitions, API call shapes | Unit tests with mocked API responses |
platform-libs/ | Service initialization, event handling | Unit tests with mocked browser APIs |
ui-features/ | Component rendering, user interactions | React Testing Library component tests |
views/ | Tab navigation, permission-gated visibility | Component tests + E2E for critical paths |
| E2E | Full user journeys (sign in, create collection, run request) | Playwright in tests-v2/ |
References
- Unit Testing Playbook — Confluence
- End to End UI Testing — Confluence
- RFC: Component Testing Layer — Ronnie Gandhi, Confluence
- CI under 5 minutes — Opi Danihelka, Confluence
- Jest → Vitest Migration Decision Record — Sachin Lohani, Confluence