128 lines
6.4 KiB
JavaScript
128 lines
6.4 KiB
JavaScript
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { chromium } from 'playwright-core';
|
|
|
|
const baseUrl = process.env.AI_DEPTH_BASE_URL || 'http://127.0.0.1:5174/';
|
|
const outputDir = process.env.AI_DEPTH_OUTPUT_DIR || 'test-results/ai-depth';
|
|
const executablePath = process.env.CHROME_EXECUTABLE_PATH || process.env.PLAYWRIGHT_CHROMIUM_PATH;
|
|
const launchOptions = {
|
|
headless: true,
|
|
};
|
|
if (executablePath) launchOptions.executablePath = executablePath;
|
|
|
|
await mkdir(outputDir, { recursive: true });
|
|
const browser = await chromium.launch(launchOptions);
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 900 }, deviceScaleFactor: 1 });
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push({ type: 'pageerror', message: error.message }));
|
|
page.on('console', (message) => {
|
|
if (message.type() === 'error') errors.push({ type: 'console', message: message.text() });
|
|
});
|
|
page.on('requestfailed', (request) => errors.push({ type: 'requestfailed', url: request.url(), message: request.failure()?.errorText ?? '' }));
|
|
|
|
await page.addInitScript(() => {
|
|
const workerResults = [];
|
|
Object.defineProperty(window, '__aiDepthWorkerResults', { value: workerResults });
|
|
const NativeWorker = window.Worker;
|
|
window.Worker = new Proxy(NativeWorker, {
|
|
construct(target, args) {
|
|
const worker = new target(...args);
|
|
worker.addEventListener('message', (event) => {
|
|
const result = event.data;
|
|
if (result?.type !== 'result' || !(result.values instanceof ArrayBuffer)) return;
|
|
const values = new Uint8Array(result.values);
|
|
const landmarks = {
|
|
nose: [235, 310],
|
|
leftEye: [198, 278],
|
|
rightEye: [249, 273],
|
|
face: [221, 267],
|
|
leftEar: [158, 213],
|
|
rightEar: [262, 206],
|
|
body: [139, 419],
|
|
};
|
|
const samples = Object.fromEntries(Object.entries(landmarks).map(([name, [x, y]]) => {
|
|
const sx = Math.min(result.width - 1, Math.max(0, Math.round(x * result.width / 415)));
|
|
const sy = Math.min(result.height - 1, Math.max(0, Math.round(y * result.height / 739)));
|
|
return [name, { x: sx, y: sy, depth: values[sy * result.width + sx] / 255 }];
|
|
}));
|
|
workerResults.push({ width: result.width, height: result.height, samples });
|
|
});
|
|
return worker;
|
|
},
|
|
});
|
|
});
|
|
|
|
const report = { baseUrl, screenshots: {}, errors };
|
|
try {
|
|
await page.goto(baseUrl, { waitUntil: 'domcontentloaded' });
|
|
await page.locator('#sample').click();
|
|
await page.waitForFunction(() => document.querySelector('#depth-provenance')?.textContent?.startsWith('수작업 테스트 Depth'), null, { timeout: 20000 });
|
|
await page.locator('#ai-depth').click();
|
|
await page.waitForFunction(() => {
|
|
const provenance = document.querySelector('#depth-provenance')?.textContent ?? '';
|
|
const status = document.querySelector('#status')?.textContent ?? '';
|
|
return provenance.startsWith('AI 추정') || status.includes('AI 깊이 생성 실패');
|
|
}, null, { timeout: Number(process.env.AI_DEPTH_TIMEOUT_MS || 240000) });
|
|
|
|
const status = await page.locator('#status').textContent();
|
|
const provenance = await page.locator('#depth-provenance').textContent();
|
|
if (!provenance?.startsWith('AI 추정')) throw new Error(`AI inference did not complete: ${status}`);
|
|
await page.screenshot({ path: `${outputDir}/no-glass.png`, fullPage: true });
|
|
report.screenshots.noGlass = `${outputDir}/no-glass.png`;
|
|
report.statusAfterInference = status;
|
|
report.provenance = provenance;
|
|
report.depthResults = await page.evaluate(() => window.__aiDepthWorkerResults);
|
|
|
|
const stage = await page.locator('#canvas-wrap').boundingBox();
|
|
if (!stage) throw new Error('Canvas stage is unavailable for drag QA.');
|
|
await page.mouse.move(stage.x + stage.width * 0.5, stage.y + stage.height * 0.5);
|
|
await page.mouse.down();
|
|
await page.mouse.move(stage.x + stage.width * 0.62, stage.y + stage.height * 0.48, { steps: 5 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(250);
|
|
await page.screenshot({ path: `${outputDir}/no-glass-drag.png`, fullPage: true });
|
|
report.screenshots.noGlassDrag = `${outputDir}/no-glass-drag.png`;
|
|
|
|
await page.locator('#view-mode').selectOption('final');
|
|
await page.waitForTimeout(300);
|
|
await page.screenshot({ path: `${outputDir}/final-drag.png`, fullPage: true });
|
|
report.screenshots.finalAfterDrag = `${outputDir}/final-drag.png`;
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
await page.waitForTimeout(300);
|
|
report.mobileHorizontalOverflow = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
|
|
if (report.mobileHorizontalOverflow) throw new Error('The page has horizontal overflow at 390px.');
|
|
await page.screenshot({ path: `${outputDir}/final-mobile.png`, fullPage: true });
|
|
report.screenshots.finalMobile = `${outputDir}/final-mobile.png`;
|
|
await page.setViewportSize({ width: 1280, height: 900 });
|
|
|
|
await page.locator('#view-mode').selectOption('noglass');
|
|
await page.locator('#ai-depth').click();
|
|
await page.waitForFunction(() => document.querySelector('#ai-depth')?.textContent?.includes('생성 중'), null, { timeout: 5000 });
|
|
await page.locator('#image').setInputFiles('public/assets/qa/dog1.jpeg');
|
|
await page.waitForFunction(() => {
|
|
const button = document.querySelector('#ai-depth');
|
|
const provenance = document.querySelector('#depth-provenance');
|
|
return button && !button.disabled && provenance?.hidden;
|
|
}, null, { timeout: 20000 });
|
|
await page.waitForTimeout(1000);
|
|
report.photoSwitchDuringAI = {
|
|
passed: await page.locator('#depth-provenance').isHidden(),
|
|
buttonEnabled: await page.locator('#ai-depth').isEnabled(),
|
|
};
|
|
if (!report.photoSwitchDuringAI.passed || !report.photoSwitchDuringAI.buttonEnabled) {
|
|
throw new Error('AI result was applied after the active photo changed.');
|
|
}
|
|
} catch (error) {
|
|
report.failure = error instanceof Error ? error.message : String(error);
|
|
report.status = await page.locator('#status').textContent().catch(() => '');
|
|
report.provenance = await page.locator('#depth-provenance').textContent().catch(() => '');
|
|
report.depthResults = await page.evaluate(() => window.__aiDepthWorkerResults).catch(() => []);
|
|
} finally {
|
|
report.errors = errors;
|
|
await writeFile(`${outputDir}/report.json`, JSON.stringify(report, null, 2));
|
|
await browser.close();
|
|
}
|
|
|
|
console.log(JSON.stringify(report, null, 2));
|
|
if (report.failure) process.exitCode = 1;
|