226 lines
7.0 KiB
TypeScript
226 lines
7.0 KiB
TypeScript
/**
|
|
* Generate test fixtures by taking screenshots of static HTML fixture pages.
|
|
* This creates controlled test images for template matching verification.
|
|
*/
|
|
|
|
import puppeteer from 'puppeteer';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
|
|
const FIXTURE_HTML_DIR = path.join(__dirname, '../resources/iracing-hosted-sessions');
|
|
const OUTPUT_DIR = path.join(__dirname, '../resources/test-fixtures');
|
|
|
|
async function generateFixtures(): Promise<void> {
|
|
console.log('🚀 Starting fixture generation...');
|
|
|
|
// Ensure output directory exists
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
console.log(`📁 Created output directory: ${OUTPUT_DIR}`);
|
|
}
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
});
|
|
|
|
try {
|
|
const page = await browser.newPage();
|
|
|
|
// Set viewport to match typical screen size (Retina 2x)
|
|
await page.setViewport({
|
|
width: 1920,
|
|
height: 1080,
|
|
deviceScaleFactor: 2, // Retina display
|
|
});
|
|
|
|
// List of HTML fixtures to screenshot
|
|
const fixtures = [
|
|
{ file: '01-hosted-racing.html', name: 'hosted-racing' },
|
|
{ file: '02-create-a-race.html', name: 'create-race' },
|
|
{ file: '03-race-information.html', name: 'race-information' },
|
|
];
|
|
|
|
for (const fixture of fixtures) {
|
|
const htmlPath = path.join(FIXTURE_HTML_DIR, fixture.file);
|
|
|
|
if (!fs.existsSync(htmlPath)) {
|
|
console.log(`⚠️ Skipping ${fixture.file} - file not found`);
|
|
continue;
|
|
}
|
|
|
|
console.log(`📸 Processing ${fixture.file}...`);
|
|
|
|
// Load the HTML file
|
|
await page.goto(`file://${htmlPath}`, {
|
|
waitUntil: 'networkidle0',
|
|
timeout: 30000,
|
|
});
|
|
|
|
// Take screenshot
|
|
const outputPath = path.join(OUTPUT_DIR, `${fixture.name}-screenshot.png`);
|
|
await page.screenshot({
|
|
path: outputPath,
|
|
fullPage: false, // Just the viewport
|
|
});
|
|
|
|
console.log(`✅ Saved: ${outputPath}`);
|
|
}
|
|
|
|
console.log('\n🎉 Fixture generation complete!');
|
|
console.log(`📁 Screenshots saved to: ${OUTPUT_DIR}`);
|
|
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
// Also create a simple synthetic test pattern for algorithm verification
|
|
async function createSyntheticTestPattern(): Promise<void> {
|
|
const sharp = (await import('sharp')).default;
|
|
|
|
console.log('\n🔧 Creating synthetic test patterns...');
|
|
|
|
// Ensure output directory exists
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
console.log(`📁 Created output directory: ${OUTPUT_DIR}`);
|
|
}
|
|
|
|
// Create a simple test image (red square on white background)
|
|
const width = 200;
|
|
const height = 200;
|
|
const channels = 4;
|
|
|
|
// White background with a distinct blue rectangle in the center
|
|
const imageData = Buffer.alloc(width * height * channels);
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * channels;
|
|
|
|
// Create a blue rectangle from (50,50) to (150,150)
|
|
if (x >= 50 && x < 150 && y >= 50 && y < 150) {
|
|
imageData[idx] = 0; // R
|
|
imageData[idx + 1] = 0; // G
|
|
imageData[idx + 2] = 255; // B
|
|
imageData[idx + 3] = 255; // A
|
|
} else {
|
|
// White background
|
|
imageData[idx] = 255; // R
|
|
imageData[idx + 1] = 255; // G
|
|
imageData[idx + 2] = 255; // B
|
|
imageData[idx + 3] = 255; // A
|
|
}
|
|
}
|
|
}
|
|
|
|
const testImagePath = path.join(OUTPUT_DIR, 'synthetic-test-image.png');
|
|
await sharp(imageData, {
|
|
raw: { width, height, channels },
|
|
})
|
|
.png()
|
|
.toFile(testImagePath);
|
|
|
|
console.log(`✅ Saved synthetic test image: ${testImagePath}`);
|
|
|
|
// Create a template (the blue rectangle portion)
|
|
const templateWidth = 100;
|
|
const templateHeight = 100;
|
|
const templateData = Buffer.alloc(templateWidth * templateHeight * channels);
|
|
|
|
for (let y = 0; y < templateHeight; y++) {
|
|
for (let x = 0; x < templateWidth; x++) {
|
|
const idx = (y * templateWidth + x) * channels;
|
|
// Blue fill
|
|
templateData[idx] = 0; // R
|
|
templateData[idx + 1] = 0; // G
|
|
templateData[idx + 2] = 255; // B
|
|
templateData[idx + 3] = 255; // A
|
|
}
|
|
}
|
|
|
|
const templatePath = path.join(OUTPUT_DIR, 'synthetic-template.png');
|
|
await sharp(templateData, {
|
|
raw: { width: templateWidth, height: templateHeight, channels },
|
|
})
|
|
.png()
|
|
.toFile(templatePath);
|
|
|
|
console.log(`✅ Saved synthetic template: ${templatePath}`);
|
|
|
|
// Create a more realistic pattern with gradients (better for NCC)
|
|
const gradientWidth = 400;
|
|
const gradientHeight = 300;
|
|
const gradientData = Buffer.alloc(gradientWidth * gradientHeight * channels);
|
|
|
|
for (let y = 0; y < gradientHeight; y++) {
|
|
for (let x = 0; x < gradientWidth; x++) {
|
|
const idx = (y * gradientWidth + x) * channels;
|
|
|
|
// Create gradient background
|
|
const bgGray = Math.floor((x / gradientWidth) * 128 + 64);
|
|
|
|
// Add a distinct pattern in the center (button-like)
|
|
if (x >= 150 && x < 250 && y >= 100 && y < 150) {
|
|
// Darker rectangle with slight gradient
|
|
const buttonGray = 50 + Math.floor((x - 150) / 100 * 30);
|
|
gradientData[idx] = buttonGray;
|
|
gradientData[idx + 1] = buttonGray;
|
|
gradientData[idx + 2] = buttonGray + 20; // Slight blue tint
|
|
gradientData[idx + 3] = 255;
|
|
} else {
|
|
gradientData[idx] = bgGray;
|
|
gradientData[idx + 1] = bgGray;
|
|
gradientData[idx + 2] = bgGray;
|
|
gradientData[idx + 3] = 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
const gradientImagePath = path.join(OUTPUT_DIR, 'gradient-test-image.png');
|
|
await sharp(gradientData, {
|
|
raw: { width: gradientWidth, height: gradientHeight, channels },
|
|
})
|
|
.png()
|
|
.toFile(gradientImagePath);
|
|
|
|
console.log(`✅ Saved gradient test image: ${gradientImagePath}`);
|
|
|
|
// Extract the button region as a template
|
|
const buttonTemplateWidth = 100;
|
|
const buttonTemplateHeight = 50;
|
|
const buttonTemplateData = Buffer.alloc(buttonTemplateWidth * buttonTemplateHeight * channels);
|
|
|
|
for (let y = 0; y < buttonTemplateHeight; y++) {
|
|
for (let x = 0; x < buttonTemplateWidth; x++) {
|
|
const idx = (y * buttonTemplateWidth + x) * channels;
|
|
const buttonGray = 50 + Math.floor(x / 100 * 30);
|
|
buttonTemplateData[idx] = buttonGray;
|
|
buttonTemplateData[idx + 1] = buttonGray;
|
|
buttonTemplateData[idx + 2] = buttonGray + 20;
|
|
buttonTemplateData[idx + 3] = 255;
|
|
}
|
|
}
|
|
|
|
const buttonTemplatePath = path.join(OUTPUT_DIR, 'gradient-button-template.png');
|
|
await sharp(buttonTemplateData, {
|
|
raw: { width: buttonTemplateWidth, height: buttonTemplateHeight, channels },
|
|
})
|
|
.png()
|
|
.toFile(buttonTemplatePath);
|
|
|
|
console.log(`✅ Saved gradient button template: ${buttonTemplatePath}`);
|
|
}
|
|
|
|
// Run both
|
|
async function main(): Promise<void> {
|
|
try {
|
|
await createSyntheticTestPattern();
|
|
await generateFixtures();
|
|
} catch (error) {
|
|
console.error('❌ Error generating fixtures:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main(); |