Skip to content

Commit

Permalink
MWPW-147289: [Nala] Refine screenshot diff tool: add IT s3 support, r…
Browse files Browse the repository at this point in the history
…efine take APIS
  • Loading branch information
JackySun9 committed May 16, 2024
1 parent 6398a70 commit 457d801
Show file tree
Hide file tree
Showing 12 changed files with 2,138 additions and 188 deletions.
98 changes: 98 additions & 0 deletions libs/screenshot/cleans3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* eslint-disable no-restricted-syntax */
const readline = require('readline');
// eslint-disable-next-line import/no-extraneous-dependencies
const { S3Client, ListObjectsV2Command, DeleteObjectsCommand } = require('@aws-sdk/client-s3');

const S3REGION = 'us-west-1';
const S3ENDPOINT = 'https://s3-sj3.corp.adobe.com';

async function* listObjects(s3, params) {
let isTruncated = false;
let token;
do {
const command = new ListObjectsV2Command({ ...params, ContinuationToken: token });
// eslint-disable-next-line no-await-in-loop
const response = await s3.send(command);
yield response.Contents;
({ IsTruncated: isTruncated, NextContinuationToken: token } = response);
} while (isTruncated);
}

function askQuestion(query) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(query, (ans) => {
rl.close();
resolve(ans);
});
});
}

async function main() {
const bucket = process.argv[2];
const s3Path = process.argv[3];

if (!bucket || !s3Path) {
console.log('Usage: node cleans3.js <bucket> <path>');
process.exit(1);
}

const s3key = process.env.s3accesskey;
const s3secret = process.env.s3secretkey;

const s3 = new S3Client({
region: S3REGION,
endpoint: S3ENDPOINT,
credentials: {
accessKeyId: s3key,
secretAccessKey: s3secret,
},
forcePathStyle: true,
});

const params = { Bucket: bucket, Prefix: s3Path, MaxKeys: 1000 };

let totalSize = 0;
const toBeDeleted = {
Bucket: bucket,
Delete: {
Objects: [],
Quiet: false,
},
};

for await (const contents of listObjects(s3, params)) {
if (contents === undefined || contents.length === 0) {
console.log('No objects to delete.');
return; // Skip to next iteration if current is empty
}

for (const obj of contents) {
totalSize += obj.Size;
console.log(`${obj.Key}, ${obj.LastModified}, ${obj.Size}`);
toBeDeleted.Delete.Objects.push({ Key: obj.Key });
}
}

if (toBeDeleted.Delete.Objects.length > 0) {
const answer = await askQuestion('Are you sure you want to delete these files? (yes/no): ');
if (answer.toLowerCase() === 'yes') {
const deleteCommand = new DeleteObjectsCommand(toBeDeleted);
await s3.send(deleteCommand);
toBeDeleted.Delete.Objects = [];
console.log('Files deleted successfully.');
} else {
console.log('Deletion canceled.');
}
} else {
console.log('No files to delete.');
}

console.log(`Total Size: ${totalSize}`);
}

main();
91 changes: 91 additions & 0 deletions libs/screenshot/compare.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions
import { getComparator } from 'playwright-core/lib/utils';
import fs from 'fs';
import axios from 'axios';
import path from 'path';

const S3URL = 'https://s3-sj3.corp.adobe.com/milo';

async function downloadImage(url, localPath) {
const writer = fs.createWriteStream(localPath);

const res = await axios.get(url, { responseType: 'stream' });

res.data.pipe(writer);

return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
}

async function getSavedImages(s3Url, curEntries) {
const response = await axios.get(`${s3Url}/results.json`);
const preEntries = response.data;

if (Object.keys(curEntries).length !== Object.keys(preEntries).length) {
throw new Error(`Previous one has ${Object.keys(preEntries).length} items,
but the current one has ${Object.keys(curEntries).length}`);
}

// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(curEntries)) {
const entry = value[0];
if (!entry.b) {
entry.b = entry.a;
}

const basename = path.basename(preEntries[key][0].a);
entry.a = `${preEntries[key][0].a}`.replace('.png', '-a.png');
console.log(`Downloading ${s3Url}/${basename}`);
// eslint-disable-next-line no-await-in-loop
await downloadImage(`${s3Url}/${basename}`, entry.a);
}
}

async function main() {
const localPath = process.argv[2];
const s3Url = `${S3URL}/${localPath}`;

if (!localPath || !s3Url) {
console.log('Usage: node compare.js <localPath>');
process.exit(1);
}

const curEntries = JSON.parse(fs.readFileSync(`${localPath}/results.json`));

if (s3Url) {
await getSavedImages(s3Url, curEntries);
}

const results = {};

// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(curEntries)) {
const result = {};
const entry = value[0];
console.log(entry);

const baseImage = fs.readFileSync(entry.a);
const currImage = fs.readFileSync(entry.b);
result.order = 1;
result.a = entry.a;
result.b = entry.b;
result.urls = entry.urls;

const comparator = getComparator('image/png');
const diffImage = comparator(baseImage, currImage);

if (diffImage) {
const diffName = `${entry.b}`.replace('.png', '-diff.png');
fs.writeFileSync(diffName, diffImage.diff);
result.diff = diffName;
console.info('Differences found');
}
results[key] = [result];
}

fs.writeFileSync(`${localPath}/results.json`, JSON.stringify(results, null, 2));
}

main();
34 changes: 34 additions & 0 deletions libs/screenshot/merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const fs = require('fs');
const path = require('path');

