Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
- name: Run Playwright tests
run: npm run test:e2e

- name: Check README screenshot
run: npm run screenshots:check

- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A Vue 3 component for rendering PDFs with draggable and resizable element overla

**[Demo](https://libresign.github.io/pdf-elements/)** · [Examples](examples/)

<img width="754" height="607" alt="image" src="https://github.com/user-attachments/assets/65009896-21ab-4ec5-9548-2707f8d16cdf" />
![The pdf-elements demo with a sample PDF loaded and a signature element placed on the first page](img/screenshot/demo.png)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you regenerate this screenshot after #107 is merged?

The current image still has the invisible action icon above the signature. Since this image will be shown in the README, I think we should avoid committing that visual issue in the final screenshot.


## Development

Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ path = [
"examples/**",
"tests/**",
"dist/**",
"img/**",
]
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 LibreCode coop and contributors"
Expand Down
Binary file added img/screenshot/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 42 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"screenshots:update": "node scripts/generate-screenshots.mjs",
"screenshots:check": "node scripts/check-screenshots.mjs",
"lint": "eslint . --ext .vue,.ts,.js --max-warnings=0",
"lint:fix": "eslint . --ext .vue,.ts,.js --fix"
},
Expand All @@ -71,6 +73,8 @@
"eslint-plugin-vue": "^10.8.0",
"globals": "^17.4.0",
"happy-dom": "^20.7.0",
"pixelmatch": "^7.2.0",
"pngjs": "^7.0.0",
"postcss": "^8.5.6",
"rollup-plugin-visualizer": "^7.0.0",
"typescript": "^6.0.2",
Expand Down
60 changes: 60 additions & 0 deletions scripts/check-screenshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

import { mkdir, readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import pixelmatch from 'pixelmatch'
import { PNG } from 'pngjs'
import { generateScreenshot, packageRoot, screenshotPath } from './generate-screenshots.mjs'


const maxDiffRatio = 0.001

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use a smaller viewport here, closer to the screenshot that we have in the README today?

With 1280x900 there is a lot of empty space on the sides and at the bottom. A smaller viewport would keep the screenshot more focused on the PDF and the elements.


const diffPath = path.join(packageRoot, 'test-results', 'demo-screenshot-diff.png')
const currentPath = path.join(packageRoot, 'test-results', 'demo-screenshot-current.png')

async function main() {
const committed = PNG.sync.read(await readFile(screenshotPath))
const currentImage = await generateScreenshot()
const current = PNG.sync.read(currentImage)

if (committed.width !== current.width || committed.height !== current.height) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we also save the generated screenshot when the image dimensions are different?

Right now this throws before demo-screenshot-current.png is written to test-results/. A viewport change can cause exactly this case, so the generated image would not be available in the CI artifacts when we need to inspect it.

throw new Error(
`Screenshot size changed: committed ${committed.width}x${committed.height}, ` +
`generated ${current.width}x${current.height}. ` +
'Run "npm run screenshots:update" and commit the result.'
)
}

const {width, height} = committed
const diff = new PNG({width, height})
const changedPixels = pixelmatch(committed.data, current.data, diff.data, width, height, {
threshold: 0.1,
})

const totalPixels = width * height
const ratio = changedPixels / totalPixels
const report = `${changedPixels} of ${totalPixels} pixels differ (${(ratio * 100).toFixed(4)}%)`

if (ratio <= maxDiffRatio) {
globalThis.console.log(`Screenshot is up to date: ${report}.`)
return
}

await mkdir(path.dirname(diffPath), {recursive: true})
await writeFile(currentPath, currentImage)
await writeFile(diffPath, PNG.sync.write(diff))
Comment thread
vitormattos marked this conversation as resolved.

throw new Error(
`${report}, above the allowed ${(maxDiffRatio * 100).toFixed(4)}%.\n` +
`Generated screenshot written to ${path.relative(packageRoot, currentPath)}.\n` +
`Visual diff written to ${path.relative(packageRoot, diffPath)}.\n` +
'If the change is expected, run "npm run screenshots:update" and commit the result.'
)
}

main().catch((error) => {
globalThis.console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
})
Loading
Loading