mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
CI has been failing on unstable for weeks, across four unit files and a set of e2e specs. None of it was a product regression: every failure is a test that encodes behavior a later commit deliberately changed. Each is corrected against the spec that superseded it, not adjusted until it passed. Unit: - video-modal-moderation: the fixture set flat `blurThumbnail`/`trustedMuteCount`, but normalizeVideoModerationContext derives shouldShow from `original.blurThumbnail`/`trustedCount`, so the assertion ran against a context whose early return had just deleted the blur it was checking.4525c1c6also made a lone trusted mute a non-blocking ranking signal (its sibling test was renamed to say exactly that; this one was missed). Fixture now mirrors what moderationDecorator emits. - trustBootstrap: asserted a subscription to onWhitelistChange that18e08bf2deliberately removed -- buildTrustedSeeds derives seeds from the super admin, editors and fallback seeds, so a whitelist change cannot alter them. The removal is now pinned so it is not reinstated by reflex. - moderation-service:18e08bf2removed `status.whitelisted ||` from isTrustedReporter (being allowed to publish is not a statement that your reports carry weight). A whitelisted reporter now counts toward the report total but not toward trusted. - playbackService_forcedSource: forced P2P now falls back to a hosted URL when the torrent definitively fails -- playbackService documents the change and its rollback. The old assertion did not merely fail, it HUNG: forced sources skip the start timeout, so the now-reachable hosted attempt waited forever on a <video> that never emits. E2E: - ui-modals: asserted exactly one `.bv-modal` per component page, but profile-modal.html also bundles #storageCorsModal (d7ad1516). Requires at least one and drives the primary, which is what the test's own injected querySelector already used. - coverage-utility-modules: used `author:abc123`, which9afbc0cbcorrectly began rejecting as undecodable. Uses a real pubkey for the success path and pins the rejection separately. - webseed-playback: asserted the FINAL src equals the CDN URL while also tolerating "No playable source found" in its status check -- contradictory, since a failed attempt calls resetVideoElement() and clears src. Headless CI cannot decode the archive.org H.264 sample. Now records src attachments and asserts the URL was attached, which is the behavior under test. - nostr-runtime-coverage: asserted a legacy plaintext session-actor entry reads back as null. Since8692dac6added per-account storage, readV1Entry PURGES the plaintext entry and the reader falls back to the sole remaining encrypted account -- so null no longer describes the security property. Asserts the property directly: no plaintext key survives, in the entry or in raw storage. Also drops the syncAuthUiState share-gate wiring added ind141c316. It was redundant: Application.updateShareNostrAuthState() already fires on init, login, logout, pubkey-change and signer-change and forwards to videoModal.setShareNostrAuthState(). That call short-circuited on its own `if (!this.videoModal?.setShareNostrAuthState) return;` guard purely because the method did not exist -- adding it (d141c316) was the whole fix. The tests now pin that contract instead of a second, competing code path. All 416 unit files, all three CI shards, both DM suites and lint are green. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
import { test, expect } from "./helpers/instrumentedTest";
|
|
|
|
test.describe("Webseed Stream Playback", () => {
|
|
const VIDEO_URL =
|
|
"https://ia800205.us.archive.org/2/items/PopeyeTheSailor-AncientFistory/PopeyeTheSailor-AncientFistory_512kb.mp4";
|
|
const INFO_HASH = "c59cf2122f84f28f72755cf5b829f2cf9f539e99";
|
|
const MAGNET_URI = `magnet:?xt=urn:btih:${INFO_HASH}`;
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// networkidle can be flaky if background polling/relays are active.
|
|
// We rely on explicit selector waits instead.
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
|
|
try {
|
|
// Wait for either the video list (success) or the error message (failure)
|
|
await Promise.race([
|
|
page.waitForSelector('#videoList', { timeout: 30000 }),
|
|
page.waitForSelector('.text-critical-strong', { timeout: 30000 }).then(async (el) => {
|
|
const text = await el?.innerText();
|
|
throw new Error(`View load failed with message: ${text}`);
|
|
})
|
|
]);
|
|
} catch (error) {
|
|
// If timeout occurs, capture page content for debugging
|
|
const body = await page.innerHTML('body');
|
|
console.error('Test failed to load video list. Body content:', body.substring(0, 500));
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
test("plays directly from HTML link (CDN mode)", async ({ page }) => {
|
|
// Record every src the player attaches. Asserting the FINAL src is wrong:
|
|
// this test already tolerates "No playable source found" in its status
|
|
// check (headless CI often cannot decode the archive.org H.264 sample), and
|
|
// when the attempt fails resetVideoElement() clears src back to "". The
|
|
// behaviour under test is that CDN mode ATTACHES the hosted URL, which is
|
|
// observable regardless of whether the remote file then decodes.
|
|
await page.evaluate(() => {
|
|
(window as any).__attachedSrcs = [];
|
|
new MutationObserver((mutations) => {
|
|
for (const mutation of mutations) {
|
|
const target = mutation.target as HTMLElement;
|
|
if (mutation.attributeName === "src" && target.tagName === "VIDEO") {
|
|
const value = target.getAttribute("src");
|
|
if (value) {
|
|
(window as any).__attachedSrcs.push(value);
|
|
}
|
|
}
|
|
}
|
|
}).observe(document.body, {
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ["src"],
|
|
});
|
|
});
|
|
|
|
// 1. Trigger playback with just the URL
|
|
await page.evaluate(async (url) => {
|
|
const { getApplication } = await import("/js/applicationContext.js");
|
|
const app = getApplication();
|
|
if (!app) throw new Error("Application not found");
|
|
|
|
await app.playVideoWithoutEvent({
|
|
url,
|
|
title: "Test CDN Playback",
|
|
});
|
|
}, VIDEO_URL);
|
|
|
|
// 2. Verify player modal opens
|
|
const modal = page.locator("#playerModal");
|
|
await expect(modal).not.toHaveClass(/hidden/);
|
|
|
|
// 3. Verify status indicates hosted playback attempt
|
|
// In headless environments with limited codecs, actual playback might stall at "Checking..."
|
|
// or fail immediately with "No playable source found" if the browser lacks H.264 support (common in CI)
|
|
const status = page.locator("#modalStatus");
|
|
await expect(status).toHaveText(/Checking hosted URL|Streaming from hosted URL|No playable source found/i);
|
|
|
|
// Verify source toggle state (CDN active)
|
|
const urlToggle = page.locator('[data-source-toggle="url"]');
|
|
await expect(urlToggle).toHaveAttribute("aria-pressed", "true");
|
|
|
|
// Check the player attached the hosted URL (see the observer above).
|
|
await expect
|
|
.poll(async () => await page.evaluate(() => (window as any).__attachedSrcs))
|
|
.toContain(VIDEO_URL);
|
|
});
|
|
|
|
test("falls back to WebTorrent using HTML link as webseed when CDN fails", async ({ page }) => {
|
|
test.setTimeout(90000); // Increase timeout for fallback logic
|
|
|
|
// 1. Intercept and fail the direct request to the MP4
|
|
await page.route(VIDEO_URL, (route) => route.abort());
|
|
|
|
// 2. Trigger playback with URL and Magnet
|
|
await page.evaluate(async ({ url, magnet }) => {
|
|
const { getApplication } = await import("/js/applicationContext.js");
|
|
const app = getApplication();
|
|
if (!app) throw new Error("Application not found");
|
|
|
|
// We don't await this because if it hangs on probe/fallback, we want to assert on UI changes
|
|
app.playVideoWithoutEvent({
|
|
url,
|
|
magnet,
|
|
title: "Test WebSeed Fallback",
|
|
});
|
|
}, { url: VIDEO_URL, magnet: MAGNET_URI });
|
|
|
|
// 3. Verify player modal opens
|
|
const modal = page.locator("#playerModal");
|
|
await expect(modal).not.toHaveClass(/hidden/);
|
|
|
|
// 4. Verify source toggle state changes to P2P/Torrent
|
|
const torrentToggle = page.locator('[data-source-toggle="torrent"]');
|
|
// Wait for fallback to occur (probe timeout + watchdog stall detection)
|
|
await expect(torrentToggle).toHaveAttribute("aria-pressed", "true", { timeout: 45000 });
|
|
|
|
// 5. Verify status text indicates WebTorrent usage
|
|
const status = page.locator("#modalStatus");
|
|
await expect(status).toBeVisible();
|
|
});
|
|
});
|