Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
runtime_sandbox: ${{ steps.plan.outputs.runtime_sandbox }}
script_mode: ${{ steps.plan.outputs.script_mode }}
storage_stress: ${{ steps.plan.outputs.storage_stress }}
storybook: ${{ steps.plan.outputs.storybook }}
unit: ${{ steps.plan.outputs.unit }}
workspaces: ${{ steps.plan.outputs.workspaces }}
steps:
Expand Down Expand Up @@ -161,15 +162,26 @@ jobs:
# e2e step just built; same xvfb pattern.
- name: Alignment audit
run: xvfb-run -a node scripts/audit-alignment.mjs
# Storybook is where pixel work happens (see stories/FIDELITY.md), but
# nothing verified that it still renders: story typechecking rides along
# in `typecheck`, while the static build and the render/play smoke ran
# only if someone remembered to run them locally.
#
# The E2E suite above drives Electron through `_electron.launch`, which
# uses Electron's own binary — it never downloads a browser. The smoke
# calls `chromium.launch()`, so the Chromium build must be fetched here
# explicitly; without it the step fails on a missing executable.

# Storybook build + render smoke. Independent of Electron e2e: the smoke
# launches Playwright Chromium, not `_electron.launch`, so it does not
# need xvfb or the desktop cold-start suite. See stories/FIDELITY.md.
storybook:
needs: changes
if: needs.changes.outputs.storybook == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
# Stories import @maka/core / @maka/ui package exports (dist/). The old
# e2e job paid for this via `build:with-deps`; the split job must too.
- name: Build workspace packages
run: npm --workspace @maka/desktop run build:workspace-deps
# The smoke calls `chromium.launch()`; Electron's binary is irrelevant.
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Build Storybook
Expand Down
33 changes: 2 additions & 31 deletions apps/desktop/e2e/bot-onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,8 @@ test('IM 快捷接入完成真实 QR session、扫码状态和本机凭据落盘
const qr = dialog.getByRole('img', { name: '配置钉钉二维码' });
await expect(qr).toHaveAttribute('src', /^data:image\/png;base64,/);
await expect(dialog.getByText('请使用钉钉扫描二维码并确认授权')).toBeVisible();

// A QR code the user cannot fully see cannot be scanned. What that requires is
// a relationship, not a fixed size: the image fills its frame (so it is never
// shrunk to a corner of it), the frame is square, the code is wholly on screen
// at a scannable size, and the dialog stays inside the window. The frame's
// 284px and the dialog's 522px cap are the design tokens; these are not.
//
// `toBeInViewport()` alone would NOT carry this: its default `ratio: 0` passes
// on any positive intersection, so a QR with one corner on screen clears it.
const dialogBox = await dialog.boundingBox();
const qrFrameBox = await dialog.locator('.settingsBotOnboardingQrFrame').boundingBox();
const qrBox = await qr.boundingBox();
const viewport = await page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
expect(dialogBox).not.toBeNull();
expect(qrFrameBox).not.toBeNull();
expect(qrBox).not.toBeNull();

// The image fills its frame, up to the frame's 1px border on each side.
expect(qrFrameBox!.width - qrBox!.width).toBeLessThanOrEqual(2);
expect(qrFrameBox!.height - qrBox!.height).toBeLessThanOrEqual(2);
// Square, and large enough for a phone camera to resolve the modules.
expect(Math.abs(qrBox!.width - qrBox!.height)).toBeLessThanOrEqual(1);
expect(qrBox!.width).toBeGreaterThanOrEqual(160);
// Centred in the dialog it belongs to.
expect(
Math.abs((dialogBox!.x + dialogBox!.width / 2) - (qrBox!.x + qrBox!.width / 2)),
).toBeLessThan(2);
// Wholly on screen — every edge, not merely intersecting.
await expect(qr).toBeInViewport({ ratio: 1 });
expect(dialogBox!.y).toBeGreaterThanOrEqual(0);
expect(dialogBox!.y + dialogBox!.height).toBeLessThanOrEqual(viewport.height);
// QR square / fill-frame geometry is pinned in chat-shell-layout-contract
// (settingsBotOnboardingQrFrame CSS). This journey owns session + secret isolation.

await expect(dialog.getByText('已扫码,请在钉钉中完成确认')).toBeVisible({ timeout: 4_000 });
await expect(dialog.getByText('钉钉 已连接')).toBeVisible({ timeout: 5_000 });
Expand Down
172 changes: 0 additions & 172 deletions apps/desktop/e2e/chat-chrome-style.spec.ts

This file was deleted.

78 changes: 4 additions & 74 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,15 @@ export const test = base.extend<{
window: Page;
modelPickerLongWindow: Page;
longTranscriptWindow: Page;
chatChromeDarwinWindow: Page;
chatChromeWin32Window: Page;
sidebarLongSessionsWindow: Page;
disclosureOutputWindow: Page;
sandboxBoundaryWindow: Page;
readOnlyBoundaryWindow: Page;
staleSessionsWindow: Page;
sessionWorkbarWindow: Page;
botSettingsWindow: Page;
/** #1361: Permissions page with the typed OS-permission snapshot fixture. */
permissionSettingsWindow: Page;
usageSettingsWindow: Page;
searchSettingsWindow: Page;
zhLocaleWindow: Page;
enLocaleWindow: Page;
localeSwitchWindow: Page;
invocableSkillsWindow: Page;
planRemindersWindow: Page;
Expand Down Expand Up @@ -316,23 +311,6 @@ export const test = base.extend<{
use,
);
},
// Chat-chrome contract (#1312): the long-transcript shell booted with a
// FORCED platform (app:info override), so `data-os` — and with it the
// darwin glass cascade vs the opaque base cascade — is native from the
// first frame on any host. No post-boot attribute flip, which Chromium's
// style recalc resolves relative colors against stale values for.
chatChromeDarwinWindow: async ({}, use) => {
await withE2eWindow(
{ seed: false, readinessSelector: '.maka-turn', e2eFixtureScenario: 'long-transcript', locale: 'zh', platform: 'darwin' },
use,
);
},
chatChromeWin32Window: async ({}, use) => {
await withE2eWindow(
{ seed: false, readinessSelector: '.maka-turn', e2eFixtureScenario: 'long-transcript', locale: 'zh', platform: 'win32' },
use,
);
},
// Sandbox-boundary takeover: boots a deterministic expansion request in the
// real desktop shell so the composer-slot placement and non-modal behavior
// are covered without a provider or test-only renderer state path.
Expand Down Expand Up @@ -382,11 +360,9 @@ export const test = base.extend<{
use,
);
},
// #1361: Permission Center with a typed OS-permission snapshot (see
// `main/permission-snapshot-e2e-fixture.ts`). The narrow-layout contract is
// about rows that carry grant buttons, which the host's real TCC state cannot
// guarantee — a granted dev machine renders none, and Linux CI reports most
// permissions as `unsupported`.
// One live window-floor smoke: permission rows with the three-button guided
// screen-recording shape. CSS contracts pin declarations; this measures
// scrollWidth containment at SAFE_MIN_WIDTH (480).
permissionSettingsWindow: async ({}, use) => {
await withE2eWindow(
{
Expand All @@ -398,52 +374,6 @@ export const test = base.extend<{
use,
);
},
// #1364: Usage with seeded request traffic + details-on settings, so the
// request-log Table actually renders (the default window fixture keeps
// `showDetails` false and has no logs — the table CSS could regress without
// failing anything).
usageSettingsWindow: async ({}, use) => {
await withE2eWindow(
{
seed: false,
// The tabs bar, not the table: the renderer's first stats fetch can
// race the fixture seeding, so the spec refreshes until the seeded
// request log lands.
readinessSelector: '.settingsUsageTabsBar',
e2eFixtureScenario: 'settings-usage',
locale: 'zh',
},
use,
);
},
// #1364: Web Search with a configured Tavily key; queries are answered by
// the typed fixture in `main/web-search-e2e-fixture.ts` (e2e runs offline),
// so the hostile-width result list is reachable deterministically.
searchSettingsWindow: async ({}, use) => {
await withE2eWindow(
{
seed: false,
readinessSelector: '.settingsWebSearchQueryInputRow',
e2eFixtureScenario: 'settings-search',
locale: 'zh',
},
use,
);
},
// Representative e2e-fixture renderer launches in both supported locales.
// These use the same production LocaleProvider override path as screenshot capture.
zhLocaleWindow: async ({}, use) => {
await withE2eWindow(
{ seed: false, readinessSelector: '.appFrame', e2eFixtureScenario: 'all', locale: 'zh' },
use,
);
},
enLocaleWindow: async ({}, use) => {
await withE2eWindow(
{ seed: false, readinessSelector: '.appFrame', e2eFixtureScenario: 'all', locale: 'en' },
use,
);
},
// Keep this fixture unpinned so the Follow system assertion observes the
// actual host language while the legacy fixtures remain deterministic.
localeSwitchWindow: async ({}, use) => {
Expand Down
Loading
Loading