Skip to content
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
26 changes: 26 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,29 @@ jobs:
playwright-report/
test-results/
retention-days: 7

screenshot-container:
runs-on: ubuntu-latest
name: Screenshot check in container
container:
image: mcr.microsoft.com/playwright:v1.63.0-noble
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install dependencies
run: npm ci

- name: Check README screenshot
run: npm run screenshots:check
Comment thread
vitormattos marked this conversation as resolved.

- name: Upload screenshot artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: screenshot-diff
path: test-results/
if-no-files-found: ignore
retention-days: 7
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)
Comment thread
vitormattos marked this conversation as resolved.

## 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
64 changes: 64 additions & 0 deletions scripts/check-screenshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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
Comment thread
vitormattos marked this conversation as resolved.

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) {
Comment thread
vitormattos marked this conversation as resolved.
await mkdir(path.dirname(currentPath), { recursive: true })
await writeFile(currentPath, currentImage)

throw new Error(
`Screenshot size changed: committed ${committed.width}x${committed.height}, ` +
`generated ${current.width}x${current.height}.\n` +
`Generated screenshot written to ${path.relative(packageRoot, currentPath)}.\n` +
'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