diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json new file mode 100644 index 00000000..71b2d1bc --- /dev/null +++ b/tests/playwright/package-lock.json @@ -0,0 +1,56 @@ +{ + "name": "api-examples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "playwright": "^1.60.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/tests/playwright/package.json b/tests/playwright/package.json new file mode 100644 index 00000000..f2eab146 --- /dev/null +++ b/tests/playwright/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "playwright": "^1.60.0" + } +} diff --git a/tests/playwright/test-results/.last-run.json b/tests/playwright/test-results/.last-run.json new file mode 100644 index 00000000..5fca3f84 --- /dev/null +++ b/tests/playwright/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/tests/playwright/test_notebook.mjs b/tests/playwright/test_notebook.mjs new file mode 100644 index 00000000..994a5926 --- /dev/null +++ b/tests/playwright/test_notebook.mjs @@ -0,0 +1,92 @@ +import { chromium } from "playwright"; + +const URL = + "http://localhost:8000/lab/index.html?path=experiments/relax_structure_with_uma.ipynb"; + +(async () => { + const browser = await chromium.launch({ channel: "chrome", headless: false }); + const page = await browser.newPage(); + + console.log("⏳ Navigating to JupyterLite..."); + await page.goto(URL, { waitUntil: "domcontentloaded", timeout: 60_000 }); + + // Wait for kernel idle + console.log("⏳ Waiting for Pyodide kernel..."); + await page.waitForFunction( + () => { + const ind = document.querySelector(".jp-Notebook-ExecutionIndicator"); + return ind?.dataset?.status === "idle" && document.querySelectorAll(".jp-CodeCell").length > 5; + }, + { timeout: 120_000, polling: 2_000 } + ); + console.log("✅ Kernel idle!"); + + // Click Restart & Run All + await page.locator('button[data-command="runmenu:restart-and-run-all"]').click(); + await page.waitForTimeout(1_000); + try { + await page.locator(".jp-Dialog button", { hasText: /restart/i }).click({ timeout: 3_000 }); + console.log("✅ Restart & Run All confirmed!"); + } catch { + console.log("⚠ No dialog"); + } + + // Monitor with logging until all cells done + console.log("\n📊 Monitoring..."); + const start = Date.now(); + + while (Date.now() - start < 300_000) { + await page.waitForTimeout(10_000); + + const s = await page.evaluate(() => { + const cells = document.querySelectorAll(".jp-CodeCell"); + let running = 0, done = 0, errors = 0; + cells.forEach((c) => { + const p = c.querySelector(".jp-InputPrompt")?.textContent?.trim() || ""; + if (p === "[*]:") running++; + else if (/\[\d+\]:/.test(p)) done++; + if (c.querySelector(".jp-RenderedText[data-mime-type='application/vnd.jupyter.stderr']")) errors++; + }); + const k = document.querySelector(".jp-Notebook-ExecutionIndicator")?.dataset?.status; + return { running, done, errors, total: cells.length, k }; + }); + + const t = ((Date.now() - start) / 1000) | 0; + console.log(` [${t}s] kernel=${s.k} running=${s.running} done=${s.done}/${s.total} errors=${s.errors}`); + + if (s.k === "idle" && s.running === 0 && s.done > 0) break; + } + + // Final report + console.log("\n" + "=".repeat(60)); + const results = await page.evaluate(() => + Array.from(document.querySelectorAll(".jp-CodeCell")).map((c, i) => { + let t = ""; + for (const o of c.querySelectorAll(".jp-OutputArea-output")) t += o.textContent; + return { cell: i + 1, err: t.includes("Traceback"), out: t }; + }) + ); + + let failures = 0; + for (const r of results) { + if (r.err) { + failures++; + console.log(`Cell ${r.cell} ❌`); + console.log(r.out.substring(0, 1500)); + console.log(); + } else { + const preview = r.out.trim().substring(0, 120).replace(/\n/g, " | "); + console.log(`Cell ${r.cell} ✅${preview ? ": " + preview : ""}`); + } + } + + console.log("=".repeat(60)); + if (failures > 0) { + console.log(`\n⚠ ${failures} cell(s) failed`); + process.exitCode = 1; + } else { + console.log(`\n🎉 ALL ${results.length} CELLS PASSED!`); + } + + await browser.close(); +})();