Examples

Vitest Integration

Using nut.js with Vitest for fast, modern desktop automation testing

vitesttestinge2e

This guide shows how to use nut.js with Vitest, a blazing-fast unit test framework powered by Vite.

Installation

bash
npm install --save-dev vitest @nut-tree/nut-js @nut-tree/nl-matcher

Vitest Configuration

Create vitest.config.ts:

typescript
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    // Desktop tests need more time
    testTimeout: 30000,
    hookTimeout: 10000,

    // Run tests sequentially (required for desktop automation)
    pool: 'forks',
    poolOptions: {
      forks: {
        singleFork: true,
      },
    },

    // Setup file
    setupFiles: ['./vitest.setup.ts'],
  },
})

Setup File

Create vitest.setup.ts:

typescript
import { beforeAll, afterEach } from 'vitest'
import { keyboard, mouse, screen } from '@nut-tree/nut-js'

beforeAll(() => {
  // Configure nut.js
  keyboard.config.autoDelayMs = 50
  mouse.config.autoDelayMs = 50
  mouse.config.mouseSpeed = 1500
})

afterEach(async (ctx) => {
  // Screenshot on failure
  if (ctx.task.result?.state === 'fail') {
    const name = ctx.task.name.replace(/\s+/g, '-')
    await screen.capture(`./screenshots/${name}-${Date.now()}.png`)
  }
})

Example Test Suite

typescript
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import {
  keyboard,
  mouse,
  screen,
  clipboard,
  Key,
  centerOf,
  imageResource,
  Region,
  sleep,
} from '@nut-tree/nut-js'

describe('Text Editor Automation', () => {
  beforeAll(async () => {
    // Open a text editor
    if (process.platform === 'darwin') {
      await keyboard.pressKey(Key.LeftSuper, Key.Space)
      await keyboard.releaseKey(Key.LeftSuper, Key.Space)
      await sleep(300)
      await keyboard.type('TextEdit')
      await keyboard.pressKey(Key.Enter)
      await keyboard.releaseKey(Key.Enter)
      await sleep(1000)
    }
  })

  afterAll(async () => {
    // Close without saving
    await keyboard.pressKey(Key.LeftSuper, Key.Q)
    await keyboard.releaseKey(Key.LeftSuper, Key.Q)
    await sleep(300)
    // Don't save dialog
    await keyboard.pressKey(Key.LeftSuper, Key.D)
    await keyboard.releaseKey(Key.LeftSuper, Key.D)
  })

  it('should type text into the editor', async () => {
    const testText = 'Hello from Vitest!'

    await keyboard.type(testText)

    // Verify by selecting all and checking
    await keyboard.type(Key.LeftSuper, Key.A)
    await sleep(200)

    // Copy to clipboard
    await keyboard.type(Key.LeftSuper, Key.C)

    const clipboardText = await clipboard.paste()
    expect(clipboardText).toContain(testText)
  })

  it('should perform find and replace', async () => {
    // Clear and type new text
    await keyboard.type(Key.LeftSuper, Key.A)
    await keyboard.type('foo bar foo baz foo')

    // Open find and replace
    await keyboard.type(Key.LeftSuper, Key.LeftAlt, Key.F)
    await sleep(500)

    // Type search term
    await keyboard.type('foo')
    await keyboard.type(Key.Tab)
    await keyboard.type('replaced')

    // Replace all
    await keyboard.type(Key.LeftSuper, Key.LeftAlt, Key.G)

    await sleep(300)

    // Close find dialog
    await keyboard.type(Key.Escape)
  })
})

Custom Matchers for Vitest

Create custom matchers in your setup file:

typescript
import { expect } from 'vitest'
import { screen, imageResource, vitestMatchers } from '@nut-tree/nut-js'
import { useNlMatcher } from '@nut-tree/nl-matcher'

useNlMatcher()

expect.extend(vitestMatchers)

Usage:

typescript
it('should display the welcome screen', async () => {
  await expect(screen).toShow(imageResource('welcome-dialog.png'))
})

Running Tests

bash
# Run all tests
npx vitest run

# Watch mode
npx vitest

# With UI
npx vitest --ui

CI Configuration

Example GitHub Actions workflow:

yaml
name: Desktop Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci

      - run: npx vitest run
        env:
          CI: true

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: screenshots
          path: screenshots/

Was this page helpful?