Skip to content

[INT-20] Additional CLI options for PDF upload #195

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

Merged
merged 5 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions visual-js/visual-snapshots/src/api/visual-snapshots-api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { BuildStatus, DiffingMethod, VisualApi } from "@saucelabs/visual";
import { formatString } from "../utils/format.js";

export interface CreateVisualSnapshotsParams {
branch: string;
buildName: string;
defaultBranch: string;
project: string;
customId: string;
buildId: string;
buildId?: string;
suiteName?: string;
testName?: string;
snapshotName?: string;
}

export class VisualSnapshotsApi {
Expand All @@ -17,17 +21,30 @@
}

public async generateAndSendPdfFileSnapshots(
filename: string,
pdfFilePages: AsyncGenerator<Buffer>,
params: CreateVisualSnapshotsParams
) {
const buildId = await this.createBuild(params);
const buildId = params.buildId ?? (await this.createBuild(params));
const testName = params.testName
? formatString(params.testName, { filename })
: undefined;

const snapshotFormat = this.getSnapshotFormat(params.snapshotName);

let pageNumber = 1;
for await (const pdfPageImage of pdfFilePages) {
const snapshotName = formatString(snapshotFormat, {
filename,
page: pageNumber,
});

await this.uploadImageAndCreateSnapshot(
pdfPageImage,
buildId,
`page-${pageNumber}`
snapshotName,
testName,
params.suiteName
);
pageNumber++;
}
Expand All @@ -52,7 +69,9 @@
private async uploadImageAndCreateSnapshot(
snapshot: Buffer,
buildId: string,
snapshotName: string
snapshotName: string,
testName?: string,
suiteName?: string
) {
const uploadId = await this.api.uploadSnapshot({
buildId,
Expand All @@ -66,6 +85,8 @@
uploadId,
name: snapshotName,
diffingMethod: DiffingMethod.Balanced,
testName,
suiteName,
});

console.info(`Created a snapshot ${snapshotName} for build ${buildId}.`);
Expand All @@ -77,7 +98,7 @@
});
console.info(`Build ${buildId} finished.`);

const buildStatus = (await this.api.buildStatus(buildId))!;

Check warning on line 101 in visual-js/visual-snapshots/src/api/visual-snapshots-api.ts

View workflow job for this annotation

GitHub Actions / build

Forbidden non-null assertion
if (
[BuildStatus.Running, BuildStatus.Queued].includes(buildStatus.status)
) {
Expand All @@ -90,4 +111,17 @@
);
}
}

private getSnapshotFormat(format: string | undefined) {
if (!format) {
return `page-{page}`;
}

// Page number is always required to make the snapshot names unique
if (!format.includes("{page}")) {
format = format += "-{page}";
}

return format;
}
}
2 changes: 2 additions & 0 deletions visual-js/visual-snapshots/src/app/pdf-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { initializeVisualApi } from "../api/visual-client.js";
import { PdfConverter } from "./pdf-converter.js";
import { VisualConfig } from "@saucelabs/visual";
import path from "path";

export interface PdfCommandParams
extends VisualConfig,
Expand All @@ -24,6 +25,7 @@ export class PdfCommandHandler {

const pdfPageImages = pdfConverter.convertPagesToImages(pdfFilePath);
await visualSnapshots.generateAndSendPdfFileSnapshots(
path.basename(pdfFilePath),
pdfPageImages,
params
);
Expand Down
10 changes: 9 additions & 1 deletion visual-js/visual-snapshots/src/commands/options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Option } from "commander";
import { EOL } from "os";
import { parseUuid } from "./validate.js";

export const usernameOption = new Option(
"-u, --user <user>",
Expand Down Expand Up @@ -63,11 +64,18 @@ export const buildIdOption = new Option(
"By default, this is not set and we create / finish a build during setup / teardown." +
EOL +
"If not provided, SAUCE_VISUAL_BUILD_ID environment variable will be used."
).env("SAUCE_VISUAL_BUILD_ID");
)
.env("SAUCE_VISUAL_BUILD_ID")
.argParser(parseUuid);

export const customIdOption = new Option(
"--custom-id <custom-id>",
"For advanced users, a user-supplied custom ID to identify this build. Can be used in CI to identify / check / re-check the status of a single build. Usage suggestions: CI pipeline ID." +
EOL +
"If not provided, SAUCE_VISUAL_CUSTOM_ID environment variable will be used."
).env("SAUCE_VISUAL_CUSTOM_ID");

