UNPKG

@google/model-viewer

Version:

Easily display interactive 3D models on the web and in AR!

149 lines • 7.32 kB
/* * Copyright 2018 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var _a; const { promises: fs } = require('fs'); const puppeteer = require('puppeteer'); const path = require('path'); const { PNG } = require('pngjs'); const makeDir = require('make-dir'); import { ImageComparator } from './common.js'; import { ConfigReader } from './config-reader.js'; const $configReader = Symbol('configReader'); export class ArtifactCreator { constructor(config, rootDirectory, baseUrl) { this.config = config; this.rootDirectory = rootDirectory; this.baseUrl = baseUrl; this[_a] = new ConfigReader(this.config); console.log('🌈 Preparing to capture screenshots for fidelity comparison'); } get outputDirectory() { return path.join(path.resolve(this.rootDirectory), 'results'); } get goldens() { return this.config.renderers.map(renderer => (Object.assign({}, renderer, { file: `${renderer.name}-golden.png` }))); } async captureAndAnalyzeScreenshots(scenarioWhitelist = null) { const { scenarios, analysisThresholds } = this.config; const analyzedScenarios = []; const { goldens, outputDirectory } = this; for (const scenario of scenarios) { const { name: scenarioName, dimensions } = scenario; if (scenarioWhitelist != null && !scenarioWhitelist.has(scenarioName)) { continue; } console.log(`\nšŸŽØ Scenario: ${scenarioName}`); const scenarioOutputDirectory = path.join(outputDirectory, scenarioName); await makeDir(scenarioOutputDirectory); const screenshot = await this.captureScreenshot('model-viewer', scenarioName, dimensions, path.join(scenarioOutputDirectory, 'model-viewer.png')); const analysisResults = await this.analyze(screenshot, goldens, scenario, dimensions, analysisThresholds); const scenarioRecord = Object.assign({ analysisResults }, scenario); console.log(`\nšŸ’¾ Recording analysis`); await fs.writeFile(path.join(outputDirectory, scenarioName, 'analysis.json'), JSON.stringify(scenarioRecord)); analyzedScenarios.push(scenario); } console.log('šŸ’¾ Recording configuration'); const finalConfig = Object.assign({}, this.config, { scenarios: analyzedScenarios }); await fs.writeFile(path.join(outputDirectory, 'config.json'), JSON.stringify(finalConfig)); return scenarios; } async analyze(screenshot, goldens, scenario, dimensions, analysisThresholds) { const analysisResults = []; const { rootDirectory, outputDirectory } = this; const { name: scenarioName, exclude } = scenario; for (const goldenConfig of goldens) { const { name: rendererName } = goldenConfig; if (exclude != null && exclude.includes(rendererName)) { continue; } console.log(`\nšŸ” Comparing <model-viewer> to ${goldenConfig.description}`); const thresholdResults = []; const goldenPath = path.join(rootDirectory, 'goldens', scenarioName, goldenConfig.file); const golden = await fs.readFile(goldenPath); const screenshotImage = PNG.sync.read(screenshot).data; const goldenImage = PNG.sync.read(golden).data; const comparator = new ImageComparator(screenshotImage, goldenImage, dimensions); await fs.writeFile(path.join(outputDirectory, scenarioName, goldenConfig.file), golden); for (const threshold of analysisThresholds) { console.log(`\n šŸ“ Using threshold ${threshold.toFixed(1)}`); const { analysis } = comparator.analyze(threshold); const { matchingRatio, averageDistanceRatio, mismatchingAverageDistanceRatio } = analysis; thresholdResults.push(analysis); console.log(` šŸ“Š Matching pixels: ${(matchingRatio * 100).toFixed(2)}%`); console.log(` šŸ“Š Mean color distance: ${(averageDistanceRatio * 100).toFixed(2)}%`); console.log(` šŸ“Š Mean color distance (mismatching pixels only): ${(mismatchingAverageDistanceRatio * 100).toFixed(2)}%`); } analysisResults.push(thresholdResults); } return analysisResults; } async captureScreenshot(renderer, scenarioName, dimensions, outputPath = path.join(this.outputDirectory, 'model-viewer.png')) { const devicePixelRatio = 2; const scaledWidth = dimensions.width; const scaledHeight = dimensions.height; const rendererConfig = this[$configReader].rendererConfig(renderer); if (rendererConfig == null) { console.log(`āš ļø Renderer "${renderer}" is not configured. Did you add it to the test config?`); return; } console.log(`šŸš€ Launching browser`); const browser = await puppeteer.launch({ defaultViewport: { width: scaledWidth, height: scaledHeight, deviceScaleFactor: devicePixelRatio }, headless: false }); const page = await browser.newPage(); const url = `${this.baseUrl}?hide-ui&config=../../config.json&scenario=${encodeURIComponent(scenarioName)}`; page.on('error', (error) => { console.log(`🚨 ${error}`); }); page.on('console', async (message) => { const args = await Promise.all(message.args().map((arg) => arg.jsonValue())); if (args.length) { console.log(`āž”ļø`, ...args); } }); console.log(`šŸ—ŗ Navigating to ${url}`); await page.goto(url); console.log(`šŸ–Œ Rendering ${scenarioName} with ${rendererConfig.description}`); await page.evaluate(async () => { const modelBecomesReady = self.modelLoaded ? Promise.resolve() : new Promise((resolve, reject) => { const timeout = setTimeout(reject, 10000); self.addEventListener('model-ready', () => { clearTimeout(timeout); resolve(); }, { once: true }); }); await modelBecomesReady; }); console.log(`šŸ–¼ Capturing screenshot`); try { await fs.mkdir(this.outputDirectory); } catch (e) { // Ignored... } const screenshot = await page.screenshot({ path: outputPath }); await browser.close(); return screenshot; } } _a = $configReader; //# sourceMappingURL=artifact-creator.js.map