mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge branch 'unstable' into agents/daily/onboarding-audit-12406050567777306953
This commit is contained in:
14 files changed
+9575
-71
No files matched your search
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
# perf-agent completed
|
||||
|
||||
- **Date**: 2026-02-22
|
||||
- **Agent**: perf-agent
|
||||
- **Summary**: Ran search patterns, generated `perf/hits-2026-02-22.json` and `perf/daily-perf-report-2026-02-22.md`.
|
||||
- **Files Created**:
|
||||
- `perf/hits-2026-02-22.json`
|
||||
- `perf/daily-perf-report-2026-02-22.md`
|
||||
@@ -523,6 +523,18 @@ function extractNpubsFromEvent(event) {
|
||||
|
||||
async function fetchLatestListEvent(filter, contextLabel = "admin-list") {
|
||||
const nostrClient = requireNostrClient();
|
||||
|
||||
if (!nostrClient.pool && nostrClient.initPromise) {
|
||||
try {
|
||||
await nostrClient.initPromise;
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
`[adminListStore] Awaiting nostrClient init failed during ${contextLabel} fetch:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const relays = ensureNostrReady();
|
||||
|
||||
const kind = (Array.isArray(filter?.kinds) && filter.kinds.length)
|
||||
@@ -827,6 +839,18 @@ function publishListWithFirstAcceptance(pool, relays, event, options = {}) {
|
||||
|
||||
async function persistNostrState(actorNpub, updates = {}) {
|
||||
const nostrClient = requireNostrClient();
|
||||
|
||||
if (!nostrClient.pool && nostrClient.initPromise) {
|
||||
try {
|
||||
await nostrClient.initPromise;
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[adminListStore] Awaiting nostrClient init failed during persist:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ensureNostrReady();
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
|
||||
+39
-28
@@ -86,27 +86,32 @@ export function createUiCoordinator(deps) {
|
||||
},
|
||||
|
||||
applyAuthenticatedUiState() {
|
||||
if (this.loginButton) {
|
||||
this.loginButton.classList.add("hidden");
|
||||
this.loginButton.setAttribute("hidden", "");
|
||||
const loginButton = this.loginButton || document.getElementById("loginButton");
|
||||
if (loginButton) {
|
||||
loginButton.classList.add("hidden");
|
||||
loginButton.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
if (this.logoutButton) {
|
||||
this.logoutButton.classList.remove("hidden");
|
||||
const logoutButton = this.logoutButton || document.getElementById("logoutButton");
|
||||
if (logoutButton) {
|
||||
logoutButton.classList.remove("hidden");
|
||||
}
|
||||
|
||||
if (this.userStatus) {
|
||||
this.userStatus.classList.add("hidden");
|
||||
const userStatus = this.userStatus || document.getElementById("userStatus");
|
||||
if (userStatus) {
|
||||
userStatus.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (this.uploadButton) {
|
||||
this.uploadButton.classList.remove("hidden");
|
||||
this.uploadButton.removeAttribute("hidden");
|
||||
const uploadButton = this.uploadButton || document.getElementById("uploadButton");
|
||||
if (uploadButton) {
|
||||
uploadButton.classList.remove("hidden");
|
||||
uploadButton.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
if (this.profileButton) {
|
||||
this.profileButton.classList.remove("hidden");
|
||||
this.profileButton.removeAttribute("hidden");
|
||||
const profileButton = this.profileButton || document.getElementById("profileButton");
|
||||
if (profileButton) {
|
||||
profileButton.classList.remove("hidden");
|
||||
profileButton.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
const subscriptionsLink = this.resolveSubscriptionsLink();
|
||||
@@ -126,31 +131,37 @@ export function createUiCoordinator(deps) {
|
||||
},
|
||||
|
||||
applyLoggedOutUiState() {
|
||||
if (this.loginButton) {
|
||||
this.loginButton.classList.remove("hidden");
|
||||
this.loginButton.removeAttribute("hidden");
|
||||
const loginButton = this.loginButton || document.getElementById("loginButton");
|
||||
if (loginButton) {
|
||||
loginButton.classList.remove("hidden");
|
||||
loginButton.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
if (this.logoutButton) {
|
||||
this.logoutButton.classList.add("hidden");
|
||||
const logoutButton = this.logoutButton || document.getElementById("logoutButton");
|
||||
if (logoutButton) {
|
||||
logoutButton.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (this.userStatus) {
|
||||
this.userStatus.classList.add("hidden");
|
||||
const userStatus = this.userStatus || document.getElementById("userStatus");
|
||||
if (userStatus) {
|
||||
userStatus.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (this.userPubKey) {
|
||||
this.userPubKey.textContent = "";
|
||||
const userPubKey = this.userPubKey || document.getElementById("userPubKey");
|
||||
if (userPubKey) {
|
||||
userPubKey.textContent = "";
|
||||
}
|
||||
|
||||
if (this.uploadButton) {
|
||||
this.uploadButton.classList.add("hidden");
|
||||
this.uploadButton.setAttribute("hidden", "");
|
||||
const uploadButton = this.uploadButton || document.getElementById("uploadButton");
|
||||
if (uploadButton) {
|
||||
uploadButton.classList.add("hidden");
|
||||
uploadButton.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
if (this.profileButton) {
|
||||
this.profileButton.classList.add("hidden");
|
||||
this.profileButton.setAttribute("hidden", "");
|
||||
const profileButton = this.profileButton || document.getElementById("profileButton");
|
||||
if (profileButton) {
|
||||
profileButton.classList.add("hidden");
|
||||
profileButton.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
const subscriptionsLink = this.resolveSubscriptionsLink();
|
||||
|
||||
+51
-2
@@ -350,8 +350,17 @@ function setTestRelays(relayUrls, options = {}) {
|
||||
|
||||
try {
|
||||
if (relayManager && typeof relayManager.setEntries === "function") {
|
||||
const entries = normalizedRelays.map((url) => ({ url, mode: "both" }));
|
||||
// Overwrite defaults so resets/fallbacks use test relays
|
||||
if (relayManager.defaultEntries) {
|
||||
relayManager.defaultEntries = entries.map((e) => ({
|
||||
...e,
|
||||
read: true,
|
||||
write: true,
|
||||
}));
|
||||
}
|
||||
relayManager.setEntries(
|
||||
normalizedRelays.map((url) => ({ url, mode: "both" })),
|
||||
entries,
|
||||
{ allowEmpty: false, updateClient: true },
|
||||
);
|
||||
applied = true;
|
||||
@@ -472,7 +481,7 @@ function getAppState() {
|
||||
function getFeedItems() {
|
||||
const cards = document.querySelectorAll("[data-video-card]");
|
||||
return Array.from(cards).map((card) => ({
|
||||
title: card.querySelector("[data-video-title]")?.textContent?.trim() || "",
|
||||
title: card.querySelector("[data-video-title]")?.getAttribute("data-video-title") || "",
|
||||
pubkey: card.getAttribute("data-video-pubkey") || "",
|
||||
dTag: card.getAttribute("data-video-dtag") || "",
|
||||
hasUrl: Boolean(card.getAttribute("data-play-url")),
|
||||
@@ -564,6 +573,46 @@ export function installTestHarness() {
|
||||
|
||||
installListSyncCapture();
|
||||
|
||||
// Patch relayManager to respect test overrides during login/logout
|
||||
if (relayManager) {
|
||||
const originalLoadRelayList = relayManager.loadRelayList;
|
||||
relayManager.loadRelayList = async function (pubkey) {
|
||||
// Heuristic: If we are in test mode and the current relays are different from defaults,
|
||||
// assume they are test relays and preserve them.
|
||||
const isTest = isTestMode();
|
||||
const currentEntries = JSON.stringify(this.entries.map(e => ({ url: e.url, mode: e.mode })));
|
||||
const defaultEntries = JSON.stringify(this.defaultEntries.map(e => ({ url: e.url, mode: e.mode })));
|
||||
|
||||
if (isTest && currentEntries !== defaultEntries) {
|
||||
// We pretend we loaded successfully but found no new events to apply,
|
||||
// so the existing (test) relays remain active.
|
||||
return { ok: true, source: "test-override-preserved", events: [] };
|
||||
}
|
||||
|
||||
if (typeof originalLoadRelayList === "function") {
|
||||
return originalLoadRelayList.call(this, pubkey);
|
||||
}
|
||||
return { ok: false, reason: "original-method-missing" };
|
||||
};
|
||||
|
||||
const originalReset = relayManager.reset;
|
||||
relayManager.reset = function () {
|
||||
if (isTestMode()) {
|
||||
const overrides = getTestRelayOverrides();
|
||||
if (overrides && overrides.length > 0) {
|
||||
this.lastEvent = null;
|
||||
this.loadedPubkey = null;
|
||||
this.lastLoadSource = "test-override";
|
||||
setTestRelays(overrides, { persist: false });
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (typeof originalReset === "function") {
|
||||
originalReset.call(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.__bitvidTest__ = Object.freeze({
|
||||
version: HARNESS_VERSION,
|
||||
loginWithNsec,
|
||||
|
||||
@@ -805,7 +805,9 @@ export class UploadModal {
|
||||
|
||||
|
||||
updateVideoProgress(fraction, text) {
|
||||
if (text) this.statusText.uploadMain.textContent = text;
|
||||
if (text && this.statusText?.uploadMain) {
|
||||
this.statusText.uploadMain.textContent = text;
|
||||
}
|
||||
|
||||
if (fraction === null) {
|
||||
// Indeterminate or error
|
||||
@@ -813,18 +815,28 @@ export class UploadModal {
|
||||
}
|
||||
|
||||
const pct = Math.round(fraction * 100);
|
||||
this.inputs.progress.value = pct;
|
||||
this.statusText.uploadPercent.textContent = `${pct}%`;
|
||||
if (this.inputs?.progress) {
|
||||
this.inputs.progress.value = pct;
|
||||
}
|
||||
if (this.statusText?.uploadPercent) {
|
||||
this.statusText.uploadPercent.textContent = `${pct}%`;
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbnailProgress(fraction, text) {
|
||||
if (text) this.statusText.thumbnailMain.textContent = text;
|
||||
if (text && this.statusText?.thumbnailMain) {
|
||||
this.statusText.thumbnailMain.textContent = text;
|
||||
}
|
||||
|
||||
if (fraction === null) return;
|
||||
|
||||
const pct = Math.round(fraction * 100);
|
||||
this.inputs.thumbnailProgress.value = pct;
|
||||
this.statusText.thumbnailPercent.textContent = `${pct}%`;
|
||||
if (this.inputs?.thumbnailProgress) {
|
||||
this.inputs.thumbnailProgress.value = pct;
|
||||
}
|
||||
if (this.statusText?.thumbnailPercent) {
|
||||
this.statusText.thumbnailPercent.textContent = `${pct}%`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -911,6 +923,9 @@ export class UploadModal {
|
||||
}
|
||||
|
||||
toggleStorageView(viewName) {
|
||||
if (!this.storageViews?.summary || !this.storageViews?.empty) {
|
||||
return;
|
||||
}
|
||||
if (viewName === "summary") {
|
||||
this.storageViews.summary.classList.remove("hidden");
|
||||
this.storageViews.empty.classList.add("hidden");
|
||||
@@ -968,11 +983,11 @@ export class UploadModal {
|
||||
|
||||
updateLockUi() {
|
||||
const locked = !this.isStorageUnlocked;
|
||||
if (this.statusText.storageLock) {
|
||||
if (this.statusText?.storageLock) {
|
||||
this.statusText.storageLock.textContent = locked ? "Locked 🔒" : "Unlocked 🔓";
|
||||
this.statusText.storageLock.className = locked ? "text-xs text-critical" : "text-xs text-success";
|
||||
}
|
||||
if (this.toggles.storageUnlock) {
|
||||
if (this.toggles?.storageUnlock) {
|
||||
// Show unlock button only if locked AND we have a configuration to unlock
|
||||
if (locked && this.storageConfigured) {
|
||||
this.toggles.storageUnlock.classList.remove("hidden");
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Daily Performance Report - 2026-02-22
|
||||
|
||||
## Summary
|
||||
Total Hits: 1331
|
||||
|
||||
### Hits by Pattern
|
||||
- Nostr/Relay/Auth: 278
|
||||
- Promise Concurrency: 66
|
||||
- WebTorrent: 776
|
||||
- Timeouts/Intervals: 155
|
||||
- Visibility: 19
|
||||
- Workers: 37
|
||||
|
||||
## Top Files by Activity
|
||||
- js/webtorrent.js: 100 hits
|
||||
- js/services/playbackService.js: 82 hits
|
||||
- js/app.js: 72 hits
|
||||
- js/app/playbackCoordinator.js: 71 hits
|
||||
- js/ui/components/UploadModal.js: 64 hits
|
||||
- js/app/authSessionCoordinator.js: 47 hits
|
||||
- js/ui/components/VideoCard.js: 34 hits
|
||||
- js/ui/applicationBootstrap.js: 31 hits
|
||||
- js/ui/components/EditModal.js: 30 hits
|
||||
- js/nostr/client.js: 28 hits
|
||||
|
||||
## Potential P0/P1 Candidates (Sample)
|
||||
These locations involve concurrency or heavy relay operations:
|
||||
|
||||
- **js/adminListStore.js:567** (Nostr/Relay/Auth): `events = await nostrClient.pool.list(relays, [normalizedFilter]);`
|
||||
- **js/app.js:2828** (Nostr/Relay/Auth): `if (!nostrClient?.pool || typeof nostrClient.pool.list !== "function") {`
|
||||
- **js/app.js:2833** (Nostr/Relay/Auth): `const events = await nostrClient.pool.list(relayList, [`
|
||||
- **js/channelProfile.js:5402** (Nostr/Relay/Auth): `const fallbackEvents = await nostrClient.pool.list(`
|
||||
- **js/channelProfile.js:5415** (Nostr/Relay/Auth): `(url) => nostrClient.pool.list([url], [filter]),`
|
||||
- **js/dmDecryptor.js:456** (Promise Concurrency): `return await Promise.any(decryptors.map(attemptUnwrap));`
|
||||
- **js/embed.js:274** (Nostr/Relay/Auth): `const events = await pool.list(relayList, [filter]);`
|
||||
- **js/embed.js:282** (Nostr/Relay/Auth): `devLogger.warn("[embed] Failed to fetch naddr via pool.list:", error);`
|
||||
- **js/feedEngine/watchHistoryFeed.js:406** (Nostr/Relay/Auth): `const events = await nostrClient.pool.list(mergedRelays, filters);`
|
||||
- **js/feedEngine/watchHistoryFeed.js:514** (Nostr/Relay/Auth): `const events = await nostrClient.pool.list(relays, [filter]);`
|
||||
|
||||
## Actions Taken
|
||||
- Ran search patterns and generated inventory.
|
||||
- No automatic fixes applied in this run.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -83,10 +83,17 @@ test.describe("Accessibility and keyboard navigation", () => {
|
||||
|
||||
// When: login button receives focus and Enter is pressed
|
||||
const loginBtn = page.locator('[data-testid="login-button"]');
|
||||
await expect(loginBtn).toBeVisible();
|
||||
await loginBtn.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
// Then: login modal opens
|
||||
// Wait for the modal to be attached to the DOM first (it might be loaded dynamically)
|
||||
await page.waitForSelector('[data-testid="login-modal"]', {
|
||||
state: "attached",
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const modal = document.querySelector(
|
||||
@@ -273,10 +280,7 @@ test.describe("Accessibility and keyboard navigation", () => {
|
||||
'[data-testid="upload-modal"]',
|
||||
);
|
||||
if (!(modal instanceof HTMLElement)) return false;
|
||||
return (
|
||||
modal.getAttribute("data-open") === "true" &&
|
||||
!modal.classList.contains("hidden")
|
||||
);
|
||||
return !modal.classList.contains("hidden");
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
@@ -297,13 +297,14 @@ test.describe("Feed pagination and stress scenarios", () => {
|
||||
const attrs = await page.evaluate(() => {
|
||||
const el = document.querySelector("[data-video-card]");
|
||||
if (!el) return null;
|
||||
const titleEl = el.querySelector("[data-video-title]");
|
||||
return {
|
||||
hasTitle: el.hasAttribute("data-video-title"),
|
||||
hasTitle: !!titleEl,
|
||||
hasPubkey: el.hasAttribute("data-video-pubkey"),
|
||||
hasDtag: el.hasAttribute("data-video-dtag"),
|
||||
hasPlayUrl: el.hasAttribute("data-play-url"),
|
||||
hasPlayMagnet: el.hasAttribute("data-play-magnet"),
|
||||
title: el.getAttribute("data-video-title"),
|
||||
title: titleEl ? titleEl.getAttribute("data-video-title") : null,
|
||||
pubkey: el.getAttribute("data-video-pubkey"),
|
||||
dtag: el.getAttribute("data-video-dtag"),
|
||||
};
|
||||
|
||||
@@ -115,10 +115,14 @@ async function gotoWithTestMode(page: Page, relayUrl: string, path = "/") {
|
||||
const testUrl = `${path}${separator}__test__=1&__testRelays__=${encodeURIComponent(relayUrl)}`;
|
||||
|
||||
// Set up localStorage before navigation
|
||||
await page.addInitScript(() => {
|
||||
await page.addInitScript((url) => {
|
||||
localStorage.setItem("hasSeenDisclaimer", "true");
|
||||
localStorage.setItem("__bitvidTestMode__", "1");
|
||||
});
|
||||
// Inject test relays to ensure robust fallback if URL parsing fails
|
||||
localStorage.setItem("__bitvidTestRelays__", JSON.stringify([url]));
|
||||
// Disable whitelist mode to ensure seeded test events are accessible
|
||||
localStorage.setItem("bitvid_admin_whitelist_mode", "false");
|
||||
}, relayUrl);
|
||||
|
||||
await page.goto(testUrl);
|
||||
|
||||
@@ -127,6 +131,11 @@ async function gotoWithTestMode(page: Page, relayUrl: string, path = "/") {
|
||||
() => typeof (window as any).__bitvidTest__ === "object",
|
||||
{ timeout: 15000 },
|
||||
);
|
||||
|
||||
// Explicitly set test relays to ensure they are active and recognized by the relayManager patch
|
||||
await page.evaluate((url) => {
|
||||
(window as any).__bitvidTest__.setTestRelays([url], { persist: false });
|
||||
}, relayUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,6 +98,11 @@ test.describe("Login and authentication flows", () => {
|
||||
(window as any).__bitvidTest__.logout();
|
||||
});
|
||||
|
||||
// Wait for state to settle with a longer timeout
|
||||
await page.waitForFunction(() => {
|
||||
return (window as any).__bitvidTest__.getAppState().isLoggedIn === false;
|
||||
}, { timeout: 15000 });
|
||||
|
||||
const loggedOut = await page.evaluate(() => {
|
||||
return (window as any).__bitvidTest__.getAppState();
|
||||
});
|
||||
@@ -245,6 +250,11 @@ test.describe("Login and authentication flows", () => {
|
||||
{ timeout: 15000 },
|
||||
);
|
||||
|
||||
// Select the nsec provider
|
||||
const nsecProvider = page.locator('button[data-provider-id="nsec"]');
|
||||
await expect(nsecProvider).toBeVisible();
|
||||
await nsecProvider.click();
|
||||
|
||||
// Then: nsec input elements should be present in the DOM
|
||||
const nsecInput = page.locator('[data-testid="nsec-secret-input"]');
|
||||
const nsecSubmit = page.locator('[data-testid="nsec-submit"]');
|
||||
|
||||
@@ -35,6 +35,11 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
// Wait for the modal element to be present in DOM before clicking
|
||||
// This ensures event listeners (like VideoModalController's body click delegate)
|
||||
// have likely been initialized by the app boot sequence.
|
||||
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
||||
|
||||
// When: user clicks the video card
|
||||
const card = page.locator("[data-video-card]").first();
|
||||
await expect(card).toBeVisible();
|
||||
@@ -65,6 +70,9 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
// Wait for modal presence to ensure app is fully initialized
|
||||
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
||||
|
||||
await page.locator("[data-video-card]").first().click();
|
||||
|
||||
const playerModal = page.locator("#playerModal");
|
||||
@@ -101,6 +109,8 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
||||
|
||||
// When: user opens the video
|
||||
await page.locator("[data-video-card]").first().click();
|
||||
|
||||
@@ -147,6 +157,8 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
||||
|
||||
// When: user opens the video
|
||||
await page.locator("[data-video-card]").first().click();
|
||||
|
||||
@@ -195,32 +207,31 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
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 });
|
||||
|
||||
// When: user closes the modal
|
||||
// Try various close mechanisms
|
||||
const closeBtn = page.locator(
|
||||
"#playerModal .modal-close, #playerModal [data-close], #playerModal [data-dismiss], #closePlayerModal",
|
||||
);
|
||||
if ((await closeBtn.count()) > 0) {
|
||||
await closeBtn.first().click({ force: true });
|
||||
// Use the standard close button ID which is bound in VideoModal.js
|
||||
const closeBtn = page.locator("#closeModal");
|
||||
await closeBtn.waitFor({ state: "visible", timeout: 5000 });
|
||||
await closeBtn.click();
|
||||
|
||||
// Then: modal is hidden again
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const modal = document.querySelector("#playerModal");
|
||||
if (!modal) return true;
|
||||
return (
|
||||
modal.classList.contains("hidden") ||
|
||||
modal.getAttribute("data-open") === "false"
|
||||
);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}
|
||||
// Then: modal is hidden again
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const modal = document.querySelector("#playerModal");
|
||||
if (!modal) return true;
|
||||
return (
|
||||
modal.classList.contains("hidden") ||
|
||||
modal.getAttribute("data-open") === "false"
|
||||
);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
// App state should still be valid
|
||||
const state = await page.evaluate(() => {
|
||||
@@ -249,6 +260,8 @@ test.describe("Video playback scenarios", () => {
|
||||
return (window as any).__bitvidTest__.waitForFeedItems(1, 15000);
|
||||
});
|
||||
|
||||
await page.locator("#playerModal").waitFor({ state: "attached", timeout: 15000 });
|
||||
|
||||
// When: user opens the video
|
||||
await page.locator("[data-video-card]").first().click();
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ test.describe("Video upload and discovery", () => {
|
||||
await page.waitForFunction(() => {
|
||||
const m = document.querySelector('[data-testid="upload-modal"]');
|
||||
if (!(m instanceof HTMLElement)) return false;
|
||||
return (
|
||||
m.getAttribute("data-open") === "true" &&
|
||||
!m.classList.contains("hidden")
|
||||
);
|
||||
return !m.classList.contains("hidden");
|
||||
}, { timeout: 10000 });
|
||||
|
||||
await expect(
|
||||
@@ -204,7 +201,9 @@ test.describe("Video upload and discovery", () => {
|
||||
const card = page.locator("[data-video-card]").first();
|
||||
await expect(card).toBeVisible();
|
||||
|
||||
const title = await card.getAttribute("data-video-title");
|
||||
// The title attribute is on the h3 element inside the card
|
||||
const titleEl = card.locator("[data-video-title]");
|
||||
const title = await titleEl.getAttribute("data-video-title");
|
||||
expect(title).toBe("Attributed Video");
|
||||
|
||||
const pubkey = await card.getAttribute("data-video-pubkey");
|
||||
|
||||
Reference in new issue
Block a user