diff --git a/package-lock.json b/package-lock.json index dcef29d446..91eec4fbac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36011,7 +36011,9 @@ "waku-run": "dist/src/cli.js" }, "devDependencies": { + "@playwright/test": "^1.51.1", "@types/chai": "^4.3.11", + "@types/express": "^4.17.21", "@types/mocha": "^10.0.6", "@waku/core": "*", "@waku/interfaces": "*", @@ -36019,6 +36021,8 @@ "@waku/utils": "*", "chai": "^4.3.10", "cspell": "^8.6.1", + "esbuild": "^0.21.5", + "express": "^4.21.2", "mocha": "^10.3.0", "npm-run-all": "^4.1.5", "ts-node": "^10.9.2", @@ -36029,6 +36033,436 @@ "node": ">=18" } }, + "packages/run/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/run/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "packages/sdk": { "name": "@waku/sdk", "version": "0.0.35", diff --git a/packages/run/package.json b/packages/run/package.json index 7d58720bbb..a96a8da5e1 100644 --- a/packages/run/package.json +++ b/packages/run/package.json @@ -32,32 +32,40 @@ ], "scripts": { "build": "tsc", + "build:web": "esbuild web/index.ts --bundle --format=esm --platform=browser --outdir=dist/web && cp web/index.html dist/web/index.html", "prepublishOnly": "npm run build", "start": "tsx scripts/start.ts", "stop": "docker compose down", "restart": "npm run stop && npm run start", "logs": "docker compose logs -f", "info": "tsx scripts/info.ts", - "test": "NODE_ENV=test node ./src/run-tests.js \"tests/**/*.spec.ts\"", + "test": "NODE_ENV=test node ./src/run-tests.js \"tests/basic.spec.ts\"", + "test:browser": "npm run build:web && npx playwright test --reporter=line", "fix": "run-s fix:*", - "fix:lint": "eslint src scripts tests --fix", + "fix:lint": "eslint src scripts tests web --fix", "check": "run-s check:*", "check:tsc": "tsc -p tsconfig.dev.json", - "check:lint": "eslint src scripts tests", + "check:lint": "eslint src scripts tests web", "check:spelling": "cspell \"{README.md,src/**/*.ts,scripts/**/*.ts,tests/**/*.ts}\"" }, "engines": { "node": ">=18" }, - "devDependencies": { - "@types/chai": "^4.3.11", - "@types/mocha": "^10.0.6", + "dependencies": { "@waku/core": "*", "@waku/interfaces": "*", "@waku/sdk": "*", - "@waku/utils": "*", + "@waku/utils": "*" + }, + "devDependencies": { + "@playwright/test": "^1.51.1", + "@types/chai": "^4.3.11", + "@types/express": "^4.17.21", + "@types/mocha": "^10.0.6", "chai": "^4.3.10", "cspell": "^8.6.1", + "esbuild": "^0.21.5", + "express": "^4.21.2", "mocha": "^10.3.0", "npm-run-all": "^4.1.5", "ts-node": "^10.9.2", diff --git a/packages/run/playwright.config.ts b/packages/run/playwright.config.ts new file mode 100644 index 0000000000..0aca782b77 --- /dev/null +++ b/packages/run/playwright.config.ts @@ -0,0 +1,22 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + testMatch: "**/browser.spec.ts", + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: "line", + timeout: 120000, // 2 minutes per test + use: { + trace: "on-first-retry", + headless: true + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] } + } + ] +}); diff --git a/packages/run/tests/basic.spec.ts b/packages/run/tests/basic.spec.ts index 2e7e521cc8..995ed83a93 100644 --- a/packages/run/tests/basic.spec.ts +++ b/packages/run/tests/basic.spec.ts @@ -2,7 +2,7 @@ import { execSync } from "child_process"; import { createEncoder } from "@waku/core"; import type { LightNode } from "@waku/interfaces"; -import { createLightNode, Protocols, waitForRemotePeer } from "@waku/sdk"; +import { createLightNode, Protocols } from "@waku/sdk"; import { createRoutingInfo } from "@waku/utils"; import { expect } from "chai"; diff --git a/packages/run/tests/browser.spec.ts b/packages/run/tests/browser.spec.ts new file mode 100644 index 0000000000..8c9f8ae2f5 --- /dev/null +++ b/packages/run/tests/browser.spec.ts @@ -0,0 +1,189 @@ +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { Browser, chromium, expect, Page, test } from "@playwright/test"; + +import { startTestServer, stopTestServer } from "./test-server.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +test.describe.configure({ mode: "serial" }); + +test.describe("Waku Run - Browser Test", () => { + let browser: Browser; + let page: Page; + const testPort = 8080; + const baseUrl = `http://localhost:${testPort}`; + + test.beforeAll(async () => { + // Start test HTTP server + await startTestServer(testPort); + + // Start docker compose + execSync("docker compose up -d", { + stdio: "inherit", + cwd: join(__dirname, "..") + }); + + // Wait for nodes to be ready + const maxRetries = 30; + const retryDelay = 2000; + let ready = false; + + for (let i = 0; i < maxRetries; i++) { + try { + await fetch("http://127.0.0.1:8646/debug/v1/info"); + await fetch("http://127.0.0.1:8647/debug/v1/info"); + ready = true; + break; + } catch { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + } + } + + if (!ready) { + throw new Error("Nodes failed to start within expected time"); + } + + // Connect the two nwaku nodes together + const node1Info = await fetch("http://127.0.0.1:8646/debug/v1/info").then( + (r) => r.json() + ); + const peer1Multiaddr = node1Info.listenAddresses[0]; + + await fetch("http://127.0.0.1:8647/admin/v1/peers", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([peer1Multiaddr]) + }); + + // Wait for connection to establish + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Launch browser + browser = await chromium.launch({ + headless: true, + args: ["--no-sandbox", "--disable-setuid-sandbox"] + }); + + page = await browser.newPage(); + + // Forward browser console to test logs + page.on("console", (msg) => { + const type = msg.type(); + const text = msg.text(); + console.log(`[Browser Console ${type.toUpperCase()}] ${text}`); + }); + + page.on("pageerror", (error) => { + console.error("[Browser Page Error]", error.message); + }); + + // Navigate to test page + await page.goto(`${baseUrl}/index.html`, { + // cspell:ignore networkidle - Playwright waitUntil option + waitUntil: "networkidle" + }); + + // Wait for wakuBrowser to be available + await page.waitForFunction( + () => { + return ( + (window as any).wakuBrowser && + typeof (window as any).wakuBrowser.createAndStartNode === "function" + ); + }, + { timeout: 10000 } + ); + }); + + test.afterAll(async () => { + if (page) { + try { + await page.evaluate(() => (window as any).wakuBrowser.stop()); + } catch { + // Ignore errors + } + } + + if (browser) { + await browser.close(); + } + + execSync("docker compose down", { + stdio: "inherit", + cwd: join(__dirname, "..") + }); + + await stopTestServer(); + }); + + test("should initialize Waku node in browser", async () => { + test.setTimeout(120000); // 2 minutes timeout for this test + + const node1Port = process.env.NODE1_WS_PORT || "60000"; + const node2Port = process.env.NODE2_WS_PORT || "60001"; + + // Fetch node info to get peer IDs + const node1Info = await fetch("http://127.0.0.1:8646/debug/v1/info").then( + (r) => r.json() + ); + const node2Info = await fetch("http://127.0.0.1:8647/debug/v1/info").then( + (r) => r.json() + ); + + const peer1 = node1Info.listenAddresses[0].split("/p2p/")[1]; + const peer2 = node2Info.listenAddresses[0].split("/p2p/")[1]; + + const config = { + bootstrapPeers: [ + `/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${peer1}`, + `/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${peer2}` + ], + networkConfig: { + clusterId: 0, + numShardsInCluster: 8 + } + }; + + // Create and start waku node in browser + console.log("Creating Waku node in browser with config:", config); + + const createResult = await page.evaluate(async (cfg) => { + try { + console.log("Browser: Starting node creation..."); + const result = await (window as any).wakuBrowser.createAndStartNode( + cfg + ); + console.log("Browser: Node created successfully"); + return { success: true, result }; + } catch (error: any) { + console.error("Browser: Error creating node:", error); + return { success: false, error: error.message || String(error) }; + } + }, config); + + console.log("Create result:", createResult); + expect(createResult.success).toBe(true); + + // Verify the node was created and has the expected properties + const nodeInfo = await page.evaluate(() => { + const waku = (window as any).wakuBrowser.waku; + if (!waku) { + return { created: false }; + } + return { + created: true, + hasLightPush: !!waku.lightPush, + peerId: waku.libp2p.peerId.toString() + }; + }); + + console.log("Node info:", nodeInfo); + expect(nodeInfo.created).toBe(true); + expect(nodeInfo.hasLightPush).toBe(true); + expect(nodeInfo.peerId).toBeDefined(); + }); +}); diff --git a/packages/run/tests/test-server.ts b/packages/run/tests/test-server.ts new file mode 100644 index 0000000000..33aa1901fd --- /dev/null +++ b/packages/run/tests/test-server.ts @@ -0,0 +1,38 @@ +import type { Server } from "http"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import express from "express"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +let server: Server | null = null; + +export async function startTestServer(port: number = 8080): Promise { + const app = express(); + const webDir = join(__dirname, "..", "dist", "web"); + + app.use(express.static(webDir)); + + return new Promise((resolve, reject) => { + server = app + .listen(port, () => { + resolve(port); + }) + .on("error", (error: NodeJS.ErrnoException) => { + reject(error); + }); + }); +} + +export async function stopTestServer(): Promise { + if (server) { + return new Promise((resolve) => { + server!.close(() => { + server = null; + resolve(); + }); + }); + } +} diff --git a/packages/run/tsconfig.dev.json b/packages/run/tsconfig.dev.json index 2e8879a53e..09d78ee4ab 100644 --- a/packages/run/tsconfig.dev.json +++ b/packages/run/tsconfig.dev.json @@ -3,5 +3,5 @@ "compilerOptions": { "rootDir": "." }, - "include": ["src", "scripts", "tests"] + "include": ["src", "scripts", "tests", "web", "playwright.config.ts"] } diff --git a/packages/run/web/index.html b/packages/run/web/index.html new file mode 100644 index 0000000000..35ae40648d --- /dev/null +++ b/packages/run/web/index.html @@ -0,0 +1,13 @@ + + + + + + Waku Run Browser Test + + +

Waku Run Browser Test

+

This page is used for automated browser testing of js-waku.

+ + + diff --git a/packages/run/web/index.ts b/packages/run/web/index.ts new file mode 100644 index 0000000000..374c58e44b --- /dev/null +++ b/packages/run/web/index.ts @@ -0,0 +1,103 @@ +/* eslint-disable no-console */ +import { createEncoder } from "@waku/core"; +import type { LightNode } from "@waku/interfaces"; +import { createLightNode, Protocols } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; + +declare global { + interface Window { + wakuBrowser: WakuBrowser; + } +} + +class WakuBrowser { + private waku: LightNode | null = null; + + public async createAndStartNode(config: { + bootstrapPeers: string[]; + networkConfig: { clusterId: number; numShardsInCluster: number }; + }): Promise<{ success: boolean }> { + console.log("Creating light node..."); + this.waku = await createLightNode({ + defaultBootstrap: false, + bootstrapPeers: config.bootstrapPeers, + networkConfig: config.networkConfig + }); + + console.log("Starting node..."); + await this.waku.start(); + + // Explicitly dial each bootstrap peer + console.log("Dialing bootstrap peers..."); + for (const peer of config.bootstrapPeers) { + try { + console.log(`Dialing ${peer}...`); + await this.waku.dial(peer); + console.log(`Successfully dialed ${peer}`); + } catch (error) { + console.warn(`Failed to dial ${peer}:`, error); + } + } + + console.log("Waiting for peers..."); + try { + await this.waku.waitForPeers([Protocols.LightPush], 30000); // 30 second timeout + console.log("Peers found!"); + } catch (error) { + console.warn("Timeout waiting for peers, continuing anyway:", error); + // Continue anyway - we can still try to send messages + } + + // Check connected peers + const peers = this.waku.libp2p.getPeers(); + console.log( + `Connected to ${peers.length} peers:`, + peers.map((p) => p.toString()) + ); + + return { success: true }; + } + + public async sendLightPushMessage( + contentTopic: string, + message: string + ): Promise<{ + success: boolean; + successCount: number; + failureCount: number; + }> { + if (!this.waku) { + throw new Error("Waku node not started"); + } + + const networkConfig = { + clusterId: 0, + numShardsInCluster: 8 + }; + + const routingInfo = createRoutingInfo(networkConfig, { contentTopic }); + const encoder = createEncoder({ contentTopic, routingInfo }); + + const result = await this.waku.lightPush.send(encoder, { + payload: new TextEncoder().encode(message) + }); + + return { + success: result.successes.length > 0, + successCount: result.successes.length, + failureCount: result.failures?.length || 0 + }; + } + + public async stop(): Promise<{ success: boolean }> { + if (this.waku) { + await this.waku.stop(); + this.waku = null; + } + return { success: true }; + } +} + +// Expose to window +window.wakuBrowser = new WakuBrowser(); +console.log("WakuBrowser initialized");