diff --git a/.gitignore b/.gitignore index 6fe4bcd..bb07c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,5 @@ dist Report/ .DS_Store -Summary/ \ No newline at end of file +Summary/ +.vscode \ No newline at end of file diff --git a/bin/cli.js b/bin/cli.js index dba521d..1ea5a71 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +require('dotenv').config() const { Command } = require('commander'); const { ar } = require('date-fns/locale'); const { Generate,Summary } = require('../src'); @@ -7,7 +8,7 @@ const {endOfDay,startOfDay} = require('date-fns') program.name('percy-report') .description('Generate Percy Reports & Download Images Locally') -.version('0.0.1') +.version('0.0.2') program.command('generate') .description('Genetate Report') diff --git a/package-lock.json b/package-lock.json index 31ba501..2d9ada7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@percy/cli": "^1.10.3", + "@percy/cli": "^1.16.0", "axios": "^0.27.2", "chromedriver": "^105.0.0", "commander": "^9.4.0", "date-fns": "^2.29.3", + "dotenv": "^16.0.3", "ejs": "^3.1.8" }, "bin": { @@ -704,6 +705,14 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", @@ -2195,6 +2204,11 @@ "path-type": "^4.0.0" } }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, "ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", diff --git a/package.json b/package.json index 64211f3..32553bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "percy-report", - "version": "0.0.1", + "version": "0.0.2", "description": "Percy Report Generator", "main": "index.js", "scripts": {}, @@ -26,6 +26,7 @@ "chromedriver": "^105.0.0", "commander": "^9.4.0", "date-fns": "^2.29.3", + "dotenv": "^16.0.3", "ejs": "^3.1.8" }, "devDependencies": { diff --git a/src/generate.js b/src/generate.js index 98f7061..8ff0503 100644 --- a/src/generate.js +++ b/src/generate.js @@ -1,3 +1,4 @@ + const { Parser } = require('./response-parser') const { Axios } = require('axios') const fs = require('fs') @@ -27,6 +28,7 @@ module.exports.Generate = async function (config) { throw res.data } }) + const isApp = buildDetails['data']['attributes']['type'] == 'app' while (buildDetails.data && buildDetails.data.attributes.state !== 'finished') { console.log("Waiting for build to complete on Percy...") await wait(30000) @@ -41,6 +43,7 @@ module.exports.Generate = async function (config) { throw new Error("Build Failed with an Error on Percy Server. Please check your percy dashboard for more information.") } } + console.log(`Generating report for Build ID ${buildId}`) let snapshotsData = await axios.get(`/snapshots?build_id=${buildId}`, { responseType: 'json' }).then((res) => { if (res.status == 200) { @@ -54,7 +57,6 @@ module.exports.Generate = async function (config) { buildURL = buildDetails['data']['attributes']['web-url'] projectURL = buildURL.split("/builds/")[0] projectName = projectURL.split('/').slice(-1)[0] - console.log(projectURL); let report = { totalScreenshots: 0, @@ -64,7 +66,8 @@ module.exports.Generate = async function (config) { unreviewedSnapshots: 0, widths: [], browsers: [], - projectURL : projectURL, + devices: [], + projectURL: projectURL, buildNumber: buildDetails['data']['attributes']['build-number'], projectName: projectName } @@ -79,13 +82,29 @@ module.exports.Generate = async function (config) { let base = images['base'] = getComparisonImage(comp, 'base-screenshot') let head = images['head'] = getComparisonImage(comp, 'head-screenshot') let diff = images['diff'] = getComparisonImage(comp, 'diff-image') - let browser = getComparisonBrowser(comp) + let compTag; + if (isApp) { + let device = getComparisonDevice(comp) + compTag = device.name + if(!report.devices.includes(device.name)){ + report.devices.push(device.name) + } + } else { + let browser = getComparisonBrowser(comp) + compTag = device.nam + if (!report.browsers.includes(browser.name)) { + report.browsers.push(browser.name) + } + if (!report.widths.includes(comparison['width'])) { + report.widths.push(comparison['width']) + } + } if (downloadImages) { ['base', 'head', 'diff'].forEach((val) => { if (images[val]) { images[val].file = downloadImage({ name: String(snapshot?.['attributes'].name).replace('/', '-'), - browser: browser.name, + compTag, width: images[val].width, type: val, baseDir, @@ -102,13 +121,9 @@ module.exports.Generate = async function (config) { report['unreviewedScreenshots']++ flagChanged = true } - Object.assign(comparison, comp.attributes, { images }, { browser: browser.name || '' }) - if (!report.browsers.includes(browser.name)) { - report.browsers.push(browser.name) - } - if (!report.widths.includes(comparison['width'])) { - report.widths.push(comparison['width']) - } + Object.assign(comparison, comp.attributes, { images }, isApp?{ device:compTag }:{browser:compTag}) + + comparison['diff-percentage'] = (comparison['diff-ratio'] * 100).toFixed(2) comparison['diff-color'] = "yellow" if (comparison['diff-percentage'] > diffThreshold) { @@ -122,11 +137,12 @@ module.exports.Generate = async function (config) { return formattedSnapshot }) fs.writeFileSync(`${baseDir}/report.json`, JSON.stringify(report, undefined, 2)) - HtmlReportGenerator(config, report) + HtmlReportGenerator(config, report, isApp) console.log("Build Report Generated.") return report } + function getComparisonImage(comparison, key) { let screenshot = comparison.relationships[key] if (!screenshot) return; @@ -141,20 +157,24 @@ function getComparisonBrowser(comparison) { return comparison.relationships['browser']?.relationships['browser-family']?.attributes } +function getComparisonDevice(comparison) { + return comparison.relationships['comparison-tag']?.attributes +} + function downloadImage(options) { - let { name, browser, width, type, baseDir, url } = options - if(!fs.existsSync(`${baseDir}/${type}`)){ - fs.mkdirSync(`${baseDir}/${type}`,{recursive:true}) + let { name, width, type, baseDir, url,compTag } = options + if (!fs.existsSync(`${baseDir}/${type}`)) { + fs.mkdirSync(`${baseDir}/${type}`, { recursive: true }) } - let path = `${baseDir}/${type}/${name}-${browser}-${width}.png` + let path = `${baseDir}/${type}/${name}-${compTag}-${width}.png` try { - new Axios({ responseType: 'arraybuffer',url:url }).get(url).then((file) => { + new Axios({ responseType: 'arraybuffer', url: url }).get(url).then((file) => { console.log("Download Complete:" + path) fs.writeFileSync(path, file.data) }).catch((err) => { console.error("Failed to Download: " + path) }) - return `./${type}/${name}-${browser}-${width}.png`; + return `file:./${type}/${name}-${compTag}-${width}.png`; } catch { } diff --git a/src/html-render.js b/src/html-render.js index 46e2ff3..8b827b2 100644 --- a/src/html-render.js +++ b/src/html-render.js @@ -1,24 +1,64 @@ const fs = require('fs') const ejs = require('ejs'); const path = require('path') -function HtmlReportGenerator(config,jsonReport){ - let { buildId, downloadPath } = config - let template_path = path.resolve(__dirname,'template/report.html') - const template = fs.readFileSync(template_path,{encoding:'utf-8'}).toString() - let htmlReport = ejs.render(template,{buildId,...jsonReport}) - fs.writeFileSync(`${downloadPath}/${buildId}/report.html`,htmlReport) + +function HtmlReportGenerator(config, jsonReport, isApp) { + if (isApp) { + let { + buildId, + downloadPath + } = config + let template_path = path.resolve(__dirname, 'template/app-report.html') + const template = fs.readFileSync(template_path, { + encoding: 'utf-8' + }).toString() + let htmlReport = ejs.render(template, { + buildId, + ...jsonReport + }) + fs.writeFileSync(`${downloadPath}/${buildId}/app-report.html`, htmlReport) + } else { + let { + buildId, + downloadPath + } = config + let template_path = path.resolve(__dirname, 'template/report.html') + const template = fs.readFileSync(template_path, { + encoding: 'utf-8' + }).toString() + let htmlReport = ejs.render(template, { + buildId, + ...jsonReport + }) + fs.writeFileSync(`${downloadPath}/${buildId}/report.html`, htmlReport) + } } -function HtmlSummary(summary,filename){ - let template_path = path.resolve(__dirname,'template/summary.html') - const template = fs.readFileSync(template_path,{encoding:"utf-8"}).toString() - try{ - let htmlSummary = ejs.render(template,summary) - fs.writeFileSync(filename,htmlSummary) - }catch(err){ - console.log(err) +function HtmlSummary(summary, filename, isApp) { + if (isApp) { + let template_path = path.resolve(__dirname, 'template/app-summary.html') + const template = fs.readFileSync(template_path, { + encoding: "utf-8" + }).toString() + try { + let htmlSummary = ejs.render(template, summary) + fs.writeFileSync(filename, htmlSummary) + } catch (err) { + console.log(err) + } + } else { + let template_path = path.resolve(__dirname, 'template/summary.html') + const template = fs.readFileSync(template_path, { + encoding: "utf-8" + }).toString() + try { + let htmlSummary = ejs.render(template, summary) + fs.writeFileSync(filename, htmlSummary) + } catch (err) { + console.log(err) + } } - + } module.exports.HtmlReportGenerator = HtmlReportGenerator diff --git a/src/summary.js b/src/summary.js index a098e29..edce511 100644 --- a/src/summary.js +++ b/src/summary.js @@ -35,6 +35,7 @@ module.exports.Summary = async function (opts) { throw res.data } }) + const isApp = project['data']['attributes']['type'] == 'app' let projectId = project.data.id let projectURL = "https://percy.io/"+project.data.attributes['full-slug'] let projectName = project.data.attributes.name @@ -130,7 +131,7 @@ module.exports.Summary = async function (opts) { fs.mkdirSync('Summary',{recursive:true}) } fs.writeFileSync(`Summary/${summary.projectName}-${Date.now()}.json`,JSON.stringify(summary,undefined,2)) - HtmlSummary(summary,`Summary/${summary.projectName}-${Date.now()}.html`) + HtmlSummary(summary,`Summary/${summary.projectName}-${Date.now()}.html`, isApp) console.log("Summary report generated") return summary; diff --git a/src/template/app-report.html b/src/template/app-report.html new file mode 100644 index 0000000..f4638d4 --- /dev/null +++ b/src/template/app-report.html @@ -0,0 +1,180 @@ + + + + + + + + Document + + + + + + +
+

<%= projectName %>

+

Go to Percy Dashboard Build

+ + + + + + + + + + + + + + + + + + + +
Build NumberDevice CountTotal SnapshotsTotal ScreenshotsSnapshots UnreviewedScreenshots Unreviewed
+ <%= buildNumber %> + <%= devices.length %> + <%= totalSnapshots %> + + <%= totalScreenshots %> + + <%= unreviewedSnapshots %> + + <%= unreviewedScreenshots %> +
+
+
+ + +
+
+ + +
+
+
+ <% for(let snapshot of details){ %> + <% for(let comparison of snapshot.comparisons){ %> +
+
+ + + + + + + + +
+
+ +
+ <% if(!comparison.images['base'] || comparison.images['diff'] || comparison.images['head']){ %> +
+ + <% if (comparison.images['diff']) { %> + <% } %> +
+ <% } %> +
+ <% }} %> +
+ + + \ No newline at end of file diff --git a/src/template/app-summary.html b/src/template/app-summary.html new file mode 100644 index 0000000..881d4b6 --- /dev/null +++ b/src/template/app-summary.html @@ -0,0 +1,142 @@ + + + + + + + + Document + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Summary Report <%=new Date(startDate).toDateString()%> - <%=new Date(endDate).toDateString()%>
Project Name <%=projectName%>
Total Builds <%=totalBuilds%>
Total Builds Approved <%=totalBuildsApproved%>
Total Builds Unreviewed + + <%=totalBuildsUnreviewed%> + +
Total Builds Failed + + <%=totalBuildsFailed%> + +
Total Builds Requesting Changes <%=totalBuildsRequestingChanges%>
Total Snapshots <%=totalSnapshots%>
Total Snapshots Requesting Changes <%=totalSnapshotsRequestingChanges%>
Total Snapshots Unreviewed <%=totalSnapshotsUnreviewed%>
Total Snapshots Reviewed <%=totalSnapshotsReviewed%>
Total Comparisons <%=totalComparisons%>
+ +
+ + + + + + + + + + + + <% for(let unreviewedBuild of unreviewedBuilds){ %> + + + + + <% } %> + +
Unreviewed Builds
Build IDBuild Created At
+ Build <%= unreviewedBuild['buildNo'] %> + + <%= new Date(unreviewedBuild['timestamp']) %> +
+
+ +
+ + + + + + + + + + + + <% for(let failedBuild of failedBuilds){ %> + + + + + <% } %> + +
Failed Builds
Build IDBuild Created At
+ Build <%= failedBuild['buildNo'] %> + + <%= new Date(failedBuild['timestamp']) %> +
+
+
+ + \ No newline at end of file