mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* feat(web): switch creation types from the composer dropdown * fix(web): limit type picker changes to dropdown interaction * fix(web): separate creation type menu rows by one pixel * fix(web): default Home to Prototype and migrate type picker coverage * test(web): cover default selection and dropdown loading boundaries * test(web): activate team catalog before context submission * fix(web): keep loading type picker on the capsule surface --------- Co-authored-by: wangchenglong <honam884844@gmail.com> Co-authored-by: Siri-Ray <2667192167@qq.com>
181 lines
7.0 KiB
TypeScript
181 lines
7.0 KiB
TypeScript
import { expect, test } from '@/playwright/suite';
|
|
import { ensureRailOpen } from '@/playwright/rail';
|
|
import { settingsSurface } from '@/playwright/amr';
|
|
import { routeAgents, suppressWhatsNew } from '@/playwright/mock-factory';
|
|
import { T } from '@/timeouts';
|
|
import type { Page } from '@playwright/test';
|
|
|
|
const STORAGE_KEY = 'open-design:config';
|
|
|
|
test.describe.configure({ timeout: T.xlong });
|
|
|
|
async function waitForLoadingToClear(page: Page) {
|
|
await expect(page.getByText('Loading OpenDesign…')).toHaveCount(0, { timeout: T.long });
|
|
}
|
|
|
|
async function gotoEntryHome(page: Page) {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await waitForLoadingToClear(page);
|
|
const privacyDialog = page.getByRole('dialog').filter({ hasText: 'Help us improve OpenDesign' });
|
|
if (await privacyDialog.isVisible().catch(() => false)) {
|
|
await privacyDialog.getByRole('button', { name: /I get it|not now|got it|don't share/i }).click();
|
|
}
|
|
// The settings button moved into the collapsed-by-default rail (#5517), so
|
|
// the hero is the reliable "entry is ready" signal now.
|
|
await expect(page.getByTestId('home-hero')).toBeVisible();
|
|
await expect(page.getByTestId('home-hero-input')).toBeVisible();
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// The entry home mounts `WhatsNewPopup` (EntryShell.tsx) and its backdrop sits
|
|
// at z-index 1500 — above the z-index 120 chrome that owns the rail/settings
|
|
// controls this spec clicks. A live release card would swallow those clicks.
|
|
await suppressWhatsNew(page);
|
|
await page.addInitScript((key) => {
|
|
window.localStorage.clear();
|
|
window.sessionStorage.clear();
|
|
window.localStorage.setItem(
|
|
key,
|
|
JSON.stringify({
|
|
mode: 'daemon',
|
|
apiKey: '',
|
|
baseUrl: 'https://api.anthropic.com',
|
|
model: 'claude-sonnet-4-5',
|
|
agentId: 'codex',
|
|
skillId: null,
|
|
designSystemId: null,
|
|
onboardingCompleted: true,
|
|
agentModels: { codex: { model: 'default', reasoning: 'default' } },
|
|
privacyDecisionAt: 1,
|
|
telemetry: { metrics: false, content: false, artifactManifest: false },
|
|
}),
|
|
);
|
|
}, STORAGE_KEY);
|
|
|
|
await page.route('**/api/github/open-design', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ stargazers_count: 51600 }),
|
|
});
|
|
});
|
|
|
|
await routeAgents(page, [
|
|
{
|
|
id: 'codex',
|
|
name: 'Codex CLI',
|
|
bin: 'codex',
|
|
available: true,
|
|
version: '0.80.0',
|
|
path: '/usr/local/bin/codex',
|
|
models: [{ id: 'default', label: 'Default' }],
|
|
},
|
|
{
|
|
id: 'mock',
|
|
name: 'Mock Agent',
|
|
bin: 'mock-agent',
|
|
available: true,
|
|
version: 'test',
|
|
models: [{ id: 'default', label: 'Default' }],
|
|
},
|
|
]);
|
|
|
|
await page.route('**/api/app-config', async (route) => {
|
|
if (route.request().method() !== 'GET') {
|
|
await route.continue();
|
|
return;
|
|
}
|
|
await route.fulfill({
|
|
json: {
|
|
config: {
|
|
onboardingCompleted: true,
|
|
agentId: 'codex',
|
|
skillId: null,
|
|
designSystemId: null,
|
|
mode: 'daemon',
|
|
agentModels: { codex: { model: 'default', reasoning: 'default' } },
|
|
privacyDecisionAt: 1,
|
|
telemetry: { metrics: false, content: false, artifactManifest: false },
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
// #5517 deleted the entry topbar outright (`EntryShell` renders
|
|
// `entry-shell--no-header` and its comment spells this out). Everything the bar
|
|
// used to carry either moved or went away:
|
|
// • GitHub star badge — `GithubStarBadge` is no longer rendered anywhere
|
|
// • Discord badge — removed
|
|
// • "Use everywhere" — removed; the guide keeps its Integrations tab
|
|
// • Settings button — moved into the (collapsed-by-default) rail footer
|
|
// • Execution pill — moved into the Home composer footer
|
|
// The chip/link inventory and external-link-contract specs are therefore gone;
|
|
// what remains below pins the two controls that survived, at their new homes.
|
|
|
|
test('[P2] home chrome exposes the composer execution pill and the rail settings entry', async ({ page }) => {
|
|
await gotoEntryHome(page);
|
|
|
|
await expect(page.locator('.entry-main__topbar')).toHaveCount(0);
|
|
await expect(page.getByTestId('inline-model-switcher-chip')).toBeVisible();
|
|
|
|
await ensureRailOpen(page);
|
|
await expect(page.getByTestId('entry-settings-button')).toBeVisible();
|
|
});
|
|
|
|
test('[P1] home execution pill reflects the selected Local CLI agent and opens the switcher', async ({ page }) => {
|
|
await gotoEntryHome(page);
|
|
|
|
// The composer pill is icon-only now: the selected agent + model live on its
|
|
// accessible name / tooltip rather than in visible text.
|
|
const pill = page.getByTestId('inline-model-switcher-chip');
|
|
await expect(pill).toHaveAttribute('aria-label', /Codex CLI/i);
|
|
await expect(pill).toHaveAttribute('aria-label', /default/i);
|
|
|
|
await pill.click();
|
|
|
|
const popover = page.getByTestId('inline-model-switcher-popover');
|
|
await expect(popover).toBeVisible();
|
|
// The composer's switcher is the compact variant: it drops the Local CLI /
|
|
// BYOK segmented control and the full agent list, opening straight on the
|
|
// CURRENT agent's models plus an entry into the execution settings where a
|
|
// different agent is chosen.
|
|
await expect(popover.getByTestId('inline-model-switcher-compact-model-default')).toBeVisible();
|
|
await expect(popover.getByTestId('inline-model-switcher-mode-daemon')).toHaveCount(0);
|
|
await expect(popover.getByTestId('inline-model-switcher-agent-codex')).toHaveCount(0);
|
|
await expect(popover.getByTestId('inline-model-switcher-open-settings')).toBeVisible();
|
|
});
|
|
|
|
test('[P1] rail settings entry opens settings and closes the execution popover', async ({ page }) => {
|
|
await gotoEntryHome(page);
|
|
|
|
const pill = page.getByTestId('inline-model-switcher-chip');
|
|
const popover = page.getByTestId('inline-model-switcher-popover');
|
|
|
|
await pill.click();
|
|
await expect(popover).toBeVisible();
|
|
|
|
// Settings is a rail nav item now, so the rail has to be expanded before it
|
|
// is interactive (collapsed the rail is `inert`).
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-settings-button').click();
|
|
await expect(settingsSurface(page)).toBeVisible();
|
|
await expect(popover).toHaveCount(0);
|
|
});
|
|
|
|
test('[P2] returning from another entry view via the home nav reaches the home hero', async ({ page }) => {
|
|
await gotoEntryHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-nav-design-systems').click();
|
|
await expect(page).toHaveURL(/\/design-systems$/);
|
|
|
|
// The logo doubles as a hover-to-collapse control now, so home is reached
|
|
// through the explicit Home nav item rather than clicking the brand mark.
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-nav-home').click();
|
|
await expect(page.getByTestId('home-hero')).toBeVisible();
|
|
await expect(page.getByTestId('home-hero-input')).toBeVisible();
|
|
await expect(page.getByTestId('home-hero-template-picker')).toBeVisible();
|
|
});
|