Skip to main content

Local Testing Guide

This guide provides a comprehensive testing plan for TinyEmulator, covering development server setup, unit testing, integration testing, and deployment verification.

Environment Setup​

Prerequisites​

ToolVersionRequired
Node.js>= 18.xYes
npm>= 8.xYes
Git>= 2.xYes

Clone Repository​

git clone https://github.com/SowrdLonn/tinyemulator.git
cd tinyemulator

Install Dependencies​

npm install

Development Server​

Start Dev Server​

npm run dev

The development server runs on http://localhost:3307.

Configuration​

Port: 3307 (configured in vite.config.js)

Key Features:

  • Hot module replacement (HMR)
  • Static file serving from public/
  • Core files served from /cores/
  • BIOS files served from /bios/

Access from Local Network​

npm run dev -- --host 0.0.0.0

Access from other devices on the same network: http://<your-ip>:3307

Unit Testing​

Test Framework​

TinyEmulator uses Vitest for unit testing. The framework is already installed but not yet configured with tests.

Test Configuration​

Create a vitest.config.js file in the project root:

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react-swc';

export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.js',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});

Setup File​

Create src/setupTests.js:

import '@testing-library/jest-dom';

Test Script​

Add test script to package.json:

{
"scripts": {
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage"
}
}

Example Tests​

Test URL Parameter Parsing​

Create src/utils/url.test.js:

import { describe, it, expect } from 'vitest';
import { UrlUtil } from './url';

describe('UrlUtil', () => {
it('should parse simple parameters', () => {
const url = '?core=nes&rom=https://example.com/game.nes';
expect(UrlUtil.getParam(url, 'core')).toBe('nes');
expect(UrlUtil.getParam(url, 'rom')).toBe('https://example.com/game.nes');
});

it('should return null for missing parameters', () => {
const url = '?core=nes';
expect(UrlUtil.getParam(url, 'rom')).toBeNull();
});

it('should handle encoded parameters', () => {
const url = '?gameName=Super%20Mario';
expect(UrlUtil.getParam(url, 'gameName')).toBe('Super%20Mario');
});
});

Test Props Encoding/Decoding​

Create src/utils/apps/props.test.js:

import { describe, it, expect } from 'vitest';
import { AppProps } from './props';

describe('AppProps', () => {
it('should encode and decode props correctly', () => {
const original = {
type: 'nes',
title: 'Test Game',
app: 'nes',
uid: 'test-123',
rom: 'https://example.com/game.nes'
};

const encoded = AppProps.encode(original);
const decoded = AppProps.decode(encoded);

expect(decoded.type).toBe(original.type);
expect(decoded.title).toBe(original.title);
expect(decoded.rom).toBe(original.rom);
});

it('should handle nested props', () => {
const original = {
type: 'gba',
title: 'Game',
props: {
rom: 'test.rom',
zoomLevel: 2
}
};

const encoded = AppProps.encode(original);
const decoded = AppProps.decode(encoded);

expect(decoded.props).toEqual(original.props);
});
});

Test EmulatorJS Compatibility​

Create src/App.test.jsx:

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

describe('EmulatorJS Compatibility', () => {
it('should support EJS_CORE_MAP mappings', () => {
const EJS_CORE_MAP = {
nes: 'nes',
snes: 'snes',
gba: 'gba',
gb: 'gb',
gbc: 'gbc',
genesis: 'genesis',
megadrive: 'genesis',
n64: 'n64',
psx: 'psx',
arcade: 'arcade',
fbneo: 'arcade',
};

expect(EJS_CORE_MAP.megadrive).toBe('genesis');
expect(EJS_CORE_MAP.fbneo).toBe('arcade');
expect(EJS_CORE_MAP.nes).toBe('nes');
});
});

Run Tests​

# Run all tests
npm run test

# Run tests in watch mode
npm run test:watch

# Generate coverage report
npm run test:coverage

Component Testing​

Using React Testing Library​

Create src/components/GameCard.test.jsx:

import { render, screen } from '@testing-library/react';
import GameCard from './GameCard';

describe('GameCard', () => {
const mockGame = {
id: '1',
title: 'Super Mario',
platform: 'nes',
cover: 'https://example.com/cover.png'
};

it('should render game title', () => {
render(<GameCard game={mockGame} />);
expect(screen.getByText('Super Mario')).toBeInTheDocument();
});

it('should render platform badge', () => {
render(<GameCard game={mockGame} />);
expect(screen.getByText('NES')).toBeInTheDocument();
});
});

Netplay Testing (Online Multiplayer)​

TinyEmulator supports online multiplayer through a Lobby service and a Tunnel service. This section describes how to set up and test these services locally.

Architecture Overview​

