conciv

Testing extensions

Mount your extension in a real browser against a real server.

@conciv/extension-testkit boots your extension's real server half, builds and serves its real client half, opens a real browser page, and hands you a typed API. No mocks, no stubs: the test exercises the same contract the widget uses.

test/deploy.it.ts
import {afterAll, beforeAll, expect, it} from 'vitest'
import {getExtensionTestApi, type ExtensionTestApi} from '@conciv/extension-testkit'
import deploy from '../conciv/extensions/deploy'

let api: ExtensionTestApi

beforeAll(async () => {
  api = await getExtensionTestApi({
    server: deploy,
    clientEntry: 'conciv/extensions/deploy.tsx',
  })
})

afterAll(() => api.dispose())

it('deploys to staging', async () => {
  const result = await api.callTool('deploy_run', {env: 'staging'})
  expect(result).toMatchObject({env: 'staging'})
})

The test API

Prop

Type

Testing UI

page is a normal Playwright page. Assert through roles and text, and use waitFor for visibility:

it('shows the deploy button in the composer', async () => {
  await api.page.getByRole('button', {name: 'Deploy'}).waitFor()
})

Two clients, one canvas

For collaborative features, secondClient() opens another page against the same session. What one client writes must appear in the other:

it('syncs across clients', async () => {
  const second = await api.secondClient()
  await api.callTool('deploy_run', {env: 'staging'})
  await second.page.getByText('staging.example.com').waitFor()
  await second.close()
})

The whiteboard's own integration tests are written on this harness. Browse them in packages/extensions/whiteboard/test for real-world patterns.

On this page