mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-18 18:13:46 +03:00
215 lines
7.4 KiB
JavaScript
215 lines
7.4 KiB
JavaScript
const path = require("path");
|
|
const jwt = require("jsonwebtoken");
|
|
const dotenv = require("dotenv");
|
|
const { expect } = require("@playwright/test");
|
|
|
|
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
|
|
|
const PUBLIC_SECRET = process.env.PUBLIC_JWT_SECRET || "change_me_public";
|
|
const PUBLIC_COOKIE_NAME = process.env.PUBLIC_COOKIE_NAME || "public_jwt";
|
|
|
|
function randomDigits(length) {
|
|
let value = "";
|
|
while (value.length < length) {
|
|
value += String(Math.floor(Math.random() * 10));
|
|
}
|
|
return value.slice(0, length);
|
|
}
|
|
|
|
function randomPhone() {
|
|
return `+79${randomDigits(9)}`;
|
|
}
|
|
|
|
function createPublicCookieToken(phone) {
|
|
return jwt.sign({ sub: phone, purpose: "CREATE_REQUEST" }, PUBLIC_SECRET, {
|
|
algorithm: "HS256",
|
|
expiresIn: "7d",
|
|
});
|
|
}
|
|
|
|
async function installPromptAutoAccept(page, code = "000000") {
|
|
page.on("dialog", async (dialog) => {
|
|
if (dialog.type() === "prompt") {
|
|
await dialog.accept(code);
|
|
return;
|
|
}
|
|
await dialog.accept();
|
|
});
|
|
}
|
|
|
|
async function installOtpBypassRoutes(page) {
|
|
await page.route("**/api/public/otp/send", async (route) => {
|
|
let purpose = "CREATE_REQUEST";
|
|
try {
|
|
const body = JSON.parse(route.request().postData() || "{}");
|
|
purpose = String(body.purpose || purpose);
|
|
} catch (_) {}
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
status: "sent",
|
|
purpose,
|
|
ttl_seconds: 600,
|
|
sms_response: { provider: "e2e", status: "accepted", message: "ok" },
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/public/otp/verify", async (route) => {
|
|
let purpose = "CREATE_REQUEST";
|
|
try {
|
|
const body = JSON.parse(route.request().postData() || "{}");
|
|
purpose = String(body.purpose || purpose);
|
|
} catch (_) {}
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ status: "verified", purpose }),
|
|
});
|
|
});
|
|
}
|
|
|
|
async function preparePublicSession(context, page, appUrl, phone) {
|
|
await context.addCookies([
|
|
{
|
|
name: PUBLIC_COOKIE_NAME,
|
|
value: createPublicCookieToken(phone),
|
|
url: `${appUrl}/`,
|
|
httpOnly: true,
|
|
sameSite: "Lax",
|
|
},
|
|
]);
|
|
|
|
await installPromptAutoAccept(page);
|
|
await installOtpBypassRoutes(page);
|
|
}
|
|
|
|
async function createRequestViaLanding(page, options = {}) {
|
|
const phone = options.phone || randomPhone();
|
|
const name = options.name || `Клиент E2E ${Date.now()}`;
|
|
const description = options.description || "Проверка создания заявки через UI";
|
|
|
|
await page.goto("/");
|
|
await expect(page.getByRole("heading", { name: "Решаем сложные юридические задачи в интересах вашего бизнеса." })).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Оставить заявку" }).first().click();
|
|
await expect(page.getByRole("heading", { name: "Создание заявки" })).toBeVisible();
|
|
|
|
await page.locator("#name").fill(name);
|
|
await page.locator("#phone").fill(phone);
|
|
const topicSelect = page.locator("#topic");
|
|
await topicSelect.waitFor();
|
|
await topicSelect.selectOption({ index: 1 });
|
|
await page.locator("#description").fill(description);
|
|
await page.getByRole("button", { name: "Отправить заявку" }).click();
|
|
|
|
await expect(page.locator("#form-status")).toContainText("Заявка принята. Номер:");
|
|
const statusText = await page.locator("#form-status").innerText();
|
|
const match = statusText.match(/TRK-[A-Z0-9-]+/);
|
|
if (!match) throw new Error("Track number not found in form status");
|
|
|
|
return { trackNumber: match[0], phone, name };
|
|
}
|
|
|
|
async function openPublicCabinet(page, trackNumber) {
|
|
await page.goto(`/client.html?track=${encodeURIComponent(trackNumber)}`);
|
|
await expect(page.locator("#client-page-status")).toContainText(`Открыта заявка: ${trackNumber}`);
|
|
await expect(page.locator("#cabinet-summary")).toBeVisible();
|
|
await expect(page.locator("#cabinet-request-status")).not.toHaveText("-");
|
|
}
|
|
|
|
async function sendCabinetMessage(page, text) {
|
|
await page.locator("#cabinet-chat-body").fill(text);
|
|
await page.locator("#cabinet-chat-send").click();
|
|
await expect(page.locator("#client-page-status")).toContainText("Сообщение отправлено.");
|
|
await expect(page.locator("#cabinet-messages")).toContainText(text);
|
|
}
|
|
|
|
async function uploadCabinetFile(page, fileName = "e2e.txt", bodyText = "E2E file") {
|
|
let lastError = null;
|
|
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
await page.locator("#cabinet-file-input").setInputFiles({
|
|
name: fileName,
|
|
mimeType: "application/pdf",
|
|
buffer: Buffer.from(bodyText, "utf-8"),
|
|
});
|
|
await page.locator("#cabinet-file-upload").click();
|
|
|
|
try {
|
|
await expect(page.locator("#client-page-status")).toContainText("Файл загружен.", { timeout: 20_000 });
|
|
lastError = null;
|
|
break;
|
|
} catch (error) {
|
|
lastError = error;
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
if (lastError) throw lastError;
|
|
await expect(page.locator("#cabinet-files")).toContainText(fileName);
|
|
}
|
|
|
|
async function loginAdminPanel(page, creds) {
|
|
await page.goto("/admin");
|
|
await expect(page.getByRole("heading", { name: "Панель администратора" })).toBeVisible();
|
|
|
|
let loginVisible = false;
|
|
const startedAt = Date.now();
|
|
while (Date.now() - startedAt < 15_000) {
|
|
loginVisible = await page.locator("#login-email").isVisible().catch(() => false);
|
|
if (loginVisible) break;
|
|
const badge = (await page.locator(".badge").first().textContent().catch(() => "")) || "";
|
|
if (badge && !badge.includes("роль: -")) break;
|
|
await page.waitForTimeout(200);
|
|
}
|
|
|
|
if (loginVisible) {
|
|
await page.locator("#login-email").fill(creds.email);
|
|
await page.locator("#login-password").fill(creds.password);
|
|
await page.getByRole("button", { name: "Войти" }).click();
|
|
}
|
|
|
|
await expect(page.getByRole("heading", { name: "Панель администратора" })).toBeVisible();
|
|
}
|
|
|
|
async function openRequestsSection(page) {
|
|
await page.locator("aside .menu button[data-section='requests']").click();
|
|
await expect(page.locator("#section-requests h2")).toHaveText("Заявки");
|
|
}
|
|
|
|
function rowByTrack(page, sectionSelector, trackNumber) {
|
|
return page.locator(`${sectionSelector} table tbody tr`).filter({ hasText: trackNumber });
|
|
}
|
|
|
|
async function openDictionaryTree(page) {
|
|
const treeButton = page.locator("aside .menu button", { hasText: "Справочники" }).first();
|
|
await treeButton.click();
|
|
const afterFirstClick = await treeButton.innerText();
|
|
if (afterFirstClick.includes("▸")) {
|
|
await treeButton.click();
|
|
}
|
|
await expect(page.locator("#section-config h2")).toHaveText("Справочники");
|
|
await expect(treeButton).toContainText("▾");
|
|
await expect.poll(async () => page.locator("aside .menu .menu-tree button").count(), { timeout: 30_000 }).toBeGreaterThan(0);
|
|
}
|
|
|
|
async function selectDictionaryNode(page, label) {
|
|
await page.locator("aside .menu .menu-tree").getByRole("button", { name: label, exact: true }).click();
|
|
await expect(page.locator("#section-config .config-panel h3")).toContainText(label);
|
|
}
|
|
|
|
module.exports = {
|
|
randomPhone,
|
|
preparePublicSession,
|
|
createRequestViaLanding,
|
|
openPublicCabinet,
|
|
sendCabinetMessage,
|
|
uploadCabinetFile,
|
|
loginAdminPanel,
|
|
openRequestsSection,
|
|
rowByTrack,
|
|
openDictionaryTree,
|
|
selectDictionaryNode,
|
|
};
|