function mergeResults(folderPath) {
try {
const resultsFiles = fs.readdirSync(folderPath).filter((file) => file.startsWith('results-'));
let finalResults = {};

// eslint-disable-next-line no-restricted-syntax
for (const file of resultsFiles) {
const content = JSON.parse(fs.readFileSync(path.join(folderPath, file), 'utf-8'));
finalResults = { ...finalResults, ...content };
}

// Write the final merged results
fs.writeFileSync(`${folderPath}/results.json`, JSON.stringify(finalResults, null, 2));

// Optionally, clean up individual files
resultsFiles.forEach((file) => fs.unlinkSync(path.join(folderPath, file)));

console.log('Results merged and saved successfully.');
} catch (error) {
console.error('Error merging results:', error);
}
}

// Example usage: node thisScript.js ./path/to/folder
const folderPath = process.argv[2]; // Get folder path from command line argument
if (!folderPath) {
console.error('Please specify a folder path. e.g., screenshots/milo');
process.exit(1);
}

mergeResults(folderPath);
73 changes: 62 additions & 11 deletions libs/visualutil.js → libs/screenshot/take.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,30 @@ import { getComparator } from 'playwright-core/lib/utils';

const fs = require('fs');

async function take(page, folderPath, fileName) {
await page.screenshot({ path: `${folderPath}/${fileName}`, fullPage: true });
/**
* Take a screenshot of a page
* @param {Page} page - The page object
* @param {string} folderPath - The folder path to save the screenshot, e.g., screenshots/milo
* @param {string} fileName - The file name of the screenshot
* @param {object} options - The screenshot options, see https://playwright.dev/docs/api/class-page#page-screenshot
*/
async function take(page, folderPath, fileName, options = {}) {
const urls = [];
const result = {};
const name = `${folderPath}/${fileName}.png`;
urls.push(page.url());
options.path = name;
if (options.selector) {
await page.locator(options.selector).screenshot(options);
} else {
await page.screenshot(options);
}
result.a = name;
result.urls = urls.join(' | ');
return result;
}

async function takeOne(page, url, callback, folderPath, fileName, isFullPage = true) {
async function takeOne(page, url, callback, folderPath, fileName, options = {}) {
const urls = [];
const result = {};

Expand All @@ -18,8 +37,14 @@ async function takeOne(page, url, callback, folderPath, fileName, isFullPage = t
urls.push(url);
if (typeof callback === 'function') { await callback(); }
const name = `${folderPath}/${fileName}.png`;
await page.screenshot({ path: name, fullPage: isFullPage });
options.path = name;
if (options.selector) {
await page.locator(options.selector).screenshot(options);
} else {
await page.screenshot(options);
}

result.order = 1;
result.a = name;
result.urls = urls.join(' | ');
return result;
Expand All @@ -35,6 +60,7 @@ async function takeTwo(page, urlStable, callbackStable, urlBeta, callbackBeta, f
if (typeof callbackStable === 'function') { await callbackStable(); }
const nameStable = `${folderPath}/${fileName}-a.png`;
await page.screenshot({ path: nameStable, fullPage: true });
result.order = 1;
result.a = nameStable;

console.info(`[Test Page]: ${urlBeta}`);
Expand All @@ -60,6 +86,7 @@ async function takeTwoAndCompare(page, urlStable, callbackStable, urlBeta, callb
const nameStable = `${folderPath}/${fileName}-a.png`;
await page.screenshot({ path: nameStable, fullPage: true });
const baseImage = fs.readFileSync(nameStable);
result.order = 1;
result.a = nameStable;

console.info(`[Test Page]: ${urlBeta}`);
Expand All @@ -85,23 +112,47 @@ async function takeTwoAndCompare(page, urlStable, callbackStable, urlBeta, callb
return result;
}

function compareScreenshots(stableArray, betaArray, folderPath) {
function compareScreenshots(stableArray, betaArray) {
const results = [];
const comparator = getComparator('image/png');
for (let i = 0; i < stableArray.length; i += 1) {
if (betaArray[i].slice(-10) === stableArray[i].slice(-10)) {
const stableImage = fs.readFileSync(`${folderPath}/${stableArray[i]}`);
const betaImage = fs.readFileSync(`${folderPath}/${betaArray[i]}`);
if (betaArray[i].a.slice(-10) === stableArray[i].a.slice(-10)) {
const result = {};
const urls = [];
result.order = i + 1;
result.a = `${stableArray[i].a}`;
result.b = `${betaArray[i].a}`;
urls.push(stableArray[i].urls);
urls.push(betaArray[i].urls);
const stableImage = fs.readFileSync(`${stableArray[i].a}`);
const betaImage = fs.readFileSync(`${betaArray[i].a}`);
const diffImage = comparator(stableImage, betaImage);

if (diffImage) {
fs.writeFileSync(`${folderPath}/${stableArray[i]}-diff.png`, diffImage.diff);
result.diff = `${stableArray[i].a}-diff.png`;
fs.writeFileSync(`${stableArray[i].a}-diff.png`, diffImage.diff);
console.info('Differences found');
}
result.urls = urls.join(' | ');
results.push(result);
} else {
console.info('Screenshots are not matched');
console.info(`${stableArray[i]} vs ${betaArray[i]}`);
console.info(`${stableArray[i].a} vs ${betaArray[i].a}`);
}
}
return results;
}

function writeResultsToFile(folderPath, testInfo, results) {
const resultFilePath = `${folderPath}/results-${testInfo.workerIndex}.json`;
fs.writeFileSync(resultFilePath, JSON.stringify(results, null, 2));
}

module.exports = { takeTwoAndCompare, compareScreenshots, takeOne, takeTwo, take };
module.exports = {
takeTwoAndCompare,
compareScreenshots,
takeOne,
takeTwo,
take,
writeResultsToFile,
};
Loading

0 comments on commit 457d801

Please sign in to comment.