Files
bitvid/tests/e2e/video-edit-delete-execution.spec.ts
google-labs-jules[bot]andPR0M3TH3AN d0cab85b25 test: increase playwright e2e timeouts to reduce flakiness
Double default timeouts and extend the explicit waitForFeedItems calls across the e2e test suite to accommodate slower CI runners. This allows the docs-code-investigator scheduler run to pass successfully without being artificially blocked by environmental flakiness.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-03-02 20:43:39 +00:00

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, 60000),
);
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: 60000 });
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: 60000 });
// 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: 60000 });
// 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: 60000 });
// 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: 60000 });
// 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: 60000 });
});
});
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: 60000 });
// 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: 60000 });
// When: the user clicks "Delete all versions"
await page.locator("#confirmDeleteVideo").click();
// Then: the delete modal should close
await expect(deleteModal).toHaveClass(/hidden/, { timeout: 60000 });
});
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: 60000 });
// 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: 60000 });
// 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");
});
});