Skip to content

Commit d8feea2

Browse files
[INT-18] send pdf snapshots to Visual (#191)
- convert pdf file into images (one image per page) - create a build - create a snapshot for each pdf page image - finish the build
1 parent bd7ba3a commit d8feea2

13 files changed

+3126
-129
lines changed

Diff for: visual-js/visual-snapshots/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

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

5+
## Requirements
6+
7+
```sh
8+
node >= 18
9+
```
10+
511
## Installation
612

713
```sh
@@ -27,3 +33,8 @@ Run tests:
2733
```sh
2834
npm run test
2935
```
36+
37+
## Reusing pdf conversion code
38+
39+
While it is possible to use `VisualSnapshotsApi` outside this package, please bear in mind it can only be used with ESM modules.
40+
CommonJS modules are not supported.

Diff for: visual-js/visual-snapshots/package-lock.json

+2,659-118
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: visual-js/visual-snapshots/package.json

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
"name": "@saucelabs/visual-snapshots",
33
"description": "CLI which generates Visual snapshots from a data source such as pdf",
44
"version": "0.1.0",
5-
"main": "lib/index.js",
5+
"main": "./lib/src/index.js",
66
"license": "MIT",
7-
"bin": "./lib/index.js",
7+
"bin": "./lib/src/index.js",
88
"files": [
99
"lib",
1010
"README.md"
1111
],
1212
"type": "module",
1313
"engines": {
14-
"node": "^16.13 || >=18"
14+
"node": ">=18"
1515
},
1616
"keywords": [
1717
"saucelabs",
@@ -24,10 +24,14 @@
2424
"watch": "tsc-watch --declaration -p .",
2525
"lint": "eslint \"{src,test}/**/*.ts\"",
2626
"lint-fix": "eslint \"{src,test}/**/*.ts\" --fix",
27-
"test": "jest"
27+
"test": "jest",
28+
"test-update-snapshots": "jest -u",
29+
"test-with-coverage": "jest --collect-coverage"
2830
},
2931
"dependencies": {
30-
"commander": "^12.0.0"
32+
"@saucelabs/visual": "^0.13.0",
33+
"commander": "^12.0.0",
34+
"pdf-to-img": "~4.4.0"
3135
},
3236
"devDependencies": {
3337
"@types/jest": "29.5.14",

Diff for: visual-js/visual-snapshots/src/api/visual-client.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getApi, VisualConfig } from "@saucelabs/visual";
2+
3+
const clientVersion = "PKG_VERSION";
4+
5+
export const initializeVisualApi = (params: VisualConfig) =>
6+
getApi(params, {
7+
userAgent: `visual-snapshots/${clientVersion}`,
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { BuildStatus, DiffingMethod, VisualApi } from "@saucelabs/visual";
2+
3+
export interface CreateVisualSnapshotsParams {
4+
branch: string;
5+
buildName: string;
6+
defaultBranch: string;
7+
project: string;
8+
customId: string;
9+
buildId: string;
10+
}
11+
12+
export class VisualSnapshotsApi {
13+
private api: VisualApi;
14+
15+
constructor(api: VisualApi) {
16+
this.api = api;
17+
}
18+
19+
public async generateAndSendPdfFileSnapshots(
20+
pdfFilePages: AsyncGenerator<Buffer>,
21+
params: CreateVisualSnapshotsParams,
22+
) {
23+
const buildId = await this.createBuild(params);
24+
25+
let pageNumber = 1;
26+
for await (const pdfPageImage of pdfFilePages) {
27+
await this.uploadImageAndCreateSnapshot(
28+
pdfPageImage,
29+
buildId,
30+
`page-${pageNumber}`,
31+
);
32+
pageNumber++;
33+
}
34+
35+
await this.finishBuild(buildId);
36+
}
37+
38+
private async createBuild(
39+
params: CreateVisualSnapshotsParams,
40+
): Promise<string> {
41+
const build = await this.api.createBuild({
42+
name: params.buildName,
43+
branch: params.branch,
44+
defaultBranch: params.defaultBranch,
45+
project: params.project,
46+
customId: params.customId,
47+
});
48+
console.info(`Build ${build.id} created: ${build.url}`);
49+
return build.id;
50+
}
51+
52+
private async uploadImageAndCreateSnapshot(
53+
snapshot: Buffer,
54+
buildId: string,
55+
snapshotName: string,
56+
) {
57+
const uploadId = await this.api.uploadSnapshot({
58+
buildId,
59+
image: { data: snapshot },
60+
});
61+
62+
console.info(`Uploaded image to build ${buildId}: upload id=${uploadId}.`);
63+
64+
await this.api.createSnapshot({
65+
buildId,
66+
uploadId,
67+
name: snapshotName,
68+
diffingMethod: DiffingMethod.Balanced,
69+
});
70+
71+
console.info(`Created a snapshot ${snapshotName} for build ${buildId}.`);
72+
}
73+
74+
private async finishBuild(buildId: string) {
75+
await this.api.finishBuild({
76+
uuid: buildId,
77+
});
78+
console.info(`Build ${buildId} finished.`);
79+
80+
const buildStatus = (await this.api.buildStatus(buildId))!;
81+
if (
82+
[BuildStatus.Running, BuildStatus.Queued].includes(buildStatus.status)
83+
) {
84+
console.info(
85+
`Build ${buildId} finished but snapshots haven't been compared yet. Check the build status in a few moments.`,
86+
);
87+
} else {
88+
console.info(
89+
`Build ${buildId} finished (status=${buildStatus.status}, unapprovedCount=${buildStatus.unapprovedCount}, errorCount=${buildStatus.errorCount}).`,
90+
);
91+
}
92+
}
93+
}

