logos-storage-modules-e2e/tests/logos-ui-module-tests.mjs

170 lines
6.5 KiB
JavaScript
Raw Permalink Normal View History

2026-06-09 17:28:47 -03:00
#!/usr/bin/env node
import { resolve } from "node:path";
import { strict as assert } from "node:assert";
const MAGIC_RETRY_NUMBER = 3;
const qtMcpRoot = process.env.LOGOS_QT_MCP
const {run, test} = await import(resolve(qtMcpRoot, "test-framework/framework.mjs"));
const storageCoreLgx = process.env.STORAGE_CORE_LGX;
const storageUiLgx = process.env.STORAGE_UI_LGX;
async function sleepAsync(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sha1digest(path) {
const { createHash } = await import("node:crypto");
const { readFile } = await import("node:fs/promises");
const hash = createHash("sha1");
hash.update(await readFile(path));
return hash.digest("hex");
}
async function genRandomFile(path, size) {
const { randomBytes } = await import("node:crypto");
const { writeFile } = await import("node:fs/promises");
await writeFile(path, randomBytes(size));
}
async function findDialogByProperty(app, propertyName, value) {
const response = await app.listFileDialogs();
if (!response.ok) {
throw new Error(response.error);
}
const dialog = response.dialogs.find(dialog => dialog[propertyName] === value);
if (!dialog) {
throw new Error(`Dialog with property ${propertyName} = ${value} not found`);
}
return dialog;
}
async function findDialogByTitle(app, title) {
return findDialogByProperty(app, "title", title);
}
// Accepts a file dialog, while handling accept flakiness both for
// QFileDialog and QQuickFileDialog.
async function accept(app, dialogId) {
try {
// Accepts will sometimes be ignored, so we need to try multiple times.
do {
await app.fileDialogAction(dialogId, "accept");
await sleepAsync(300);
await findDialogByProperty(app, "id", dialogId);
// QQuickDialogs remain in the object tree but their "visible" state
// changes, so that's what we probe for.
let visible = await app.getProperty(dialogId, "visible");
if (!visible) {
break;
}
} while (true);
} catch (error) {
// For QFileDialog, the dialog will simply disappear once accept succeeds,
// which will cause subsequent find and click attempts to raise errors.
// So getting here means we're done.
}
}
// Reliably selects a folder in QQuickFileDialog while dealing with all
// the flakiness. Not needed for QDialog.
async function qtquickFileDialogSelect(app, dialogId, triggerName, path, folder) {
for (let i = 0; i < MAGIC_RETRY_NUMBER; i++) {
// With QQuickFileDialog, we need to select BEFORE clicking the trigger button.
await app.fileDialogAction(dialogId, "select", path);
await app.clickByProperty("objectName", triggerName);
await app.waitFor(() => app.expectPropertyOnObject(dialogId, "visible", true));
// Folders, on the other hand, need to be set AFTER the dialog is visible or it won't work!
if (folder) {
await app.fileDialogAction(dialogId, "selectFolder", folder);
}
// Even then, it might happen that selection does not work. In that case, we try again.
try {
await app.waitFor(() => app.expectPropertyOnObject(dialogId, "selectedFile", `file://${path}`));
return;
} catch (error) {
// Cancel and try again.
await app.fileDialogAction(dialogId, "cancel");
await app.waitFor(() => app.expectPropertyOnObject(dialogId, "visible", false));
}
}
throw new Error("Failed to select file in dialog");
}
async function selectCard(app, cardName) {
await app.waitFor(async() => {
await app.clickByProperty("objectName", `${cardName}MouseArea`);
const card = await app.findByProperty("objectName", cardName);
await app.expectPropertyOnObject(card.matches[0].id, "selected", true);
});
}
async function uiInstallModule(app, moduleTab, modulePath) {
console.log("Install module ", modulePath, " in tab ", moduleTab);
await app.click("Modules");
await app.click(moduleTab);
await app.click("Install LGX Package");
let dialog = await app.waitFor(
() => findDialogByTitle(app, "Select Plugin to Install")
);
await app.fileDialogAction(dialog.id, "select", modulePath);
await accept(app, dialog.id);
await app.expectTexts(["Install"]);
await app.click("Install", {exact: true});
console.log("Install succeeded.");
}
test("storage: assert basic module functionality", async (app) => {
// Installs modules.
await uiInstallModule(app, "Core Modules", storageCoreLgx);
await uiInstallModule(app, "UI Modules", storageUiLgx);
// Opens the storage app.
console.log("1. Opening storage.");
await app.waitFor(() => app.click("Storage"));
// Goes through the onboarding guide. Note that these options
// may not work on your machine.
console.log("2. Onboarding guide: step 1.");
await app.waitFor(() => app.expectTexts(["Guided"]));
await selectCard(app, "guidedCard");
await app.click("Continue");
console.log("3. Onboarding guide: step 2.");
await app.waitFor(() => app.expectTexts(["UPnP"]));
await selectCard(app, "upnpCard"); // In CI should use Port Forwarding
await app.waitFor(() => app.click("Continue"));
console.log("4. Onboarding guide: wait for node to be connected.");
await app.waitFor(() => app.expectTexts(
["Your node is up and reachable."],
{timeout: 20000, interval: 500, description: "Waiting for node to be up and reachable"}
));
await app.click("Continue");
console.log("5. Publish file.");
// Sets up a random file and publishes it.
await genRandomFile("/tmp/testfile.bin", 5 * 1024 * 1024); // 5MB file
const uploadDialog = await app.waitFor(() => findDialogByProperty(app, "objectName", "uploadDialog"));
await app.waitFor(() => qtquickFileDialogSelect(app, uploadDialog.id, "uploadButton", "/tmp/testfile.bin"));
await accept(app, uploadDialog.id);
await app.waitFor(() => app.expectTexts(["Complete"]));
console.log("6. Download file.");
// Downloads the file we just uploaded to a different location.
const saveDialog = await app.waitFor(() => findDialogByProperty(app, "objectName", "saveDialog"));
// We can't change the filename in the dialog, so write to the current folder.
await qtquickFileDialogSelect(app, saveDialog.id, "downloadButton", `${process.env.PWD}/testfile.bin`, process.env.PWD);
await accept(app, saveDialog.id);
console.log("7. Check file integrity.");
// Checks that the file was downloaded correctly.
const downloadedFile = `${process.env.PWD}/testfile.bin`;
const originalDigest = await sha1digest("/tmp/testfile.bin");
const downloadedDigest = await sha1digest(downloadedFile);
assert(originalDigest === downloadedDigest, "File digest mismatch");
});
run();