feat: implement marquee scrolling for video card titles

- Replaced CSS truncation with a custom marquee structure in `VideoCard.js` for titles and author names.
- Added `css/tailwind.source.css` utilities for `.marquee-host`, `.marquee-static`, and `.marquee-track` animations.
- Implemented `ResizeObserver` (shared singleton) to detect overflow and enable marquee only when needed.
- Implemented `IntersectionObserver` (shared singleton) to trigger marquee on mobile when the card is in the center of the viewport.
- Configured dynamic animation duration based on text content width to ensure constant scrolling speed.
- Verified visual behavior via Playwright screenshots.
This commit is contained in:
google-labs-jules[bot] committed 2026-01-21 20:22:49 +00:00
1 parent 34d57c2072
commit c719bdf558
5 files changed
+194 -65

No files matched your search

+1 -1
View File
@@ -11,4 +11,4 @@
--player-modal-primary-inline-padding-lg,clamp(var(--space-sm),2.75vw,var(--space-lg))
)}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem;min-width:0;min-height:0}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem;min-width:0;min-height:0;width:100%;align-self:stretch}.video-modal__similar{display:none}@media (min-width:1024px){#playerModal.video-modal__similar{display:block}.video-modal__similar{display:flex;flex-direction:column;gap:1.5rem;align-self:stretch;flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module{height:100%;display:flex;flex:1 1 auto;min-height:0;flex-direction:column}#playerModal .video-modal__similar .player-modal__module-list{flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module-scroll{height:100%;max-height:none;min-height:0}}#playerModal .player-modal__module-item{width:100%}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))!important}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media,#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))!important}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-2xs),.85vw,var(--space-sm))}.player-modal__similar-card{outline:2px solid transparent;outline-offset:2px}.player-modal__similar-card:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--surface-base);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1);--tw-ring-color:var(--color-accent)}#playerModal.player-modal__similar-card{display:block}.player-modal__similar-card{position:relative;displayLine truncated
--motion-video-card-backdrop-translate
);--video-card-backdrop-scale:1.12;--video-card-backdrop-blur:var(--blur-video-card-backdrop-strong);--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card:focus-visible{border-color:var(--color-accent);--tw-border-opacity:0.4;--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card[data-state=critical]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--surface-raised-critical)}.watch-history-card[data-state=private]{background-color:var(--surface-raised-private);--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg)}.watch-history-card[data-state=disabled],.watch-history-card[data-state=private]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card[data-state=disabled]{opacity:.6;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.watch-history-card[data-alert=nsfw-owner]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--color-black)}.watch-history-card[data-alert=nsfw-owner]:hover{--tw-border-opacity:0.6;--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card img[data-thumbnail-state=blurred],.watch-history-card img[data-visual-state=blurred]{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(.4,0,.2,1);filter:blur(var(--blur-thumbnail-blur));transform:scale(1.05)}.watch-history-card[data-owner-is-viewer=true][data-url-health-state=offline][data-stream-health-state=unhealthy]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--surface-raised-critical)}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max));flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}.watch-history-card[data-motion=enter]{opacity:0;animation:card-motion-enter .22s ease-out forwards}#playerModal.watch-history-card{display:block}.watch-history-card{display:flex;flex-wrap:wrap;align-items:stretch;gap:1.25rem;padding:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(0,0,.2,1)}@media (prefers-reduced-motion:reduce){.watch-history-card,.watch-history-card[data-motion=enter]{transition-property:none}.watch-history-card[data-motion=enter]{opacity:1;animation:none}}.watch-history-card:hover{--tw-translate-y:-0.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (min-width:640px){.watch-history-card{gap:1.5rem;padding:1.25rem}}.watch-history-card--private{border-color:var(--color-info);--tw-border-opacity:0.6}#playerModal.watch-histoLine truncated
);--video-card-backdrop-scale:1.12;--video-card-backdrop-blur:var(--blur-video-card-backdrop-strong);--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card:focus-visible{border-color:var(--color-accent);--tw-border-opacity:0.4;--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card[data-state=critical]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--surface-raised-critical)}.watch-history-card[data-state=private]{background-color:var(--surface-raised-private);--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg)}.watch-history-card[data-state=disabled],.watch-history-card[data-state=private]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card[data-state=disabled]{opacity:.6;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.watch-history-card[data-alert=nsfw-owner]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--color-black)}.watch-history-card[data-alert=nsfw-owner]:hover{--tw-border-opacity:0.6;--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.watch-history-card img[data-thumbnail-state=blurred],.watch-history-card img[data-visual-state=blurred]{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(.4,0,.2,1);filter:blur(var(--blur-thumbnail-blur));transform:scale(1.05)}.watch-history-card[data-owner-is-viewer=true][data-url-health-state=offline][data-stream-health-state=unhealthy]{border-width:1px;border-color:var(--color-critical);--tw-border-opacity:0.4;background-color:var(--surface-raised-critical)}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max));flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.watch-history-card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}.watch-history-card[data-motion=enter]{opacity:0;animation:card-motion-enter .22s ease-out forwards}#playerModal.watch-history-card{display:block}@media (min-width:1024px){.watch-history-card:hover .marquee-host.can-marquee .marquee-static{opacity:0}.watch-history-card:hover .marquee-host.can-marquee .marquee-anim{opacity:1}.watch-history-card:hover .marquee-host.can-marquee .marquee-track{animation-play-state:running}}@media (max-width:1023.98px){.watch-history-card[data-mobile-active=true] .marquee-host.can-marquee .marquee-static{opacity:0}.watch-history-card[data-mobile-active=true] .marquee-host.can-marquee .marquee-anim{opacity:1}.watch-history-card[data-mobile-active=true] .marquee-host.can-marquee .marquee-track{animation-play-state:running}}.watch-history-card{display:flex;flex-wrap:wrap;align-items:stretch;gap:1.25rem;padding:1rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(0,0,.2,1)}@media (prefers-reduced-motion:reduce){.watch-history-card,.watch-history-card[data-motion=enter]{transition-property:none}.watch-history-card[data-motion=enter]{oLine truncated
+72
View File
@@ -5849,3 +5849,75 @@
max-block-size: none;
}
}
@layer components {
.marquee-host {
position: relative;
overflow: hidden;
white-space: nowrap;
/* Optional: Ensure it takes layout space */
width: 100%;
}
.marquee-static {
@apply truncate;
opacity: 1;
transition: opacity 0.3s ease;
}
.marquee-anim {
position: absolute;
inset: 0;
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
mask-image: linear-gradient(to right, transparent, black 4%, black 96%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 4%, black 96%, transparent);
}
.marquee-track {
display: flex;
width: max-content;
animation: marquee-scroll linear infinite;
animation-duration: var(--marquee-duration, 10s);
animation-play-state: paused;
}
.marquee-item {
flex-shrink: 0;
padding-right: 2rem; /* Gap between loop items */
}
@keyframes marquee-scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
/* Desktop Hover State */
@media (min-width: 1024px) {
.card:hover .marquee-host.can-marquee .marquee-static {
opacity: 0;
}
.card:hover .marquee-host.can-marquee .marquee-anim {
opacity: 1;
}
.card:hover .marquee-host.can-marquee .marquee-track {
animation-play-state: running;
}
}
/* Mobile Active State */
@media (max-width: 1023.98px) {
.card[data-mobile-active="true"] .marquee-host.can-marquee .marquee-static {
opacity: 0;
}
.card[data-mobile-active="true"] .marquee-host.can-marquee .marquee-anim {
opacity: 1;
}
.card[data-mobile-active="true"] .marquee-host.can-marquee .marquee-track {
animation-play-state: running;
}
}
}
+121 -10
View File
@@ -386,6 +386,8 @@ export class VideoCard {
this.root = root;
VideoCard.observeViewport(this.root);
this.root.dataset.component = "video-card";
this.syncCardState();
this.syncMotionState();
@@ -449,17 +451,17 @@ export class VideoCard {
isCompact ? "text-base" : "text-lg",
isCompact ? "font-semibold" : "font-bold",
"text-text",
"line-clamp-2",
"cursor-pointer",
];
if (isCompact) {
titleClassNames.push("leading-snug");
}
const title = this.createElement("h3", {
classNames: titleClassNames,
textContent: this.video.title
});
const title = this.buildMarqueeStructure(
"h3",
titleClassNames,
this.video.title
);
title.dataset.videoId = this.video.id;
this.titleEl = title;
@@ -645,7 +647,7 @@ export class VideoCard {
applyIdentityToAuthorElements() {
const nameLabel = this.identity?.name || "Unknown";
if (this.authorNameEl) {
this.authorNameEl.textContent = nameLabel || "Unknown";
this.updateMarqueeText(this.authorNameEl, nameLabel || "Unknown");
}
if (this.authorPicEl) {
@@ -918,16 +920,17 @@ export class VideoCard {
const authorMeta = this.createElement("div", { classNames: ["min-w-0"] });
const authorName = this.createElement("p", {
classNames: [
const authorName = this.buildMarqueeStructure(
"p",
[
isCompact ? "text-xs" : "text-sm",
"text-muted",
"author-name",
"cursor-pointer",
"video-card__author-name",
],
textContent: "Loading name...",
});
"Loading name..."
);
if (this.video.pubkey) {
authorName.dataset.pubkey = this.video.pubkey;
}
@@ -2945,6 +2948,114 @@ export class VideoCard {
return false;
}
buildMarqueeStructure(tagName, classNames, textContent, attrs = {}) {
const host = this.createElement(tagName, {
classNames: [...classNames, "marquee-host"],
attrs,
});
// Use span instead of div to ensure valid HTML when nested in h3/p
const staticEl = this.createElement("span", {
classNames: ["marquee-static", "block"],
textContent,
});
const animEl = this.createElement("span", {
classNames: ["marquee-anim"],
attrs: { "aria-hidden": "true" },
});
const track = this.createElement("span", { classNames: ["marquee-track"] });
const item1 = this.createElement("span", {
classNames: ["marquee-item"],
textContent,
});
const item2 = this.createElement("span", {
classNames: ["marquee-item"],
textContent,
});
track.appendChild(item1);
track.appendChild(item2);
animEl.appendChild(track);
host.appendChild(staticEl);
host.appendChild(animEl);
host._marqueeRefs = { staticEl, item1, item2, track };
VideoCard.observeMarquee(host);
return host;
}
updateMarqueeText(host, text) {
if (!host) return;
if (!host._marqueeRefs) {
host.textContent = text;
return;
}
const { staticEl, item1, item2 } = host._marqueeRefs;
if (staticEl) staticEl.textContent = text;
if (item1) item1.textContent = text;
if (item2) item2.textContent = text;
// Trigger check immediately if possible, or wait for observer
VideoCard.checkMarqueeOverflow(host);
}
// Moved checkMarqueeOverflow to static to be shared by observer
static checkMarqueeOverflow(host) {
if (!host || !host._marqueeRefs) return;
const { staticEl, track, item1 } = host._marqueeRefs;
// Use item1 width (full content width) vs host width
const contentWidth = item1 ? item1.offsetWidth : staticEl.scrollWidth;
const availableWidth = host.clientWidth;
const isOverflowing = contentWidth > availableWidth;
if (isOverflowing) {
host.classList.add("can-marquee");
// Speed: 50px/s
// Distance is contentWidth (one item width including padding)
const distance = contentWidth;
const duration = distance / 50;
const safeDuration = Math.max(duration, 2);
track.style.setProperty("--marquee-duration", `${safeDuration}s`);
} else {
host.classList.remove("can-marquee");
}
}
static observeMarquee(element) {
if (!VideoCard.marqueeResizeObserver) {
VideoCard.marqueeResizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
VideoCard.checkMarqueeOverflow(entry.target);
}
});
}
VideoCard.marqueeResizeObserver.observe(element);
}
static observeViewport(element) {
if (!VideoCard.viewportObserver) {
VideoCard.viewportObserver = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.setAttribute("data-mobile-active", "true");
} else {
entry.target.removeAttribute("data-mobile-active");
}
}
},
{ rootMargin: "-40% 0px -40% 0px", threshold: 0 }
);
}
VideoCard.viewportObserver.observe(element);
}
createElement(tagName, { classNames = [], attrs = {}, textContent } = {}) {
const el = this.document.createElement(tagName);
classNames
Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

-54
View File
@@ -1,54 +0,0 @@
import sys
import os
import time
from playwright.sync_api import sync_playwright
def verify_feed_info(page):
print("Pre-seeding localStorage...")
page.goto("http://localhost:8000") # Go to root first to set storage origin
page.evaluate("window.localStorage.setItem('hasSeenDisclaimer', 'true')")
print("Navigating to For You view...")
page.goto("http://localhost:8000/#view=for-you")
print("Waiting for feed info trigger...")
trigger = page.locator("#feedInfoTrigger")
try:
trigger.wait_for(state="visible", timeout=10000)
except Exception:
page.screenshot(path="verification/debug_not_found.png")
raise
# Ensure no modals are blocking
# We can try to force click or wait for potential overlays to disappear
# But pre-seeding should handle the main one.
print("Clicking trigger...")
trigger.click()
print("Waiting for popover...")
popover = page.locator(".popover-panel")
popover.wait_for(state="visible", timeout=5000)
text = popover.inner_text()
print(f"Popover text: {text}")
expected = "Most Recent Video - Disinterests Tags - Watch History + Interests Tags"
if expected not in text:
raise Exception(f"Unexpected text: {text}")
page.screenshot(path="verification/feed_info.png")
print("Screenshot saved to verification/feed_info.png")
if __name__ == "__main__":
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
try:
verify_feed_info(page)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
finally:
browser.close()