export const suiteNameOption = new Option(
"--suite-name <suite-name>",
"The name of the suite you would like to appear in the Sauce Visual dashboard."
);
21 changes: 20 additions & 1 deletion visual-js/visual-snapshots/src/commands/pdf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Command } from "commander";
import { Command, Option } from "commander";
import {
accessKeyOption,
branchOption,
Expand All @@ -8,9 +8,25 @@ import {
defaultBranchOption,
projectOption,
regionOption,
suiteNameOption,
usernameOption,
} from "./options.js";
import { PdfCommandHandler, PdfCommandParams } from "../app/pdf-handler.js";
import { EOL } from "os";

export const testNameOption = new Option(
"--test-name <test-name>",
"The name of the test you would like to appear in the Sauce Visual dashboard." +
EOL +
"Supports the following parameters: {filename}"
);

export const snapshotNameOption = new Option(
"--snapshot-name <snapshot-name>",
"The name of the snapshot you would like to appear in the Sauce Visual dashboard." +
EOL +
" Supports the following parameters: {filename}, {page}"
);

export const pdfCommand = (clientVersion: string) => {
return new Command()
Expand All @@ -26,6 +42,9 @@ export const pdfCommand = (clientVersion: string) => {
.addOption(projectOption)
.addOption(buildIdOption)
.addOption(customIdOption)
.addOption(suiteNameOption)
.addOption(testNameOption)
.addOption(snapshotNameOption)
.action((pdfFilePath: string, params: PdfCommandParams) => {
new PdfCommandHandler(clientVersion)
.handle(pdfFilePath, params)
Expand Down
25 changes: 25 additions & 0 deletions visual-js/visual-snapshots/src/commands/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { InvalidArgumentError } from "commander";

const UUID_REGEX =
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
const DASHLESS_UUID_REGEX = /^[a-f0-9]{32}$/i;

export function parseUuid(input: string) {
if (UUID_REGEX.test(input)) {
return input;
}

if (DASHLESS_UUID_REGEX.test(input)) {
return (
`${input.substring(0, 8)}-` +
`${input.substring(8, 12)}-` +
`${input.substring(12, 16)}-` +
`${input.substring(16, 20)}-` +
`${input.substring(20, 32)}`
);
}

throw new InvalidArgumentError(
"Expected UUID in form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx or 32 hexadecimal characters."
);
}
13 changes: 13 additions & 0 deletions visual-js/visual-snapshots/src/utils/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Replaces all occurrences of keys in format of `{key}` in `value` with `data[key]`.
*
* If `key` does not exist in data, it is left as it is.
*/
export function formatString(
value: string,
data: Record<string, string | number>
) {
return Object.entries(data)
.map(([k, v]) => [k, v.toString()] as const)
.reduce((current, [k, v]) => current.replaceAll(`{${k}}`, v), value);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params and build-id difffing finished 1`] = `
[
[
"Uploaded image to build custom-build-id: upload id=upload-id-0.",
],
[
"Created a snapshot custom-snapshot-name-filename.pdf-1 for build custom-build-id.",
],
[
"Uploaded image to build custom-build-id: upload id=upload-id-1.",
],
[
"Created a snapshot custom-snapshot-name-filename.pdf-2 for build custom-build-id.",
],
[
"Build custom-build-id finished.",
],
[
"Build custom-build-id finished (status=APPROVED, unapprovedCount=0, errorCount=0).",
],
]
`;

exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params and build-id difffing unfinished 1`] = `
[
[
"Uploaded image to build custom-build-id: upload id=upload-id-0.",
],
[
"Created a snapshot custom-snapshot-name-filename.pdf-1 for build custom-build-id.",
],
[
"Uploaded image to build custom-build-id: upload id=upload-id-1.",
],
[
"Created a snapshot custom-snapshot-name-filename.pdf-2 for build custom-build-id.",
],
[
"Build custom-build-id finished.",
],
[
"Build custom-build-id finished but snapshots haven't been compared yet. Check the build status in a few moments.",
],
]
`;

exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing finished 1`] = `
[
[
Expand All @@ -9,13 +55,13 @@ exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing fi
"Uploaded image to build build-id: upload id=upload-id-0.",
],
[
"Created a snapshot page-1 for build build-id.",
"Created a snapshot custom-snapshot-name-filename.pdf-1 for build build-id.",
],
[
"Uploaded image to build build-id: upload id=upload-id-1.",
],
[
"Created a snapshot page-2 for build build-id.",
"Created a snapshot custom-snapshot-name-filename.pdf-2 for build build-id.",
],
[
"Build build-id finished.",
Expand All @@ -35,13 +81,13 @@ exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing un
"Uploaded image to build build-id: upload id=upload-id-0.",
],
[
"Created a snapshot page-1 for build build-id.",
"Created a snapshot custom-snapshot-name-filename.pdf-1 for build build-id.",
],
[
"Uploaded image to build build-id: upload id=upload-id-1.",
],
[
"Created a snapshot page-2 for build build-id.",
"Created a snapshot custom-snapshot-name-filename.pdf-2 for build build-id.",
],
[
"Build build-id finished.",
Expand Down
Loading