|  | /* | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements.  See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership.  The ASF licenses this file | 
|  | * to you 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. | 
|  | */ | 
|  |  | 
|  |  | 
|  | const fs = require('fs'); | 
|  | const path = require('path'); | 
|  | const util = require('util'); | 
|  | const { RESULT_FILE_NAME } = require('./store'); | 
|  |  | 
|  | const readFileAsync = util.promisify(fs.readFile); | 
|  |  | 
|  | function resolveImagePath(imageUrl) { | 
|  | if (!imageUrl) { | 
|  | return ''; | 
|  | } | 
|  |  | 
|  | // The original image path is relative to the client. | 
|  | return imageUrl.replace(/\\/g, '/').replace(/\.\.\/tmp/g, './tmp'); | 
|  | } | 
|  |  | 
|  | async function inlineImage(imageUrl) { | 
|  | if (!imageUrl) { | 
|  | return ''; | 
|  | } | 
|  | try { | 
|  | let fullPath = path.join(__dirname, resolveImagePath(imageUrl)); | 
|  | // let img = await jimp.read(fullPath); | 
|  | // img.quality(70); | 
|  | // return img.getBase64Async('image/jpeg'); | 
|  | let imgBuffer = await readFileAsync(fullPath); | 
|  | return 'data:image/webp;base64,' + imgBuffer.toString('base64'); | 
|  | } | 
|  | catch (e) { | 
|  | console.error(e); | 
|  | return ''; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | async function genDetail(test) { | 
|  | let shotDetail = ''; | 
|  | let prevShotDesc = ''; | 
|  | let failed = 0; | 
|  | for (let shot of test.results) { | 
|  | if (shot.diffRatio < 0.001) { | 
|  | continue; | 
|  | } | 
|  | failed++; | 
|  |  | 
|  | // Batch same description shot | 
|  | if (shot.desc !== prevShotDesc) { | 
|  | shotDetail += `\n#### ${shot.desc}`; | 
|  | prevShotDesc = shot.desc; | 
|  | } | 
|  |  | 
|  | let [expectedUrl, actualUrl, diffUrl] = await Promise.all([ | 
|  | inlineImage(shot.expected), | 
|  | inlineImage(shot.actual), | 
|  | inlineImage(shot.diff) | 
|  | // resolveImagePath(shot.expected), | 
|  | // resolveImagePath(shot.actual), | 
|  | // resolveImagePath(shot.diff) | 
|  | ]); | 
|  | shotDetail += ` | 
|  | <div style="margin-top:10px"> | 
|  | <figure style="width: 30%;display:inline-block;margin:0 10px;"> | 
|  | <img src="${expectedUrl}" style="width:100%" /> | 
|  | <figcaption>Expected ${test.expectedVersion}</figcaption> | 
|  | </figure> | 
|  | <figure style="width: 30%;display:inline-block;margin:0 10px;"> | 
|  | <img src="${actualUrl}" style="width:100%" /> | 
|  | <figcaption>Actual ${test.actualVersion}</figcaption> | 
|  | </figure> | 
|  | <figure style="width: 30%;display:inline-block;margin:0 10px;"> | 
|  | <img src="${diffUrl}" style="width:100%" /> | 
|  | <figcaption>Diff(${shot.diffRatio && shot.diffRatio.toFixed(3)})</figcaption> | 
|  | </figure> | 
|  | </div> | 
|  | `; | 
|  | } | 
|  | return { | 
|  | content: shotDetail, | 
|  | failed, | 
|  | total: test.results.length | 
|  | }; | 
|  | } | 
|  |  | 
|  | module.exports = async function(testDir) { | 
|  | let sections = []; | 
|  |  | 
|  | let failedTest = 0; | 
|  |  | 
|  | const tests = JSON.parse(fs.readFileSync( | 
|  | path.join(testDir, RESULT_FILE_NAME), 'utf-8' | 
|  | )); | 
|  |  | 
|  | for (let test of tests) { | 
|  | let detail = await genDetail(test); | 
|  |  | 
|  | if (detail.failed > 0) { | 
|  | failedTest++; | 
|  | let title = `${failedTest}. ${test.name} (Failed ${detail.failed} / ${detail.total})`; | 
|  | console.log(title); | 
|  |  | 
|  | let sectionText = ` | 
|  | <div style="margin-top: 100px;height: 20px;border-top: 1px solid #aaa"></div> | 
|  | <a id="${test.name}"></a> | 
|  |  | 
|  | <h2>${title}</h2> | 
|  |  | 
|  | ${detail.content} | 
|  | `; | 
|  |  | 
|  | sections.push({ | 
|  | content: sectionText, | 
|  | title, | 
|  | id: test.name | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | let htmlText = '<h1> Visual Regression Test Report</h1>\n'; | 
|  | htmlText += ` | 
|  | <p>Total: ${tests.length}</p> | 
|  | <p>Failed: ${failedTest}</p> | 
|  | `; | 
|  | htmlText += '<ul>\n' + sections.map(section => { | 
|  | return `<li><a href="${section.id}">${section.title}</a></li>`; | 
|  | }).join('\n') + '</ul>'; | 
|  | htmlText += sections.map(section => section.content).join('\n\n'); | 
|  |  | 
|  | const file = path.join(testDir, 'report.html'); | 
|  | fs.writeFileSync(file, htmlText, 'utf-8'); | 
|  | return file; | 
|  | } | 
|  |  |