1
0

Remove e2e folder

This commit is contained in:
2026-03-25 19:28:55 +01:00
parent dfe13b093d
commit 202b827d8b
18 changed files with 81 additions and 82 deletions

39
tests/helpers/assets.js Normal file
View File

@@ -0,0 +1,39 @@
const fs = require('fs');
const path = require('path');
const { WEBROOT, WEBROOT_FIRMWARE, FIRMWARE_PATH } = require('./paths');
function hasNickelMenuAssets() {
return fs.existsSync(path.join(WEBROOT, 'nickelmenu', 'NickelMenu.zip'))
&& fs.existsSync(path.join(WEBROOT, 'nickelmenu', 'features', 'custom-menu', 'items'));
}
function hasKoreaderAssets() {
return fs.existsSync(path.join(WEBROOT, 'koreader', 'koreader-kobo.zip'))
&& fs.existsSync(path.join(WEBROOT, 'koreader', 'release.json'));
}
function hasReaderlyAssets() {
return fs.existsSync(path.join(WEBROOT, 'readerly', 'KF_Readerly.zip'));
}
function hasFirmwareZip() {
return fs.existsSync(FIRMWARE_PATH);
}
function setupFirmwareSymlink() {
try { fs.unlinkSync(WEBROOT_FIRMWARE); } catch {}
fs.symlinkSync(path.resolve(FIRMWARE_PATH), WEBROOT_FIRMWARE);
}
function cleanupFirmwareSymlink() {
try { fs.unlinkSync(WEBROOT_FIRMWARE); } catch {}
}
module.exports = {
hasNickelMenuAssets,
hasKoreaderAssets,
hasReaderlyAssets,
hasFirmwareZip,
setupFirmwareSymlink,
cleanupFirmwareSymlink,
};

View File

