Files
HouseHub/test-engine.js
T
2026-05-04 11:34:33 +02:00

340 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* PachaFamily - Test Engine 🦙
* Moteur de tests automatisés (E2E) - Version Anti-Flakiness
*/
const PachaTestEngine = {
arena: document.getElementById("test-arena"),
reportBox: document.getElementById("test-report"),
doc: null,
// ==========================================
// 🛠️ HELPERS
// ==========================================
wait: (ms) => new Promise((r) => setTimeout(r, ms)),
log: function (msg, icon = "️") {
this.reportBox.value += `[${new Date().toLocaleTimeString()}] ${icon} ${msg}\n`;
this.reportBox.scrollTop = this.reportBox.scrollHeight;
},
assert: function (cond, msgPass, msgFail) {
this.log(cond ? msgPass : msgFail || msgPass, cond ? "✅" : "❌");
},
// 🛡️ NOUVEAU : Attend le rechargement de la page après une action
actionAndWaitForReload: async function (actionFn, timeout = 5000) {
return new Promise(async (resolve) => {
let reloaded = false;
// On écoute le prochain rechargement de l'iframe
this.arena.onload = () => {
reloaded = true;
this.doc = this.arena.contentWindow.document;
this.arena.contentWindow.confirm = () => true;
this.arena.contentWindow.alert = () => true;
resolve();
};
await actionFn(); // On lance le clic ou la soumission
// Sécurité anti-blocage (si le fetch échoue et ne recharge pas la page)
setTimeout(() => {
if (!reloaded) {
this.log(
"⚠️ Le rechargement de la page n'a pas eu lieu dans le temps imparti.",
"⏱️",
);
resolve();
}
}, timeout);
});
},
load: async function (url) {
this.log(`Chargement de la page : ${url}`, "🔄");
return new Promise((resolve) => {
this.arena.onload = () => {
this.doc = this.arena.contentWindow.document;
this.arena.contentWindow.confirm = () => true;
this.arena.contentWindow.alert = () => true;
resolve();
};
this.arena.src = url;
});
},
get: function (sel) {
return this.doc.querySelector(sel);
},
// 🖱️ Clic unique et infaillible
click: async function (sel, delay = 500) {
const el = typeof sel === "string" ? this.get(sel) : sel;
if (el) {
// 1. Coupe les popups bloquantes
this.arena.contentWindow.confirm = () => true;
this.arena.contentWindow.alert = () => true;
// 2. Un seul et unique clic !
el.click();
await this.wait(delay);
return true;
}
return false;
},
fill: function (sel, val) {
const el = this.get(sel);
if (el) el.value = val;
},
select: async function (sel, val, delay = 300) {
const el = this.get(sel);
if (el) {
el.value = val;
el.dispatchEvent(new Event("change"));
await this.wait(delay);
}
},
findInTable: function (text) {
return Array.from(this.doc.querySelectorAll("td, div")).find((el) =>
el.textContent.includes(text),
);
},
// ==========================================
// 🧪 SCÉNARIO 1 : SUIVI MENSUEL
// ==========================================
runBudgetTests: async function () {
this.reportBox.value = "";
this.log("=== 🚀 DÉBUT DU SCÉNARIO : SUIVI BUDGET ===", "INFO");
await this.load("budget.php?tab=suivi");
await this.wait(1000);
if (!this.get(".btn-add-item")) {
this.log("Mois clôturé, test annulé. Déverrouille-le !", "⚠️");
return;
}
await this.click("button[onclick=\"toggleDiv('pendingDetailsList')\"]");
this.assert(
this.get("#pendingDetailsList").style.display !== "none",
"Déploiement : Charges à venir",
);
await this.click(".btn-add-item");
await this.select("#modalCatSelect", "Autres");
this.fill("#modalAmount", "42.42");
this.fill("#modalLabelInput", "TEST_AUTO_PACHA");
this.log("Enregistrement de la dépense...", "WARN");
await this.actionAndWaitForReload(async () => {
await this.click('#manualExpenseModal button[type="submit"]');
});
let targetTd = Array.from(this.doc.querySelectorAll("td")).find((el) =>
el.textContent.includes("TEST_AUTO_PACHA"),
);
this.assert(
targetTd !== undefined,
"Dépense créée avec succès (42.42€).",
"Échec : Dépense introuvable.",
);
if (targetTd) {
this.log("Nettoyage de la base de données...", "WARN");
await this.actionAndWaitForReload(async () => {
await this.click(
targetTd.closest("tr").querySelector('a[href*="delete_expense"]'),
);
});
const checkGone = Array.from(this.doc.querySelectorAll("td")).find((el) =>
el.textContent.includes("TEST_AUTO_PACHA"),
);
this.assert(
checkGone === undefined,
"Base de données nettoyée avec succès.",
);
}
this.log("=== 🏁 FIN DU SCÉNARIO ===", "INFO");
},
// ==========================================
// 🧪 SCÉNARIO 2 : BUDGET PRÉVISIONNEL
// ==========================================
testBudgetPrev: async function () {
this.reportBox.value = "";
this.log("=== 🚀 DÉBUT DU SCÉNARIO : BUDGET PRÉVISIONNEL ===", "INFO");
await this.load("budget.php?tab=budget_prev");
await this.wait(1000);
this.log("🔍 Étape 1 : Test du mode Somme flottant...", "INFO");
await this.click("#fabSumMode");
this.assert(
this.doc.body.classList.contains("sum-mode-active"),
"Le mode somme s'active.",
"Le bouton somme ne répond pas.",
);
const firstSalaryInput = this.get('input[data-field="salary"]');
if (firstSalaryInput) {
await this.click(firstSalaryInput, 500);
this.assert(
firstSalaryInput.classList.contains("sum-selected"),
"La cellule est bien sélectionnée.",
);
const sumValue = this.get("#sumResultValue").innerText;
this.assert(
sumValue !== "0,00 €" && sumValue !== "0 €",
`Le total interactif affiche : ${sumValue}`,
"Le calcul de la somme a échoué.",
);
}
await this.click(".pf-sum-close");
this.assert(
!this.doc.body.classList.contains("sum-mode-active"),
"Le mode somme se désactive.",
);
this.log("🔍 Étape 2 : Test de sauvegarde de la note...", "INFO");
const noteArea = this.get("#monthNoteArea");
if (noteArea) {
const oldNote = noteArea.value;
this.fill("#monthNoteArea", "🤖 Test Auto");
await this.click('button[onclick^="saveGenericNote"]', 1500);
const indicator = this.get("#note-save-indicator");
this.assert(
indicator !== null,
"Sauvegarde asynchrone exécutée.",
"L'indicateur de sauvegarde ne s'est pas affiché.",
);
this.fill("#monthNoteArea", oldNote);
await this.click('button[onclick^="saveGenericNote"]', 500);
}
const uniqueCatName = "TEST_CAT_" + Math.floor(Math.random() * 10000);
this.log(
`🔍 Étape 3 : Création de la catégorie '${uniqueCatName}'...`,
"INFO",
);
await this.click('button[onclick*="addCatModal"]');
this.assert(
this.get("#addCatModal").style.display === "flex",
"Ouverture de la modale d'ajout.",
);
this.fill('#addCatModal input[name="name"]', uniqueCatName);
await this.select('#addCatModal select[name="target"]', "vers commune");
this.log("Attente du rechargement après création...", "WARN");
// 🔄 UTILISATION DU NOUVEAU HELPER SYNCHRONE
await this.actionAndWaitForReload(async () => {
await this.click('#addCatModal button[type="submit"]');
});
const newCatCell = Array.from(
this.doc.querySelectorAll(".prev-alloc-table td, .prev-alloc-table div"),
).find((el) => el.textContent.includes(uniqueCatName));
this.assert(
newCatCell !== undefined,
`SUCCÈS : '${uniqueCatName}' insérée au tableau !`,
"ÉCHEC : Ligne introuvable après rechargement.",
);
if (newCatCell) {
this.log("🧹 Étape 4 : Nettoyage de la BDD...", "WARN");
const row = newCatCell.closest("tr");
const delBtn = row.querySelector(".delete");
if (delBtn) {
// 🔄 UTILISATION DU NOUVEAU HELPER SYNCHRONE
await this.actionAndWaitForReload(async () => {
await this.click(delBtn);
});
const checkGone = Array.from(
this.doc.querySelectorAll(".prev-alloc-table td"),
).find((td) => td.textContent.includes(uniqueCatName));
this.assert(
checkGone === undefined,
"NETTOYAGE PARFAIT : Ligne effacée.",
"La ligne est restée dans le tableau.",
);
} else {
this.log("Bouton de suppression (.delete) introuvable.", "FAIL");
}
}
this.log("=== 🏁 FIN DU SCÉNARIO ===", "INFO");
},
};
// ============================================================================
// ROUTEUR DU LABORATOIRE ET BOUTON COPIER
// ============================================================================
document.getElementById("btn-run-test")?.addEventListener("click", async () => {
const selectedTest = document.getElementById("test-selector").value;
const btnRun = document.getElementById("btn-run-test");
btnRun.disabled = true;
btnRun.style.opacity = "0.5";
try {
switch (selectedTest) {
case "budget_suivi":
await PachaTestEngine.runBudgetTests();
break;
case "budget_prev":
await PachaTestEngine.testBudgetPrev();
break;
default:
PachaTestEngine.log(`Scénario non implémenté : ${selectedTest}`, "⚠️");
}
} catch (error) {
PachaTestEngine.log(`Erreur JS critique : ${error.message}`, "❌");
console.error(error);
}
btnRun.disabled = false;
btnRun.style.opacity = "1";
});
// 📋 Bouton "Copier"
document
.getElementById("btn-copy-report")
?.addEventListener("click", async () => {
const textArea = document.getElementById("test-report");
const reportText = textArea.value;
const successMsg = window.I18N
? window.I18N.tests_report_copied || "Rapport copié !"
: "Rapport copié !";
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(reportText);
alert(successMsg);
} else {
textArea.select();
document.execCommand("copy");
window.getSelection().removeAllRanges();
alert(successMsg);
}
} catch (err) {
alert("Erreur lors de la copie du rapport.");
console.error(err);
}
});