Skip to content

Commit 2d31ac8

Browse files
authored
fix(action): delete S3 images for hash when visual tests pass on retry (#759)
1 parent 8758403 commit 2d31ac8

7 files changed

Lines changed: 147 additions & 13 deletions

File tree

action/dist/main.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81447,6 +81447,9 @@ function putObject(input) {
8144781447
function copyObject(input) {
8144881448
return s3Client.send(new import_client_s3.CopyObjectCommand(input));
8144981449
}
81450+
function deleteObjects(input) {
81451+
return s3Client.send(new import_client_s3.DeleteObjectsCommand(input));
81452+
}
8145081453
function encodeS3CopySource(bucket, key) {
8145181454
return `${bucket}/${key.split("/").map(encodeURIComponent).join("/")}`;
8145281455
}
@@ -156282,6 +156285,26 @@ var uploadOriginalNewImages = async (hash) => {
156282156285
}
156283156286
return uploadOriginalNewPngs(screenshotsDirectory, bucketName, `${ORIGINAL_NEW_IMAGES_DIRECTORY}/${hash}/`);
156284156287
};
156288+
var deleteHashImages = async (hash) => {
156289+
const bucketName = getInput("bucket-name", { required: true });
156290+
const [newImageKeys, originalImageKeys] = await Promise.all([
156291+
getKeysFromS3(NEW_IMAGES_DIRECTORY, hash, bucketName),
156292+
getKeysFromS3(ORIGINAL_NEW_IMAGES_DIRECTORY, hash, bucketName)
156293+
]);
156294+
const keysToDelete = [...newImageKeys, ...originalImageKeys];
156295+
if (!keysToDelete.length) {
156296+
info(`No images found in S3 for hash ${hash}. Skipping deletion.`);
156297+
return;
156298+
}
156299+
await deleteObjects({
156300+
Bucket: bucketName,
156301+
Delete: {
156302+
Objects: keysToDelete.map((Key) => ({ Key })),
156303+
Quiet: true
156304+
}
156305+
});
156306+
info(`Deleted ${keysToDelete.length} image(s) for ${hash}`);
156307+
};
156285156308

156286156309
// ../node_modules/@actions/github/lib/context.js
156287156310
import { readFileSync, existsSync as existsSync2 } from "fs";
@@ -160257,8 +160280,12 @@ var run = async () => {
160257160280
}
160258160281
const latestVisualRegressionStatus = commitHash ? await getLatestVisualRegressionStatus(commitHash) : null;
160259160282
const isRetry = context2.runAttempt > 1;
160260-
if (diffFileCount === 0 && newFileCount === 0) {
160283+
const testsPassed = diffFileCount === 0 && newFileCount === 0;
160284+
if (testsPassed) {
160261160285
info("All visual tests passed, and no diffs found!");
160286+
if (isRetry) {
160287+
await deleteHashImages(hash);
160288+
}
160262160289
if (!commitHash)
160263160290
return;
160264160291
if (isRetry) {
@@ -160323,5 +160350,5 @@ var run = async () => {
160323160350
// src/main.ts
160324160351
run();
160325160352

160326-
//# debugId=0149D6459182F2B064756E2164756E21
160353+
//# debugId=47BE8D1DB8D72B8E64756E2164756E21
160327160354
//# sourceMappingURL=main.js.map

action/dist/main.js.map

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

action/src/run.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
warning
88
} from '@actions/core';
99
import {
10+
deleteHashImages,
1011
downloadBaseImages,
1112
uploadAllImages,
1213
uploadOriginalNewImages
@@ -127,9 +128,14 @@ export const run = async () => {
127128

128129
const isRetry = context.runAttempt > 1;
129130

130-
if (diffFileCount === 0 && newFileCount === 0) {
131+
const testsPassed = diffFileCount === 0 && newFileCount === 0;
132+
if (testsPassed) {
131133
info('All visual tests passed, and no diffs found!');
132134

135+
if (isRetry) {
136+
await deleteHashImages(hash);
137+
}
138+
133139
if (!commitHash) return;
134140
if (isRetry) {
135141
warning(

action/src/s3-operations.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import {
66
NEW_IMAGE_NAME,
77
ORIGINAL_NEW_IMAGES_DIRECTORY
88
} from 'shared/constants';
9-
import { getObject, listAllObjects, listObjects, putObject } from 'shared/s3';
9+
import {
10+
deleteObjects,
11+
getKeysFromS3,
12+
getObject,
13+
listAllObjects,
14+
listObjects,
15+
putObject
16+
} from 'shared/s3';
1017
import { map } from 'bluebird';
1118
import * as path from 'path';
1219
import * as fs from 'fs';
@@ -242,6 +249,32 @@ export const uploadOriginalNewImages = async (hash: string) => {
242249
);
243250
};
244251

252+
export const deleteHashImages = async (hash: string) => {
253+
const bucketName = getInput('bucket-name', { required: true });
254+
255+
const [newImageKeys, originalImageKeys] = await Promise.all([
256+
getKeysFromS3(NEW_IMAGES_DIRECTORY, hash, bucketName),
257+
getKeysFromS3(ORIGINAL_NEW_IMAGES_DIRECTORY, hash, bucketName)
258+
]);
259+
260+
const keysToDelete = [...newImageKeys, ...originalImageKeys];
261+
262+
if (!keysToDelete.length) {
263+
info(`No images found in S3 for hash ${hash}. Skipping deletion.`);
264+
return;
265+
}
266+
267+
await deleteObjects({
268+
Bucket: bucketName,
269+
Delete: {
270+
Objects: keysToDelete.map(Key => ({ Key })),
271+
Quiet: true
272+
}
273+
});
274+
275+
info(`Deleted ${keysToDelete.length} image(s) for ${hash}`);
276+
};
277+
245278
export const uploadBaseImages = async (newFilePaths: string[]) => {
246279
info(`Uploading ${newFilePaths.length} base image(s)`);
247280
return map(newFilePaths, newFilePath =>

action/test/run.test.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ const listObjectsMock = mock();
6363
const getObjectMock = mock();
6464
const putObjectMock = mock();
6565
const copyObjectMock = mock();
66+
const deleteObjectsMock = mock();
67+
const getKeysFromS3Mock = mock();
6668
const updateBaseImagesMock = mock();
6769
async function listAllObjects(
6870
input: { Bucket: string; Prefix: string },
@@ -83,11 +85,12 @@ mock.module('shared/s3', () => ({
8385
s3Client: {},
8486
listObjects: listObjectsMock,
8587
listAllObjects,
86-
getKeysFromS3: mock(),
88+
getKeysFromS3: getKeysFromS3Mock,
8789
updateBaseImages: updateBaseImagesMock,
8890
getObject: getObjectMock,
8991
putObject: putObjectMock,
90-
copyObject: copyObjectMock
92+
copyObject: copyObjectMock,
93+
deleteObjects: deleteObjectsMock
9194
}));
9295

9396
const jimpImageMock = {
@@ -215,6 +218,8 @@ describe('main', () => {
215218
getObjectMock.mockResolvedValue({ Body: null });
216219
putObjectMock.mockResolvedValue({});
217220
copyObjectMock.mockResolvedValue({});
221+
deleteObjectsMock.mockResolvedValue({});
222+
getKeysFromS3Mock.mockResolvedValue([]);
218223
updateBaseImagesMock.mockResolvedValue(undefined);
219224
mkdirMock.mockResolvedValue(undefined);
220225
readFileMock.mockResolvedValue(Buffer.from('image-data'));
@@ -750,6 +755,59 @@ describe('main', () => {
750755
expect(createCommitStatusMock).toHaveBeenCalled();
751756
});
752757

758+
it('should delete S3 images for hash when tests pass on retry', async () => {
759+
githubContext.runAttempt = 2;
760+
execMock.mockResolvedValue(0);
761+
globMock.mockResolvedValue(['path/to/screenshots/base.png']);
762+
getKeysFromS3Mock.mockResolvedValueOnce([
763+
'new-images/sha/component/new.png'
764+
]);
765+
getKeysFromS3Mock.mockResolvedValueOnce([]);
766+
await runAction();
767+
expect(deleteObjectsMock).toHaveBeenCalledWith({
768+
Bucket: 'some-bucket',
769+
Delete: {
770+
Objects: [{ Key: 'new-images/sha/component/new.png' }],
771+
Quiet: true
772+
}
773+
});
774+
});
775+
776+
it('should delete S3 images for hash when tests pass on retry with diff-id input', async () => {
777+
githubContext.runAttempt = 2;
778+
getInputMock.mockImplementation(name => diffIdInputMap[name]);
779+
execMock.mockResolvedValue(0);
780+
globMock.mockResolvedValue(['path/to/screenshots/base.png']);
781+
getKeysFromS3Mock.mockResolvedValueOnce([
782+
'new-images/uniqueId/component/new.png'
783+
]);
784+
getKeysFromS3Mock.mockResolvedValueOnce([]);
785+
await runAction();
786+
expect(deleteObjectsMock).toHaveBeenCalledWith({
787+
Bucket: 'some-bucket',
788+
Delete: {
789+
Objects: [{ Key: 'new-images/uniqueId/component/new.png' }],
790+
Quiet: true
791+
}
792+
});
793+
});
794+
795+
it('should not delete S3 images when tests pass on first attempt', async () => {
796+
execMock.mockResolvedValue(0);
797+
globMock.mockResolvedValue(['path/to/screenshots/base.png']);
798+
await runAction();
799+
expect(deleteObjectsMock).not.toHaveBeenCalled();
800+
});
801+
802+
it('should skip deletion when no images exist in S3 on retry', async () => {
803+
githubContext.runAttempt = 2;
804+
execMock.mockResolvedValue(0);
805+
globMock.mockResolvedValue(['path/to/screenshots/base.png']);
806+
getKeysFromS3Mock.mockResolvedValue([]);
807+
await runAction();
808+
expect(deleteObjectsMock).not.toHaveBeenCalled();
809+
});
810+
753811
it('should call setFailed with the diff message when visual-test-command-fails-on-diff is true', async () => {
754812
execMock.mockResolvedValue(0);
755813
getBooleanInputMock.mockImplementation(name =>

shared/s3.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
PutObjectCommandOutput,
1212
CopyObjectCommand,
1313
CopyObjectCommandInput,
14-
CopyObjectCommandOutput
14+
CopyObjectCommandOutput,
15+
DeleteObjectsCommand,
16+
DeleteObjectsCommandInput,
17+
DeleteObjectsCommandOutput
1518
} from '@aws-sdk/client-s3';
1619
import {
1720
BASE_IMAGES_DIRECTORY,
@@ -61,6 +64,12 @@ export function copyObject(
6164
return s3Client.send(new CopyObjectCommand(input));
6265
}
6366

67+
export function deleteObjects(
68+
input: DeleteObjectsCommandInput
69+
): Promise<DeleteObjectsCommandOutput> {
70+
return s3Client.send(new DeleteObjectsCommand(input));
71+
}
72+
6473
function encodeS3CopySource(bucket: string, key: string): string {
6574
return `${bucket}/${key.split('/').map(encodeURIComponent).join('/')}`;
6675
}

shared/test/s3.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ mock.module('@aws-sdk/client-s3', () => ({
3737
ListObjectsV2Command,
3838
GetObjectCommand: class {},
3939
PutObjectCommand: class {},
40-
CopyObjectCommand
40+
CopyObjectCommand,
41+
DeleteObjectsCommand: class {}
4142
}));
4243

4344
const {

0 commit comments

Comments
 (0)