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
| Tool | Version | Required |
|---|---|---|
| Node.js | >= 18.x | Yes |
| npm | >= 8.x | Yes |
| Git | >= 2.x | Yes |
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
| Service | Port | Protocol | Description |
|---|---|---|---|
| Lobby Server | 8082 | HTTP | Room management, user authentication |
| Tunnel Server | 8080 | WebSocket | P2P relay for netplay connections |
| TinyEmulator | 3307 | HTTP | Frontend emulator |
1. Start Lobby Service
The Lobby service is part of the retro-server project, built with Go.
Prerequisites
| Tool | Version | Required |
|---|---|---|
| Go | >= 1.20 | Yes |
| SQLite | 3.x | Yes (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
| Endpoint | Method | Description |
|---|---|---|
/api/v1/health | GET | Health check |
/api/v1/room | POST | Create room |
/api/v1/room/list | GET | List rooms |
/api/v1/auth/register | POST | Register user |
/api/v1/auth/login | POST | Login 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:
- Open two browser windows/tabs at
http://localhost:3307 - In Window 1:
- Go to "Multiplayer" -> "Create Room"
- Select a game (e.g., NES - Super Mario)
- Click "Create"
- Copy the room code
- In Window 2:
- Go to "Multiplayer" -> "Join Room"
- Paste the room code
- Click "Join"
- Verify both players see the game screen
- Test controls work for both players
Expected Results:
- Room created successfully
- Second player joins without error
- Game syncs between both players
- Controls work independently
Test Scenario 2: Share Game via Link
Steps:
- In Window 1:
- Start a game
- Click "Share" button
- Copy the share link
- Open the link in Window 2
- 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:
- Stop the tunnel service
- Try to create a room
- 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:
- Open Chrome DevTools (F12)
- Go to "Network" tab
- Filter by "WS"
- Select the tunnel connection
- 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
| Error | Cause | Solution |
|---|---|---|
| "Cannot connect to lobby" | Lobby service not running | Start retro-server |
| "Tunnel connection failed" | Tunnel service not running | Start tunnel service |
| "Room not found" | Room expired or invalid code | Create new room |
| "Sync lost" | Network instability | Check connection |
| "Game desync" | Latency too high | Use 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
| Scenario | Test URL | Expected Result |
|---|---|---|
| Basic Embed | https://play.tinyemulator.com | Emulator homepage loads |
| EmulatorJS Format | ?core=nes&rom=https://files.xxyx.net/1.nes&single=1 | NES game loads directly |
| Props Format | ?props=<Base64>&single=1 | Game loads via encoded props |
| Single Player Mode | &single=1 | Netplay UI hidden |
| Game Name | &gameName=Super Mario | Title displayed correctly |
Manual Testing Steps
- Load Game: Click "Super Mario" button in EmulatorJS format
- Verify Load: Check that the game starts within 5 seconds
- Test Controls: Press keyboard or touch controls
- Test Fullscreen: Click fullscreen button
- Switch Games: Click another game button
- 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 assetsdist/index.htmlexists
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 buildsucceeds -
dist/index.htmlcontains 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
- Check browser console for errors
- Verify core files are accessible
- Check network tab for failed requests
- 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/