+
+
diff --git a/landing/locales/ar.json b/landing/locales/ar.json
index fc8c1c2ff..78b3cb620 100644
--- a/landing/locales/ar.json
+++ b/landing/locales/ar.json
@@ -299,7 +299,8 @@
"terminal": "في الطرفية لديك",
"refreshNotice": "جارٍ تحديث بيانات المكونات الإضافية. حاول مرة أخرى بعد قليل.",
"otherMethods": "طرق تثبيت أخرى",
- "planNotice": "أمر واحد يخطط لكل وكيل محدد قبل تغيير الملفات",
+ "watchDemo": "شاهد العرض التوضيحي",
+ "demoVideoTitle": "عرض توضيحي لتثبيت Universal Agent Plugins",
"supportedClients": "العملاء المدعومون",
"clientNote": "يثبّت CLI أو يحضّر التنسيق الأصلي الذي يدعمه كل عميل. قد يطلب منك بعض العملاء إكمال التفعيل أو تسجيل الدخول.",
"install": "تثبيت {name}",
diff --git a/landing/locales/en.json b/landing/locales/en.json
index cd85a168f..8cac8e2f1 100644
--- a/landing/locales/en.json
+++ b/landing/locales/en.json
@@ -453,7 +453,8 @@
"terminal": "In your terminal",
"refreshNotice": "Plugin data is refreshing. Try again shortly.",
"otherMethods": "Other installation methods",
- "planNotice": "One command plans every selected agent before changing files",
+ "watchDemo": "Watch demo",
+ "demoVideoTitle": "Universal Agent Plugins install demo",
"supportedClients": "Supported clients",
"clientNote": "The CLI installs or prepares the native format each client supports. Some clients may ask you to finish activation or sign in.",
"install": "Install {name}",
diff --git a/landing/locales/es.json b/landing/locales/es.json
index 281cb6f68..2ab94dca4 100644
--- a/landing/locales/es.json
+++ b/landing/locales/es.json
@@ -436,7 +436,8 @@
"terminal": "en tu terminal",
"refreshNotice": "Los datos del complemento son refrescantes. Vuelve a intentarlo en breve.",
"otherMethods": "Otros métodos de instalación",
- "planNotice": "Un comando planifica cada agente seleccionado antes de cambiar archivos",
+ "watchDemo": "Ver demostración",
+ "demoVideoTitle": "Demostración de instalación de Universal Agent Plugins",
"supportedClients": "Clientes apoyados",
"clientNote": "La CLI instala o prepara el formato nativo que admite cada cliente. Algunos clientes pueden pedirle que finalice la activación o inicie sesión.",
"install": "Instalar {name}",
diff --git a/landing/locales/fr.json b/landing/locales/fr.json
index fa80dae8f..f7552a444 100644
--- a/landing/locales/fr.json
+++ b/landing/locales/fr.json
@@ -436,7 +436,8 @@
"terminal": "Dans votre terminal",
"refreshNotice": "Les données du plugin sont actualisées. Réessayez sous peu.",
"otherMethods": "Autres méthodes d'installation",
- "planNotice": "Une commande planifie chaque agent sélectionné avant de modifier les fichiers",
+ "watchDemo": "Voir la démo",
+ "demoVideoTitle": "Démo d’installation d’Universal Agent Plugins",
"supportedClients": "Clients pris en charge",
"clientNote": "La CLI installe ou prépare le format natif pris en charge par chaque client. Certains clients peuvent vous demander de terminer l'activation ou de vous connecter.",
"install": "Installer {name}",
diff --git a/landing/locales/hi.json b/landing/locales/hi.json
index edd83b337..dfb244929 100644
--- a/landing/locales/hi.json
+++ b/landing/locales/hi.json
@@ -436,7 +436,8 @@
"terminal": "आपके टर्मिनल में",
"refreshNotice": "प्लगइन डेटा ताज़ा है. शीघ्र ही पुनः प्रयास करें.",
"otherMethods": "अन्य स्थापना विधियाँ",
- "planNotice": "फ़ाइलें बदलने से पहले एक कमांड प्रत्येक चयनित एजेंट की योजना बनाता है",
+ "watchDemo": "डेमो देखें",
+ "demoVideoTitle": "Universal Agent Plugins इंस्टॉलेशन डेमो",
"supportedClients": "समर्थित ग्राहक",
"clientNote": "सीएलआई प्रत्येक क्लाइंट द्वारा समर्थित मूल प्रारूप को स्थापित या तैयार करता है। कुछ ग्राहक आपसे सक्रियण समाप्त करने या साइन इन करने के लिए कह सकते हैं।",
"install": "{name} स्थापित करें",
diff --git a/landing/locales/pt.json b/landing/locales/pt.json
index c5ba81ec8..a4d8c24f3 100644
--- a/landing/locales/pt.json
+++ b/landing/locales/pt.json
@@ -299,7 +299,8 @@
"terminal": "No seu terminal",
"refreshNotice": "Os dados de plugins estão sendo atualizados. Tente novamente em instantes.",
"otherMethods": "Outros métodos de instalação",
- "planNotice": "Um comando planeja cada agente selecionado antes de alterar arquivos",
+ "watchDemo": "Ver demonstração",
+ "demoVideoTitle": "Demonstração de instalação do Universal Agent Plugins",
"supportedClients": "Clientes compatíveis",
"clientNote": "A CLI instala ou prepara o formato nativo compatível com cada cliente. Alguns clientes podem pedir que você conclua a ativação ou faça login.",
"install": "Instalar {name}",
diff --git a/landing/locales/ru.json b/landing/locales/ru.json
index f040e8e00..90a32e41f 100644
--- a/landing/locales/ru.json
+++ b/landing/locales/ru.json
@@ -436,7 +436,8 @@
"terminal": "В терминале",
"refreshNotice": "Данные плагина обновляются. Повторите попытку чуть позже.",
"otherMethods": "Другие способы установки",
- "planNotice": "Одна команда планирует действия для каждого выбранного агента до изменения файлов",
+ "watchDemo": "Смотреть демо",
+ "demoVideoTitle": "Демонстрация установки Universal Agent Plugins",
"supportedClients": "Поддерживаемые клиенты",
"clientNote": "CLI устанавливает или подготавливает нативный формат, поддерживаемый каждым клиентом. Некоторые клиенты могут попросить завершить активацию или вход.",
"install": "Установить {name}",
diff --git a/landing/locales/uk.json b/landing/locales/uk.json
index 79a51de01..a6b483538 100644
--- a/landing/locales/uk.json
+++ b/landing/locales/uk.json
@@ -436,7 +436,8 @@
"terminal": "У вашому терміналі",
"refreshNotice": "Дані плагіна оновлюються. Спробуйте ще раз невдовзі.",
"otherMethods": "Інші способи встановлення",
- "planNotice": "Одна команда планує дії для кожного вибраного агента перед зміною файлів",
+ "watchDemo": "Переглянути демо",
+ "demoVideoTitle": "Демонстрація встановлення Universal Agent Plugins",
"supportedClients": "Підтримувані клієнти",
"clientNote": "CLI встановлює або готує нативний формат, який підтримує кожен клієнт. Деякі клієнти можуть попросити завершити активацію або ввійти.",
"install": "Встановити {name}",
diff --git a/landing/locales/zh.json b/landing/locales/zh.json
index 7ae38c8ad..c69358e07 100644
--- a/landing/locales/zh.json
+++ b/landing/locales/zh.json
@@ -436,7 +436,8 @@
"terminal": "在您的终端中",
"refreshNotice": "插件数据正在刷新。稍后再试。",
"otherMethods": "其他安装方式",
- "planNotice": "一个命令在更改文件之前规划每个选定的代理",
+ "watchDemo": "观看演示",
+ "demoVideoTitle": "Universal Agent Plugins 安装演示",
"supportedClients": "支持的客户",
"clientNote": "CLI 安装或准备每个客户端支持的本机格式。有些客户端可能会要求您完成激活或登录。",
"install": "安装{name}",
diff --git a/landing/nuxt.config.ts b/landing/nuxt.config.ts
index 056f7d2cb..9aa5bcee9 100644
--- a/landing/nuxt.config.ts
+++ b/landing/nuxt.config.ts
@@ -35,6 +35,9 @@ const registryPagesOrigin =
const discoveryKeyID = process.env.NUXT_PUBLIC_DISCOVERY_KEY_ID || 'uap-discovery-2026-01';
const discoveryPublicKey =
process.env.NUXT_PUBLIC_DISCOVERY_PUBLIC_KEY || 'IxWvGuscXR9crlCrGyBQZNqroYNVPbBA1B3pnjSffhc=';
+// Public playback ID for the owner-uploaded product film; an empty override disables it.
+const muxPlaybackId =
+ process.env.NUXT_PUBLIC_MUX_PLAYBACK_ID ?? 'whg5hoTZLPJclfEYfp8OEJQu6Gcuqc2519U6nhx7AP8';
function resolveRegistrySnapshot(): string {
const explicit = process.env.UAP_SIGNED_SNAPSHOT_PATH;
@@ -187,6 +190,7 @@ export default defineNuxtConfig({
registryPagesOrigin,
discoveryKeyID,
discoveryPublicKey,
+ muxPlaybackId,
},
},
});
diff --git a/landing/plugins/scroll-reveal.client.ts b/landing/plugins/scroll-reveal.client.ts
index c1a622ed0..9503c361a 100644
--- a/landing/plugins/scroll-reveal.client.ts
+++ b/landing/plugins/scroll-reveal.client.ts
@@ -32,7 +32,7 @@ export default defineNuxtPlugin((nuxtApp) => {
observer?.unobserve(section);
}
},
- { rootMargin: '0px 0px -8% 0px', threshold: 0.06 },
+ { rootMargin: '0px 0px 160px 0px', threshold: 0 },
);
for (const section of revealable) {
if (section.isConnected) observer.observe(section);
@@ -40,5 +40,7 @@ export default defineNuxtPlugin((nuxtApp) => {
});
};
- nuxtApp.hook('page:finish', () => { requestAnimationFrame(revealSections); });
+ nuxtApp.hook('page:finish', () => {
+ requestAnimationFrame(revealSections);
+ });
});
diff --git a/landing/tests/browser/hero-demo-video.spec.ts b/landing/tests/browser/hero-demo-video.spec.ts
new file mode 100644
index 000000000..f9831a30b
--- /dev/null
+++ b/landing/tests/browser/hero-demo-video.spec.ts
@@ -0,0 +1,104 @@
+import { mkdirSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { expect, test } from '@playwright/test';
+import { setSignedMirrorTestTime } from './i18n-state.helpers';
+
+test('demo loads its player only after keyboard activation and keeps installation in place', async ({
+ page,
+}) => {
+ await setSignedMirrorTestTime(page);
+ const playerRequests: string[] = [];
+ await page.route('https://player.mux.com/**', (route) => {
+ playerRequests.push(route.request().url());
+ return route.fulfill({
+ contentType: 'text/html',
+ body: 'Player boundary',
+ });
+ });
+ for (const width of [1440, 390, 320]) {
+ await page.setViewportSize({ width, height: 1000 });
+ await page.goto('./');
+ const demo = page.locator('.hero-demo-video');
+ const button = demo.getByRole('button', { name: 'Watch demo' });
+ await expect(button).toBeVisible();
+ await expect(demo.locator('iframe')).toHaveCount(0);
+ const requestsBefore = playerRequests.length;
+ const installBefore = (await page.locator('.hero__window').boundingBox())!;
+ const demoBefore = (await demo.boundingBox())!;
+ expect(demoBefore.width).toBeLessThanOrEqual(480);
+ expect(installBefore.y - (demoBefore.y + demoBefore.height)).toBeGreaterThanOrEqual(27);
+ if (width === 1440) {
+ const header = (await page.getByRole('banner').boundingBox())!;
+ const topGap = demoBefore.y - (header.y + header.height);
+ expect(demoBefore.width).toBe(480);
+ expect(topGap).toBeGreaterThanOrEqual(16);
+ expect(topGap).toBeLessThanOrEqual(32);
+ }
+ await button.focus();
+ await page.keyboard.press('Enter');
+ const frame = demo.locator('iframe');
+ await expect(frame).toBeVisible();
+ await expect(frame).toHaveAttribute(
+ 'src',
+ /^https:\/\/player\.mux\.com\/[a-zA-Z0-9]+\?autoplay=true$/,
+ );
+ await expect(frame).toHaveAttribute('title', 'Universal Agent Plugins install demo');
+ await expect(frame).toHaveAttribute('allowfullscreen', '');
+ await expect.poll(() => playerRequests.length).toBe(requestsBefore + 1);
+ const installAfter = (await page.locator('.hero__window').boundingBox())!;
+ expect(Math.abs(installAfter.y - installBefore.y)).toBeLessThan(1);
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(
+ true,
+ );
+ }
+});
+
+test('owner-uploaded Mux film actually plays on desktop and mobile', async ({ page }) => {
+ test.skip(
+ process.env.UAP_E2E_LIVE_VIDEO !== '1',
+ 'Explicit live CDN check; ordinary CI tests the player boundary offline.',
+ );
+ test.setTimeout(120_000);
+ await setSignedMirrorTestTime(page);
+ for (const width of [1440, 390]) {
+ await page.setViewportSize({ width, height: 1000 });
+ await page.goto('./');
+ const demo = page.locator('.hero-demo-video');
+ await demo.scrollIntoViewIfNeeded();
+ await expect(page.getByRole('link', { name: /^Explore [1-9]/ })).toBeVisible({ timeout: 30_000 });
+ await expect
+ .poll(
+ () =>
+ demo
+ .locator('img')
+ .evaluate((image: HTMLImageElement) => image.complete && image.naturalWidth > 0),
+ { timeout: 30_000 },
+ )
+ .toBe(true);
+ const directory = process.env.UAP_LANDING_SCREENSHOT_DIR;
+ if (directory) {
+ mkdirSync(directory, { recursive: true });
+ await page.screenshot({ path: resolve(directory, `hero-video-poster-${width}.png`) });
+ }
+ await demo.getByRole('button', { name: 'Watch demo' }).click();
+ const video = page.frameLocator('.hero-demo-video iframe').locator('video');
+ await expect(video).toBeAttached({ timeout: 30_000 });
+ await expect
+ .poll(() => video.evaluate((element: HTMLVideoElement) => element.currentTime), {
+ timeout: 30_000,
+ })
+ .toBeGreaterThan(1);
+ const media = await video.evaluate((element: HTMLVideoElement) => ({
+ duration: element.duration,
+ error: element.error?.code ?? null,
+ paused: element.paused,
+ }));
+ expect(media.duration).toBeGreaterThan(42);
+ expect(media.duration).toBeLessThan(43);
+ expect(media.error).toBeNull();
+ expect(media.paused).toBe(false);
+ if (directory)
+ await page.screenshot({ path: resolve(directory, `hero-video-playing-${width}.png`) });
+ await video.evaluate((element: HTMLVideoElement) => element.pause());
+ }
+});
diff --git a/landing/tests/browser/hero-layout.spec.ts b/landing/tests/browser/hero-layout.spec.ts
new file mode 100644
index 000000000..c52468b5f
--- /dev/null
+++ b/landing/tests/browser/hero-layout.spec.ts
@@ -0,0 +1,180 @@
+import { mkdirSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { expect, test, type Page } from '@playwright/test';
+import { hydrated, setSignedMirrorTestTime, wording } from './i18n-state.helpers';
+
+const exactCommand = 'npx universal-agent-plugins add context7 --target codex,cursor,vscode';
+
+async function capture(page: Page, name: string) {
+ const directory = process.env.UAP_LANDING_SCREENSHOT_DIR;
+ if (!directory) return;
+ mkdirSync(directory, { recursive: true });
+ await page.screenshot({ path: resolve(directory, `${name}.png`) });
+}
+
+async function waitForCommand(page: Page) {
+ await hydrated(page);
+ await expect(page.locator('.hero__window .command-snippet')).toBeVisible({
+ timeout: 15_000,
+ });
+}
+
+test.beforeEach(async ({ page }) => {
+ await setSignedMirrorTestTime(page);
+ await page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
+});
+
+test('hero controls and 13 client links remain complete across four viewports', async ({
+ page,
+}) => {
+ for (const width of [1440, 1024, 390, 320]) {
+ await page.setViewportSize({ width, height: width >= 1024 ? 1000 : 844 });
+ await page.goto('./');
+ await waitForCommand(page);
+
+ expect(
+ await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth),
+ ).toBe(true);
+
+ const items = page.locator('.client-strip > li');
+ await expect(items).toHaveCount(13);
+ const geometry = await items.evaluateAll((nodes) =>
+ nodes.map((node) => {
+ const rect = node.getBoundingClientRect();
+ return {
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ right: rect.right,
+ center: rect.x + rect.width / 2,
+ };
+ }),
+ );
+ const rows = [...new Set(geometry.map((item) => Math.round(item.y)))];
+ if (width >= 1440) {
+ expect(rows).toHaveLength(1);
+ } else if (width < 1024) {
+ expect(rows.length).toBeGreaterThanOrEqual(3);
+ }
+ for (const item of geometry) {
+ expect(item.x).toBeGreaterThanOrEqual(0);
+ expect(item.right).toBeLessThanOrEqual(width + 0.5);
+ }
+ expect(
+ await page
+ .locator('.client-strip__copy strong')
+ .evaluateAll((nodes) =>
+ nodes.every(
+ (node) =>
+ getComputedStyle(node).textOverflow !== 'ellipsis' &&
+ node.scrollWidth <= node.clientWidth,
+ ),
+ ),
+ ).toBe(true);
+
+ const captions = page.locator('.hero-quick-start__caption');
+ const controls = page.locator(
+ '.hero-quick-start__step .app-multiselect__trigger, .hero-quick-start__command',
+ );
+ await expect(captions).toHaveCount(2);
+ for (let index = 0; index < 2; index += 1) {
+ const caption = (await captions.nth(index).boundingBox())!;
+ const control = (await controls.nth(index).boundingBox())!;
+ const step = (await page.locator('.hero-quick-start__step').nth(index).boundingBox())!;
+ expect(caption.height).toBeLessThanOrEqual(24);
+ expect(caption.y + caption.height).toBeLessThanOrEqual(control.y + 0.5);
+ expect(control.width).toBeGreaterThanOrEqual(step.width - 1);
+ }
+
+ const code = page.locator('.hero-quick-start__command pre');
+ const copy = page.locator('.hero-quick-start__command button');
+ const codeBox = (await code.boundingBox())!;
+ const copyBox = (await copy.boundingBox())!;
+ expect(codeBox.x + codeBox.width).toBeLessThanOrEqual(copyBox.x + 0.5);
+ expect(await code.evaluate((node) => node.scrollWidth <= node.clientWidth)).toBe(true);
+
+ await capture(page, `hero-layout-${width}`);
+ }
+});
+
+test('selected targets produce one complete copyable command with keyboard activation', async ({
+ page,
+}) => {
+ test.setTimeout(60_000);
+ for (const width of [1440, 320]) {
+ await page.setViewportSize({ width, height: width === 1440 ? 1000 : 844 });
+ await page.goto('./');
+ await waitForCommand(page);
+
+ const trigger = page.locator('.hero-quick-start__step .app-multiselect__trigger');
+ await trigger.focus();
+ await page.keyboard.press('Enter');
+ for (const name of [/^Codex /, /^Cursor /, /^VS Code /]) {
+ await page.getByRole('checkbox', { name }).click();
+ }
+ await page.keyboard.press('Escape');
+ await expect(trigger).toBeFocused();
+
+ const command = page.locator('.hero-quick-start__command');
+ await expect(command.locator('code')).toHaveText(exactCommand);
+ const copy = command.locator('button');
+ await expect(copy).toHaveAccessibleName('Copy command');
+ await copy.focus();
+ await page.keyboard.press('Enter');
+ await expect(copy).toHaveAccessibleName(wording('en')('registryUi.command.copiedLabel'));
+ expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(exactCommand);
+ await capture(page, `hero-command-${width}`);
+ }
+});
+
+test('catalog reveal is early on navigation, compact trust badges stay compact, and video preview waits for a real Mux asset', async ({
+ page,
+}) => {
+ test.setTimeout(60_000);
+ await page.emulateMedia({ reducedMotion: 'no-preference' });
+ await page.setViewportSize({ width: 1440, height: 600 });
+ await page.goto('./');
+ await expect(page.locator('html')).toHaveClass(/scroll-reveal-active/, { timeout: 15_000 });
+ const catalog = page.locator('.catalog-wrap');
+ await catalog.evaluate((node) => {
+ window.scrollTo({
+ top: node.getBoundingClientRect().top + window.scrollY - window.innerHeight + 20,
+ behavior: 'instant',
+ });
+ });
+ await expect
+ .poll(() => catalog.evaluate((node) => node.getBoundingClientRect().top))
+ .toBeLessThanOrEqual(580);
+ await expect(catalog).toHaveClass(/scroll-reveal--visible/);
+ await page.goto('./');
+ await page.locator('.hero__actions .button--primary').click();
+ await expect(catalog).toHaveClass(/scroll-reveal--visible/);
+ await expect(catalog).toHaveCSS('opacity', '1');
+
+ await page.goto('./#plugins');
+ await expect(catalog).toHaveClass(/scroll-reveal--visible/);
+ await page.emulateMedia({ reducedMotion: 'reduce' });
+ await page.goto('./');
+ await expect(catalog).toHaveClass(/scroll-reveal--visible/);
+
+ await page.goto('./plugins/');
+ await expect(page.locator('.catalog')).toHaveAttribute('data-discovery-state', /current|cached/, {
+ timeout: 15_000,
+ });
+ await page.getByRole('searchbox', { name: 'Search plugins' }).fill('context7');
+ const badge = page.locator('.plugin-card__ribbon').first();
+ await expect(badge).toHaveText('Reviewed');
+ const badgeBox = (await badge.boundingBox())!;
+ expect(badgeBox.width).toBeLessThan(90);
+
+ await page.goto('./');
+ await expect(page.locator('.hero-demo-video')).toBeVisible();
+ await expect(page.locator('.hero-demo-video button')).toHaveAccessibleName('Watch demo');
+ const preview = (await page.locator('.hero-demo-video').boundingBox())!;
+ const install = (await page.locator('.hero__window').boundingBox())!;
+ expect(preview.width).toBe(480);
+ expect(install.y - (preview.y + preview.height)).toBeGreaterThanOrEqual(27);
+ await expect(page.locator('iframe[src*="player.mux.com"]')).toHaveCount(0);
+ await expect(page.locator('.hero-quick-start__footer')).toHaveCount(0);
+ await capture(page, 'hero-early-reveal');
+});
diff --git a/landing/tests/browser/landing.spec.ts b/landing/tests/browser/landing.spec.ts
index 0d55d440d..1becc0600 100644
--- a/landing/tests/browser/landing.spec.ts
+++ b/landing/tests/browser/landing.spec.ts
@@ -5,6 +5,25 @@ import { hydrated, setSignedMirrorTestTime } from './i18n-state.helpers';
test.beforeEach(async ({ page }) => setSignedMirrorTestTime(page));
+async function waitForFullDirectory(page: import('@playwright/test').Page) {
+ await expect(page.locator('.catalog')).toHaveAttribute('data-discovery-state', /current|cached/, {
+ timeout: 15_000,
+ });
+ // The live catalog changes size. Prove pagination beyond the rendered cards,
+ // rather than requiring a historical number of community packages.
+ await expect
+ .poll(
+ async () => {
+ const summary = await page.locator('.catalog-count').innerText();
+ const total = Number(summary.match(/([\d,]+) plugins/)?.[1]?.replaceAll(',', '') ?? 0);
+ const shown = await page.locator('.plugin-card').count();
+ return shown > 0 && total > shown;
+ },
+ { timeout: 15_000 },
+ )
+ .toBe(true);
+}
+
const parseJsonLd = async (page: import('@playwright/test').Page) => {
const values = await page.locator('script[type="application/ld+json"]').allTextContents();
return values.flatMap((value) => {
@@ -69,9 +88,7 @@ test('homepage installs with auto-detection and exposes the full directory', asy
await expect(securityTooltip).toContainText(/exact indexed revision [0-9a-f]{12}/);
await expect(securityTooltip).toContainText(/does not run the plugin or guarantee safety/i);
await expect(page.getByText(/guarantee of safety/i)).toHaveCount(0);
- await expect(page.locator('.catalog-count')).toContainText(/[2-9],?\d{3} plugins/, {
- timeout: 15_000,
- });
+ await waitForFullDirectory(page);
const catalogSummary = await page.locator('.catalog-count').innerText();
const totalPlugins = Number(catalogSummary.match(/([\d,]+) plugins/)![1].replaceAll(',', ''));
await expect(explorePlugins).toHaveAccessibleName(
@@ -136,7 +153,7 @@ test('community catalog uses the live registry when the baked snapshot is unavai
await expect(page.locator('.catalog')).toHaveAttribute('data-discovery-state', /current|cached/, {
timeout: 45_000,
});
- await expect(page.locator('.catalog-count')).toContainText(/[2-9],?\d{3} plugins/);
+ await waitForFullDirectory(page);
await expect(page.locator('.discovery-status--stale')).toHaveCount(0);
await expect(page.locator('.discovery-status--unavailable')).toHaveCount(0);
});
@@ -456,9 +473,7 @@ test('directory filters and reviewed detail keep automatic detection as the defa
page,
}) => {
await page.goto('./plugins');
- await expect(page.locator('.catalog-count')).toContainText(/[2-9],?\d{3}/, {
- timeout: 15_000,
- });
+ await waitForFullDirectory(page);
await page.getByPlaceholder(/Search by name/).fill('gitlab');
const gitlabCard = page
.locator('.plugin-card')
diff --git a/landing/tests/browser/visual-controls.spec.ts b/landing/tests/browser/visual-controls.spec.ts
index c9f4ca283..3bac82952 100644
--- a/landing/tests/browser/visual-controls.spec.ts
+++ b/landing/tests/browser/visual-controls.spec.ts
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
test('flat filters retain keyboard selection, category search, and reset', async ({ page }) => {
await page.goto('./');
- await expect(page.getByText('In your terminal', { exact: true })).toBeVisible();
+ await expect(page.locator('.hero-quick-start__label').filter({ hasText: 'Copy and run' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Other installation methods' })).toHaveAttribute(
'href',
'https://github.com/777genius/universal-agent-plugins#quick-start',