|
After Width: | Height: | Size: 608 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 617 KiB |
|
After Width: | Height: | Size: 588 KiB |
|
After Width: | Height: | Size: 524 KiB |
|
After Width: | Height: | Size: 613 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 552 KiB |
|
After Width: | Height: | Size: 392 KiB |
|
After Width: | Height: | Size: 388 KiB |
|
After Width: | Height: | Size: 414 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 811 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 782 KiB |
|
After Width: | Height: | Size: 748 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 782 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 722 KiB |
|
After Width: | Height: | Size: 741 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 722 KiB |
231
tests/visual/r2-uat-mega.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const TOKEN = '36a36b027129a154c0e86122ed56927409b612b6eb41f612b9177c85848719d3';
|
||||
const BASE_URL = process.env.WAGGLE_E2E_BASE_URL ?? 'http://localhost:3333';
|
||||
const SS = 'UAT 3/mega-test-v2/screenshots';
|
||||
|
||||
async function setupPage(page: Page, theme = 'dark') {
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
await page.evaluate((t: string) => {
|
||||
localStorage.setItem('waggle:onboarding', JSON.stringify({ completed: true }));
|
||||
localStorage.setItem('waggle-theme', t);
|
||||
}, theme);
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
async function clickNavView(page: Page, viewName: string): Promise<boolean> {
|
||||
const all = await page.locator('button').all();
|
||||
for (const btn of all) {
|
||||
const t = (await btn.textContent().catch(() => '')).trim();
|
||||
const a = (await btn.getAttribute('aria-label').catch(() => '') || '');
|
||||
if (t.toLowerCase() === viewName.toLowerCase() || a.toLowerCase().includes(viewName.toLowerCase())) {
|
||||
await btn.click();
|
||||
await page.waitForTimeout(800);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
test.describe('R2 - Mega UAT Visual', () => {
|
||||
test.use({ viewport: { width: 1920, height: 1080 } });
|
||||
|
||||
test('40 - chat view dark 1920x1080', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
await page.screenshot({ path: `${SS}/40-chat-dark.png` });
|
||||
|
||||
const html = await page.content();
|
||||
const honeyCount = (html.match(/honey|amber|#F5A|#f5a/gi) || []).length;
|
||||
const hexCount = (html.match(/hex|hexagon|honeycomb/gi) || []).length;
|
||||
const emojiCount = (html.match(/[\u{1F300}-\u{1FFFF}]/gu) || []).length;
|
||||
const imgCount = (html.match(/<img/g) || []).length;
|
||||
const hasBee = html.toLowerCase().includes('bee');
|
||||
const bodyFont = await page.evaluate(() => getComputedStyle(document.body).fontFamily);
|
||||
const bgColor = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
|
||||
const navTxts = await page.locator('nav button, aside button').allTextContents();
|
||||
|
||||
console.log(`honey/amber refs: ${honeyCount}`);
|
||||
console.log(`hex/hexagon refs: ${hexCount}`);
|
||||
console.log(`emoji in HTML: ${emojiCount}`);
|
||||
console.log(`img tags: ${imgCount}`);
|
||||
console.log(`bee refs: ${hasBee}`);
|
||||
console.log(`body font: ${bodyFont}`);
|
||||
console.log(`body bg: ${bgColor}`);
|
||||
console.log(`nav buttons: ${navTxts.slice(0, 8).join(' | ')}`);
|
||||
|
||||
expect(bgColor).not.toBe('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
test('41 - home cockpit view', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
const found = await clickNavView(page, 'Home');
|
||||
const btns = await page.locator('nav button, aside button').allTextContents();
|
||||
console.log(`Nav buttons: ${btns.join(' | ')}`);
|
||||
console.log(`Found cockpit: ${found}`);
|
||||
await page.screenshot({ path: `${SS}/41-cockpit.png` });
|
||||
const html = await page.content();
|
||||
console.log(`Has KPI/metric: ${html.includes('kpi') || html.includes('KPI') || html.includes('metric')}`);
|
||||
console.log(`Has cost chart: ${html.includes('cost') || html.includes('Cost')}`);
|
||||
console.log(`Has heartbeat/health: ${html.includes('health') || html.includes('Health')}`);
|
||||
});
|
||||
|
||||
test('42 - memory browser', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
await clickNavView(page, 'Memory');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: `${SS}/42-memory.png` });
|
||||
const html = await page.content();
|
||||
console.log(`Has source dots: ${html.includes('dot') || html.includes('source-type') || html.includes('hex')}`);
|
||||
console.log(`Has bee researcher: ${html.includes('bee') && html.includes('empty')}`);
|
||||
});
|
||||
|
||||
test('43 - settings tabs', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
await clickNavView(page, 'Settings');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: `${SS}/43-settings.png` });
|
||||
const tabCount = await page.locator('[role="tab"]').count();
|
||||
const tabTexts = await page.locator('[role="tab"]').allTextContents();
|
||||
console.log(`Settings tab count: ${tabCount}`);
|
||||
console.log(`Tab labels: ${tabTexts.join(' | ')}`);
|
||||
const html = await page.content();
|
||||
console.log(`Has DEFAULT badge: ${html.includes('DEFAULT') || html.includes('default-badge')}`);
|
||||
console.log(`Has model grid: ${html.includes('model') || html.includes('Model')}`);
|
||||
});
|
||||
|
||||
test('44 - capabilities', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
await clickNavView(page, 'Capabilities');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: `${SS}/44-capabilities.png` });
|
||||
const html = await page.content();
|
||||
const hasWave8A = html.includes('Wave 8A') || html.includes('wave-8a');
|
||||
const beeImgCount = await page.locator('img[src*="bee"], img[alt*="bee"]').count();
|
||||
console.log(`Has Wave 8A text (should be false): ${hasWave8A}`);
|
||||
console.log(`Bee img elements: ${beeImgCount}`);
|
||||
});
|
||||
|
||||
test('45 - onboarding re-trigger', async ({ page }) => {
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
await page.evaluate(() => {
|
||||
localStorage.removeItem('waggle:onboarding');
|
||||
});
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
||||
await page.waitForTimeout(2000);
|
||||
await page.screenshot({ path: `${SS}/45-onboarding.png` });
|
||||
const html = await page.content();
|
||||
console.log(`Onboarding welcome: ${html.includes('Welcome') || html.includes('welcome')}`);
|
||||
console.log(`Has Hive: ${html.includes('Hive') || html.includes('hive')}`);
|
||||
console.log(`Has bee mascot: ${html.toLowerCase().includes('bee')}`);
|
||||
console.log(`Has hex/progress dots: ${html.includes('hex') || html.includes('progress')}`);
|
||||
console.log(`Has provider selection: ${html.includes('provider') || html.includes('API Key')}`);
|
||||
});
|
||||
|
||||
test('46 - events', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
await clickNavView(page, 'Events');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: `${SS}/46-events.png` });
|
||||
const html = await page.content();
|
||||
console.log(`Has timeline: ${html.includes('timeline') || html.includes('Timeline') || html.includes('event')}`);
|
||||
console.log(`Has filter: ${html.includes('filter') || html.includes('Filter')}`);
|
||||
});
|
||||
|
||||
test('47 - light mode', async ({ page }) => {
|
||||
await setupPage(page, 'light');
|
||||
expect(await clickNavView(page, 'Chat')).toBe(true);
|
||||
await page.screenshot({ path: `${SS}/47-light-chat.png` });
|
||||
const bgColor = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
|
||||
const htmlClass = await page.evaluate(() => document.documentElement.className);
|
||||
console.log(`Body bg (light): ${bgColor}`);
|
||||
console.log(`HTML classes: ${htmlClass}`);
|
||||
const isLight = !bgColor.includes('17,') && !bgColor.includes('20,') && !bgColor.includes('0, 0, 0');
|
||||
console.log(`Light mode applied: ${isLight}`);
|
||||
expect(await page.evaluate(() => document.documentElement.getAttribute('data-theme'))).toBe('light');
|
||||
expect(isLight).toBe(true);
|
||||
|
||||
await clickNavView(page, 'Home');
|
||||
await page.screenshot({ path: `${SS}/47-light-cockpit.png` });
|
||||
await clickNavView(page, 'Memory');
|
||||
await page.screenshot({ path: `${SS}/47-light-memory.png` });
|
||||
});
|
||||
|
||||
test('48-49 - brand assets and custom icons', async ({ page }) => {
|
||||
const r1 = await page.goto(`${BASE_URL}/brand/logo.jpeg`);
|
||||
const s1 = r1?.status();
|
||||
const r2 = await page.goto(`${BASE_URL}/brand/logo-light.jpeg`);
|
||||
const s2 = r2?.status();
|
||||
console.log(`logo.jpeg: ${s1}`);
|
||||
console.log(`logo-light.jpeg: ${s2}`);
|
||||
|
||||
await setupPage(page, 'dark');
|
||||
const iconImgs = await page.locator('img[src*="icon-"]').count();
|
||||
const svgNavIcons = await page.locator('nav svg, aside svg').count();
|
||||
const emojiInNav = await page.evaluate(() => {
|
||||
const navEl = document.querySelector('nav, aside, [role="navigation"]');
|
||||
if (!navEl) return 0;
|
||||
const txt = navEl.textContent || '';
|
||||
return (txt.match(/[\u{1F300}-\u{1FFFF}]/gu) || []).length;
|
||||
});
|
||||
console.log(`icon- img tags: ${iconImgs}`);
|
||||
console.log(`nav SVG elements: ${svgNavIcons}`);
|
||||
console.log(`emoji in nav: ${emojiInNav}`);
|
||||
|
||||
expect(s1).toBe(200);
|
||||
});
|
||||
|
||||
test('50 - bee character count', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
const beeImgs = await page.locator('img[src*="bee"], img[alt*="bee"], img[alt*="Bee"]').count();
|
||||
const html = await page.content();
|
||||
const beeInText = (html.match(/\bbee\b/gi) || []).length;
|
||||
console.log(`Bee img elements: ${beeImgs}`);
|
||||
console.log(`bee word in HTML: ${beeInText}`);
|
||||
});
|
||||
|
||||
test('51-53 - contrast readability', async ({ page }) => {
|
||||
await setupPage(page, 'dark');
|
||||
const metrics = await page.evaluate(() => {
|
||||
const bodyColor = getComputedStyle(document.body).color;
|
||||
const bodyBg = getComputedStyle(document.body).backgroundColor;
|
||||
const bodyFontSize = getComputedStyle(document.body).fontSize;
|
||||
const sidebar = document.querySelector('nav, aside, [role="navigation"]');
|
||||
const sidebarColor = sidebar ? getComputedStyle(sidebar).color : 'N/A';
|
||||
const sidebarBg = sidebar ? getComputedStyle(sidebar).backgroundColor : 'N/A';
|
||||
return { bodyColor, bodyBg, bodyFontSize, sidebarColor, sidebarBg };
|
||||
});
|
||||
console.log(JSON.stringify(metrics, null, 2));
|
||||
});
|
||||
|
||||
test('54 - 1024x768 resize', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1024, height: 768 });
|
||||
await setupPage(page, 'dark');
|
||||
await page.screenshot({ path: `${SS}/54-1024x768.png` });
|
||||
const hasHScroll = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
|
||||
console.log(`Horizontal scroll at 1024: ${hasHScroll}`);
|
||||
await clickNavView(page, 'Settings');
|
||||
await page.screenshot({ path: `${SS}/54-1024-settings.png` });
|
||||
const tabCount = await page.locator('[role="tab"]').count();
|
||||
console.log(`Settings tabs at 1024: ${tabCount}`);
|
||||
});
|
||||
|
||||
test('55 - 768x1024 mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await setupPage(page, 'dark');
|
||||
await page.screenshot({ path: `${SS}/55-768x1024.png` });
|
||||
const hasHScroll = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
|
||||
const sidebarVisible = await page.locator('nav, aside').first().isVisible().catch(() => false);
|
||||
console.log(`768 - horizontal scroll: ${hasHScroll}`);
|
||||
console.log(`768 - sidebar visible: ${sidebarVisible}`);
|
||||
expect(hasHScroll).toBe(false);
|
||||
|
||||
const coachmark = page.getByRole('dialog', { name: /waggle tips/i });
|
||||
if (await coachmark.isVisible().catch(() => false)) {
|
||||
const box = await coachmark.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(box!.x + box!.width).toBeLessThanOrEqual(768);
|
||||
}
|
||||
});
|
||||
});
|
||||
218
tests/visual/views.spec.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Legacy Visual Regression suite for the current AppShell.
|
||||
*
|
||||
* Keeps the original 7-view snapshot names, but routes directly to the modern
|
||||
* surfaces instead of clicking retired positional sidebar items.
|
||||
*/
|
||||
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
const SKIP_PARAMS = 'skipOnboarding=true&skipBoot=true&skipBriefing=true&tier=power';
|
||||
|
||||
const VIEWS = [
|
||||
{ name: 'chat', route: 'chat' },
|
||||
{ name: 'memory', route: '/memory' },
|
||||
{ name: 'events', route: '/settings/events' },
|
||||
{ name: 'capabilities', route: '/skills' },
|
||||
{ name: 'cockpit', route: '/home' },
|
||||
{ name: 'mission-control', route: '/settings/mission-control' },
|
||||
{ name: 'settings', route: '/settings?tab=models' },
|
||||
] as const;
|
||||
|
||||
const THEME_LABELS = {
|
||||
dark: 'Dark Mode',
|
||||
light: 'Light Mode',
|
||||
} as const;
|
||||
|
||||
const VISUAL_MODEL = 'openai/visual-fixture-model';
|
||||
const VISUAL_PROVIDER_META = [
|
||||
['anthropic', 'Anthropic'],
|
||||
['openai', 'OpenAI'],
|
||||
['google', 'Google'],
|
||||
['deepseek', 'DeepSeek'],
|
||||
['xai', 'xAI'],
|
||||
['mistral', 'Mistral'],
|
||||
['alibaba', 'Alibaba / Qwen'],
|
||||
['minimax', 'MiniMax'],
|
||||
['zhipu', 'GLM / Zhipu'],
|
||||
['moonshot', 'Kimi / Moonshot'],
|
||||
['perplexity', 'Perplexity'],
|
||||
['openrouter', 'OpenRouter'],
|
||||
['ollama', 'Local / Ollama'],
|
||||
] as const;
|
||||
|
||||
function routeWithSkip(route: string) {
|
||||
const sep = route.includes('?') ? '&' : '?';
|
||||
return `${route}${sep}${SKIP_PARAMS}`;
|
||||
}
|
||||
|
||||
async function firstWorkspaceChatRoute(page: Page) {
|
||||
const res = await page.request.get('/api/workspaces');
|
||||
const workspaces = await res.json();
|
||||
const workspaceId = Array.isArray(workspaces) ? workspaces[0]?.id : null;
|
||||
return workspaceId ? `/workspaces/${workspaceId}/chat` : '/home';
|
||||
}
|
||||
|
||||
async function applyTheme(page: Page, theme: 'dark' | 'light') {
|
||||
await page.addInitScript((mode) => {
|
||||
localStorage.setItem('waggle-theme', mode);
|
||||
localStorage.setItem('waggle:onboarding', JSON.stringify({
|
||||
completed: true,
|
||||
step: 7,
|
||||
tier: 'power',
|
||||
tooltipsDismissed: true,
|
||||
}));
|
||||
if (mode === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
||||
else document.documentElement.removeAttribute('data-theme');
|
||||
}, theme);
|
||||
}
|
||||
|
||||
async function stubDynamicRuntime(page: Page) {
|
||||
const json = (body: unknown) => ({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const providers = VISUAL_PROVIDER_META.map(([id, name]) => ({
|
||||
id,
|
||||
name,
|
||||
hasKey: id === 'openai',
|
||||
badge: null,
|
||||
keyUrl: null,
|
||||
requiresKey: id !== 'ollama',
|
||||
models: id === 'openai'
|
||||
? [{ id: VISUAL_MODEL, name: 'Visual Fixture Model', cost: '$$', speed: 'medium', source: 'provider-api' }]
|
||||
: [],
|
||||
modelsSource: id === 'openai' ? 'provider-api' : id === 'ollama' ? 'local-runtime' : 'requires-key',
|
||||
...(id === 'ollama' ? { reachable: false } : {}),
|
||||
}));
|
||||
|
||||
await page.route('**/api/providers', route => route.fulfill(json({
|
||||
providers,
|
||||
search: [],
|
||||
activeSearch: 'duckduckgo',
|
||||
})));
|
||||
await page.route('**/api/agent/status', route => route.fulfill(json({
|
||||
model: VISUAL_MODEL,
|
||||
tokensUsed: 0,
|
||||
costUsd: 0,
|
||||
isActive: false,
|
||||
})));
|
||||
await page.route('**/api/agent/model', route => route.fulfill(json({ model: VISUAL_MODEL })));
|
||||
await page.route('**/api/litellm/models', route => route.fulfill(json({ models: [VISUAL_MODEL] })));
|
||||
await page.route('**/api/local-inference/status', route => route.fulfill(json({
|
||||
servers: [],
|
||||
ollamaInstalled: false,
|
||||
totalLocalModels: 0,
|
||||
})));
|
||||
await page.route('**/api/settings/probe-model', route => route.fulfill(json({
|
||||
model: VISUAL_MODEL,
|
||||
configured: true,
|
||||
verified: true,
|
||||
})));
|
||||
await page.route('**/api/settings', route => {
|
||||
if (route.request().method() === 'GET') return route.fulfill(json({ defaultModel: VISUAL_MODEL }));
|
||||
return route.continue();
|
||||
});
|
||||
}
|
||||
|
||||
async function gotoVisualView(page: Page, view: typeof VIEWS[number], theme: 'dark' | 'light') {
|
||||
await stubDynamicRuntime(page);
|
||||
await applyTheme(page, theme);
|
||||
const route = view.route === 'chat' ? await firstWorkspaceChatRoute(page) : view.route;
|
||||
await page.goto(routeWithSkip(route), { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('.waggle-sidebar, [role="navigation"], main', { timeout: 15_000 });
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await waitForVisualReady(page, view.name);
|
||||
await page.evaluate((mode) => {
|
||||
localStorage.setItem('waggle-theme', mode);
|
||||
if (mode === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
||||
else document.documentElement.removeAttribute('data-theme');
|
||||
}, theme);
|
||||
await page.waitForTimeout(800);
|
||||
await stabilizeVisuals(page);
|
||||
}
|
||||
|
||||
async function waitForVisualReady(page: Page, viewName: typeof VIEWS[number]['name']) {
|
||||
await page.waitForFunction(() => !document.body.innerText.includes('Loading workspace'), null, { timeout: 15_000 }).catch(() => {});
|
||||
|
||||
if (viewName === 'chat') {
|
||||
await expect(page.locator('textarea').first()).toBeVisible({ timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'memory') {
|
||||
await expect(page.getByTestId('memory-center-app')).toBeVisible({ timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'cockpit') {
|
||||
await expect(page.locator('[data-testid="home-cockpit"], [data-testid="home-cockpit-empty"]').first()).toBeVisible({ timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'settings') {
|
||||
await expect(page.getByRole('tablist', { name: 'Settings sections' })).toBeVisible({ timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'capabilities') {
|
||||
await expect(page.locator('body')).toContainText(/skill|capabilit|marketplace/i, { timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'events') {
|
||||
await expect(page.locator('body')).toContainText(/event|timeline|agent/i, { timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
if (viewName === 'mission-control') {
|
||||
await expect(page.locator('body')).toContainText(/cockpit|health|cost/i, { timeout: 15_000 });
|
||||
}
|
||||
}
|
||||
|
||||
async function stabilizeVisuals(page: Page) {
|
||||
await page.addStyleTag({
|
||||
content: `
|
||||
[aria-label="Notifications"],
|
||||
[data-testid="statusbar-memory-count"],
|
||||
[data-testid="statusbar-tokens"],
|
||||
[data-testid="statusbar-cost"],
|
||||
[data-testid="import-reminder-banner"],
|
||||
[data-testid="import-reminder-banner-cc"],
|
||||
[data-testid="home-cockpit-facts"],
|
||||
[data-testid="home-cockpit-start-here"] h2,
|
||||
[data-testid="home-cockpit-start-here"] h2 ~ p,
|
||||
[data-testid^="home-cockpit-ws-"] .truncate,
|
||||
[data-testid^="home-cockpit-ws-"] p,
|
||||
[data-testid^="home-cockpit-continue-"] {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
const dynamicText = [
|
||||
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s/i,
|
||||
/^\d{1,2}:\d{2}$/,
|
||||
/^Last active:/i,
|
||||
/^just now$/i,
|
||||
/^\d+[mhdw] ago$/i,
|
||||
/\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\b/i,
|
||||
/^Upcoming:/i,
|
||||
];
|
||||
for (const el of Array.from(document.querySelectorAll('span, p, button, time, div'))) {
|
||||
const text = (el.textContent ?? '').trim();
|
||||
if (dynamicText.some(pattern => pattern.test(text)) && (el.children.length === 0 || el.tagName === 'BUTTON')) {
|
||||
(el as HTMLElement).style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
});
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
for (const theme of ['dark', 'light'] as const) {
|
||||
test.describe(`Visual Regression - ${THEME_LABELS[theme]}`, () => {
|
||||
for (const view of VIEWS) {
|
||||
test(`${view.name} view - ${theme}`, async ({ page }) => {
|
||||
await gotoVisualView(page, view, theme);
|
||||
await expect(page).toHaveScreenshot(`${view.name}-${theme}.png`, {
|
||||
fullPage: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||