┌─────────────────────────────────────────────────────────────────┐
│ Network Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Player 1 │─────►│ Lobby │◄─────│ Player 2 │ │
│ │ (Browser)│ │ Server │ │ (Browser)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ Tunnel │ │
│ │ Server │ WebSocket relay for peer-to-peer │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Services Required​

ServicePortProtocolDescription
Lobby Server8082HTTPRoom management, user authentication
Tunnel Server8080WebSocketP2P relay for netplay connections
TinyEmulator3307HTTPFrontend emulator

1. Start Lobby Service​

The Lobby service is part of the retro-server project, built with Go.

Prerequisites​

ToolVersionRequired
Go>= 1.20Yes
SQLite3.xYes (embedded)

Build and Run​

cd /Users/yeye/Documents/workspace/te/retro-server

# Build the server
go build -o retro-server .

# Create config directory
mkdir -p /etc/retro

# Create local configuration
cat > /etc/retro/retro.develop.yml << 'EOF'
service:
address: ":8082"
allow_origins:
- "*"
same_site_none: true
storage: "./data"

tunnel:
address: ":8088"
path: "/tunnel"
limit: 4
gap_frames: 60
gap_to_slow: 12

database:
dialect: "sqlite3"
host: "./data/retro.db"
migration: true

extension:
cors_enabled: true
EOF

# Start the lobby server
./retro-server -env develop

Verify Lobby Service​

# Check health endpoint
curl http://localhost:8082/api/v1/health

# Expected response: {"status":"ok"}

API Endpoints​

EndpointMethodDescription
/api/v1/healthGETHealth check
/api/v1/roomPOSTCreate room
/api/v1/room/listGETList rooms
/api/v1/auth/registerPOSTRegister user
/api/v1/auth/loginPOSTLogin user

2. Start Tunnel Service​

The Tunnel service handles WebSocket connections for peer-to-peer netplay.

Build and Run​

cd /Users/yeye/Documents/workspace/te/retro-server/tools/tunnel

# Build tunnel service
go build -o tunnel .

# Create tunnel config (or use lobby config)
cat > /etc/retro/retro.develop.yml << 'EOF'
service:
address: ":8082"
allow_origins:
- "*"

tunnel:
address: ":8080"
path: "/tunnel"
limit: 4
gap_frames: 60
gap_to_slow: 12

database:
dialect: "sqlite3"
host: "./data/retro.db"
EOF

# Start tunnel server
./tunnel -env develop

Verify Tunnel Service​

# Test WebSocket connection
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test" \
http://localhost:8080/tunnel

# Expected: 101 Switching Protocols

3. Configure TinyEmulator for Local Netplay​

The frontend is already configured for local development in src/config.jsx:

LobbyURL: isDev() ? "http://localhost:8082" : "https://lobby.xxyx.net",
TunnelURL: isDev() ? "ws://localhost:8080/tunnel" : "wss://tunnel.lhy53401.cn/tunnel",

Ensure isDev() returns true by checking the URL:

# Start frontend with development mode
cd /Users/yeye/Documents/workspace/te/TinyEmulator
npm run dev

4. Netplay Testing Scenarios​

Test Scenario 1: Create and Join Room​

Steps:

  1. Open two browser windows/tabs at http://localhost:3307
  2. In Window 1:
    • Go to "Multiplayer" -> "Create Room"
    • Select a game (e.g., NES - Super Mario)
    • Click "Create"
    • Copy the room code
  3. In Window 2:
    • Go to "Multiplayer" -> "Join Room"
    • Paste the room code
    • Click "Join"
  4. Verify both players see the game screen
  5. Test controls work for both players

Expected Results:

  • Room created successfully
  • Second player joins without error
  • Game syncs between both players
  • Controls work independently

Steps:

  1. In Window 1:
    • Start a game
    • Click "Share" button
    • Copy the share link
  2. Open the link in Window 2
  3. Verify the second player joins automatically

Expected Results:

  • Share link contains room information
  • Second player joins directly without room code

Test Scenario 3: Tunnel Connection Failure​

Steps:

  1. Stop the tunnel service
  2. Try to create a room
  3. Check error messages

Expected Results:

  • Clear error message about tunnel connection
  • Retry mechanism works when tunnel restarts

5. Testing Tools​

WebSocket Inspector​

Use browser DevTools to inspect WebSocket traffic:

  1. Open Chrome DevTools (F12)
  2. Go to "Network" tab
  3. Filter by "WS"
  4. Select the tunnel connection
  5. Check "Frames" for message traffic

Network Latency Testing​

# Measure latency to tunnel server
ping localhost -c 5

# Check WebSocket connection time
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:8080/tunnel

Room List API​

