Skip to content

[INT-18] send pdf snapshots to Visual #191

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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions visual-js/visual-snapshots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This package provides a CLI tool to create Visual snapshots of a provided PDF document.

## Requirements

```sh
node >= 18
```

## Installation

```sh
Expand All @@ -27,3 +33,8 @@ Run tests:
```sh
npm run test
```

## Reusing pdf conversion code

While it is possible to use `VisualSnapshotsApi` outside this package, please bear in mind it can only be used with ESM modules.
CommonJS modules are not supported.
2,777 changes: 2,659 additions & 118 deletions visual-js/visual-snapshots/package-lock.json

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions visual-js/visual-snapshots/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
"name": "@saucelabs/visual-snapshots",
"description": "CLI which generates Visual snapshots from a data source such as pdf",
"version": "0.1.0",
"main": "lib/index.js",
"main": "./lib/src/index.js",
"license": "MIT",
"bin": "./lib/index.js",
"bin": "./lib/src/index.js",
"files": [
"lib",
"README.md"
],
"type": "module",
"engines": {
"node": "^16.13 || >=18"
"node": ">=18"
},
"keywords": [
"saucelabs",
Expand All @@ -24,10 +24,14 @@
"watch": "tsc-watch --declaration -p .",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint-fix": "eslint \"{src,test}/**/*.ts\" --fix",
"test": "jest"
"test": "jest",
"test-update-snapshots": "jest -u",
"test-with-coverage": "jest --collect-coverage"
},
"dependencies": {
"commander": "^12.0.0"
"@saucelabs/visual": "^0.13.0",
"commander": "^12.0.0",
"pdf-to-img": "~4.4.0"
},
"devDependencies": {
"@types/jest": "29.5.14",
Expand Down
8 changes: 8 additions & 0 deletions visual-js/visual-snapshots/src/api/visual-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getApi, VisualConfig } from "@saucelabs/visual";

const clientVersion = "PKG_VERSION";

export const initializeVisualApi = (params: VisualConfig) =>
getApi(params, {
userAgent: `visual-snapshots/${clientVersion}`,
});
93 changes: 93 additions & 0 deletions visual-js/visual-snapshots/src/api/visual-snapshots-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { BuildStatus, DiffingMethod, VisualApi } from "@saucelabs/visual";

export interface CreateVisualSnapshotsParams {
branch: string;
buildName: string;
defaultBranch: string;
project: string;
customId: string;
buildId: string;
}

export class VisualSnapshotsApi {
private api: VisualApi;

constructor(api: VisualApi) {
this.api = api;
}

public async generateAndSendPdfFileSnapshots(
pdfFilePages: AsyncGenerator<Buffer>,
params: CreateVisualSnapshotsParams,
) {
const buildId = await this.createBuild(params);

let pageNumber = 1;
for await (const pdfPageImage of pdfFilePages) {
await this.uploadImageAndCreateSnapshot(
pdfPageImage,
buildId,
`page-${pageNumber}`,
);
pageNumber++;
}

await this.finishBuild(buildId);
}

private async createBuild(
params: CreateVisualSnapshotsParams,
): Promise<string> {
const build = await this.api.createBuild({
name: params.buildName,
branch: params.branch,
defaultBranch: params.defaultBranch,
project: params.project,
customId: params.customId,
});
console.info(`Build ${build.id} created: ${build.url}`);
return build.id;
}

private async uploadImageAndCreateSnapshot(
snapshot: Buffer,
buildId: string,
snapshotName: string,
) {
const uploadId = await this.api.uploadSnapshot({
buildId,
image: { data: snapshot },
});

console.info(`Uploaded image to build ${buildId}: upload id=${uploadId}.`);

await this.api.createSnapshot({
buildId,
uploadId,
name: snapshotName,
diffingMethod: DiffingMethod.Balanced,
});

console.info(`Created a snapshot ${snapshotName} for build ${buildId}.`);
}

private async finishBuild(buildId: string) {
await this.api.finishBuild({
uuid: buildId,
});
console.info(`Build ${buildId} finished.`);

const buildStatus = (await this.api.buildStatus(buildId))!;
if (
[BuildStatus.Running, BuildStatus.Queued].includes(buildStatus.status)
) {
console.info(
`Build ${buildId} finished but snapshots haven't been compared yet. Check the build status in a few moments.`,
);
} else {
console.info(
`Build ${buildId} finished (status=${buildStatus.status}, unapprovedCount=${buildStatus.unapprovedCount}, errorCount=${buildStatus.errorCount}).`,
);
}
}
}
11 changes: 11 additions & 0 deletions visual-js/visual-snapshots/src/app/pdf-converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { pdf } from "pdf-to-img";

