Files
computer-playbook/roles/web-app-zammad/files/playwright/test-websocket-realtime.js
kevinveenbirkenbachandClaude Opus 4.8 4c91df5de7 test(e2e): forbid raw page.goto, migrate all specs to Tor-resilient gotoOnion
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:59:38 +02:00

77 lines
2.7 KiB
JavaScript

const { test, expect, request } = require("@playwright/test");
const { resolveTimeout } = require("./timeouts");
const { gotoOnion } = require("./personas");
async function seedTicketViaApi(baseUrl, adminApiUsername, adminApiPassword, subject) {
const api = await request.newContext({
ignoreHTTPSErrors: true,
extraHTTPHeaders: {
Authorization: `Basic ${Buffer.from(`${adminApiUsername}:${adminApiPassword}`).toString("base64")}`,
"Content-Type": "application/json",
},
});
const resp = await api.post(`${baseUrl}/api/v1/tickets`, {
data: {
title: subject,
group: "Users",
customer: adminApiUsername,
article: { subject, body: "Seed.", type: "note", internal: false },
},
});
if (resp.status() >= 300) {
throw new Error(`Seed POST /api/v1/tickets failed: ${resp.status()} ${await resp.text()}`);
}
const ticket = await resp.json();
await api.dispose();
return ticket;
}
async function appendArticleViaApi(baseUrl, adminApiUsername, adminApiPassword, ticketId, body) {
const api = await request.newContext({
ignoreHTTPSErrors: true,
extraHTTPHeaders: {
Authorization: `Basic ${Buffer.from(`${adminApiUsername}:${adminApiPassword}`).toString("base64")}`,
"Content-Type": "application/json",
},
});
const resp = await api.post(`${baseUrl}/api/v1/ticket_articles`, {
data: { ticket_id: ticketId, subject: "ws-update", body, type: "note", internal: false },
});
await api.dispose();
if (resp.status() >= 300) {
throw new Error(`Append article failed: ${resp.status()}`);
}
}
exports.register = function (shared) {
test("administrator: zammad-websocket pushes ticket updates to an open session in real time", async ({ page }) => {
expect(shared.env.adminApiUsername, "ADMIN_API_USERNAME must be set").toBeTruthy();
expect(shared.env.adminApiPassword, "ADMIN_API_PASSWORD must be set").toBeTruthy();
const subject = `playwright-ws-${Date.now()}`;
const ticket = await seedTicketViaApi(
shared.env.zammadBaseUrl,
shared.env.adminApiUsername,
shared.env.adminApiPassword,
subject
);
await shared.signInAsApiBot(page);
await gotoOnion(page, `${shared.env.zammadBaseUrl}/#ticket/zoom/${ticket.id}`, { waitUntil: "domcontentloaded" });
await expect(page.locator("body")).toContainText(subject, { timeout: resolveTimeout(60_000) });
const wsMarker = `ws-realtime-${Date.now()}`;
await appendArticleViaApi(
shared.env.zammadBaseUrl,
shared.env.adminApiUsername,
shared.env.adminApiPassword,
ticket.id,
wsMarker
);
await expect(page.locator("body")).toContainText(wsMarker, { timeout: resolveTimeout(60_000) });
await shared.zammadLogout(page);
});
};