Files
open-design/e2e/ui/visual-entry.test.ts
T
886bcc4db0 feat(web): streamline onboarding model source flow (#6475)
* feat(web): streamline onboarding model source flow

* test(e2e): align onboarding coverage with cloud-first entry

* test(e2e): isolate cloud status in non-auth specs

* test(e2e): keep restoration focused on tab state

* test(e2e): close cloud-first onboarding gaps

* test(e2e): align runtime checks with cloud-first gate

* ci(e2e): retire automations from P0 gate

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-06 18:48:33 +08:00

236 lines
9.9 KiB
TypeScript

import { expect, test } from '@/playwright/suite';
import { ensureRailOpen, openNewProjectModal } from '@/playwright/rail';
import { T } from '@/timeouts';
import {
captureVisual,
captureVisualTarget,
configureVisualPage,
gotoVisualHome,
scrollVisualLocatorIntoStableView,
VISUAL_AMR_AGENT,
VISUAL_CLI_AGENTS,
waitForVisualFonts,
waitForVisualProjects,
} from '@/playwright/visual';
test('[P2] captures the onboarding cloud sign-in surface', async ({ page }) => {
test.setTimeout(T.xlong);
test.fail(
true,
'PR #6475 currently omits anonymous Local Agent and BYOK actions from the sign-in surface.',
);
await configureVisualPage(page, {
projects: [],
agents: [VISUAL_AMR_AGENT, ...VISUAL_CLI_AGENTS],
velaLoggedIn: false,
config: {
onboardingCompleted: false,
},
});
await page.goto('/onboarding', { waitUntil: 'domcontentloaded' });
await page.getByText('Loading Open Design…').waitFor({ state: 'hidden', timeout: T.long });
// The connect step opens on the cloud sign-in landing. Local CLI and BYOK
// remain available as secondary paths from the same first screen.
await expect(
page.getByRole('heading', { name: /Sign in to Open Design|登录 Open Design/i }),
).toBeVisible({ timeout: T.medium });
await expect(
page.getByRole('button', { name: /Sign in to Open Design|登录 Open Design/i }),
).toBeVisible();
await expect(
page.getByRole('button', { name: /Local coding agent|本地 Coding Agent/i }),
).toBeVisible();
await expect(
page.getByRole('button', { name: /Bring your own key|自己的模型 Key/i }),
).toBeVisible();
await waitForVisualFonts(page);
await captureVisual(page, 'visual-onboarding-cloud');
});
test('[P2] captures the visual home harness', async ({ page }) => {
await configureVisualPage(page, { projects: [] });
await gotoVisualHome(page);
await expect(page.getByTestId('home-hero')).toBeVisible();
await expect(page.getByTestId('home-hero-input')).toBeVisible();
await waitForVisualProjects(page, []);
await captureVisual(page, 'visual-home');
});
test('[P2] captures the home plugin catalog surface', async ({ page }) => {
test.setTimeout(90_000);
await configureVisualPage(page);
const plugins = await openVisualPluginsCatalog(page);
const catalog = plugins.locator('.plugin-marketplace__catalog');
await expect(catalog).toBeVisible();
await scrollVisualLocatorIntoStableView(page, catalog);
await expect(pluginMarketplaceCard(plugins, 'Prototype Starter')).toBeVisible();
await expect(pluginMarketplaceCard(plugins, 'Deck Writer')).toBeVisible();
await expect(plugins.locator('.plugin-marketplace__search input')).toBeVisible();
await captureVisual(page, 'visual-home-catalog');
});
test('[P2] captures the home plugin filtered surface', async ({ page }) => {
await configureVisualPage(page);
const plugins = await openVisualPluginsCatalog(page);
await plugins.locator('.plugin-marketplace__search input').fill('Deck');
await expect(pluginMarketplaceCard(plugins, 'Deck Writer')).toBeVisible();
await expect(pluginMarketplaceCard(plugins, 'Prototype Starter')).toHaveCount(0);
await captureVisual(page, 'visual-home-plugin-filter');
});
test('[P2] captures the home plugin detail surface', async ({ page }) => {
await configureVisualPage(page);
const plugins = await openVisualPluginsCatalog(page);
const card = pluginMarketplaceCard(plugins, 'Prototype Starter');
await expect(card).toBeVisible();
await card.locator('.plugin-marketplace__more').click();
await expect(card.locator('.plugin-marketplace__menu[role="menu"]')).toBeVisible();
await captureVisual(page, 'visual-plugin-details');
});
test('[P2] captures the plugin detail share menu surface', async ({ page }) => {
await configureVisualPage(page);
const plugins = await openVisualPluginsCatalog(page);
const card = pluginMarketplaceCard(plugins, 'Deck Writer');
await expect(card).toBeVisible();
const trigger = card.locator('.plugin-marketplace__more');
await trigger.click();
const popover = card.locator('.plugin-marketplace__menu[role="menu"]');
await expect(popover).toBeVisible();
await captureVisual(page, 'visual-plugin-share-menu');
await captureVisualTarget(page, 'visual-plugin-share-menu-popover', [trigger, popover]);
});
test('[P2] captures the home context picker surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await page.getByTestId('home-hero-input').fill('@visual');
const input = page.getByTestId('home-hero-input');
const picker = page.getByTestId('home-hero-plugin-picker');
await expect(picker).toBeVisible();
await expect(page.getByRole('option', { name: /Prototype Starter/i })).toBeVisible();
await captureVisual(page, 'visual-home-context-picker');
await captureVisualTarget(page, 'visual-home-context-picker-popover', [input, picker]);
});
test('[P2] captures the home staged attachment surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await page.getByTestId('home-hero-file-input').setInputFiles({
name: 'visual-brief.txt',
mimeType: 'text/plain',
buffer: Buffer.from('Visual regression fixture for staged home attachments.\n', 'utf8'),
});
await expect(page.getByTestId('home-hero-staged-files')).toContainText('visual-brief.txt');
await captureVisual(page, 'visual-home-staged-attachment');
});
test('[P2] captures the home plugin use staged surface', async ({ page }) => {
await configureVisualPage(page);
// #5517 removed Home's own plugin grid: `PluginsHomeSection` (and with it
// `plugins-home-pill-category-*` / `plugins-home__card`) now lives only in
// the unrendered legacy `PluginsView`; `EntryShell` mounts
// `ExtensionsMarketplace` on /plugins instead. The journey this capture
// exists for is unchanged — narrow the catalog, open the plugin's details,
// Use it — and Use still hands the plugin to Home's hero, which is the
// state being captured.
const plugins = await openVisualPluginsCatalog(page);
// Category chips are derived from the same `extractCategories` taxonomy the
// old Home pills used, so the fixture still lands under Prototype; the chips
// carry no per-slug testid, only the taxonomy's `Prototype` label.
await plugins
.getByTestId('plugins-category-tags')
.getByRole('button', { name: 'Prototype', exact: true })
.click();
// The filter has to really bite: Deck Writer is the deck-mode fixture.
await expect(pluginMarketplaceCard(plugins, 'Deck Writer')).toHaveCount(0);
const card = plugins.getByTestId('plugins-card-visual-prototype-starter');
await expect(card).toBeVisible();
// The row's own "Try it" button stops propagation, so target the row body —
// clicking the card anywhere else is what opens the plugin's details.
await card.locator('.plugin-marketplace__row-main').click();
// #5517 turned plugin details into a full-page route: `openCardDetail` calls
// navigate({ kind: 'marketplace-detail' }) for plugin records, so the details
// surface is `PluginDetailView` at /marketplace/<id> — not a role="dialog"
// overlay — and its Use control is the single `plugin-detail-use` button
// rather than a per-slug `plugin-details-use-<id>` menu item.
// Assert on the Use control rather than the `plugin-detail` shell: the shell
// also renders for the loading and load-failed states, so it would go green
// on a detail that never resolved.
await expect(page).toHaveURL(/\/marketplace\/visual-prototype-starter$/);
const usePlugin = page.getByTestId('plugin-detail-use');
await expect(usePlugin).toBeVisible();
await usePlugin.click();
await expect(page.getByTestId('home-hero-active-plugin')).toContainText('Prototype Starter');
await expect(page.getByTestId('home-hero-input')).toBeVisible();
await captureVisual(page, 'visual-home-plugin-use-staged');
});
test('[P2] captures the home plugin use with query surface', async ({ page }) => {
await configureVisualPage(page);
const plugins = await openVisualPluginsCatalog(page);
await plugins.locator('.plugin-marketplace__search input').fill('Deck');
const card = pluginMarketplaceCard(plugins, 'Deck Writer');
await expect(card).toBeVisible();
await card.getByRole('button', { name: 'Try it' }).click();
await expect(page.getByTestId('home-hero-active-plugin')).toContainText('Deck Writer');
await expect(page.getByTestId('home-hero-input')).toBeVisible();
await captureVisual(page, 'visual-home-plugin-use-with-query');
});
test('[P2] captures the new project modal surface', async ({ page }) => {
test.setTimeout(T.xlong);
await configureVisualPage(page);
await gotoVisualHome(page);
await openNewProjectModal(page);
await expect(page.getByTestId('new-project-name')).toBeVisible();
await captureVisual(page, 'visual-new-project-modal');
});
async function openVisualPluginsCatalog(page: import('@playwright/test').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();
// The marketplace opens on the 官方 scope, which is fed by `/api/marketplaces`
// — empty in this harness. The visual fixture plugins are user-installed, so
// switch to 个人; it is also the only scope whose cards carry the per-card
// overflow menu (share / unshare / uninstall) the menu captures need.
await plugins.getByTestId('plugins-tab-installed').click();
return plugins;
}
function pluginMarketplaceCard(root: import('@playwright/test').Locator, title: string) {
return root.locator('article.plugin-marketplace__item').filter({ hasText: title }).first();
}