mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Increases the timeout for `waitForFeedItems` from 15s to 30s in `tests/e2e/relay-resilience.spec.ts`, `tests/e2e/video-edit-delete-execution.spec.ts`, `tests/e2e/video-engagement.spec.ts`, `tests/e2e/video-upload-flow.spec.ts`, and `tests/e2e/view-navigation.spec.ts` to prevent test flakiness and CI failures in slower environments like Firefox. Co-authored-by: PR0M3TH3AN <[email protected]>
269 lines
8.3 KiB
TypeScript
269 lines
8.3 KiB
TypeScript
/**
|
|
* Video engagement scenarios: reactions (like/dislike) and comments.
|
|
*
|
|
* Scenarios covered:
|
|
* - SCN-engage-like: Clicking the like button dispatches a reaction event
|
|
* - SCN-engage-dislike: Clicking the dislike button dispatches a reaction event
|
|
* - SCN-engage-like-aria: Like button toggles aria-pressed after reaction
|
|
* - SCN-engage-comment-composer: Comment composer is present and accepts input
|
|
* - SCN-engage-comment-submit-enabled: Submit button enables when input is non-empty
|
|
* - SCN-engage-comment-elements: Comment section has expected DOM structure
|
|
* - SCN-engage-reaction-meter: Reaction meter element is present in modal
|
|
*/
|
|
|
|
import { test, expect } from "./helpers/bitvidTestFixture";
|
|
|
|
/**
|
|
* Helper: seed a video, navigate to the app, log in, open the video modal.
|
|
* Returns after the player modal is visible.
|
|
*/
|
|
async function openVideoModal(
|
|
page: any,
|
|
{ gotoApp, loginAs, seedEvent }: any,
|
|
overrides: Record<string, string> = {},
|
|
) {
|
|
await seedEvent({
|
|
title: overrides.title || "Engagement Test Video",
|
|
url: overrides.url || "https://example.com/engage.mp4",
|
|
dTag: overrides.dTag || `engage-${Date.now()}`,
|
|
});
|
|
|
|
await gotoApp();
|
|
await loginAs(page);
|
|
|
|
await page.evaluate(() => {
|
|
return (window as any).__bitvidTest__.waitForFeedItems(1, 30000);
|
|
});
|
|
|
|
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
|
|
|
await page.locator("[data-video-card]").first().click();
|
|
|
|
const playerModal = page.locator("#playerModal");
|
|
await expect(playerModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
}
|
|
|
|
test.describe("Video engagement — reactions", () => {
|
|
test("like button is present and clickable in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-like-001",
|
|
});
|
|
|
|
// Then: the like button should be visible
|
|
const likeBtn = page.locator("#modalLikeBtn");
|
|
await expect(likeBtn).toBeAttached();
|
|
await expect(likeBtn).toHaveAttribute("data-reaction", "+");
|
|
|
|
// And: it should have an accessible label
|
|
await expect(likeBtn).toHaveAttribute("aria-label", /like/i);
|
|
});
|
|
|
|
test("dislike button is present and clickable in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-dislike-001",
|
|
});
|
|
|
|
// Then: the dislike button should be visible
|
|
const dislikeBtn = page.locator("#modalDislikeBtn");
|
|
await expect(dislikeBtn).toBeAttached();
|
|
await expect(dislikeBtn).toHaveAttribute("data-reaction", "-");
|
|
|
|
// And: it should have an accessible label
|
|
await expect(dislikeBtn).toHaveAttribute("aria-label", /dislike/i);
|
|
});
|
|
|
|
test("clicking like button dispatches video:reaction event", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-like-dispatch-001",
|
|
});
|
|
|
|
// When: we listen for the reaction custom event and click like
|
|
const reactionDetail = await page.evaluate(() => {
|
|
return new Promise<any>((resolve) => {
|
|
const timeout = setTimeout(() => resolve(null), 5000);
|
|
document.addEventListener(
|
|
"video:reaction",
|
|
(e: any) => {
|
|
clearTimeout(timeout);
|
|
resolve(e.detail || { dispatched: true });
|
|
},
|
|
{ once: true },
|
|
);
|
|
const btn = document.querySelector("#modalLikeBtn") as HTMLElement;
|
|
btn?.click();
|
|
});
|
|
});
|
|
|
|
// Then: the event was dispatched (or the button was clicked without error)
|
|
// The reaction system may use a different dispatch mechanism,
|
|
// so we verify no crash and the button is still accessible
|
|
const likeBtn = page.locator("#modalLikeBtn");
|
|
await expect(likeBtn).toBeAttached();
|
|
});
|
|
|
|
test("reaction count labels are present for like and dislike", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-counts-001",
|
|
});
|
|
|
|
// Then: count labels exist
|
|
const likeCount = page.locator("[data-reaction-like-count]");
|
|
const dislikeCount = page.locator("[data-reaction-dislike-count]");
|
|
await expect(likeCount).toBeAttached();
|
|
await expect(dislikeCount).toBeAttached();
|
|
|
|
// And: they show numeric values (default 0)
|
|
const likeText = await likeCount.textContent();
|
|
const dislikeText = await dislikeCount.textContent();
|
|
expect(likeText?.trim()).toMatch(/^\d+$/);
|
|
expect(dislikeText?.trim()).toMatch(/^\d+$/);
|
|
});
|
|
|
|
test("reaction meter element is present in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-meter-001",
|
|
});
|
|
|
|
// Then: the reaction meter is in the DOM
|
|
const meter = page.locator("[data-reaction-meter]");
|
|
await expect(meter).toBeAttached();
|
|
await expect(meter).toHaveAttribute("role", "meter");
|
|
});
|
|
});
|
|
|
|
test.describe("Video engagement — comments", () => {
|
|
test("comment composer form is present in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-composer-001",
|
|
});
|
|
|
|
// Then: the comment composer form exists
|
|
const composer = page.locator("[data-comments-composer]");
|
|
await expect(composer).toBeAttached();
|
|
|
|
// And: the input textarea exists
|
|
const input = page.locator("[data-comments-input]");
|
|
await expect(input).toBeAttached();
|
|
|
|
// And: the submit button exists
|
|
const submit = page.locator("[data-comments-submit]");
|
|
await expect(submit).toBeAttached();
|
|
});
|
|
|
|
test("comment input accepts text and enables the submit button", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-input-001",
|
|
});
|
|
|
|
const input = page.locator("[data-comments-input]");
|
|
const submit = page.locator("[data-comments-submit]");
|
|
|
|
// When: the input is empty, submit should be disabled
|
|
await expect(submit).toBeDisabled();
|
|
|
|
// When: user types a comment
|
|
await input.fill("This is a test comment");
|
|
|
|
// Then: submit button should become enabled
|
|
// The app uses an input event handler to toggle the disabled attribute
|
|
await expect(submit).toBeEnabled({ timeout: 3000 });
|
|
});
|
|
|
|
test("comment list container is present in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-list-001",
|
|
});
|
|
|
|
// Then: the comment list container exists
|
|
const list = page.locator("[data-comments-list]");
|
|
await expect(list).toBeAttached();
|
|
});
|
|
|
|
test("comment count label is present in the video modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-count-label-001",
|
|
});
|
|
|
|
// Then: the comment count label exists
|
|
const countLabel = page.locator("[data-comments-count]");
|
|
await expect(countLabel).toBeAttached();
|
|
});
|
|
|
|
test("empty state is shown when no comments exist", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video modal is open with no comments
|
|
await openVideoModal(page, { gotoApp, loginAs, seedEvent }, {
|
|
dTag: "engage-empty-001",
|
|
});
|
|
|
|
// Then: the empty state message should be visible (or the list should be empty)
|
|
const emptyState = page.locator("[data-comments-empty]");
|
|
const list = page.locator("[data-comments-list]");
|
|
|
|
// Either the explicit empty state is visible, or the list has no children
|
|
const emptyVisible = await emptyState.isVisible().catch(() => false);
|
|
if (!emptyVisible) {
|
|
const childCount = await list.locator("> *").count();
|
|
expect(childCount).toBe(0);
|
|
}
|
|
});
|
|
});
|