Screenshots for quick visual audit
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,6 +36,7 @@ tests/cached_assets/
|
|||||||
tests/e2e/node_modules/
|
tests/e2e/node_modules/
|
||||||
tests/e2e/test-results/
|
tests/e2e/test-results/
|
||||||
tests/e2e/playwright-report/
|
tests/e2e/playwright-report/
|
||||||
|
tests/e2e/screenshots/
|
||||||
tests/e2e/test_firmware.zip
|
tests/e2e/test_firmware.zip
|
||||||
|
|
||||||
# NickelMenu build artifacts
|
# NickelMenu build artifacts
|
||||||
|
|||||||
17
tests/e2e/run-screenshots.sh
Executable file
17
tests/e2e/run-screenshots.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Capture screenshots of every wizard step for visual review.
|
||||||
|
#
|
||||||
|
# Usage: ./run-screenshots.sh
|
||||||
|
#
|
||||||
|
# Output: screenshots/*.png (gitignored)
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
rm -rf screenshots
|
||||||
|
|
||||||
|
npx playwright test --config screenshots.config.js --reporter=list "$@"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Screenshots saved to tests/e2e/screenshots/"
|
||||||
23
tests/e2e/screenshots.config.js
Normal file
23
tests/e2e/screenshots.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { defineConfig } = require('@playwright/test');
|
||||||
|
const base = require('./playwright.config.js');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
...base,
|
||||||
|
testMatch: 'screenshots.mjs',
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'mobile',
|
||||||
|
use: {
|
||||||
|
...base.use,
|
||||||
|
viewport: { width: 393, height: 852 },
|
||||||
|
deviceScaleFactor: 3,
|
||||||
|
isMobile: true,
|
||||||
|
hasTouch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'desktop',
|
||||||
|
use: { ...base.use, viewport: { width: 1280, height: 900 }, deviceScaleFactor: 2 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
102
tests/e2e/screenshots.mjs
Normal file
102
tests/e2e/screenshots.mjs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Capture screenshots of every step in the wizard.
|
||||||
|
* Uses the same Playwright test infrastructure and dev server as the E2E tests.
|
||||||
|
* Runs once per project (mobile + desktop) defined in screenshots.config.js.
|
||||||
|
*
|
||||||
|
* Run: ./run-screenshots.sh
|
||||||
|
*/
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { injectMockDevice } from './helpers/mock-device.js';
|
||||||
|
|
||||||
|
const shot = async (page, name, testInfo) => {
|
||||||
|
const project = testInfo.project.name;
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
await page.screenshot({ path: `screenshots/${project}/${name}.png`, fullPage: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
test('capture all steps', async ({ page }, testInfo) => {
|
||||||
|
// 1. Connect step
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||||
|
await injectMockDevice(page);
|
||||||
|
await shot(page, '01-connect', testInfo);
|
||||||
|
|
||||||
|
// 2. Connection instructions
|
||||||
|
await page.click('#btn-connect');
|
||||||
|
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||||
|
await shot(page, '02-connect-instructions', testInfo);
|
||||||
|
|
||||||
|
// 2b. Connection instructions with disclaimer open
|
||||||
|
await page.click('.disclaimer summary');
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
await shot(page, '03-connect-instructions-disclaimer', testInfo);
|
||||||
|
|
||||||
|
// 3. Device detected
|
||||||
|
await page.click('#btn-connect-ready');
|
||||||
|
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||||
|
await shot(page, '04-device', testInfo);
|
||||||
|
|
||||||
|
// 4. Mode selection
|
||||||
|
await page.click('#btn-device-next');
|
||||||
|
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||||
|
await shot(page, '05-mode-selection', testInfo);
|
||||||
|
|
||||||
|
// 5a. NickelMenu config
|
||||||
|
await page.click('input[name="mode"][value="nickelmenu"]');
|
||||||
|
await page.click('#btn-mode-next');
|
||||||
|
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||||
|
await shot(page, '06-nickelmenu-config', testInfo);
|
||||||
|
|
||||||
|
// 5b. NickelMenu features (preset)
|
||||||
|
await page.click('input[value="preset"]');
|
||||||
|
await page.click('#btn-nm-next');
|
||||||
|
await expect(page.locator('#step-nm-features')).not.toBeHidden();
|
||||||
|
await shot(page, '07-nickelmenu-features', testInfo);
|
||||||
|
|
||||||
|
// 5c. NickelMenu review
|
||||||
|
await page.click('#btn-nm-features-next');
|
||||||
|
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||||
|
await shot(page, '08-nickelmenu-review', testInfo);
|
||||||
|
|
||||||
|
// Go back to mode and try patches path
|
||||||
|
await page.click('#btn-nm-review-back');
|
||||||
|
await page.click('#btn-nm-features-back');
|
||||||
|
await page.click('#btn-nm-back');
|
||||||
|
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||||
|
await page.click('input[name="mode"][value="patches"]');
|
||||||
|
await page.click('#btn-mode-next');
|
||||||
|
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||||
|
await shot(page, '09-patches-config', testInfo);
|
||||||
|
|
||||||
|
// 6b. Expand a patch section and select a patch
|
||||||
|
const section = page.locator('.patch-file-section').first();
|
||||||
|
await section.locator('summary').click();
|
||||||
|
const patchLabel = section.locator('label').filter({ has: page.locator('.patch-name:not(.patch-name-none)') }).first();
|
||||||
|
await patchLabel.locator('input').check();
|
||||||
|
await shot(page, '10-patches-selected', testInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('incompatible firmware', async ({ page }, testInfo) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await injectMockDevice(page, { firmware: '5.0.0' });
|
||||||
|
await page.click('#btn-connect');
|
||||||
|
await page.click('#btn-connect-ready');
|
||||||
|
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||||
|
await shot(page, '11-device-incompatible', testInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unknown model', async ({ page }, testInfo) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await injectMockDevice(page, { serial: 'X9990A0000000' });
|
||||||
|
await page.click('#btn-connect');
|
||||||
|
await page.click('#btn-connect-ready');
|
||||||
|
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||||
|
await shot(page, '12-device-unknown', testInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manual mode', async ({ page }, testInfo) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.click('#btn-manual');
|
||||||
|
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||||
|
await shot(page, '13-manual-mode-selection', testInfo);
|
||||||
|
});
|
||||||
@@ -311,6 +311,13 @@ h2 {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.recommended-pill {
|
||||||
|
position: static;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* NickelMenu option radio cards */
|
/* NickelMenu option radio cards */
|
||||||
.nm-options {
|
.nm-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -475,6 +482,17 @@ h2 {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.step-actions:has(.step-actions-right) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-actions-right {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
button {
|
button {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -1019,14 +1037,19 @@ select + .fallback-hint {
|
|||||||
|
|
||||||
/* Selected patches summary on build step */
|
/* Selected patches summary on build step */
|
||||||
.selected-patches-list {
|
.selected-patches-list {
|
||||||
margin: 0 0 0.75rem 1.25rem;
|
margin: 0 0 0.75rem;
|
||||||
|
padding: 0.6rem 1rem 0.6rem 2rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-patches-list li {
|
.selected-patches-list li {
|
||||||
padding: 0.05rem 0;
|
padding: 0.1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#firmware-download-url {
|
#firmware-download-url {
|
||||||
|
|||||||
@@ -188,7 +188,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p id="device-status"></p>
|
<p id="device-status"></p>
|
||||||
<p id="device-unknown-warning" class="warning" hidden>
|
<p id="device-unknown-warning" class="warning" hidden>
|
||||||
You seem to have a Kobo device that isn't currently being detected. While you can continue since a supported software version seems to be installed, please
|
You seem to have a Kobo device that cannot be identified. While you can continue since a supported software version seems to be installed, please
|
||||||
<a href="https://github.com/nicoverbruggen/kobopatch-webui/issues/new" target="_blank">file an issue on GitHub</a>
|
<a href="https://github.com/nicoverbruggen/kobopatch-webui/issues/new" target="_blank">file an issue on GitHub</a>
|
||||||
so the developer can add this device to the list.
|
so the developer can add this device to the list.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user