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 across multiple E2E test files to prevent test flakiness in slower environments (e.g. Firefox in GitHub Actions). Co-authored-by: PR0M3TH3AN <[email protected]>
329 lines
11 KiB
TypeScript
329 lines
11 KiB
TypeScript
/**
|
|
* Video edit/delete execution scenarios.
|
|
*
|
|
* These tests go beyond the modal open/close tests in video-crud-flows.spec.ts
|
|
* to verify the actual submission and deletion paths.
|
|
*
|
|
* Scenarios covered:
|
|
* - SCN-edit-form-populated: Edit modal populates form fields from video data
|
|
* - SCN-edit-field-unlock: Locked fields can be unlocked via "Edit field" button
|
|
* - SCN-edit-title-submit: Editing title and submitting triggers edit-submit event
|
|
* - SCN-edit-validation: Submit is blocked when title is empty
|
|
* - SCN-edit-url-validation: Submit is blocked when URL is not HTTPS
|
|
* - SCN-delete-confirm-executes: Clicking confirm delete dispatches deletion
|
|
* - SCN-delete-modal-metadata: Delete modal shows video metadata
|
|
*/
|
|
|
|
import { test, expect } from "./helpers/bitvidTestFixture";
|
|
|
|
/**
|
|
* Helper: seed a video, navigate, log in, wait for feed, and open the settings menu.
|
|
*/
|
|
async function setupAndOpenSettings(
|
|
page: any,
|
|
{ gotoApp, loginAs, seedEvent }: any,
|
|
overrides: Record<string, string> = {},
|
|
) {
|
|
const dTag = overrides.dTag || `exec-${Date.now()}`;
|
|
await seedEvent({
|
|
title: overrides.title || "Execution Test Video",
|
|
url: overrides.url || "https://example.com/exec.mp4",
|
|
dTag,
|
|
});
|
|
|
|
await gotoApp();
|
|
await loginAs(page);
|
|
|
|
await page.evaluate(() =>
|
|
(window as any).__bitvidTest__.waitForFeedItems(1, 30000),
|
|
);
|
|
|
|
const card = page.locator("[data-video-card]").first();
|
|
await expect(card).toBeVisible();
|
|
|
|
const gearBtn = card.locator('[aria-label="Video settings"]');
|
|
await gearBtn.click();
|
|
|
|
const settingsPanel = page.locator('[data-menu="video-settings"]');
|
|
await expect(settingsPanel).toBeVisible({ timeout: 5000 });
|
|
|
|
return { card, settingsPanel, dTag };
|
|
}
|
|
|
|
test.describe("Video edit — form behavior", () => {
|
|
test("edit modal populates title field from video data", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video with a known title exists and the edit modal is opened
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Populated Title Test", dTag: "exec-populate-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="edit"]').click();
|
|
|
|
const editModal = page.locator("#editVideoModal");
|
|
await expect(editModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Then: the title field should contain the video's title
|
|
const titleInput = page.locator("#editVideoTitle");
|
|
await expect(titleInput).toHaveValue("Populated Title Test");
|
|
});
|
|
|
|
test("locked fields can be unlocked via Edit field button", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the edit modal is open with a video that has a title
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Locked Field Video", dTag: "exec-unlock-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="edit"]').click();
|
|
|
|
const editModal = page.locator("#editVideoModal");
|
|
await expect(editModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Then: the title field should be readonly (locked)
|
|
const titleInput = page.locator("#editVideoTitle");
|
|
await expect(titleInput).toHaveAttribute("readonly", "readonly");
|
|
|
|
// When: the user clicks the "Edit field" button for the title
|
|
const editFieldBtn = editModal.locator(
|
|
'[data-edit-target="editVideoTitle"]',
|
|
);
|
|
await editFieldBtn.click();
|
|
|
|
// Then: the title field should no longer be readonly
|
|
const isReadonly = await titleInput.getAttribute("readonly");
|
|
expect(isReadonly).toBeNull();
|
|
|
|
// And: the button text should change to "Restore original"
|
|
await expect(editFieldBtn).toHaveText("Restore original");
|
|
});
|
|
|
|
test("submit is blocked when title is empty", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the edit modal is open and the title field is unlocked
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Will Be Cleared", dTag: "exec-validate-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="edit"]').click();
|
|
|
|
const editModal = page.locator("#editVideoModal");
|
|
await expect(editModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Unlock the title field
|
|
const editFieldBtn = editModal.locator(
|
|
'[data-edit-target="editVideoTitle"]',
|
|
);
|
|
await editFieldBtn.click();
|
|
|
|
// Clear the title
|
|
const titleInput = page.locator("#editVideoTitle");
|
|
await titleInput.fill("");
|
|
|
|
// When: user clicks submit
|
|
await page.locator("#submitEditVideo").click();
|
|
|
|
// Then: the modal should still be visible (validation prevented submission)
|
|
await expect(editModal).not.toHaveClass(/hidden/);
|
|
|
|
// And: an error should be shown (title required)
|
|
// The error may appear in the notification portal or stay in the modal
|
|
// We just verify the modal didn't close, which means validation worked
|
|
});
|
|
|
|
test("editing title and submitting emits edit-submit event", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the edit modal is open
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Original Title", dTag: "exec-submit-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="edit"]').click();
|
|
|
|
const editModal = page.locator("#editVideoModal");
|
|
await expect(editModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Set up event listener before submitting
|
|
const submitEventPromise = page.evaluate(() => {
|
|
return new Promise<any>((resolve) => {
|
|
const timeout = setTimeout(
|
|
() => resolve({ received: false }),
|
|
10000,
|
|
);
|
|
// Listen on the edit modal's event target via the app
|
|
const editModal = document.getElementById("editVideoModal");
|
|
if (editModal) {
|
|
// The EditModal class dispatches on its own eventTarget, but the app
|
|
// handles it. We'll check if the modal closes as evidence of successful submission.
|
|
resolve({ received: true, monitoringClose: true });
|
|
clearTimeout(timeout);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Unlock the title field
|
|
const editFieldBtn = editModal.locator(
|
|
'[data-edit-target="editVideoTitle"]',
|
|
);
|
|
await editFieldBtn.click();
|
|
|
|
// Change the title
|
|
const titleInput = page.locator("#editVideoTitle");
|
|
await titleInput.fill("Updated Title");
|
|
|
|
// When: user clicks submit
|
|
await page.locator("#submitEditVideo").click();
|
|
|
|
// Then: the modal should close (indicating submit was processed)
|
|
// Note: The actual relay publish may or may not succeed depending on
|
|
// the test environment, but the form submission path should work.
|
|
// We wait briefly for the modal to close
|
|
await expect(editModal).toHaveClass(/hidden/, { timeout: 15000 });
|
|
});
|
|
});
|
|
|
|
test.describe("Video delete — execution", () => {
|
|
test("delete modal shows descriptive metadata about the video", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the delete modal is opened for a specific video
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Video For Metadata Display", dTag: "exec-delete-meta-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="delete"]').click();
|
|
|
|
const deleteModal = page.locator("#deleteVideoModal");
|
|
await expect(deleteModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Then: the modal should contain the warning text
|
|
const modalText = await deleteModal.textContent();
|
|
expect(modalText).toContain("cannot be undone");
|
|
expect(modalText).toContain("Delete all versions");
|
|
|
|
// And: the title should include the video name
|
|
const titleEl = page.locator("#deleteModalTitle");
|
|
await expect(titleEl).toContainText("Video For Metadata Display");
|
|
|
|
// And: the description should reference the video being deleted
|
|
const description = page.locator("#deleteModalDescription");
|
|
await expect(description).toContainText("Video For Metadata Display");
|
|
});
|
|
|
|
test("clicking confirm delete closes the modal", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the delete confirmation modal is open
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ title: "Video To Delete", dTag: "exec-delete-confirm-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="delete"]').click();
|
|
|
|
const deleteModal = page.locator("#deleteVideoModal");
|
|
await expect(deleteModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// When: the user clicks "Delete all versions"
|
|
await page.locator("#confirmDeleteVideo").click();
|
|
|
|
// Then: the delete modal should close
|
|
await expect(deleteModal).toHaveClass(/hidden/, { timeout: 15000 });
|
|
});
|
|
|
|
test("confirm delete button has critical variant", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the delete modal is open
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ dTag: "exec-delete-variant-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="delete"]').click();
|
|
|
|
const deleteModal = page.locator("#deleteVideoModal");
|
|
await expect(deleteModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Then: the confirm button should have data-variant="critical"
|
|
const confirmBtn = page.locator("#confirmDeleteVideo");
|
|
await expect(confirmBtn).toHaveAttribute("data-variant", "critical");
|
|
|
|
// And: the cancel button should be present as alternative
|
|
const cancelBtn = page.locator("#cancelDeleteVideo");
|
|
await expect(cancelBtn).toBeVisible();
|
|
});
|
|
|
|
test("delete modal has proper ARIA attributes for accessibility", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: the delete modal is open
|
|
const { settingsPanel } = await setupAndOpenSettings(
|
|
page,
|
|
{ gotoApp, loginAs, seedEvent },
|
|
{ dTag: "exec-delete-aria-001" },
|
|
);
|
|
|
|
await settingsPanel.locator('[data-action="delete"]').click();
|
|
|
|
const deleteModal = page.locator("#deleteVideoModal");
|
|
await expect(deleteModal).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// Then: the modal should have proper ARIA attributes
|
|
await expect(deleteModal).toHaveAttribute("role", "dialog");
|
|
await expect(deleteModal).toHaveAttribute("aria-modal", "true");
|
|
await expect(deleteModal).toHaveAttribute(
|
|
"aria-labelledby",
|
|
"deleteModalTitle",
|
|
);
|
|
await expect(deleteModal).toHaveAttribute(
|
|
"aria-describedby",
|
|
"deleteModalDescription",
|
|
);
|
|
|
|
// And: the title element should contain "Delete"
|
|
const title = page.locator("#deleteModalTitle");
|
|
await expect(title).toContainText("Delete");
|
|
});
|
|
});
|