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