1
0

More end-to-end tests
All checks were successful
Build and test project / build-and-test (push) Successful in 1m43s

This commit is contained in:
2026-03-17 19:10:02 +01:00
parent 4713683638
commit 596354569e
3 changed files with 190 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const zlib = require('zlib'); const zlib = require('zlib');
const JSZip = require('jszip');
// Expected SHA1 checksums for Kobo Libra Color, firmware 4.45.23646, // Expected SHA1 checksums for Kobo Libra Color, firmware 4.45.23646,
// with only "Remove footer (row3) on new home screen" enabled. // with only "Remove footer (row3) on new home screen" enabled.
@@ -296,6 +297,9 @@ test.describe('NickelMenu', () => {
await expect(page.locator('input[name="nm-cfg-simplify-tabs"]')).not.toBeChecked(); await expect(page.locator('input[name="nm-cfg-simplify-tabs"]')).not.toBeChecked();
await expect(page.locator('input[name="nm-cfg-simplify-home"]')).not.toBeChecked(); await expect(page.locator('input[name="nm-cfg-simplify-home"]')).not.toBeChecked();
// Enable simplifyHome for testing
await page.check('input[name="nm-cfg-simplify-home"]');
await expect(page.locator('#btn-nm-next')).toBeEnabled(); await expect(page.locator('#btn-nm-next')).toBeEnabled();
await page.click('#btn-nm-next'); await page.click('#btn-nm-next');
@@ -310,13 +314,36 @@ test.describe('NickelMenu', () => {
await expect(page.locator('#btn-nm-download')).toBeVisible(); await expect(page.locator('#btn-nm-download')).toBeVisible();
// Click download and wait for done step // Click download and wait for done step
await page.click('#btn-nm-download'); const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#btn-nm-download'),
]);
await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 }); await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 });
await expect(page.locator('#nm-done-status')).toContainText('ready to download'); await expect(page.locator('#nm-done-status')).toContainText('ready to download');
// Download instructions should be visible, and include eReader.conf step for sample config // Download instructions should be visible, and include eReader.conf step for sample config
await expect(page.locator('#nm-download-instructions')).not.toBeHidden(); await expect(page.locator('#nm-download-instructions')).not.toBeHidden();
await expect(page.locator('#nm-download-conf-step')).not.toBeHidden(); await expect(page.locator('#nm-download-conf-step')).not.toBeHidden();
// Verify ZIP contents
expect(download.suggestedFilename()).toBe('NickelMenu-install.zip');
const zipData = fs.readFileSync(await download.path());
const zip = await JSZip.loadAsync(zipData);
const zipFiles = Object.keys(zip.files);
// Must contain KoboRoot.tgz
expect(zipFiles).toContainEqual('.kobo/KoboRoot.tgz');
// Must contain NickelMenu items config
expect(zipFiles).toContainEqual('.adds/nm/items');
// Must contain font files (fonts checkbox is checked by default)
expect(zipFiles.some(f => f.startsWith('fonts/'))).toBe(true);
// Must NOT contain screensaver (unchecked by default)
expect(zipFiles.some(f => f.startsWith('.kobo/screensaver/'))).toBe(false);
// Verify items file has simplifyHome modifications
const itemsContent = await zip.file('.adds/nm/items').async('string');
expect(itemsContent).toContain('experimental:hide_home_row1col2_enabled:1');
expect(itemsContent).toContain('experimental:hide_home_row3_enabled:1');
}); });
test('no device — install NickelMenu only via manual download', async ({ page }) => { test('no device — install NickelMenu only via manual download', async ({ page }) => {
@@ -337,12 +364,23 @@ test.describe('NickelMenu', () => {
await expect(page.locator('#nm-review-list')).toContainText('NickelMenu (KoboRoot.tgz)'); await expect(page.locator('#nm-review-list')).toContainText('NickelMenu (KoboRoot.tgz)');
// Download // Download
await page.click('#btn-nm-download'); const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#btn-nm-download'),
]);
await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 }); await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 });
await expect(page.locator('#nm-done-status')).toContainText('ready to download'); await expect(page.locator('#nm-done-status')).toContainText('ready to download');
// eReader.conf step should be hidden for nickelmenu-only // eReader.conf step should be hidden for nickelmenu-only
await expect(page.locator('#nm-download-conf-step')).toBeHidden(); await expect(page.locator('#nm-download-conf-step')).toBeHidden();
// Verify ZIP contents — should only contain KoboRoot.tgz
expect(download.suggestedFilename()).toBe('NickelMenu-install.zip');
const zipData = fs.readFileSync(await download.path());
const zip = await JSZip.loadAsync(zipData);
const zipFiles = Object.keys(zip.files).filter(f => !zip.files[f].dir);
expect(zipFiles).toEqual(['.kobo/KoboRoot.tgz']);
}); });
test('no device — remove option is disabled in manual mode', async ({ page }) => { test('no device — remove option is disabled in manual mode', async ({ page }) => {
@@ -421,6 +459,40 @@ test.describe('NickelMenu', () => {
expect(items).toContain('experimental:hide_home_row3_enabled:1'); expect(items).toContain('experimental:hide_home_row3_enabled:1');
}); });
test('with device — install NickelMenu only and write to Kobo', async ({ page }) => {
test.skip(!hasNickelMenuAssets(), 'NickelMenu assets not found in webroot');
await connectMockDevice(page, { hasNickelMenu: false });
// Continue to mode selection
await page.click('#btn-device-next');
await page.click('#btn-mode-next');
// NickelMenu configure step
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
// Select "Install NickelMenu only"
await page.click('input[name="nm-option"][value="nickelmenu-only"]');
await expect(page.locator('#nm-config-options')).toBeHidden();
await page.click('#btn-nm-next');
// Review step
await expect(page.locator('#step-nm-review')).not.toBeHidden();
await expect(page.locator('#nm-review-list')).toContainText('NickelMenu (KoboRoot.tgz)');
// Write to device
await page.click('#btn-nm-write');
await expect(page.locator('#step-nm-done')).toBeVisible({ timeout: 30_000 });
await expect(page.locator('#nm-done-status')).toContainText('installed');
// Verify only KoboRoot.tgz was written (no config files)
const writtenFiles = await getWrittenFiles(page);
expect(writtenFiles).toContainEqual(expect.stringContaining('KoboRoot.tgz'));
// Should NOT have written items, fonts, etc.
expect(writtenFiles.filter(f => !f.includes('KoboRoot.tgz'))).toHaveLength(0);
});
test('with device — remove NickelMenu', async ({ page }) => { test('with device — remove NickelMenu', async ({ page }) => {
test.skip(!hasNickelMenuAssets(), 'NickelMenu assets not found in webroot'); test.skip(!hasNickelMenuAssets(), 'NickelMenu assets not found in webroot');

View File

@@ -6,7 +6,8 @@
"": { "": {
"name": "kobopatch-webui-e2e", "name": "kobopatch-webui-e2e",
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.50.0" "@playwright/test": "^1.50.0",
"jszip": "^3.10.1"
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
@@ -25,6 +26,13 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true,
"license": "MIT"
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -40,6 +48,57 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true,
"license": "(MIT AND Zlib)"
},
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.58.2", "version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
@@ -71,6 +130,60 @@
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true,
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"dev": true,
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
} }
} }
} }

View File

@@ -5,6 +5,7 @@
"test": "npx playwright test" "test": "npx playwright test"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.50.0" "@playwright/test": "^1.50.0",
"jszip": "^3.10.1"
} }
} }