export class PdfConverter {
public async *convertPagesToImages(
pdfFilePath: string,
): AsyncGenerator<Buffer> {
for await (const pdfPageImage of await pdf(pdfFilePath, { scale: 1 })) {
yield pdfPageImage;
}
}
}
25 changes: 25 additions & 0 deletions visual-js/visual-snapshots/src/app/pdf-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
CreateVisualSnapshotsParams,
VisualSnapshotsApi,
} from "../api/visual-snapshots-api.js";
import { initializeVisualApi } from "../api/visual-client.js";
import { PdfConverter } from "./pdf-converter.js";
import { VisualConfig } from "@saucelabs/visual";

export interface PdfCommandParams
extends VisualConfig,
CreateVisualSnapshotsParams {}

export class PdfCommandHandler {
public async handle(pdfFilePath: string, params: PdfCommandParams) {
const visualApi = initializeVisualApi(params);
const visualSnapshots = new VisualSnapshotsApi(visualApi);
const pdfConverter = new PdfConverter();

const pdfPageImages = pdfConverter.convertPagesToImages(pdfFilePath);
await visualSnapshots.generateAndSendPdfFileSnapshots(
pdfPageImages,
params,
);
}
}
4 changes: 2 additions & 2 deletions visual-js/visual-snapshots/src/commands/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Option } from "commander";
import { EOL } from "os";

export const usernameOption = new Option(
"-u, --username <username>",
"-u, --user <user>",
"Your Sauce Labs username. You can get this from the header of app.saucelabs.com." +
EOL +
"If not provided, SAUCE_USERNAME environment variable will be used.",
Expand All @@ -11,7 +11,7 @@ export const usernameOption = new Option(
.makeOptionMandatory(true);

export const accessKeyOption = new Option(
"-k, --access-key <access-key>",
"-k, --key <key>",
"Your Sauce Labs access key. You can get this from the header of app.saucelabs.com" +
EOL +
"If not provided, SAUCE_ACCESS_KEY environment variable will be used.",
Expand Down
14 changes: 10 additions & 4 deletions visual-js/visual-snapshots/src/commands/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
regionOption,
usernameOption,
} from "./options.js";
import { PdfCommandHandler, PdfCommandParams } from "../app/pdf-handler.js";

export const pdfCommand = () => {
return new Command()
Expand All @@ -25,9 +26,14 @@ export const pdfCommand = () => {
.addOption(projectOption)
.addOption(buildIdOption)
.addOption(customIdOption)
.action((pdfFilePath: string, options: Record<string, string>) => {
console.info(
`Create snapshots of a pdf file: '${pdfFilePath}' with options: ${Object.entries(options)}`,
);
.action((pdfFilePath: string, params: PdfCommandParams) => {
new PdfCommandHandler()
.handle(pdfFilePath, params)
.then(() => {
console.log("Successfully created PDF snapshots");
})
.catch((err) => {
console.error(`An error occured when creating PDF snapshots: ${err}`);
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing finished 1`] = `
[
[
"Build build-id created: http://build-url/build-id",
],
[
"Uploaded image to build build-id: upload id=upload-id-0.",
],
[
"Created a snapshot page-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.",
],
[
"Build build-id finished.",
],
[
"Build build-id finished (status=APPROVED, unapprovedCount=0, errorCount=0).",
],
]
`;

exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing unfinished 1`] = `
[
[
"Build build-id created: http://build-url/build-id",
],
[
"Uploaded image to build build-id: upload id=upload-id-0.",
],
[
"Created a snapshot page-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.",
],
[
"Build build-id finished.",
],
[
"Build build-id finished but snapshots haven't been compared yet. Check the build status in a few moments.",
],
]
`;

exports[`VisualSnapshots generateAndSendPdfFileSnapshots without params 1`] = `
[
[
"Build build-id created: http://build-url/build-id",
],
[
"Uploaded image to build build-id: upload id=upload-id-0.",
],
[
"Created a snapshot page-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.",
],
[
"Build build-id finished.",
],
[
"Build build-id finished (status=UNAPPROVED, unapprovedCount=2, errorCount=0).",
],
]
`;
Loading