Skip to main content

Playwright

Playwright specs live in packages/web/tests/. Each spec boots a real web server, creates a temp Git repo with a three-way notebook conflict, and drives a Chromium page against the resolver the same way a user would.

What belongs here

Use Playwright for resolver behavior: row rendering, conflict actions, history, settings-driven UI changes, output rendering, and notebook writes that can be proven from the browser-facing flow.

If the behavior only exists when the extension host talks to VS Code APIs or the Git extension directly, move up to the VS Code host layer instead.

CLI commands

npm run test:pw          # headless
npm run test:pw:headed # headed browser
npm run test:pw:ui # Playwright UI
npm run test:pw:debug # debug mode

# Direct Playwright invocation
npx playwright test <file>.spec.ts --reporter=line

Compile behavior

Compile before direct Playwright runs

npm run test:pw and its variants already compile the repo through pretest:pw.

If you run npx playwright test directly, compile both layers first:

npm run compile
npm run compile-tests

Key files

FilePurpose
playwright.config.tsWorker count, timeouts, global setup/teardown, reporters
packages/web/tests/fixtures.tsShared Playwright fixtures plus notebook-on-disk assertion helpers
packages/web/tests/globalSetup.tsStarts the web server once per run
packages/web/tests/globalTeardown.tsStops the shared web server
test-fixtures/shared/integrationUtils.tsBrowser helpers for reading rows, editing cells, and collecting expected UI output
packages/web/tests/*.spec.tsResolver UI specs

Adding a new Playwright spec

  1. Add the spec under packages/web/tests/ using *.spec.ts.
  2. Import test and expect from ./fixtures.
  3. Build a conflict repo from a fixture triplet.
  4. Launch a resolver session with conflictSession.
  5. Drive the UI and assert both browser state and notebook state on disk.

Typical shape:

import { test, expect } from './fixtures';

test('my scenario', async ({ conflictRepo, conflictSession }) => {
const workspacePath = conflictRepo({
base: 'general/conflict_0/base.ipynb',
current: 'general/conflict_0/current.ipynb',
incoming: 'general/conflict_0/incoming.ipynb',
});

const { page, conflictFile } = await conflictSession(workspacePath);
// drive UI
// read notebook from disk
});

The fixtures.ts extension gives you the pieces you usually need:

  • conflictRepo: creates a temp repo from base/current/incoming notebooks
  • conflictSession: opens the live resolver in Chromium
  • applyAndReadNotebook: clicks Apply and reads the resolved notebook from disk

Settings and disk assertions

Two repo-wide testing rules matter here:

  1. Respect settings explicitly.
    • Tests that depend on auto-resolve or UI toggles should use writeSettingsFile(...).
    • This is especially important for things like ui.showBaseColumn, ui.hideNonConflictOutputs, and auto-resolve behavior.
  2. Verify UI output against the notebook written to disk.
    • collectExpectedCellsFromUI(...) in test-fixtures/shared/integrationUtils.ts is the usual starting point.
    • After applying the resolution, use assertNotebookMatches(...) against the final notebook.

Representative examples:

  • packages/web/tests/perCellResolution.spec.ts
  • packages/web/tests/settingsMatrix.spec.ts
  • packages/web/tests/reorderUnmatchApplyDisk.spec.ts

Parallel settings isolation

The Playwright fixtures provision an isolated MERGENB_CONFIG_PATH per test worker so parallel runs do not fight over one shared config file. That isolation is why settings-focused specs can safely run in parallel.