Files
nexu-io__open-design/e2e/ui/visual-navigation.test.ts
wangchenglong 61dba52422 feat(web): entry follow-ups — team badge, community byline, local-agent step
Follow-ups on the refreshed entry surfaces, all driven by product review of the
running build.

Team badge (RecentProjectsStrip / recent-projects.css)
- The glyph is now remixicon `swap-2-fill` (added to the inlined path table)
  instead of the drawn group-of-people mark.
- The badge hangs off the CARD, not the cover. The cover scales 1.03 on hover,
  so a badge nested in it grew and slid outward at the very moment it faded in,
  landing flush with the card edge while ⋯ stayed 8px in. It now shares the ⋯
  anchor's positioned ancestor and its 8px inset, so both hover affordances sit
  on one line, one per edge.
- Restyled to match ⋯: 26px tall, plain panel ground, accent-coloured 12px
  label, 13px glyph, no stroke and no green wash. Tokens rather than literal
  hex, so the chip still inverts under the dark theme instead of sitting white
  beside a dark ⋯.

Community card byline (CommunityView / CommunityTemplatePreview)
- Cards carry who published the template again, from the catalogue only: a
  manifest author wins, otherwise the SOURCE answers it — bundled and
  marketplace records are Open Design's, a locally authored plugin is the
  signed-in person's own. No invented handles, and the view/remix counts that
  were removed with the placeholder byline stay out until a source exists.

Onboarding "local agent" step (EntryShell / entry-layout.css / AgentIcon)
- Continue moves up beside Back on one row above the panel, one control per
  edge, instead of sitting alone under a panel whose height changes with the
  chosen runtime.
- The CLI grid goes 2 → 3 per row, and BYOK closes it on a row of its own. The
  span is CSS; the panel also sorts BYOK last, so the scan order can never
  leave a hole above it.
- Chips left-align their content. The button primitive centers it, which left
  every logo floating mid-tile instead of leading its name.
- BYOK wears a key glyph (`key-2-fill`) rather than the initial-letter tile the
  fallback gave it: there is no vendor behind it — the runtime is whatever
  provider key the user brings.