@@ -0,0 +1,228 @@
const { expect } = require('@playwright/test');
/**
* Inject a mock File System Access API into the page, simulating a Kobo Libra Color.
* The mock provides:
* - .kobo/version file with serial N4280A0000000 and firmware 4.45.23646
* - Optionally a .adds/nm/ directory (to simulate NickelMenu being installed)
* - In-memory filesystem that tracks all writes for verification
*/
async function injectMockDevice(page, opts = {}) {
const firmware = opts.firmware || '4.45.23646';
const serial = opts.serial || 'N4280A0000000';
await page.evaluate(({ hasNickelMenu, hasKoreader, hasReaderlyFonts, hasScreensaver, firmware, serial }) => {
const filesystem = {
'.kobo': {
_type: 'dir',
'version': {
_type: 'file',
content: serial + ',4.9.77,' + firmware + ',4.9.77,4.9.77,00000000-0000-0000-0000-000000000390',
},
'Kobo': {
_type: 'dir',
'Kobo eReader.conf': {
_type: 'file',
content: '[General]\nsome=setting\n',
},
},
},
};
if (hasNickelMenu) {
filesystem['.adds'] = {
_type: 'dir',
'nm': {
_type: 'dir',
'items': { _type: 'file', content: 'menu_item:main:test:skip:' },
},
};
}
if (hasKoreader) {
if (!filesystem['.adds']) filesystem['.adds'] = { _type: 'dir' };
filesystem['.adds']['koreader'] = {
_type: 'dir',
'koreader.sh': { _type: 'file', content: '#!/bin/sh' },
};
}
if (hasReaderlyFonts) {
filesystem['fonts'] = {
_type: 'dir',
'KF_Readerly-Regular.ttf': { _type: 'file', content: '' },
'KF_Readerly-Italic.ttf': { _type: 'file', content: '' },
'KF_Readerly-Bold.ttf': { _type: 'file', content: '' },
'KF_Readerly-BoldItalic.ttf': { _type: 'file', content: '' },
};
}
if (hasScreensaver) {
if (!filesystem['.kobo']['screensaver']) {
filesystem['.kobo']['screensaver'] = { _type: 'dir' };
}
filesystem['.kobo']['screensaver']['moon.png'] = { _type: 'file', content: '' };
}
window.__mockFS = filesystem;
window.__mockWrittenFiles = {};
function makeFileHandle(dirNode, fileName, pathPrefix) {
return {
getFile: async () => {
const fileNode = dirNode[fileName];
const content = fileNode ? (fileNode.content || '') : '';
return {
text: async () => content,
arrayBuffer: async () => new TextEncoder().encode(content).buffer,
};
},
createWritable: async () => {
const chunks = [];
return {
write: async (chunk) => { chunks.push(chunk); },
close: async () => {
const first = chunks[0];
const bytes = first instanceof Uint8Array ? first : new TextEncoder().encode(String(first));
if (!dirNode[fileName]) dirNode[fileName] = { _type: 'file' };
dirNode[fileName].content = new TextDecoder().decode(bytes);
const fullPath = pathPrefix ? pathPrefix + '/' + fileName : fileName;
window.__mockWrittenFiles[fullPath] = true;
},
};
},
};
}
function makeDirHandle(node, name, pathPrefix) {
const currentPath = pathPrefix ? pathPrefix + '/' + name : name;
return {
name: name,
kind: 'directory',
getDirectoryHandle: async (childName, opts2) => {
if (node[childName] && node[childName]._type === 'dir') {
return makeDirHandle(node[childName], childName, currentPath);
}
if (opts2 && opts2.create) {
node[childName] = { _type: 'dir' };
return makeDirHandle(node[childName], childName, currentPath);
}
throw new DOMException('Not found: ' + childName, 'NotFoundError');
},
getFileHandle: async (childName, opts2) => {
if (node[childName] && node[childName]._type === 'file') {
return makeFileHandle(node, childName, currentPath);
}
if (opts2 && opts2.create) {
node[childName] = { _type: 'file', content: '' };
return makeFileHandle(node, childName, currentPath);
}
throw new DOMException('Not found: ' + childName, 'NotFoundError');
},
removeEntry: async (childName) => {
if (node[childName]) {
delete node[childName];
return;
}
throw new DOMException('Not found: ' + childName, 'NotFoundError');
},
};
}
const rootHandle = makeDirHandle(filesystem, 'KOBOeReader', '');
window.showDirectoryPicker = async () => rootHandle;
}, {
hasNickelMenu: opts.hasNickelMenu || false,
hasKoreader: opts.hasKoreader || false,
hasReaderlyFonts: opts.hasReaderlyFonts || false,
hasScreensaver: opts.hasScreensaver || false,
firmware,
serial,
});
}
/**
* Inject mock device, optionally override firmware URLs, and connect.
*/
async function connectMockDevice(page, opts = {}) {
await page.goto('/');
await expect(page.locator('h1')).toContainText('KoboPatch');
await injectMockDevice(page, opts);
if (opts.overrideFirmware) {
await overrideFirmwareURLs(page);
}
await page.click('#btn-connect');
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
await page.click('#btn-connect-ready');
await expect(page.locator('#step-device')).not.toBeHidden();
await expect(page.locator('#device-model')).toHaveText('Kobo Libra Colour');
await expect(page.locator('#device-firmware')).toHaveText('4.45.23646');
await expect(page.locator('#device-status')).toContainText('recognized');
}
/**
* Override firmware download URLs to point at the local test server.
*/
async function overrideFirmwareURLs(page) {
await page.evaluate(() => {
for (const version of Object.keys(FIRMWARE_DOWNLOADS)) {
for (const prefix of Object.keys(FIRMWARE_DOWNLOADS[version])) {
FIRMWARE_DOWNLOADS[version][prefix] = '/_test_firmware.zip';
}
}
});
}
/**
* Navigate to manual mode.
*/
async function goToManualMode(page) {
await page.goto('/');
await expect(page.locator('h1')).toContainText('KoboPatch');
await page.click('#btn-manual');
await expect(page.locator('#step-mode')).not.toBeHidden();
}
/**
* Read a file's content from the mock filesystem.
*/
async function readMockFile(page, ...pathParts) {
return page.evaluate((parts) => {
let node = window.__mockFS;
for (const part of parts) {
if (!node || !node[part]) return null;
node = node[part];
}
return node && node._type === 'file' ? (node.content || '') : null;
}, pathParts);
}
/**
* Check whether a path exists in the mock filesystem.
*/
async function mockPathExists(page, ...pathParts) {
return page.evaluate((parts) => {
let node = window.__mockFS;
for (const part of parts) {
if (!node || !node[part]) return false;
node = node[part];
}
return true;
}, pathParts);
}
/**
* Get the list of written file paths from the mock device.
*/
async function getWrittenFiles(page) {
return page.evaluate(() => Object.keys(window.__mockWrittenFiles));
}
module.exports = {
injectMockDevice,
connectMockDevice,
overrideFirmwareURLs,
goToManualMode,
readMockFile,
mockPathExists,
getWrittenFiles,
};

