Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

US152247 & US152272 - Add pixelmatch and handle images of different sizes #46

Merged
merged 22 commits into from
Jun 29, 2023

Conversation

svanherk
Copy link
Contributor

@svanherk svanherk commented Jun 28, 2023

Ok, most of the work here was around testing and the resulting code is pretty small. I'll add more comments inline, but at a high-level this PR:

  • Adds pixelmatch to compare diffs
    • If the images are the same size, the diff is created and saved and the test fails
    • If the images are different, the screenshot and golden are "grown" accordingly and compared in 9 different positions to find the best diff. This diff is saved, along with the "grown" images for the report and also for local debugging

Other diffing libraries were investigated but in the end pixelmatch was the simplest and gave us what we needed with minimal code.

@svanherk svanherk requested a review from a team as a code owner June 28, 2023 21:29
expect.fail(message);
let result = await executeServerCommand('brightspace-visual-diff-compare', { name, rect, opts });
if (result.resizeRequired) {
this.test.timeout(0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.test.timeout(0);
this.test.timeout(100000);

If we're worried about turning off the timeout, we could also pick a large number. We'll only get to this point after the screenshot is taken, so we don't need to worry about bad tests timing out - those will fail properly. It'd only be if pixelmatch or the file operations never completed that we'd be stuck.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not worried about it -- if we run into these cases we should be able to handle them separately anyway.


const isCI = !!env['CI'];
const DEFAULT_MARGIN = 10;
const DEFAULT_TOLERANCE = 0; // TODO: Support tolerance override?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently allow this to be overridden and a few places have, but I don't think we have a story for it

{ name: 'right', coord: newSize.width - original.width }
].forEach(x => { // TODO: position added for reports, remove/adjust as needed
if (original.width === newSize.width && original.height === newSize.height) {
resizedPNGs.push({ png: original, position: `${y.name}-${x.name}` });
Copy link
Contributor Author

@svanherk svanherk Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The position can be adjusted to whatever if we want to use it for the report, I don't actually use it anywhere. I used it for testing, and because it made these nested fors a lot easier to read.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the reports are going to need it, since they're going to use the resized images anyway which will always both be the same size.

Copy link
Contributor Author

@svanherk svanherk Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it was only if we wanted to call it out for extra clarity. I could remove this and switch to just comments for each value, like

	[
		0, // top
		Math.floor((newSize.height - original.height) / 2), // center
		newSize.height - original.height // bottom
	].forEach(y => {
		[
			0, // left
			Math.floor((newSize.width - original.width) / 2), // center
			newSize.width - original.width // right
		].forEach(x => {

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess keep it for now and I'll see if it can be used in a useful way. Your TODO can remind us to remove it later if not!

const screenshotFileName = `${screenshotFile}.png`;

if (command === 'brightspace-visual-diff-compare') {
if (!isCI) { // CI will be a fresh .vdiff folder each time and only one run
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about putting this whole cleanup in it's own command to make this cleaner, but then we're running

const browser = session.browser.name.toLowerCase();
const { dir, newName } = extractTestPartsFromName(payload.name);
const testPath = dirname(session.testFile).replace(rootDir, '');
const newPath = join(rootDir, PATHS.VDIFF_ROOT, testPath, dir);
const goldenFileName = `${join(newPath, PATHS.GOLDEN, browser, newName)}.png`;
const passFileName = `${join(newPath, PATHS.PASS, browser, newName)}.png`;
const screenshotFile = join(newPath, PATHS.FAIL, browser, newName);
const screenshotFileName = `${screenshotFile}.png`;
3 times per test. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... was thinking we could also refactor this a bit at this point to be like:

if (command === 'brightspace-visual-diff-compare') {
  doCompare();
} else if (command === 'brightspace-visual-diff-compare-resize') {
  doCompareResize();
}

At the very least that'd reduce a lot of the nesting, and would also let us move things out into separate files if that made sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I started doing that and then there were way too many things I was passing around as parameters without knowing what you'd need and where, so I figured that might be better once the skeleton of the report stuff is in

@@ -0,0 +1 @@
import '../element.vdiff.js';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found having this second nested test file was really helpful for all the file manipulation testing, so figured I'd just add it. Could also help with the deleting/creating/rolling up reports testing!

{ name: 'slimer-taller', action: elem => {
elem.style.width = '200px';
elem.style.height = '70px';
elem.style.textAlign = 'end';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these text-align changes make these last two tests have interesting best positions chosen

Copy link
Member

@dlockhart dlockhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! I think we'll want to refactor it at some point, but that can wait.

The tricky part is that we need to add extra data for the report before each of the return statements, which happen all over the place haha.

expect.fail(message);
let result = await executeServerCommand('brightspace-visual-diff-compare', { name, rect, opts });
if (result.resizeRequired) {
this.test.timeout(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not worried about it -- if we run into these cases we should be able to handle them separately anyway.

{ name: 'right', coord: newSize.width - original.width }
].forEach(x => { // TODO: position added for reports, remove/adjust as needed
if (original.width === newSize.width && original.height === newSize.height) {
resizedPNGs.push({ png: original, position: `${y.name}-${x.name}` });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the reports are going to need it, since they're going to use the resized images anyway which will always both be the same size.

const screenshotFileName = `${screenshotFile}.png`;

if (command === 'brightspace-visual-diff-compare') {
if (!isCI) { // CI will be a fresh .vdiff folder each time and only one run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... was thinking we could also refactor this a bit at this point to be like:

if (command === 'brightspace-visual-diff-compare') {
  doCompare();
} else if (command === 'brightspace-visual-diff-compare-resize') {
  doCompareResize();
}

At the very least that'd reduce a lot of the nesting, and would also let us move things out into separate files if that made sense.

if (screenshotImage.width === goldenImage.width && screenshotImage.height === goldenImage.height) {
const diff = new PNG({ width: screenshotImage.width, height: screenshotImage.height });
const pixelsDiff = pixelmatch(
screenshotImage.data, goldenImage.data, diff.data, screenshotImage.width, screenshotImage.height, { threshold: DEFAULT_TOLERANCE }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pass the diffMask option so that they're transparent? I've added it my branch but it keeps getting conflicts/blown away each time I rebase.

@svanherk svanherk merged commit 7af4701 into main Jun 29, 2023
1 check passed
@svanherk svanherk deleted the US152247_US152272_Add_pixelmatch branch June 29, 2023 19:15
@ghost
Copy link

ghost commented Jun 29, 2023

🎉 This PR is included in version 0.13.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@ghost ghost added the released label Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants