feat: global error fallback and e2e (#91)

* fix(ui): add global route fallback with error toast and retry

* test(e2e): cover cloud accounts error fallback UI
This commit is contained in:
vin authored and GitHub committed 2026-02-11 02:25:10 +08:00
1 parent bcca5ec935
commit b89dd2c3bb
6 files changed
+141 -8

No files matched your search

+80 -3
View File
@@ -2,13 +2,72 @@ import { test, expect, ElectronApplication } from '@playwright/test';
import { _electron as electron } from 'playwright';
import path from 'path';
const injectCloudAccountsFailureScript = `
(() => {
const START_ORPC_SERVER = 'start-orpc-server';
const originalPostMessage = window.postMessage.bind(window);
window.postMessage = function (message, targetOrigin, transfer) {
if (message === START_ORPC_SERVER && Array.isArray(transfer) && transfer[0]) {
const serverPort = transfer[0];
if (typeof serverPort.start === 'function') {
serverPort.start();
}
serverPort.onmessage = (event) => {
try {
const request = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
const requestId = request && (request.i || request.id);
const requestUrl = (request && request.p && request.p.u) || '';
if (!requestId) {
return;
}
if (requestUrl.includes('/cloud/listCloudAccounts')) {
serverPort.postMessage(
JSON.stringify({
i: requestId,
p: { s: 500, b: { json: { message: 'internal server error' } } },
}),
);
return;
}
let result = null;
if (requestUrl.includes('/process/isProcessRunning')) {
result = false;
} else if (requestUrl.includes('/cloud/getAutoSwitchEnabled')) {
result = false;
}
serverPort.postMessage(
JSON.stringify({
i: requestId,
p: { b: { json: result } },
}),
);
} catch (_error) {
// Ignore malformed messages in E2E bridge stub.
}
};
return;
}
return originalPostMessage(message, targetOrigin, transfer);
};
})();
`;
test.describe('Antigravity Manager', () => {
let electronApp: ElectronApplication;
const electronMainPath = path.join(__dirname, '../../../.vite/build/main.js');
test.beforeAll(async () => {
// Launch Electron app
electronApp = await electron.launch({
args: [path.join(__dirname, '../../../.vite/build/main.js')],
args: [electronMainPath],
});
});
@@ -23,8 +82,8 @@ test.describe('Antigravity Manager', () => {
const title = await window.title();
expect(title).toBe('Antigravity Manager');
// Check for main elements - h2 is present (contains i18n content)
await expect(window.locator('h2').first()).toBeVisible();
await expect(window.getByRole('main')).toBeVisible();
await expect(window.locator('a[href="/settings"]').first()).toBeVisible();
});
test('should navigate to settings', async () => {
@@ -38,6 +97,24 @@ test.describe('Antigravity Manager', () => {
await expect(window.locator('h2').first()).toBeVisible();
});
test('should show fallback UI when cloud accounts loading fails', async () => {
await electronApp.close();
electronApp = await electron.launch({
args: [electronMainPath],
});
const page = await electronApp.firstWindow();
await page.addInitScript(injectCloudAccountsFailureScript);
await page.reload();
await page.waitForLoadState('domcontentloaded');
const mainContent = page.getByRole('main');
await expect(mainContent.getByTestId('cloud-load-error-fallback')).toBeVisible({
timeout: 15000,
});
await expect(mainContent.getByTestId('cloud-load-error-retry')).toBeVisible();
});
// More detailed tests would require mocking IPC or having a real environment
// For now, we verify basic navigation and rendering
});