Add Playwright E2E testing framework (#106)
# Add End-to-End Testing with Playwright This PR adds comprehensive end-to-end testing capabilities using Playwright to ensure the application's core functionality works as expected. ## Key Changes: - Implemented Playwright for automated browser testing - Added test scripts for core functionality: - QR code creation and customization - QR code scanning from file uploads - Config saving and loading - Batch export functionality - Visual regression testing with snapshots ## Testing Details: - Tests verify that: - QR codes can be created with various settings - Generated QR codes can be scanned correctly - Configuration can be saved and loaded - Batch export works with CSV files - UI elements behave correctly (disabled/enabled states) ## Development Notes: - Added `test:e2e` npm script to run the tests - Updated `.gitignore` to exclude Playwright test results - Added detailed testing documentation to CONTRIBUTING.md - Included test fixtures and baseline snapshots for visual regression testing The tests provide a safety net for future development and help ensure the application remains stable as new features are added.
This commit is contained in:
parent
c2e4863efa
commit
9150dd785e
6
.gitignore
vendored
6
.gitignore
vendored
@ -28,4 +28,8 @@ coverage
|
||||
*.sw?
|
||||
dev-dist
|
||||
|
||||
.env
|
||||
.env
|
||||
|
||||
# Playwright
|
||||
test-results/
|
||||
playwright-report/
|
@ -44,6 +44,40 @@ Don't touch the other components in `src/components/` as they are generated by r
|
||||
- Presets are found in `src/utils/presets.ts`. For adding new presets, you can refer to the existing ones and fill in the necessary properties.
|
||||
- Assets are found in `src/assets/`.
|
||||
|
||||
## End-to-End (E2E) Testing
|
||||
|
||||
This project uses [Playwright](https://playwright.dev/) for end-to-end testing.
|
||||
|
||||
**Running Tests:**
|
||||
|
||||
To run all E2E tests, use the following command:
|
||||
|
||||
```bash
|
||||
pnpm test:e2e
|
||||
```
|
||||
|
||||
**Writing Tests:**
|
||||
|
||||
- Tests are located in the `tests/e2e` directory.
|
||||
- Use clear and descriptive test names.
|
||||
- Utilize Playwright's locators (e.g., `getByRole`, `getByLabel`, `getByText`, IDs) for selecting elements robustly.
|
||||
- Avoid relying on brittle selectors like complex CSS paths or auto-generated class names.
|
||||
- Use `test.beforeEach` to set up common preconditions for tests within a file (e.g., navigating to the page, clearing local storage).
|
||||
- Prefer testing file uploads over camera interactions for scanning tests, as camera access can be inconsistent in test environments.
|
||||
- Use snapshot testing (`toHaveScreenshot`) for verifying visual elements like the generated QR code. Remember to commit the baseline snapshot files located in the `tests/e2e/*.spec.ts-snapshots` directory.
|
||||
|
||||
**Debugging Failed Tests:**
|
||||
|
||||
- Playwright automatically generates an HTML report (`playwright-report/index.html`) after each run.
|
||||
- For failed tests, the report includes:
|
||||
- Detailed error messages and stack traces.
|
||||
- Screenshots taken at the point of failure (configured in `playwright.config.ts`).
|
||||
- Trace files (`*.zip` in `test-results/`) which can be viewed with `pnpm exec playwright show-trace <trace-file.zip>` for a step-by-step replay.
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- **Zip File Verification:** While tests verify that batch exports download a zip file, they do not automatically verify the _contents_ (e.g., number of images) of the zip file due to browser sandbox limitations. This requires manual inspection or a separate post-test script if full automation is needed.
|
||||
|
||||
## Adding new presets
|
||||
|
||||
An easy way to add a new preset is to create the QR code on the website first, and then save the config. The config JSON file will look something like this:
|
||||
|
BIN
exported_qr_code.png
Normal file
BIN
exported_qr_code.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
@ -7,6 +7,7 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test:e2e": "playwright test",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore --ignore-pattern \"*.json\"",
|
||||
"format": "prettier --write",
|
||||
@ -46,6 +47,7 @@
|
||||
"vue-i18n": "^9.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@rushstack/eslint-patch": "^1.10.5",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/node": "^20.17.23",
|
||||
|
56
playwright.config.ts
Normal file
56
playwright.config.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://localhost:5173',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
|
||||
/* Capture screenshot only when a test fails. */
|
||||
screenshot: 'only-on-failure'
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] }
|
||||
}
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'pnpm dev',
|
||||
url: 'http://localhost:5173',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe'
|
||||
}
|
||||
})
|
38
pnpm-lock.yaml
generated
38
pnpm-lock.yaml
generated
@ -81,6 +81,9 @@ importers:
|
||||
specifier: ^9.14.2
|
||||
version: 9.14.2(vue@3.5.13(typescript@5.8.2))
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.51.1
|
||||
version: 1.51.1
|
||||
'@rushstack/eslint-patch':
|
||||
specifier: ^1.10.5
|
||||
version: 1.10.5
|
||||
@ -1001,6 +1004,11 @@ packages:
|
||||
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.51.1':
|
||||
resolution: {integrity: sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rollup/plugin-babel@5.3.1':
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@ -1890,6 +1898,11 @@ packages:
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@ -2513,6 +2526,16 @@ packages:
|
||||
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
playwright-core@1.51.1:
|
||||
resolution: {integrity: sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.51.1:
|
||||
resolution: {integrity: sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
possible-typed-array-names@1.1.0:
|
||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -4161,6 +4184,10 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.1.1': {}
|
||||
|
||||
'@playwright/test@1.51.1':
|
||||
dependencies:
|
||||
playwright: 1.51.1
|
||||
|
||||
'@rollup/plugin-babel@5.3.1(@babel/core@7.26.9)(rollup@2.79.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.9
|
||||
@ -5195,6 +5222,9 @@ snapshots:
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@ -5775,6 +5805,14 @@ snapshots:
|
||||
|
||||
pirates@4.0.6: {}
|
||||
|
||||
playwright-core@1.51.1: {}
|
||||
|
||||
playwright@1.51.1:
|
||||
dependencies:
|
||||
playwright-core: 1.51.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
possible-typed-array-names@1.1.0: {}
|
||||
|
||||
postcss-import@15.1.0(postcss@8.5.3):
|
||||
|
24
tests/e2e/app.spec.ts
Normal file
24
tests/e2e/app.spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Mini QR/)
|
||||
})
|
||||
|
||||
test('scans a QR code from file', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
// Switch to Scan mode - select the first matching element (likely the desktop one)
|
||||
await page.getByLabel('Switch to Scan Mode').first().click()
|
||||
|
||||
// Locate the hidden file input
|
||||
const fileInput = page.locator('input[type="file"][accept="image/*"]')
|
||||
|
||||
// Set the input file using the fixture
|
||||
await fileInput.setInputFiles('tests/e2e/fixtures/test-qrcode.png')
|
||||
|
||||
// Wait for the result text to appear
|
||||
await expect(page.getByText('Test QR Data')).toBeVisible()
|
||||
})
|
283
tests/e2e/create.spec.ts
Normal file
283
tests/e2e/create.spec.ts
Normal file
@ -0,0 +1,283 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// Recreate __dirname and __filename for ES Modules
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the page before each test
|
||||
await page.goto('/')
|
||||
|
||||
// Clear localStorage to prevent state from previous tests
|
||||
await page.evaluate(() => localStorage.clear())
|
||||
|
||||
// Reload the page to apply the cleared storage (optional but safer)
|
||||
await page.reload()
|
||||
|
||||
// Make sure we are in Create mode (though it's the default after reload)
|
||||
const createModeButton = page.getByLabel('Switch to Create Mode').first()
|
||||
await expect(createModeButton).toHaveClass(/bg-white/) // Or check another attribute indicating selection
|
||||
|
||||
// Clear the data input field to ensure a clean state
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
await textInput.fill('')
|
||||
await expect(textInput).toHaveValue('') // Verify it's empty
|
||||
})
|
||||
|
||||
test('creates a QR code', async ({ page }) => {
|
||||
// Find the text area and fill it.
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
await textInput.fill('Hello Playwright!')
|
||||
|
||||
// Check if the QR code image is visible using its role and name.
|
||||
const qrCodeImage = page.getByRole('img', { name: 'QR code' })
|
||||
await expect(qrCodeImage).toBeVisible()
|
||||
})
|
||||
|
||||
test('copy and export buttons are disabled when data is empty', async ({ page }) => {
|
||||
// Check initial state (data should be empty)
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
await expect(textInput).toHaveValue('')
|
||||
|
||||
// Check buttons are disabled
|
||||
await expect(page.locator('#copy-qr-image-button')).toBeDisabled()
|
||||
await expect(page.locator('#download-qr-image-button-png')).toBeDisabled()
|
||||
await expect(page.locator('#download-qr-image-button-jpg')).toBeDisabled()
|
||||
await expect(page.locator('#download-qr-image-button-svg')).toBeDisabled()
|
||||
|
||||
// Enter some data
|
||||
await textInput.fill('Test Data')
|
||||
|
||||
// Check buttons are now enabled
|
||||
await expect(page.locator('#copy-qr-image-button')).toBeEnabled()
|
||||
await expect(page.locator('#download-qr-image-button-png')).toBeEnabled()
|
||||
await expect(page.locator('#download-qr-image-button-jpg')).toBeEnabled()
|
||||
await expect(page.locator('#download-qr-image-button-svg')).toBeEnabled()
|
||||
})
|
||||
|
||||
test('save and load QR code config works', async ({ page }) => {
|
||||
const testData = 'Config Test Data'
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
const dotsColorInput = page.locator('#dots-color') // Corrected ID
|
||||
const initialColor = await dotsColorInput.inputValue()
|
||||
const newColor = '#ff0000'
|
||||
|
||||
// Set initial data and change color
|
||||
await textInput.fill(testData)
|
||||
await dotsColorInput.fill(newColor)
|
||||
await expect(dotsColorInput).toHaveValue(newColor)
|
||||
|
||||
// Start waiting for the download before clicking the button
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.locator('#save-qr-code-config-button').click()
|
||||
const download = await downloadPromise
|
||||
|
||||
// Ensure the test-results directory exists
|
||||
const resultsDir = path.resolve(__dirname, '../../test-results')
|
||||
if (!fs.existsSync(resultsDir)) {
|
||||
fs.mkdirSync(resultsDir, { recursive: true })
|
||||
}
|
||||
// Save the downloaded file path
|
||||
const configPath = path.join(resultsDir, download.suggestedFilename())
|
||||
await download.saveAs(configPath)
|
||||
|
||||
// Clear the input and reset color (or reload the page)
|
||||
await textInput.fill('')
|
||||
await dotsColorInput.fill(initialColor) // Reset color to simulate loading
|
||||
await expect(textInput).toHaveValue('')
|
||||
await expect(dotsColorInput).toHaveValue(initialColor)
|
||||
|
||||
// Locate the load button and set the input file
|
||||
const loadConfigButton = page.locator('#load-qr-code-config-button')
|
||||
// Playwright needs a file chooser listener BEFORE the click that triggers it
|
||||
const fileChooserPromise = page.waitForEvent('filechooser')
|
||||
await loadConfigButton.click()
|
||||
const fileChooser = await fileChooserPromise
|
||||
await fileChooser.setFiles(configPath)
|
||||
|
||||
// Verify data and color are restored
|
||||
await expect(textInput).toHaveValue(testData)
|
||||
await expect(dotsColorInput).toHaveValue(newColor)
|
||||
})
|
||||
|
||||
test('save and load QR code config works with frame', async ({ page }) => {
|
||||
const testData = 'Frame Config Test'
|
||||
const frameTextData = 'Scan Me!'
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
const frameAccordionTrigger = page.getByRole('button', { name: /Frame settings/ })
|
||||
const showFrameCheckbox = page.locator('input[id="show-frame"]')
|
||||
const frameTextInput = page.locator('textarea[id="frame-text"]')
|
||||
const framePositionTopRadio = page.locator('input[id="frameTextPosition-top"]')
|
||||
|
||||
// Expand the Frame settings accordion
|
||||
await frameAccordionTrigger.click()
|
||||
|
||||
// Set initial data, enable frame, set text, and change position
|
||||
await textInput.fill(testData)
|
||||
await showFrameCheckbox.check()
|
||||
await frameTextInput.fill(frameTextData)
|
||||
await framePositionTopRadio.check() // Change from default bottom to top
|
||||
|
||||
// Verify initial set state
|
||||
await expect(showFrameCheckbox).toBeChecked()
|
||||
await expect(frameTextInput).toHaveValue(frameTextData)
|
||||
await expect(framePositionTopRadio).toBeChecked()
|
||||
|
||||
// Save config
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.locator('#save-qr-code-config-button').click()
|
||||
const download = await downloadPromise
|
||||
const configPath = './test-results/' + download.suggestedFilename()
|
||||
await download.saveAs(configPath)
|
||||
|
||||
// Clear inputs and reset frame settings
|
||||
await textInput.fill('')
|
||||
await showFrameCheckbox.uncheck()
|
||||
// frameTextInput and framePositionTopRadio might be hidden now, no need to explicitly clear/reset
|
||||
|
||||
// Verify cleared state (frame checkbox is unchecked)
|
||||
await expect(textInput).toHaveValue('')
|
||||
await expect(showFrameCheckbox).not.toBeChecked()
|
||||
|
||||
// Load config
|
||||
const loadConfigButton = page.locator('#load-qr-code-config-button')
|
||||
const fileChooserPromise = page.waitForEvent('filechooser')
|
||||
await loadConfigButton.click()
|
||||
const fileChooser = await fileChooserPromise
|
||||
await fileChooser.setFiles(configPath)
|
||||
|
||||
// Ensure accordion is open again after load if needed (config load might close it)
|
||||
// Click trigger only if checkbox isn't visible after loading
|
||||
if (!(await showFrameCheckbox.isVisible())) {
|
||||
await frameAccordionTrigger.click()
|
||||
}
|
||||
|
||||
// Verify data and frame settings are restored
|
||||
await expect(textInput).toHaveValue(testData)
|
||||
await expect(showFrameCheckbox).toBeChecked()
|
||||
await expect(frameTextInput).toHaveValue(frameTextData)
|
||||
await expect(framePositionTopRadio).toBeChecked() // Check if the non-default position was restored
|
||||
})
|
||||
|
||||
test('QR code element with frame matches snapshot', async ({ page }) => {
|
||||
const testData = 'Frame Snapshot Test'
|
||||
const frameTextData = 'Scan Frame!'
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
const frameAccordionTrigger = page.getByRole('button', { name: /Frame settings/ })
|
||||
const showFrameCheckbox = page.locator('input[id="show-frame"]')
|
||||
const frameTextInput = page.locator('textarea[id="frame-text"]')
|
||||
|
||||
// Expand the Frame settings accordion
|
||||
await frameAccordionTrigger.click()
|
||||
|
||||
// Set data, enable frame, and add frame text
|
||||
await textInput.fill(testData)
|
||||
await showFrameCheckbox.check()
|
||||
await frameTextInput.fill(frameTextData)
|
||||
|
||||
// Locate the element containing the QR code and frame
|
||||
const qrCodeExportElement = page.locator('#element-to-export')
|
||||
|
||||
// Ensure the element is visible before taking a snapshot
|
||||
await expect(qrCodeExportElement).toBeVisible()
|
||||
|
||||
// Take a snapshot of the QR code element with the frame
|
||||
await expect(qrCodeExportElement).toHaveScreenshot('qr-code-with-frame-snapshot.png')
|
||||
})
|
||||
|
||||
test('QR code element matches snapshot', async ({ page }) => {
|
||||
const testData = 'Snapshot Test'
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
await textInput.fill(testData)
|
||||
|
||||
// Locate the element containing the QR code to be exported
|
||||
const qrCodeExportElement = page.locator('#element-to-export')
|
||||
|
||||
// Ensure the element is visible before taking a snapshot
|
||||
await expect(qrCodeExportElement).toBeVisible()
|
||||
|
||||
// Take a snapshot of the QR code element
|
||||
await expect(qrCodeExportElement).toHaveScreenshot('qr-code-snapshot.png')
|
||||
|
||||
// We don't need to test the actual download functionality extensively here,
|
||||
// as the snapshot confirms the visual output. Testing downloads can be flaky.
|
||||
// But we can check if the buttons exist and are clickable.
|
||||
await expect(page.locator('#download-qr-image-button-png')).toBeEnabled()
|
||||
await expect(page.locator('#download-qr-image-button-jpg')).toBeEnabled()
|
||||
})
|
||||
|
||||
test('exported PNG can be scanned', async ({ page }) => {
|
||||
const testData = 'Export-Scan Test'
|
||||
const textInput = page.locator('textarea[id="data"]')
|
||||
await textInput.fill(testData)
|
||||
|
||||
// Wait for QR code to render
|
||||
await expect(page.getByRole('img', { name: 'QR code' })).toBeVisible()
|
||||
|
||||
// Start waiting for download and click PNG export button
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.locator('#download-qr-image-button-png').click()
|
||||
const download = await downloadPromise
|
||||
// Save to a predictable location for inspection
|
||||
const exportedPngPath = './exported_qr_code.png'
|
||||
await download.saveAs(exportedPngPath)
|
||||
|
||||
// Switch to Scan mode
|
||||
await page.getByLabel('Switch to Scan Mode').first().click()
|
||||
|
||||
// Locate the hidden file input for scanning
|
||||
const scanFileInput = page.locator('input[type="file"][accept="image/*"]')
|
||||
|
||||
// Upload the exported PNG
|
||||
await scanFileInput.setInputFiles(exportedPngPath)
|
||||
|
||||
// Wait for the result container to appear first (keep increased timeout for now)
|
||||
const resultContainer = page.locator('.capture-result')
|
||||
await expect(resultContainer).toBeVisible({ timeout: 10000 })
|
||||
|
||||
// Then verify the content within the container
|
||||
await expect(resultContainer.getByText(testData)).toBeVisible()
|
||||
})
|
||||
|
||||
test('batch export works with CSV file', async ({ page }) => {
|
||||
const csvFilePath = 'public/6_strings_batch.csv' // Path to the test CSV
|
||||
const expectedZipFilename = 'qr-codes.zip'
|
||||
|
||||
// Switch to Batch export mode
|
||||
await page.getByRole('button', { name: 'Batch export' }).click()
|
||||
|
||||
// Locate the correct hidden file input for batch CSV upload
|
||||
const batchFileInput = page.locator('input[type="file"][accept=".csv,.txt"]')
|
||||
|
||||
// Upload the CSV file
|
||||
await batchFileInput.setInputFiles(csvFilePath)
|
||||
|
||||
// Check the "Ignore header row" checkbox
|
||||
const ignoreHeaderCheckbox = page.locator('input[id="ignore-header"]')
|
||||
await ignoreHeaderCheckbox.check()
|
||||
await expect(ignoreHeaderCheckbox).toBeChecked()
|
||||
|
||||
// Wait for the UI to potentially update after file processing/checkbox click
|
||||
// Check for the preview text as an indicator
|
||||
await expect(page.getByText('5 piece(s) of data detected')).toBeVisible() // rows - 1 header
|
||||
await expect(page.getByText('First row preview:')).toBeVisible()
|
||||
await expect(page.locator('pre').getByText('https://www.esteetey.dev/')).toBeVisible()
|
||||
|
||||
// Start waiting for the download before clicking the PNG export button
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.locator('#download-qr-image-button-png').click()
|
||||
|
||||
// Wait for the download to complete
|
||||
const download = await downloadPromise
|
||||
|
||||
// Assert the downloaded file is a zip file with the expected name
|
||||
expect(download.suggestedFilename()).toBe(expectedZipFilename)
|
||||
const downloadedPath = './test-results/' + download.suggestedFilename()
|
||||
await download.saveAs(downloadedPath)
|
||||
|
||||
// TODO: Optionally add steps here to unzip and verify file count/contents
|
||||
// For now, we just check the download happened and was a zip file.
|
||||
})
|
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
tests/e2e/fixtures/test-qrcode.png
Normal file
BIN
tests/e2e/fixtures/test-qrcode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
Loading…
x
Reference in New Issue
Block a user