#!/usr/bin/env node "use strict"; const assert = require("node:assert"); const { chromium } = require("playwright"); const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000); async function main() { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext(); const page = await context.newPage(); const consoleErrors = []; const networkFailures = []; page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); }); page.on("requestfailed", (request) => { networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); }); await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); const result = await page.evaluate(async () => { const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`; const sessionA = `mnote-oo-browser-a-${suffix}`; const sessionB = `mnote-oo-browser-b-${suffix}`; const tokenA = `token-a-${suffix}`; const tokenB = `token-b-${suffix}`; async function postJson(path, payload) { const response = await fetch(path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); const body = await response.json().catch(() => null); return { status: response.status, body }; } async function getJson(path) { const response = await fetch(path); const body = response.status === 204 ? null : await response.json().catch(() => null); return { status: response.status, body }; } const registeredA = await postJson("/api/onlyoffice/bridge/session", { sessionId: sessionA, token: tokenA, editorType: "word", documentId: "doc_a", assetId: "asset_a", fileType: "docx", docKey: "doc_key_a", pageOrigin: location.origin, }); const registeredB = await postJson("/api/onlyoffice/bridge/session", { sessionId: sessionB, token: tokenB, editorType: "word", documentId: "doc_b", assetId: "asset_b", fileType: "docx", docKey: "doc_key_b", pageOrigin: location.origin, }); const wrongToken = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1`); const commandA = await postJson("/api/onlyoffice/bridge/commands", { sessionId: sessionA, token: tokenA, action: "document.insert_text", payload: { text: "A" }, }); const commandB = await postJson("/api/onlyoffice/bridge/commands", { sessionId: sessionB, token: tokenB, action: "document.insert_text", payload: { text: "B" }, }); const nextA = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1000`); const nextB = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1000`); const postedA = await postJson("/api/onlyoffice/bridge/results", { sessionId: sessionA, token: tokenA, id: nextA.body && nextA.body.id, ok: true, result: { text: "result-a" }, }); const postedB = await postJson("/api/onlyoffice/bridge/results", { sessionId: sessionB, token: tokenB, id: nextB.body && nextB.body.id, ok: true, result: { text: "result-b" }, }); const resultA = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&commandId=${encodeURIComponent(nextA.body && nextA.body.id)}&timeoutMs=1000`); const resultB = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&commandId=${encodeURIComponent(nextB.body && nextB.body.id)}&timeoutMs=1000`); const sessions = await getJson("/api/onlyoffice/bridge/sessions"); const currentBeforeClose = await getJson("/api/onlyoffice/bridge/session/current"); const closeA = await postJson("/api/onlyoffice/bridge/session/close", { sessionId: sessionA, token: tokenA, }); const nextAAfterClose = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1`); const sessionsAfterClose = await getJson("/api/onlyoffice/bridge/sessions"); return { sessionA, sessionB, registeredA, registeredB, wrongToken, commandA, commandB, nextA, nextB, postedA, postedB, resultA, resultB, sessions, currentBeforeClose, closeA, nextAAfterClose, sessionsAfterClose, }; }); assert.equal(result.registeredA.status, 200, JSON.stringify(result.registeredA)); assert.equal(result.registeredB.status, 200, JSON.stringify(result.registeredB)); assert.equal(result.registeredA.body.docKey, "doc_key_a"); assert.equal(result.registeredB.body.docKey, "doc_key_b"); assert.equal(result.registeredA.body.pageOrigin, BASE_URL); assert.equal(result.registeredB.body.pageOrigin, BASE_URL); assert.equal(result.wrongToken.status, 401, JSON.stringify(result.wrongToken)); assert.equal(result.commandA.status, 200, JSON.stringify(result.commandA)); assert.equal(result.commandB.status, 200, JSON.stringify(result.commandB)); assert.equal(result.nextA.status, 200, JSON.stringify(result.nextA)); assert.equal(result.nextB.status, 200, JSON.stringify(result.nextB)); assert.equal(result.nextA.body.action, "document.insert_text"); assert.equal(result.nextB.body.action, "document.insert_text"); assert.equal(result.nextA.body.payload.text, "A"); assert.equal(result.nextB.body.payload.text, "B"); assert.notEqual(result.nextA.body.id, result.nextB.body.id); assert.equal(result.postedA.status, 200, JSON.stringify(result.postedA)); assert.equal(result.postedB.status, 200, JSON.stringify(result.postedB)); assert.equal(result.resultA.status, 200, JSON.stringify(result.resultA)); assert.equal(result.resultB.status, 200, JSON.stringify(result.resultB)); assert.equal(result.resultA.body.result.text, "result-a"); assert.equal(result.resultB.body.result.text, "result-b"); const sessionARecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionA); const sessionBRecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionB); assert(sessionARecord, JSON.stringify(result.sessions)); assert(sessionBRecord, JSON.stringify(result.sessions)); assert.equal(sessionARecord.docKey, "doc_key_a"); assert.equal(sessionBRecord.pageOrigin, BASE_URL); assert.equal(result.currentBeforeClose.status, 200, JSON.stringify(result.currentBeforeClose)); assert(result.currentBeforeClose.body.session, JSON.stringify(result.currentBeforeClose)); assert.equal(result.closeA.status, 200, JSON.stringify(result.closeA)); assert.equal(result.closeA.body.closed, true, JSON.stringify(result.closeA)); assert.equal(result.nextAAfterClose.status, 401, JSON.stringify(result.nextAAfterClose)); assert(!result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionA)); assert(result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionB)); await browser.close(); console.log(JSON.stringify({ ok: true, task: "task516-onlyoffice-bridge-multisession-browser-smoke", baseUrl: BASE_URL, sessionA: result.sessionA, sessionB: result.sessionB, wrongToken: { status: result.wrongToken.status, code: result.wrongToken.body && result.wrongToken.body.code }, nextA: { id: result.nextA.body.id, text: result.nextA.body.payload.text }, nextB: { id: result.nextB.body.id, text: result.nextB.body.payload.text }, resultA: result.resultA.body.result, resultB: result.resultB.body.result, currentBeforeClose: { status: result.currentBeforeClose.status, sessionId: result.currentBeforeClose.body.session && result.currentBeforeClose.body.session.sessionId, }, closeA: result.closeA.body, consoleErrors, networkFailures, }, null, 2)); } main().catch((error) => { console.error(error && error.stack ? error.stack : error); process.exit(1); });