Account menu (EntryNavRail / analytics contract / locales)
- The account menu gains a 账单 row that opens the workspace billing console,
  tracked as its own element (distinct from the hover panel's credits row), with
  `entry.accountBilling` / `entry.accountGithubHelp` across the locale files and
  a test covering the row.

Also carried along: rail recent-row, run-status icon and home-hero spacing
tweaks, and the e2e AMR/visual-navigation helper updates that go with them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DPHofFYG9h3DgjgCDWLYh
2026-09-01 20:43:46 +08:00

178 lines
7.4 KiB
TypeScript

import { expect, test } from '@/playwright/suite';
import { ensureRailOpen } from '@/playwright/rail';
import { openSettingsDialog } from '@/playwright/amr';
import {
captureVisual,
configureVisualPage,
gotoVisualHome,
waitForVisualFonts,
} from '@/playwright/visual';
test('[P2] captures the projects page surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await ensureRailOpen(page);
const legacyProjectsNav = page.getByTestId('entry-nav-projects');
const teamProjectsNav = page.getByTestId('entry-nav-all-projects');
if (await legacyProjectsNav.isVisible().catch(() => false)) {
await legacyProjectsNav.click();
await expect(page).toHaveURL(/\/projects$/);
const projects = page.getByTestId('entry-view-projects');
await expect(projects.getByRole('heading', { name: 'Projects' })).toBeVisible();
await expect(projects.getByText('Launchpad dashboard').first()).toBeVisible();
} else {
if (await teamProjectsNav.isVisible().catch(() => false)) {
await teamProjectsNav.click();
await expect(page.getByRole('heading', { name: /all projects|全部项目/i })).toBeVisible();
} else {
await page.getByTestId('entry-nav-drafts').click();
await expect(page.getByTestId('recent-projects-strip')).toBeVisible();
await expect(page.getByText('Launchpad dashboard').first()).toBeVisible();
}
}
await waitForVisualFonts(page);
await captureVisual(page, 'visual-projects');
});
test('[P2] captures the projects kanban surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await ensureRailOpen(page);
const legacyProjectsNav = page.getByTestId('entry-nav-projects');
const teamProjectsNav = page.getByTestId('entry-nav-all-projects');
if (await legacyProjectsNav.isVisible().catch(() => false)) {
await legacyProjectsNav.click();
const projects = page.getByTestId('entry-view-projects');
await projects.getByTestId('designs-view-kanban').click();
await expect(projects.getByTestId('designs-view-kanban')).toHaveAttribute('aria-pressed', 'true');
await expect(projects.getByText('Launchpad dashboard').first()).toBeVisible();
} else {
if (await teamProjectsNav.isVisible().catch(() => false)) {
await teamProjectsNav.click();
await expect(page.getByRole('heading', { name: /all projects|全部项目/i })).toBeVisible();
} else {
await page.getByTestId('entry-nav-drafts').click();
await expect(page.getByTestId('recent-projects-strip')).toBeVisible();
await expect(page.getByText('Launchpad dashboard').first()).toBeVisible();
}
}
await waitForVisualFonts(page);
await captureVisual(page, 'visual-projects-kanban');
});
test('[P2] captures the design systems page surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await ensureRailOpen(page);
await page.getByTestId('entry-nav-design-systems').click();
await expect(page).toHaveURL(/\/design-systems$/);
await expect(page.getByTestId('design-systems-tab')).toBeVisible();
await page.getByRole('tab', { name: 'Official presets' }).click();
await expect(page.getByTestId('design-system-card-agentic')).toBeVisible();
await expect(page.getByTestId('design-system-card-airbnb')).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-design-systems');
});
test('[P2] captures the design system detail preview surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await ensureRailOpen(page);
await page.getByTestId('entry-nav-design-systems').click();
await page.getByRole('tab', { name: 'Official presets' }).click();
await page.getByTestId('design-system-card-agentic').click();
const detail = page.getByTestId('design-system-detail-agentic');
await expect(detail).toBeVisible();
await expect(detail.getByTestId('design-kit-view-agentic')).toBeVisible();
await expect(detail.getByTestId('design-kit-logo-section')).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-design-system-detail');
});
test('[P2] captures the plugins page surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await ensureRailOpen(page);
await page.getByTestId('entry-nav-plugins').click();
await expect(page).toHaveURL(/\/plugins$/);
const plugins = page.getByTestId('entry-view-plugins');
// The view renders `entry.navPlugins`: #5517 briefly called this surface
// 扩展/Extensions, then reverted to 插件/Plugins to match the @-mention picker.
await expect(plugins.getByRole('heading', { name: 'Plugins', exact: true })).toBeVisible();
await expect(plugins.getByTestId('plugins-tab-installed')).toBeVisible();
// The marketplace opens on the 官方 scope, fed by `/api/marketplaces` — empty
// in this harness. The fixture plugins are user-installed, so switch to 个人.
await plugins.getByTestId('plugins-tab-installed').click();
await expect(plugins.getByText('Prototype Starter').first()).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-plugins');
});
test('[P2] captures the integrations page surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
// Navigated directly: the composer "+" menu no longer carries a connectors
// row to reach this page through.
await page.goto('/integrations', { waitUntil: 'domcontentloaded' });
await expect(page).toHaveURL(/\/integrations$/);
await expect(page.getByTestId('integrations-tab-connectors')).toHaveAttribute(
'aria-selected',
'true',
);
await expect(page.getByTestId('connector-grid-wrap')).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-integrations');
});
test('[P2] captures the integrations use everywhere surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
const dialog = await openSettingsSection(page, 'settings-nav-execution');
await expect(dialog.getByRole('tablist', { name: /Execution mode/i })).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-integrations-use-everywhere');
});
test('[P2] captures the integrations MCP surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
// Navigated directly: the composer "+" menu no longer carries an MCP row to
// reach this page through.
await page.goto('/integrations', { waitUntil: 'domcontentloaded' });
await page.getByTestId('integrations-tab-mcp').click();
await expect(page).toHaveURL(/\/integrations$/);
await expect(page.getByTestId('integrations-tab-mcp')).toHaveAttribute(
'aria-selected',
'true',
);
await expect(page.getByRole('heading', { name: 'External MCP servers' })).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-integrations-mcp');
});
async function openSettingsSection(page: import('@playwright/test').Page, testId: string) {
// #5971 deleted the rail-footer settings chip (`entry-settings-button`).
// `openSettingsDialog` owns every remaining entry point — chiefly the rail's
// own item under 插件, which now renders in both identity states — including
// the rail-open handling this used to do by hand.
const dialog = await openSettingsDialog(page);
await dialog.getByTestId(testId).click();
return dialog;
}