Skip to content

Commit f21e826

Browse files
authored
Upgrade Playwright to v1.58.2 and fix JPG background color handling (#249)
This pull request upgrades Playwright from version 1.51.1 to 1.58.2 and fixes a bug where JPG exports always used a white background regardless of the configured background color. The JPG export functionality now properly respects the background color setting, using white as a fallback only when the background is set to transparent. This fix applies to both single QR code downloads and batch exports. A comprehensive end-to-end test has been added to verify that JPG files preserve the correct background color by analyzing pixel data from the exported image.
1 parent 991fe2a commit f21e826

4 files changed

Lines changed: 78 additions & 24 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
},
5454
"devDependencies": {
5555
"@eslint/js": "^9",
56-
"@playwright/test": "^1.51.1",
56+
"@playwright/test": "^1.58.2",
5757
"@tailwindcss/typography": "^0.5.16",
5858
"@types/dom-to-image": "^2.6.7",
5959
"@types/node": "^20.17.23",

pnpm-lock.yaml

Lines changed: 5 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/QRCodeCreate.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,9 @@ function downloadQRImage(format: 'png' | 'svg' | 'jpg') {
521521
jpg: {
522522
fn: downloadJpgElement,
523523
filename: `${sanitizedFilename}.jpg`,
524-
extraOptions: { bgcolor: 'white' }
524+
extraOptions: {
525+
bgcolor: styleBackground.value === 'transparent' ? '#ffffff' : styleBackground.value
526+
}
525527
}
526528
}[format]
527529
@@ -839,7 +841,13 @@ async function generateBatchQRCodes(format: 'png' | 'svg' | 'jpg') {
839841
if (format === 'png') {
840842
dataUrl = await getPngElement(el, getExportDimensions(), styledBorderRadiusFormatted.value)
841843
} else if (format === 'jpg') {
842-
dataUrl = await getJpgElement(el, getExportDimensions(), styledBorderRadiusFormatted.value)
844+
const jpgBgcolor =
845+
styleBackground.value === 'transparent' ? '#ffffff' : styleBackground.value
846+
dataUrl = await getJpgElement(
847+
el,
848+
{ ...getExportDimensions(), bgcolor: jpgBgcolor },
849+
styledBorderRadiusFormatted.value
850+
)
843851
} else {
844852
dataUrl = await getSvgString(el, getExportDimensions(), styledBorderRadiusFormatted.value)
845853
}

tests/e2e/create.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { test, expect, type Page } from '@playwright/test'
22
import fs from 'fs'
33
import path from 'path'
44
import { fileURLToPath } from 'url'
5+
import sharp from 'sharp'
56

67
// Helper for ES module scope
78
const __filename = fileURLToPath(import.meta.url)
@@ -199,6 +200,67 @@ test.describe('QR Code Creation and Management', () => {
199200
})
200201
})
201202

203+
test.describe('JPG background color preservation', () => {
204+
const tempDir = path.join(__dirname, 'temp-jpg-bg')
205+
206+
test.beforeAll(async () => {
207+
if (!fs.existsSync(tempDir)) {
208+
fs.mkdirSync(tempDir, { recursive: true })
209+
}
210+
})
211+
212+
test.afterAll(async () => {
213+
if (fs.existsSync(tempDir)) {
214+
fs.rmSync(tempDir, { recursive: true, force: true })
215+
}
216+
})
217+
218+
test('saved JPG should preserve the background color set on the default preset', async ({
219+
page
220+
}) => {
221+
// The default preset (lyqht) loads automatically with background #697d80.
222+
// Set a vivid red background that is clearly distinct from white so the
223+
// bug (bgcolor always 'white') is easy to detect.
224+
const bgColor = '#ff0000'
225+
await page.locator('#background-color').fill(bgColor)
226+
// Add margin so the top-left corner area is guaranteed background colour.
227+
await page.locator('#margin').fill('20')
228+
await page.waitForTimeout(800)
229+
230+
await openFrameSettings(page)
231+
const showFrameCheckbox = page.locator('#show-frame')
232+
await expect(showFrameCheckbox).toBeVisible()
233+
await showFrameCheckbox.uncheck()
234+
await page.waitForTimeout(500)
235+
236+
const downloadPromise = page.waitForEvent('download')
237+
await page.locator('#download-qr-image-button-jpg').click()
238+
const download = await downloadPromise
239+
240+
expect(download.suggestedFilename()).toMatch(/\.jpg$/i)
241+
242+
const downloadedFilePath = path.join(tempDir, 'bg-color-test.jpg')
243+
await download.saveAs(downloadedFilePath)
244+
expect(fs.existsSync(downloadedFilePath)).toBeTruthy()
245+
246+
// Read the top-left corner pixel (within the margin / background area).
247+
// With margin=20 modules, even a small QR code has several pixels of pure
248+
// background at the very top-left corner.
249+
const { data } = await sharp(downloadedFilePath)
250+
.extract({ left: 5, top: 5, width: 1, height: 1 })
251+
.raw()
252+
.toBuffer({ resolveWithObject: true })
253+
254+
// JPEG is lossy, allow ±20 per channel
255+
const r = data[0]
256+
const g = data[1]
257+
const b = data[2]
258+
expect(r).toBeGreaterThan(200) // red channel should be high (~255)
259+
expect(g).toBeLessThan(80) // green channel should be low (~0)
260+
expect(b).toBeLessThan(80) // blue channel should be low (~0)
261+
})
262+
})
263+
202264
test.describe('Batch Export QR Codes', () => {
203265
const simpleCsvPath = '6_strings_batch.csv' // Assuming it's in public folder
204266

0 commit comments

Comments
 (0)