Home Manual Reference Source Test

tests/system/correctness.test.js

  1. import { describe, it, expect, beforeAll, afterAll } from "vitest";
  2. import { chromium } from "playwright";
  3. import { unlinkSync } from "fs";
  4. import { tmpdir } from "os";
  5. import { join } from "path";
  6. import { findChrome } from "../../config/find-chrome.js";
  7. describe("system: pipeline workflow with default parameters", () => {
  8. let browser, page;
  9. beforeAll(async () => {
  10. const url = "http://localhost:8080";
  11. const resp = await fetch(url).catch(() => null);
  12. if (!resp?.ok) {
  13. throw new Error(
  14. `App not reachable at ${url}. Start it first: npm run app`,
  15. );
  16. }
  17. browser = await chromium.launch({
  18. executablePath: findChrome(),
  19. headless: false,
  20. args: [
  21. "--no-sandbox",
  22. "--disable-setuid-sandbox",
  23. "--disable-dev-shm-usage",
  24. ],
  25. });
  26. page = await browser.newPage();
  27. await page.goto(url);
  28. }, 30000);
  29. afterAll(async () => {
  30. await browser?.close();
  31. });
  32. it("app initializes and reaches ready state", async () => {
  33. const status = page.locator("#output-status");
  34. await status.waitFor({ state: "visible", timeout: 20000 });
  35. const text = await status.textContent();
  36. expect(text).toMatch(/Ready/);
  37. }, 30000);
  38. it("parameters round-trip through export and re-import", async () => {
  39. // export: intercept the download
  40. const [download] = await Promise.all([
  41. page.waitForEvent("download"),
  42. page.click("button:has-text('Export')"),
  43. ]);
  44. const exportPath = join(tmpdir(), "params-export.json");
  45. await download.saveAs(exportPath);
  46. // import: inject the saved file into the hidden file input
  47. const fileInput = page.locator("#import-params");
  48. await fileInput.setInputFiles(exportPath);
  49. // verify the filename field shows the imported file
  50. const nameField = page.locator("#import-params-name");
  51. await nameField.waitFor({ state: "visible", timeout: 5000 });
  52. const val = await nameField.inputValue();
  53. expect(val).toMatch(/\.json$/);
  54. unlinkSync(exportPath);
  55. }, 30000);
  56. it("Run button produces MIDI output", async () => {
  57. await page.click("#run-optimization-button");
  58. // wait until src attribute is set to a MIDI data URL
  59. await page.waitForFunction(
  60. () => {
  61. const el = document.getElementById("midi-player");
  62. return el?.getAttribute("src")?.startsWith("data:audio/midi;base64,");
  63. },
  64. { timeout: 60000 },
  65. );
  66. const src = await page.locator("#midi-player").getAttribute("src");
  67. expect(src).toMatch(/^data:audio\/midi;base64,/);
  68. }, 90000);
  69. it("MIDI player plays back from start to stop event", async () => {
  70. // wait until the player has parsed the MIDI (duration available)
  71. await page.waitForFunction(
  72. () => document.getElementById("midi-player")?.duration > 0,
  73. { timeout: 10000 },
  74. );
  75. // subscribe to start + stop BEFORE clicking play, then await both in order
  76. await page.evaluate(async () => {
  77. const player = document.getElementById("midi-player");
  78. const started = new Promise((resolve) => {
  79. player.addEventListener("start", () => resolve(), { once: true });
  80. });
  81. const stopped = new Promise((resolve) => {
  82. player.addEventListener("stop", () => resolve(), { once: true });
  83. });
  84. const btn = player.shadowRoot?.querySelector("button[title='play']");
  85. if (btn) {
  86. btn.click();
  87. } else {
  88. player.start?.();
  89. }
  90. await started;
  91. await stopped;
  92. });
  93. }, 60000);
  94. it("MIDI download button triggers a file download", async () => {
  95. const [download] = await Promise.all([
  96. page.waitForEvent("download"),
  97. page.click("button:has-text('Download')"),
  98. ]);
  99. const filename = download.suggestedFilename();
  100. expect(filename).toMatch(/\.mid$/);
  101. }, 30000);
  102. });