Testing

Vitest helpers

Reuse E2E Vitest config and provider stubs.

Use the Vitest helper when an app or package wants the same E2E project names, timeouts, retry policy, and local test file mapping as @onmax/better-auth-nimiq-pay-e2e.

Do not use it for unit tests. Keep fast unit tests in their own Vitest project and enable these E2E projects only when the environment is ready.

Create the E2E config

Use createVitestE2EConfig() in a package-level vitest.config.ts.

vitest.config.ts
import { createVitestE2EConfig } from '@onmax/better-auth-nimiq-pay-e2e/vitest'

export default createVitestE2EConfig({
  enabled: process.env.E2E_ENABLED === '1',
  testTimeoutMs: 90_000,
})

When enabled, the helper creates three Node projects:

projects
e2e-direct-sign-in -> test/e2e/local-auth.e2e.test.ts
e2e-playground -> test/e2e/playground.e2e.test.ts
e2e-qr-local-auth -> test/e2e/qr-local-auth.e2e.test.ts

Expected behavior:

output
CI retries each E2E project once.
Local runs do not retry by default.
Disabled E2E config keeps project include lists empty.

Gate E2E in scripts

Use an explicit environment flag so local pnpm test does not start E2E work by accident.

package.json
{
  "scripts": {
    "test:e2e:local": "E2E_ENABLED=1 NIMIQ_PAY_E2E_MODE=local vitest run --project e2e-direct-sign-in",
    "test:e2e:qr": "E2E_ENABLED=1 NIMIQ_PAY_E2E_MODE=local vitest run --project e2e-qr-local-auth"
  }
}

Use local E2E scripts for pull requests. Keep bridge scripts separate because they wait for an injected provider.

Stub the mini-app provider

Use createStubNimiqProvider() when tests need listAccounts() and sign() behavior without a real host.

test/provider-stub.test.ts
import { expect, it } from 'vitest'
import { isNimiqProviderError } from '@onmax/better-auth-nimiq/provider'
import { createStubNimiqProvider } from '@onmax/better-auth-nimiq-pay-e2e/providers'

it('signs a challenge with the local provider', async () => {
  const provider = createStubNimiqProvider()

  const accounts = await provider.listAccounts()
  const signed = await provider.sign('Sign in to Test App')

  if (isNimiqProviderError(signed))
    throw new Error(signed.error.message)

  expect(accounts[0]).toMatch(/^NQ/)
  expect(signed.publicKey).toMatch(/^[a-f0-9]+$/i)
  expect(signed.signature).toMatch(/^[a-f0-9]+$/i)
})

Use the stub for deterministic auth-flow tests. Do not use it to test host permissions, provider injection timing, or browser bridge compatibility.

Wait for the bridge provider

Use createBridgeNimiqProvider() only when the page is expected to expose window.nimiq.

test/wait-for-provider.test.ts
import { expect, it } from 'vitest'
import {
  createBridgeNimiqProvider,
  createStubNimiqProvider,
} from '@onmax/better-auth-nimiq-pay-e2e/providers'

it('resolves when the injected provider appears', async () => {
  const provider = createStubNimiqProvider()

  await expect(createBridgeNimiqProvider({
    source: () => provider,
    timeoutMs: 1_000,
    intervalMs: 50,
  })).resolves.toBe(provider)
})

Failure mode:

test/provider-timeout.test.ts
import { expect, it } from 'vitest'
import { createBridgeNimiqProvider } from '@onmax/better-auth-nimiq-pay-e2e/providers'

it('fails with bridge guidance', async () => {
  await expect(createBridgeNimiqProvider({
    source: () => null,
    timeoutMs: 30,
    intervalMs: 10,
  })).rejects.toThrow('window.nimiq provider')
})

Use the provider subpath in tests and helper packages. The root package also re-exports the provider helpers for scenario code.

Workspace scripts

The package ships these scripts for its own E2E suite:

terminal
pnpm --filter @onmax/better-auth-nimiq-pay-e2e test:e2e:local
pnpm --filter @onmax/better-auth-nimiq-pay-e2e test:e2e:smoke
pnpm --filter @onmax/better-auth-nimiq-pay-e2e test:e2e:qr
pnpm --filter @onmax/better-auth-nimiq-pay-e2e test:e2e:bridge

Use test:e2e:smoke for local direct and QR coverage. Use test:e2e:bridge only where the injected provider is expected.

Copyright © 2026