diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afcbe295..6dc5260c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -478,7 +478,7 @@ jobs: run: npm ci - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + run: npx playwright install --with-deps chromium firefox - name: Run headless E2E tests run: npm run test:e2e diff --git a/js/ui/components/SearchFilterModal.js b/js/ui/components/SearchFilterModal.js index 570eccc8..552f7628 100644 --- a/js/ui/components/SearchFilterModal.js +++ b/js/ui/components/SearchFilterModal.js @@ -234,8 +234,8 @@ function syncStateToControls(filters) { const safeFilters = filters || DEFAULT_FILTERS; // Date - dateStartInput.value = formatDateInputValue(safeFilters.dateRange?.after); - dateEndInput.value = formatDateInputValue(safeFilters.dateRange?.before); + if (dateStartInput) dateStartInput.value = formatDateInputValue(safeFilters.dateRange?.after); + if (dateEndInput) dateEndInput.value = formatDateInputValue(safeFilters.dateRange?.before); // Logic to highlight correct date chip if it matches // (Simplified: just highlight 'All Time' if no dates, otherwise clear chips) @@ -254,13 +254,13 @@ function syncStateToControls(filters) { }); // Sort - sortSelect.value = safeFilters.sort || "relevance"; + if (sortSelect) sortSelect.value = safeFilters.sort || "relevance"; // Author - authorInput.value = safeFilters.authorPubkeys?.join(", ") || ""; + if (authorInput) authorInput.value = safeFilters.authorPubkeys?.join(", ") || ""; // Tags - tagsInput.value = ""; + if (tagsInput) tagsInput.value = ""; // We separate tags that are in chips vs typed const activeTags = new Set(safeFilters.tags || []); const tagBtns = tagChipsContainer.querySelectorAll("button"); diff --git a/js/ui/components/UploadModal.js b/js/ui/components/UploadModal.js index 40f446d5..2ea49100 100644 --- a/js/ui/components/UploadModal.js +++ b/js/ui/components/UploadModal.js @@ -1223,19 +1223,19 @@ export class UploadModal { } // Clear Result Inputs - if (this.results.videoUrl) this.results.videoUrl.value = ""; - if (this.results.magnet) this.results.magnet.value = ""; - if (this.results.torrentUrl) this.results.torrentUrl.value = ""; + if (this.results?.videoUrl) this.results.videoUrl.value = ""; + if (this.results?.magnet) this.results.magnet.value = ""; + if (this.results?.torrentUrl) this.results.torrentUrl.value = ""; // Reset Inputs - if (this.inputs.file) { + if (this.inputs?.file) { this.inputs.file.value = ""; } - if (this.inputs.thumbnailFile) { + if (this.inputs?.thumbnailFile) { this.inputs.thumbnailFile.value = ""; } // Re-enable thumbnail input if it was disabled by an upload - if (this.inputs.thumbnail) { + if (this.inputs?.thumbnail) { this.inputs.thumbnail.disabled = false; if (this.inputs.thumbnail.placeholder && this.inputs.thumbnail.placeholder.startsWith("Selected:")) { this.inputs.thumbnail.placeholder = "https://example.com/thumbnail.jpg"; diff --git a/tests/e2e/accessibility-keyboard-nav.spec.ts b/tests/e2e/accessibility-keyboard-nav.spec.ts index 349d0255..dfa0b950 100644 --- a/tests/e2e/accessibility-keyboard-nav.spec.ts +++ b/tests/e2e/accessibility-keyboard-nav.spec.ts @@ -87,18 +87,8 @@ test.describe("Accessibility and keyboard navigation", () => { await page.keyboard.press("Enter"); // Then: login modal opens - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="login-modal"]', - ); - if (!(modal instanceof HTMLElement)) return false; - return modal.getAttribute("data-open") === "true"; - }, - { timeout: 15000 }, - ); - const modal = page.locator('[data-testid="login-modal"]'); + await expect(modal).toBeVisible({ timeout: 15000 }); await expect(modal).toHaveAttribute("data-open", "true"); }); }); @@ -127,19 +117,8 @@ test.describe("Accessibility and keyboard navigation", () => { await page.keyboard.press("Escape"); // Then: modal closes (data-open becomes false or modal is hidden) - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="login-modal"]', - ); - if (!(modal instanceof HTMLElement)) return true; - return ( - modal.getAttribute("data-open") === "false" || - modal.classList.contains("hidden") - ); - }, - { timeout: 10000 }, - ); + const modal = page.locator('[data-testid="login-modal"]'); + await expect(modal).toBeHidden({ timeout: 10000 }); }); test("login modal contains focusable elements", async ({ @@ -150,16 +129,8 @@ test.describe("Accessibility and keyboard navigation", () => { await gotoApp(); await page.locator('[data-testid="login-button"]').click(); - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="login-modal"]', - ); - if (!(modal instanceof HTMLElement)) return false; - return modal.getAttribute("data-open") === "true"; - }, - { timeout: 15000 }, - ); + const modal = page.locator('[data-testid="login-modal"]'); + await expect(modal).toBeVisible({ timeout: 15000 }); // Then: modal has focusable interactive elements const focusableCount = await page.evaluate(() => { @@ -267,19 +238,8 @@ test.describe("Accessibility and keyboard navigation", () => { await expect(uploadBtn).toBeVisible(); await uploadBtn.click(); - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="upload-modal"]', - ); - if (!(modal instanceof HTMLElement)) return false; - return ( - modal.getAttribute("data-open") === "true" && - !modal.classList.contains("hidden") - ); - }, - { timeout: 10000 }, - ); + const modal = page.locator('[data-testid="upload-modal"]'); + await expect(modal).toBeVisible({ timeout: 10000 }); // Then: form fields are focusable const titleInput = page.locator('[data-testid="upload-title"]'); diff --git a/tests/e2e/login-flows.spec.ts b/tests/e2e/login-flows.spec.ts index ba7207f8..074b0fa6 100644 --- a/tests/e2e/login-flows.spec.ts +++ b/tests/e2e/login-flows.spec.ts @@ -179,21 +179,8 @@ test.describe("Login and authentication flows", () => { await page.locator('[data-testid="login-button"]').click(); // Then: login modal becomes visible - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="login-modal"]', - ); - if (!(modal instanceof HTMLElement)) return false; - return ( - modal.getAttribute("data-open") === "true" && - !modal.classList.contains("hidden") - ); - }, - { timeout: 15000 }, - ); - const modal = page.locator('[data-testid="login-modal"]'); + await expect(modal).toBeVisible({ timeout: 15000 }); await expect(modal).toHaveAttribute("data-open", "true"); }); @@ -205,16 +192,8 @@ test.describe("Login and authentication flows", () => { await gotoApp(); await page.locator('[data-testid="login-button"]').click(); - await page.waitForFunction( - () => { - const modal = document.querySelector( - '[data-testid="login-modal"]', - ); - if (!(modal instanceof HTMLElement)) return false; - return modal.getAttribute("data-open") === "true"; - }, - { timeout: 15000 }, - ); + const modal = page.locator('[data-testid="login-modal"]'); + await expect(modal).toBeVisible({ timeout: 15000 }); // Then: at least one provider button is visible const providers = page.locator( diff --git a/tests/e2e/video-playback-scenarios.spec.ts b/tests/e2e/video-playback-scenarios.spec.ts index f2c0eeb4..9dbba408 100644 --- a/tests/e2e/video-playback-scenarios.spec.ts +++ b/tests/e2e/video-playback-scenarios.spec.ts @@ -209,18 +209,8 @@ test.describe("Video playback scenarios", () => { await closeBtn.first().click({ force: true }); // Then: modal is hidden again - await page.waitForFunction( - () => { - const modal = document.querySelector("#playerModal"); - if (!modal) return true; - return ( - modal.classList.contains("hidden") || - modal.style.display === "none" || - modal.getAttribute("data-open") === "false" - ); - }, - { timeout: 10000 }, - ); + const modal = page.locator("#playerModal"); + await expect(modal).toBeHidden({ timeout: 10000 }); } // App state should still be valid