# Get room list
curl http://localhost:8082/api/v1/room/list

# Response example:
# {"rooms":[{"id":"abc123","game":"nes","players":1,"maxPlayers":2}]}

6. Docker Local Deployment​

Alternatively, use Docker Compose for local testing:

cd /Users/yeye/Documents/workspace/te/retro-docker/lobby

# Start services
docker-compose up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f

7. Troubleshooting Netplay Issues​

Common Errors​

ErrorCauseSolution
"Cannot connect to lobby"Lobby service not runningStart retro-server
"Tunnel connection failed"Tunnel service not runningStart tunnel service
"Room not found"Room expired or invalid codeCreate new room
"Sync lost"Network instabilityCheck connection
"Game desync"Latency too highUse closer relay server

Debug Commands​

# Check if lobby is responding
curl http://localhost:8082/api/v1/health

# Check tunnel WebSocket
wscat -c ws://localhost:8080/tunnel

# Check room list
curl http://localhost:8082/api/v1/room/list

# View lobby logs
tail -f /var/log/retro-server.log

Firewall Configuration​

Ensure ports are open for local testing:

# macOS - check firewall
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --listapps

# Allow ports
sudo ufw allow 8082/tcp
sudo ufw allow 8080/tcp
sudo ufw allow 3307/tcp

Iframe Integration Testing​

Local Test Page​

A comprehensive iframe integration test page is available:

# Start the website development server
cd ../website
npm install
npm run dev

Access the test page at http://localhost:3000/iframe-test.html

Test Scenarios​

ScenarioTest URLExpected Result
Basic Embedhttps://play.tinyemulator.comEmulator homepage loads
EmulatorJS Format?core=nes&rom=https://files.xxyx.net/1.nes&single=1NES game loads directly
Props Format?props=<Base64>&single=1Game loads via encoded props
Single Player Mode&single=1Netplay UI hidden
Game Name&gameName=Super MarioTitle displayed correctly

Manual Testing Steps​

  1. Load Game: Click "Super Mario" button in EmulatorJS format
  2. Verify Load: Check that the game starts within 5 seconds
  3. Test Controls: Press keyboard or touch controls
  4. Test Fullscreen: Click fullscreen button
  5. Switch Games: Click another game button
  6. Verify Cleanup: Previous game unloads properly

Build Verification​

Build for Production​

npm run build

Expected output:

  • No errors in console
  • dist/ directory created with all assets
  • dist/index.html exists

Build for Specific Platforms​

# Android
npm run build-android

# iOS
npm run build-ios

# HarmonyOS (single file)
npm run build-harmony

Build Checks​

# Check build output
ls -la dist/

# Verify index.html exists
test -f dist/index.html && echo "✓ index.html exists"

# Verify core files copied
ls dist/cores/ | wc -l && echo "cores copied"

# Verify BIOS files copied
ls dist/bios/ | wc -l && echo "BIOS files copied"

Deployment Verification​

Local Preview​

npm run preview

Access at http://localhost:4173

Test Checklist​

Before Deployment:

  • npm run build succeeds
  • dist/index.html contains correct JS/CSS references
  • dist/cores/ contains all emulator cores
  • dist/bios/ contains all BIOS files
  • ESLint passes: npm run lint
  • Unit tests pass: npm run test

After Deployment:

  • Homepage loads at /
  • Emulator loads at /app/nes/
  • Iframe embed works with EmulatorJS format
  • Iframe embed works with props format
  • CDN resources load correctly
  • CORS headers are properly configured

Common Issues​

Port Already in Use​

lsof -ti:3307 | xargs kill -9
npm run dev

Build Fails Due to Memory​

export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

CORS Issues When Loading ROMs​

Ensure your ROM server has CORS enabled:

# Test CORS headers
curl -I https://your-cdn.com/game.nes | grep -i access-control

Emulator Not Loading​

  1. Check browser console for errors
  2. Verify core files are accessible
  3. Check network tab for failed requests
  4. Ensure WebGL is enabled in browser

Debugging Tips​

Console Logs​

// Add debug logging in src/App.jsx
console.log('AppParams:', game);
console.log('URL:', window.location.search);

Network Requests​

Use browser DevTools to inspect:

  • Core file downloads (/cores/*.zip)
  • ROM file downloads
  • API requests

React DevTools​

Install React DevTools browser extension to inspect:

  • Component tree
  • State changes
  • Props flow

CI/CD Integration​

GitHub Actions Example​

Create .github/workflows/test.yml:

name: Test

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm install
- run: npm run lint
- run: npm run test
- run: npm run build

Build Artifacts​

- uses: actions/upload-artifact@v4
with:
name: build
path: dist/

Resources​