Files
pargar/frontend/e2e/video.spec.ts
T

76 lines
3.0 KiB
TypeScript
Raw Permalink Normal View History

import { test, expect } from "@playwright/test";
import { BASE, requireAuth, injectAuth } from "./helpers";
test.describe("Video player & media signing", () => {
test("lesson endpoint returns a signed videoUrl", async ({ page }) => {
const auth = requireAuth();
const treeRes = await page.request.get(`${BASE}/api/tree`, {
headers: { Authorization: `Bearer ${auth.accessToken}` },
});
expect(treeRes.status()).toBe(200);
const tree = await treeRes.json();
const firstLesson = tree.chapters?.[0]?.lessons?.[0];
if (!firstLesson) { test.skip(true, "No lessons in tree"); return; }
const lessonRes = await page.request.get(`${BASE}/api/lessons/${firstLesson.id}`, {
headers: { Authorization: `Bearer ${auth.accessToken}` },
});
expect(lessonRes.status()).toBe(200);
const lesson = await lessonRes.json();
expect(lesson.videoUrl).toBeTruthy();
expect(lesson.videoUrl).toMatch(/^https?:\/\//);
// Must be signed (has query params) or served via /media/ proxy
const url = new URL(lesson.videoUrl);
const isSigned = url.searchParams.size > 0 || lesson.videoUrl.includes("/media/");
expect(isSigned, `videoUrl not signed: ${lesson.videoUrl}`).toBe(true);
});
test("player page renders without JS errors for first lesson", async ({ page }) => {
const auth = requireAuth();
const treeRes = await page.request.get(`${BASE}/api/tree`, {
headers: { Authorization: `Bearer ${auth.accessToken}` },
});
const tree = await treeRes.json();
const firstLesson = tree.chapters?.[0]?.lessons?.[0];
if (!firstLesson) { test.skip(true, "No lessons in tree"); return; }
const jsErrors: string[] = [];
page.on("pageerror", (err) => jsErrors.push(err.message));
await injectAuth(page);
await page.goto(`/player/${firstLesson.id}`);
await page.waitForLoadState("networkidle", { timeout: 20_000 });
// Filter known expected media errors (video load failures in headless are expected)
const realErrors = jsErrors.filter(
(e) => !e.includes("video") && !e.includes("media") && !e.includes("hls") && !e.includes("AbortError")
);
expect(realErrors, `JS errors on player page: ${realErrors.join(", ")}`).toHaveLength(0);
});
test("admin videos panel fires no media requests on grid load", async ({ page }) => {
requireAuth();
const videoRequests: string[] = [];
page.on("request", (req) => {
if (req.resourceType() === "media" || /\.(mp4|m3u8|ts)(\?|$)/.test(req.url())) {
videoRequests.push(req.url());
}
});
await injectAuth(page);
await page.goto("/admin");
await page.waitForLoadState("networkidle", { timeout: 15_000 });
// Click Videos tab if present
const videosTab = page.getByRole("button", { name: /ویدیو/i });
if (await videosTab.count() > 0) {
await videosTab.first().click();
await page.waitForLoadState("networkidle", { timeout: 10_000 });
}
expect(videoRequests, `Unexpected media requests on grid load: ${videoRequests.join(", ")}`).toHaveLength(0);
});
});