28
tests/helpers/paths.js Normal file
View File

@@ -0,0 +1,28 @@
const path = require('path');
const CACHED_ASSETS = path.resolve(__dirname, '..', 'cached_assets');
const FIRMWARE_PATH = path.join(CACHED_ASSETS, 'kobo-update-4.45.23646.zip');
const WEBROOT = path.resolve(__dirname, '..', '..', 'web', 'dist');
const WEBROOT_FIRMWARE = path.join(WEBROOT, '_test_firmware.zip');
// Expected SHA1 checksums for Kobo Libra Color, firmware 4.45.23646,
// with only "Remove footer (row3) on new home screen" enabled.
const EXPECTED_SHA1 = {
'usr/local/Kobo/libnickel.so.1.0.0': 'ef64782895a47ac85f0829f06fffa4816d23512d',
'usr/local/Kobo/nickel': '80a607bac515457a6864be8be831df631a01005c',
'usr/local/Kobo/libadobe.so': '02dc99c71c4fef75401cd49ddc2e63f928a126e1',
'usr/local/Kobo/librmsdk.so.1.0.0': 'e3819260c9fc539a53db47e9d3fe600ec11633d5',
};
// SHA1 of the original unmodified KoboRoot.tgz inside firmware 4.45.23646.
const ORIGINAL_TGZ_SHA1 = 'b5c3307e8e7ec036f4601135f0b741c37b899db4';
module.exports = {
FIRMWARE_PATH,
WEBROOT,
WEBROOT_FIRMWARE,
EXPECTED_SHA1,
ORIGINAL_TGZ_SHA1,
};

33
tests/helpers/tar.js Normal file
View File

@@ -0,0 +1,33 @@
/**
* Parse a tar archive (uncompressed) and return a map of entry name -> Buffer.
*/
function parseTar(buffer) {
const entries = {};
let offset = 0;
while (offset < buffer.length) {
const header = buffer.subarray(offset, offset + 512);
if (header.every(b => b === 0)) break;
let name = header.subarray(0, 100).toString('utf8').replace(/\0+$/, '');
const prefix = header.subarray(345, 500).toString('utf8').replace(/\0+$/, '');
if (prefix) name = prefix + '/' + name;
name = name.replace(/^\.\//, '');
const sizeStr = header.subarray(124, 136).toString('utf8').replace(/\0+$/, '').trim();
const size = parseInt(sizeStr, 8) || 0;
const typeFlag = header[156];
offset += 512;
if (typeFlag === 48 || typeFlag === 0) {
entries[name] = buffer.subarray(offset, offset + size);
}
offset += Math.ceil(size / 512) * 512;
}
return entries;
}
module.exports = { parseTar };