Diff for: visual-js/visual-snapshots/src/app/pdf-converter.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { pdf } from "pdf-to-img";
2+
3+
export class PdfConverter {
4+
public async *convertPagesToImages(
5+
pdfFilePath: string,
6+
): AsyncGenerator<Buffer> {
7+
for await (const pdfPageImage of await pdf(pdfFilePath, { scale: 1 })) {
8+
yield pdfPageImage;
9+
}
10+
}
11+
}

Diff for: visual-js/visual-snapshots/src/app/pdf-handler.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
CreateVisualSnapshotsParams,
3+
VisualSnapshotsApi,
4+
} from "../api/visual-snapshots-api.js";
5+
import { initializeVisualApi } from "../api/visual-client.js";
6+
import { PdfConverter } from "./pdf-converter.js";
7+
import { VisualConfig } from "@saucelabs/visual";
8+
9+
export interface PdfCommandParams
10+
extends VisualConfig,
11+
CreateVisualSnapshotsParams {}
12+
13+
export class PdfCommandHandler {
14+
public async handle(pdfFilePath: string, params: PdfCommandParams) {
15+
const visualApi = initializeVisualApi(params);
16+
const visualSnapshots = new VisualSnapshotsApi(visualApi);
17+
const pdfConverter = new PdfConverter();
18+
19+
const pdfPageImages = pdfConverter.convertPagesToImages(pdfFilePath);
20+
await visualSnapshots.generateAndSendPdfFileSnapshots(
21+
pdfPageImages,
22+
params,
23+
);
24+
}
25+
}

Diff for: visual-js/visual-snapshots/src/commands/options.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Option } from "commander";
22
import { EOL } from "os";
33

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

1313
export const accessKeyOption = new Option(
14-
"-k, --access-key <access-key>",
14+
"-k, --key <key>",
1515
"Your Sauce Labs access key. You can get this from the header of app.saucelabs.com" +
1616
EOL +
1717
"If not provided, SAUCE_ACCESS_KEY environment variable will be used.",

Diff for: visual-js/visual-snapshots/src/commands/pdf.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
regionOption,
1111
usernameOption,
1212
} from "./options.js";
13+
import { PdfCommandHandler, PdfCommandParams } from "../app/pdf-handler.js";
1314

1415
export const pdfCommand = () => {
1516
return new Command()
@@ -25,9 +26,14 @@ export const pdfCommand = () => {
2526
.addOption(projectOption)
2627
.addOption(buildIdOption)
2728
.addOption(customIdOption)
28-
.action((pdfFilePath: string, options: Record<string, string>) => {
29-
console.info(
30-
`Create snapshots of a pdf file: '${pdfFilePath}' with options: ${Object.entries(options)}`,
31-
);
29+
.action((pdfFilePath: string, params: PdfCommandParams) => {
30+
new PdfCommandHandler()
31+
.handle(pdfFilePath, params)
32+
.then(() => {
33+
console.log("Successfully created PDF snapshots");
34+
})
35+
.catch((err) => {
36+
console.error(`An error occured when creating PDF snapshots: ${err}`);
37+
});
3238
});
3339
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing finished 1`] = `
4+
[
5+
[
6+
"Build build-id created: http://build-url/build-id",
7+
],
8+
[
9+
"Uploaded image to build build-id: upload id=upload-id-0.",
10+
],
11+
[
12+
"Created a snapshot page-1 for build build-id.",
13+
],
14+
[
15+
"Uploaded image to build build-id: upload id=upload-id-1.",
16+
],
17+
[
18+
"Created a snapshot page-2 for build build-id.",
19+
],
20+
[
21+
"Build build-id finished.",
22+
],
23+
[
24+
"Build build-id finished (status=APPROVED, unapprovedCount=0, errorCount=0).",
25+
],
26+
]
27+
`;
28+
29+
exports[`VisualSnapshots generateAndSendPdfFileSnapshots with params difffing unfinished 1`] = `
30+
[
31+
[
32+
"Build build-id created: http://build-url/build-id",
33+
],
34+
[
35+
"Uploaded image to build build-id: upload id=upload-id-0.",
36+
],
37+
[
38+
"Created a snapshot page-1 for build build-id.",
39+
],
40+
[
41+
"Uploaded image to build build-id: upload id=upload-id-1.",
42+
],
43+
[
44+
"Created a snapshot page-2 for build build-id.",
45+
],
46+
[
47+
"Build build-id finished.",
48+
],
49+
[
50+
"Build build-id finished but snapshots haven't been compared yet. Check the build status in a few moments.",
51+
],
52+
]
53+
`;
54+
55+
exports[`VisualSnapshots generateAndSendPdfFileSnapshots without params 1`] = `
56+
[
57+
[
58+
"Build build-id created: http://build-url/build-id",
59+
],
60+
[
61+
"Uploaded image to build build-id: upload id=upload-id-0.",
62+
],
63+
[
64+
"Created a snapshot page-1 for build build-id.",
65+
],
66+
[
67+
"Uploaded image to build build-id: upload id=upload-id-1.",
68+
],
69+
[
70+
"Created a snapshot page-2 for build build-id.",
71+
],
72+
[
73+
"Build build-id finished.",
74+
],
75+
[
76+
"Build build-id finished (status=UNAPPROVED, unapprovedCount=2, errorCount=0).",
77+
],
78+
]
79+
`;

0 commit comments

Comments
 (0)