Skip to content

electrosaur-labs/uxp-test-runner

Repository files navigation

UXP Test Runner

Generic testing framework for Adobe UXP plugins with automated test execution

A standalone, reusable testing framework that any UXP plugin can use for automated testing.

License Tests Used By


Features

Vitest Compatibility - Write tests in modern vitest syntax (Architecture 2.0) ✅ 16 Comprehensive Assertion Methods - assertEqual, assertThrows, assertIncludes, and more ✅ Terminal-to-Photoshop Automation - Run tests from command line via Socket.IO bridge ✅ Dual Testing Approach - Fast Node.js unit tests + real UXP integration tests ✅ Self-Hosting Meta-Tests - Framework tests itself to ensure correctness ✅ Test Organization - describe blocks, lifecycle hooks (beforeEach, afterEach) ✅ Zero Production Dependencies - Core framework is pure JavaScript


Quick Start

1. Install

git clone https://github.com/electrosaur-labs/uxp-test-runner.git
cd uxp-test-runner
npm install

2. Run Node.js Tests (No Photoshop Required)

npm test

Result: 44 tests pass (17 adapter + 27 pixel assertions) in ~300ms ⚡

3. Run UXP Tests (Requires Photoshop)

Terminal A - Start Proxy Server:

npm run proxy

Terminal B - Build and Load Plugin:

npm run build:dev

Then load dist/manifest.json in UXP Developer Tool

Terminal B - Run Automated Tests:

npm run test:uxp

Result: Tests run automatically in Photoshop via WebSocket bridge 🎉


Usage

Run Tests from Terminal (Automated)

# Fast Node.js unit tests
npm test                  # 63 tests, 36ms

# Full UXP integration tests (requires Photoshop + proxy running)
npm run test:uxp         # 58 tests, 216ms

# Run both
npm run test:all

Run Tests Manually in Photoshop

  1. PluginsUXP Test RunnerRun Tests
  2. Check console for detailed output
  3. Alert dialog shows summary

Writing Tests

Basic Test

runner.test("basic arithmetic", () => {
    TestRunner.assertEqual(1 + 1, 2);
    TestRunner.assert(true, "Should pass");
});

Organized Test Suites

runner.describe("Math Operations", () => {
    runner.test("addition works", () => {
        TestRunner.assertEqual(5 + 3, 8);
    });

    runner.test("subtraction works", () => {
        TestRunner.assertEqual(10 - 4, 6);
    });
});

Lifecycle Hooks

runner.describe("Database Tests", () => {
    let db;

    runner.beforeEach(() => {
        db = createTestDatabase();
    });

    runner.afterEach(() => {
        db.cleanup();
    });

    runner.test("can insert records", () => {
        db.insert({ name: "test" });
        TestRunner.assertEqual(db.count(), 1);
    });
});

Async Tests

runner.test("async operation", async () => {
    const result = await fetchData();
    TestRunner.assertEqual(result.status, "success");
});

Vitest Syntax (Architecture 2.0) 🆕

You can now write tests using modern vitest syntax! The adapter layer translates vitest's expect() API to TestRunner assertions.

import { describe, test, expect } from 'vitest';

describe("Math Operations", () => {
    test("addition works", () => {
        expect(2 + 2).toBe(4);
        expect([1, 2, 3]).toContain(2);
    });

    test("async operations", async () => {
        const result = await fetchData();
        expect(result).toEqual({ status: "success" });
    });
});

Run vitest tests:

npm run test:vitest          # Run once
npm run test:vitest:watch    # Watch mode
npm run test:vitest:coverage # With coverage

How it works:

  • In Node.js: Uses real vitest (fast, native coverage)
  • In UXP builds: Webpack redirects to adapter layer
  • Same tests run in both environments!

Assertion Methods (16 Total)

Method Description
assert(condition, message) Basic assertion
assertEqual(actual, expected, message) Strict equality (===)
assertNotEqual(actual, expected, message) Strict inequality (!==)
assertTrue(value, message) Expects exactly true
assertFalse(value, message) Expects exactly false
assertNull(value, message) Expects null
assertUndefined(value, message) Expects undefined
assertThrows(fn, errorType, message) Expects function to throw
assertDoesNotThrow(fn, message) Expects no error
assertIncludes(array, value, message) Array contains value
assertArrayEqual(actual, expected, message) Deep array equality
assertObjectEqual(actual, expected, message) Deep object equality
assertGreaterThan(actual, expected, message) Numeric comparison
assertLessThan(actual, expected, message) Numeric comparison
assertMatch(string, regex, message) Regex pattern match
assertInstanceOf(obj, Constructor, message) Type checking

Architecture

Three-Layer Proxy Pattern

┌─────────────────┐
│  Terminal CLI   │  npm run test:uxp
└────────┬────────┘
         │ Socket.IO (WebSocket)
         ▼
┌─────────────────┐
│  Proxy Server   │  localhost:3001
└────────┬────────┘
         │ Socket.IO (WebSocket)
         ▼
