Update README, speed up tests
This commit is contained in:
42
README.md
42
README.md
@@ -50,18 +50,33 @@ web/
|
|||||||
style.css
|
style.css
|
||||||
js/
|
js/
|
||||||
app.js # Orchestrator: shared state, device connection, mode selection, error/retry, dialogs
|
app.js # Orchestrator: shared state, device connection, mode selection, error/retry, dialogs
|
||||||
dom.js # Shared DOM helpers ($, $q, $qa, formatMB, populateSelect, triggerDownload)
|
dom.js # Shared DOM/utility helpers ($, $q, populateSelect, renderNmCheckboxList, populateList, fetchOrThrow, triggerDownload)
|
||||||
nav.js # Step navigation, progress bar, step history, card radio interactivity
|
nav.js # Step navigation, progress bar, step history, card radio interactivity
|
||||||
|
strings.js # Localized UI strings
|
||||||
|
analytics.js # Privacy-focused analytics wrapper (Umami)
|
||||||
|
flows/
|
||||||
nickelmenu-flow.js # NickelMenu flow: config, features, review, install, done
|
nickelmenu-flow.js # NickelMenu flow: config, features, review, install, done
|
||||||
patches-flow.js # Custom patches flow: configure, build, install/download
|
patches-flow.js # Custom patches flow: configure, build, install/download
|
||||||
kobo-device.js # KoboModels, KoboDevice class
|
services/
|
||||||
|
kobo-device.js # KoboModels, KoboDevice class (File System Access API)
|
||||||
kobo-software-urls.js # Fetches download URLs from JSON, getSoftwareUrl, getDevicesForVersion
|
kobo-software-urls.js # Fetches download URLs from JSON, getSoftwareUrl, getDevicesForVersion
|
||||||
nickelmenu/ # NickelMenu feature modules + installer orchestrator
|
|
||||||
patch-ui.js # PatchUI: loads patches, parses YAML, renders toggle UI
|
|
||||||
patch-runner.js # KoboPatchRunner: spawns Web Worker per build
|
patch-runner.js # KoboPatchRunner: spawns Web Worker per build
|
||||||
|
ui/
|
||||||
|
patch-ui.js # PatchUI: loads patches, parses YAML, renders toggle UI
|
||||||
|
workers/
|
||||||
patch-worker.js # Web Worker: loads WASM, runs patchFirmware()
|
patch-worker.js # Web Worker: loads WASM, runs patchFirmware()
|
||||||
strings.js # Localized UI strings
|
|
||||||
wasm_exec.js # Go WASM runtime (copied from Go SDK by build.sh, gitignored)
|
wasm_exec.js # Go WASM runtime (copied from Go SDK by build.sh, gitignored)
|
||||||
|
nickelmenu/
|
||||||
|
installer.js # NickelMenu installer orchestrator: collects files, writes to device or builds ZIP
|
||||||
|
features/
|
||||||
|
helpers.js # Shared postProcess helpers (appendToNmConfig, prependToNmConfig)
|
||||||
|
custom-menu/ # Required preset menu items
|
||||||
|
readerly-fonts/ # Font installation
|
||||||
|
koreader/ # KOReader e-reader installation
|
||||||
|
simplify-tabs/ # Navigation tab configuration
|
||||||
|
hide-recommendations/ # Home screen recommendations toggle
|
||||||
|
hide-notices/ # Home screen notices toggle
|
||||||
|
screensaver/ # Screensaver image installation
|
||||||
patches/
|
patches/
|
||||||
index.json # Available patch manifest
|
index.json # Available patch manifest
|
||||||
downloads.json # Firmware download URLs by version/serial (may be auto-generated)
|
downloads.json # Firmware download URLs by version/serial (may be auto-generated)
|
||||||
@@ -75,7 +90,7 @@ web/
|
|||||||
dist/ # Build output (gitignored, fully regenerable)
|
dist/ # Build output (gitignored, fully regenerable)
|
||||||
bundle.js # esbuild output (minified, content-hashed)
|
bundle.js # esbuild output (minified, content-hashed)
|
||||||
index.html # Generated with cache-busted references
|
index.html # Generated with cache-busted references
|
||||||
css/ favicon/ patches/ nickelmenu/ readerly/ koreader/ wasm/ js/wasm_exec.js
|
css/ favicon/ patches/ nickelmenu/ readerly/ koreader/ wasm/ js/workers/
|
||||||
build.mjs # esbuild build script + asset copy
|
build.mjs # esbuild build script + asset copy
|
||||||
package.json # esbuild, jszip
|
package.json # esbuild, jszip
|
||||||
|
|
||||||
@@ -106,7 +121,8 @@ tests/
|
|||||||
paths.js # Test asset paths, expected checksums
|
paths.js # Test asset paths, expected checksums
|
||||||
tar.js # Tar archive parser for output verification
|
tar.js # Tar archive parser for output verification
|
||||||
integration.spec.js # Playwright E2E tests
|
integration.spec.js # Playwright E2E tests
|
||||||
playwright.config.js
|
playwright.config.js # Parallel by default; serial when --headed or --slow
|
||||||
|
global-setup.js # Creates firmware symlink once before all tests
|
||||||
run-e2e.sh
|
run-e2e.sh
|
||||||
|
|
||||||
# Root scripts
|
# Root scripts
|
||||||
@@ -164,13 +180,15 @@ This downloads the latest release directly into `web/dist/koreader/`, skipping t
|
|||||||
|
|
||||||
## Building the frontend
|
## Building the frontend
|
||||||
|
|
||||||
The JS source lives in `web/src/js/` as ES modules, organized around the two main user flows:
|
The JS source lives in `web/src/js/` as ES modules, organized by role:
|
||||||
|
|
||||||
- **`app.js`** — the orchestrator: creates shared state, handles device connection, mode selection, error recovery, and dialogs. Delegates to the two flow modules below.
|
- **`app.js`** — the orchestrator: creates shared state, handles device connection, mode selection, error recovery, and dialogs. Delegates to the two flow modules below.
|
||||||
- **`nickelmenu-flow.js`** — the entire NickelMenu path (config, features, review, install, done).
|
- **`flows/`** — the two main user journeys: `nickelmenu-flow.js` (install/configure/remove NickelMenu) and `patches-flow.js` (configure/build/install custom patches).
|
||||||
- **`patches-flow.js`** — the entire custom patches path (configure, build, install/download).
|
- **`services/`** — modules that wrap external APIs with no DOM dependencies: `kobo-device.js` (File System Access API), `kobo-software-urls.js` (firmware URL lookup), `patch-runner.js` (Web Worker manager).
|
||||||
|
- **`ui/`** — UI rendering: `patch-ui.js` (patch list rendering and toggle UI).
|
||||||
|
- **`workers/`** — Web Worker files (not bundled, loaded at runtime): `patch-worker.js` (loads WASM, runs patcher).
|
||||||
|
- **`dom.js`** — shared DOM/utility helpers (`$`, `$q`, `renderNmCheckboxList`, `populateList`, `fetchOrThrow`, etc.) used across modules.
|
||||||
- **`nav.js`** — step navigation, progress bar, and step history (shared by both flows).
|
- **`nav.js`** — step navigation, progress bar, and step history (shared by both flows).
|
||||||
- **`dom.js`** — tiny DOM utility helpers (`$`, `$q`, `$qa`, etc.) used everywhere.
|
|
||||||
|
|
||||||
Flow modules receive a shared `state` object by reference and call back into the orchestrator via `state.showError()` and `state.goToModeSelection()` when they need to cross module boundaries. esbuild bundles everything into a single `web/dist/bundle.js`.
|
Flow modules receive a shared `state` object by reference and call back into the orchestrator via `state.showError()` and `state.goToModeSelection()` when they need to cross module boundaries. esbuild bundles everything into a single `web/dist/bundle.js`.
|
||||||
|
|
||||||
@@ -230,6 +248,8 @@ cd tests/e2e
|
|||||||
./run-e2e.sh
|
./run-e2e.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, tests run in parallel across 4 workers. When `--headed` or `--slow` is passed, tests run serially with a single worker so you can follow along in the browser.
|
||||||
|
|
||||||
To run with a visible browser window:
|
To run with a visible browser window:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
8
tests/e2e/global-setup.js
Normal file
8
tests/e2e/global-setup.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const { setupFirmwareSymlink, cleanupFirmwareSymlink, hasFirmwareZip } = require('./helpers/assets');
|
||||||
|
|
||||||
|
module.exports = function globalSetup() {
|
||||||
|
if (hasFirmwareZip()) setupFirmwareSymlink();
|
||||||
|
|
||||||
|
// Return a teardown function (Playwright >= 1.30)
|
||||||
|
return () => cleanupFirmwareSymlink();
|
||||||
|
};
|
||||||
@@ -6,13 +6,10 @@ const zlib = require('zlib');
|
|||||||
const JSZip = require('jszip');
|
const JSZip = require('jszip');
|
||||||
|
|
||||||
const { FIRMWARE_PATH, EXPECTED_SHA1, ORIGINAL_TGZ_SHA1 } = require('./helpers/paths');
|
const { FIRMWARE_PATH, EXPECTED_SHA1, ORIGINAL_TGZ_SHA1 } = require('./helpers/paths');
|
||||||
const { hasNickelMenuAssets, hasKoreaderAssets, hasReaderlyAssets, hasFirmwareZip, setupFirmwareSymlink, cleanupFirmwareSymlink } = require('./helpers/assets');
|
const { hasNickelMenuAssets, hasKoreaderAssets, hasReaderlyAssets, hasFirmwareZip } = require('./helpers/assets');
|
||||||
const { injectMockDevice, connectMockDevice, overrideFirmwareURLs, goToManualMode, readMockFile, mockPathExists, getWrittenFiles } = require('./helpers/mock-device');
|
const { injectMockDevice, connectMockDevice, overrideFirmwareURLs, goToManualMode, readMockFile, mockPathExists, getWrittenFiles } = require('./helpers/mock-device');
|
||||||
const { parseTar } = require('./helpers/tar');
|
const { parseTar } = require('./helpers/tar');
|
||||||
|
|
||||||
test.afterEach(() => {
|
|
||||||
cleanupFirmwareSymlink();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// NickelMenu
|
// NickelMenu
|
||||||
@@ -600,7 +597,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('no device — full manual mode patching pipeline', async ({ page }) => {
|
test('no device — full manual mode patching pipeline', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
await goToManualMode(page);
|
await goToManualMode(page);
|
||||||
|
|
||||||
// Select "Custom Patches" mode
|
// Select "Custom Patches" mode
|
||||||
@@ -684,7 +681,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('no device — restore original firmware', async ({ page }) => {
|
test('no device — restore original firmware', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
await goToManualMode(page);
|
await goToManualMode(page);
|
||||||
|
|
||||||
// Select "Custom Patches" mode
|
// Select "Custom Patches" mode
|
||||||
@@ -815,7 +812,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('with device — apply patches and verify checksums', async ({ page }) => {
|
test('with device — apply patches and verify checksums', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
// Override firmware URLs BEFORE connecting so the app captures the local URL
|
// Override firmware URLs BEFORE connecting so the app captures the local URL
|
||||||
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
||||||
|
|
||||||
@@ -890,7 +887,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('with device — restore original firmware', async ({ page }) => {
|
test('with device — restore original firmware', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
// Override firmware URLs BEFORE connecting so the app captures the local URL
|
// Override firmware URLs BEFORE connecting so the app captures the local URL
|
||||||
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
||||||
|
|
||||||
@@ -932,7 +929,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('with device — build failure shows Go Back and returns to patches', async ({ page }) => {
|
test('with device — build failure shows Go Back and returns to patches', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
||||||
|
|
||||||
// Select Custom Patches
|
// Select Custom Patches
|
||||||
@@ -969,7 +966,7 @@ test.describe('Custom patches', () => {
|
|||||||
test('with device — real patch failure with Go Back (Allow rotation)', async ({ page }) => {
|
test('with device — real patch failure with Go Back (Allow rotation)', async ({ page }) => {
|
||||||
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
test.skip(!hasFirmwareZip(), `Firmware not found at ${FIRMWARE_PATH}`);
|
||||||
|
|
||||||
setupFirmwareSymlink();
|
|
||||||
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
await connectMockDevice(page, { hasNickelMenu: false, overrideFirmware: true });
|
||||||
|
|
||||||
// Select Custom Patches
|
// Select Custom Patches
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
const { defineConfig } = require('@playwright/test');
|
const { defineConfig } = require('@playwright/test');
|
||||||
|
|
||||||
|
const serial = parseInt(process.env.SLOW_MO || '0', 10) > 0 || process.argv.includes('--headed');
|
||||||
|
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
testDir: '.',
|
testDir: '.',
|
||||||
testMatch: '*.spec.js',
|
testMatch: '*.spec.js',
|
||||||
timeout: 300_000,
|
timeout: 300_000,
|
||||||
retries: 0,
|
retries: 0,
|
||||||
|
workers: serial ? 1 : 4,
|
||||||
|
fullyParallel: !serial,
|
||||||
|
globalSetup: './global-setup.js',
|
||||||
expect: {
|
expect: {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
},
|
},
|
||||||
@@ -13,7 +18,7 @@ module.exports = defineConfig({
|
|||||||
actionTimeout: 10_000,
|
actionTimeout: 10_000,
|
||||||
launchOptions: {
|
launchOptions: {
|
||||||
args: ['--disable-dev-shm-usage'],
|
args: ['--disable-dev-shm-usage'],
|
||||||
slowMo: parseInt(process.env.SLOW_MO || '0', 10),
|
serialMo: parseInt(process.env.SLOW_MO || '0', 10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
webServer: {
|
webServer: {
|
||||||
|
|||||||
Reference in New Issue
Block a user