┌─────────────────┐
│  UXP Plugin     │  Photoshop
└─────────────────┘

Why? UXP plugins cannot be Socket.IO servers (security constraint), so proxy routes commands between CLI and plugin.


Project Structure

uxp-test-runner/
├── src/
│   ├── core/
│   │   └── TestRunner.js          # Pure JS test framework (460 lines)
│   ├── server/
│   │   └── test-proxy.js          # Socket.IO proxy server (279 lines)
│   ├── plugin/
│   │   └── ws-client.js           # Auto-reconnecting WebSocket client (242 lines)
│   ├── cli/
│   │   └── run-tests.js           # Terminal CLI client (324 lines)
│   ├── tests/
│   │   ├── frameworkTests.js      # Self-hosting tests (463 lines)
│   │   └── separationEngineTests.js
│   └── index.js                   # UXP entry point
├── test/
│   └── testrunner.test.js         # Node.js Mocha unit tests (607 lines)
├── ARCHITECTURE.md                # Complete code review & walkthrough
├── PHASE1-COMPLETE.md             # Socket.IO bridge implementation notes
├── META-TESTING-COMPLETE.md       # Dual testing approach documentation
└── package.json

npm Scripts

Command Description
npm test Run Node.js unit tests (default)
npm run test:node Mocha unit tests (fast, 36ms)
npm run test:uxp Run tests in Photoshop via WebSocket
npm run test:all Run both Node.js and UXP tests
npm run proxy Start Socket.IO proxy server
npm run build Production build (no WebSocket client)
npm run build:dev Development build (with WebSocket client)

Testing Approach

1. Node.js Unit Tests (Primary Development)

Run: npm test

Speed: ⚡ 36ms for 63 tests

Purpose:

  • Test framework logic in isolation
  • Fast feedback loop for TDD
  • No Photoshop required
  • CI/CD friendly

What's Tested:

  • All 16 assertion methods
  • Lifecycle hooks (beforeEach, afterEach, beforeAll, afterAll)
  • Test organization (describe, skip, only)
  • Result tracking
  • Error handling

2. Self-Hosting UXP Tests (Meta-Testing)

Run: Automatically included when tests execute in Photoshop

Purpose:

  • Framework tests itself (meta!)
  • Proves correctness in real UXP environment
  • Dogfooding ensures API is usable

Result: 45 self-hosting tests verify framework behavior


3. Integration Tests (Full Stack)

Run: npm run test:uxp or manual trigger in Photoshop

Purpose:

  • Test in real Photoshop environment
  • Verify UXP API access
  • End-to-end automation testing

Dependencies

Production

Zero! Core framework has no dependencies.

Development

  • express (^5.2.1) - HTTP server for proxy
  • socket.io (^4.8.3) - WebSocket server
  • socket.io-client (^4.8.3) - WebSocket client
  • mocha (^11.7.5) - Node.js test runner
  • webpack (^5.89.0) - Bundler

Documentation

For Users (Getting Started)

📖 README.md (this file)

For Code Reviewers (Deep Dive)

📚 ARCHITECTURE.md - Complete walkthrough including:

  • High-level architecture
  • Component breakdown
  • Data flow diagrams
  • Key design decisions
  • File-by-file explanation
  • How to extend the framework

Implementation Notes

📋 PHASE1-COMPLETE.md - Socket.IO bridge implementation 🧪 META-TESTING-COMPLETE.md - Dual testing strategies


Contributing

Adding New Assertion Methods

  1. Add method to src/core/TestRunner.js
  2. Add Node.js test to test/testrunner.test.js
  3. Add self-hosting test to src/tests/frameworkTests.js
  4. Run npm test to verify

Adding New Test Suites

  1. Create file in src/tests/
  2. Export registration function
  3. Import and call in src/index.js

See ARCHITECTURE.md for detailed extension guide.


Troubleshooting

Plugin not connecting to proxy

Check:

  1. Proxy running? npm run proxy
  2. Plugin loaded? Check UXP Developer Tool
  3. Console errors? Check Photoshop console
  4. Proxy status: curl http://localhost:3001/status

Tests timing out

Possible causes:

  • Plugin not connected
  • Proxy not routing messages
  • Test actually hanging

Debug:

  • Check proxy console for message routing
  • Add console.log in test functions
  • Use .only() to isolate failing test

License

Apache License 2.0

Relicensed from GPL-3.0 for explicit patent protection. See LICENSE file.

Third-Party Attributions

This project adapts patterns from:

  • Bolt UXP (MIT) - WebSocket client patterns
  • Adobe MCP (MIT) - Proxy server architecture

See NOTICE file for complete attributions.


Acknowledgments

Inspired by:

  • Bolt UXP by Hyper Brew LLC
  • Adobe MCP by Mike Chambers
  • Jest/Mocha test framework API design

Links


Built with Claude Code

About

Generic testing framework for Adobe UXP plugins

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors