mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
fix the setup
This commit is contained in:
@@ -22,8 +22,8 @@
|
||||
"bundle:intel": "node scripts/prepare-platform-binaries.js && npm run make -- --arch=x64 && cd out/Goose-darwin-x64 && ditto -c -k --sequesterRsrc --keepParent Goose.app Goose_intel_mac.zip",
|
||||
"debug": "echo 'run --remote-debugging-port=8315' && lldb out/Goose-darwin-arm64/Goose.app",
|
||||
"test-e2e": "npm run generate-api && playwright test",
|
||||
"test-e2e:packaged": "npm run generate-api && NO_TEST_OVERLAY=1 playwright test tests/e2e/app.spec.ts",
|
||||
"test-e2e:packaged:video": "npm run playwright:install && npm run generate-api && PW_ELECTRON_VIDEO=1 NO_TEST_OVERLAY=1 playwright test tests/e2e/app.spec.ts",
|
||||
"test-e2e:non-video": "npm run generate-api && NO_TEST_OVERLAY=1 playwright test tests/e2e/app.spec.ts",
|
||||
"test-e2e:video": "npm run playwright:install && npm run generate-api && PW_ELECTRON_VIDEO=1 NO_TEST_OVERLAY=1 playwright test tests/e2e/app.spec.ts",
|
||||
"test-e2e:dev": "npm run generate-api && playwright test --reporter=list --retries=0 --max-failures=1",
|
||||
"test-e2e:ui": "npm run generate-api && playwright test --ui",
|
||||
"test-e2e:debug": "npm run generate-api && playwright test --debug",
|
||||
|
||||
@@ -54,12 +54,14 @@ export const SessionsList: React.FC<SessionsListProps> = ({
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="overflow-hidden mt-[2px]"
|
||||
data-testid="chat-sessions-list"
|
||||
>
|
||||
<div className="bg-background-primary rounded-lg py-1 flex flex-col gap-[2px]">
|
||||
{/* New Chat button as first item */}
|
||||
{onNewChat && (
|
||||
<div
|
||||
onClick={onNewChat}
|
||||
data-testid="chat-start-new"
|
||||
className={cn(
|
||||
'w-full text-left py-1.5 px-2 text-xs rounded-md',
|
||||
'hover:bg-background-tertiary transition-colors',
|
||||
@@ -126,6 +128,7 @@ export const SessionsList: React.FC<SessionsListProps> = ({
|
||||
{onShowAll && sessions.length > 0 && (
|
||||
<div
|
||||
onClick={onShowAll}
|
||||
data-testid="chat-show-all"
|
||||
className={cn(
|
||||
'w-full text-left py-1.5 px-2 text-xs rounded-md',
|
||||
'hover:bg-background-tertiary transition-colors',
|
||||
|
||||
@@ -98,6 +98,7 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="alert-box"
|
||||
className={cn('flex flex-col gap-2 px-3 py-3', alertStyles[alert.type], className)}
|
||||
onMouseDown={(e) => {
|
||||
// Prevent popover from closing when clicking inside the alert box
|
||||
@@ -251,14 +252,17 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
|
||||
</div>
|
||||
<div className="flex justify-between items-baseline text-[11px]">
|
||||
<div className="flex gap-1 items-baseline">
|
||||
<span className={'dark:text-black/60 text-white/60'}>
|
||||
<span
|
||||
className={'dark:text-black/60 text-white/60'}
|
||||
data-testid="alert-progress-current"
|
||||
>
|
||||
{formatTokenCount(alert.progress!.current)}
|
||||
</span>
|
||||
<span className={'dark:text-black/40 text-white/40'}>
|
||||
{Math.round((alert.progress!.current / alert.progress!.total) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<span className={'dark:text-black/60 text-white/60'}>
|
||||
<span className={'dark:text-black/60 text-white/60'} data-testid="alert-progress-total">
|
||||
{formatTokenCount(alert.progress!.total)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -205,6 +205,7 @@ export default function BottomMenuAlertPopover({ alerts }: AlertPopoverProps) {
|
||||
<div className="relative">
|
||||
<button
|
||||
ref={triggerRef}
|
||||
data-testid="bottom-menu-alert-trigger"
|
||||
className="cursor-pointer flex items-center justify-center min-w-5 min-h-5 rounded hover:bg-background-secondary"
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
|
||||
@@ -98,8 +98,8 @@ async function selectProvider(mainWindow: Page, provider: Provider) {
|
||||
|
||||
// Navigate to home/chat after provider configuration
|
||||
console.log('Navigating to home/chat...');
|
||||
const homeButton = mainWindow.getByTestId('sidebar-home-button');
|
||||
if (await homeButton.isVisible().catch(() => false)) {
|
||||
const homeButton = mainWindow.getByRole('button', { name: /^home$/i }).first();
|
||||
if (await homeButton.isVisible({ timeout: 3000 }).catch(() => false)) {
|
||||
await homeButton.click();
|
||||
}
|
||||
|
||||
@@ -108,6 +108,24 @@ async function selectProvider(mainWindow: Page, provider: Provider) {
|
||||
|
||||
}
|
||||
|
||||
async function openSettings(mainWindow: Page) {
|
||||
const settingsButton = mainWindow.getByRole('button', { name: /^settings$/i }).first();
|
||||
await expect(settingsButton).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await settingsButton.click();
|
||||
}
|
||||
|
||||
async function openHome(mainWindow: Page) {
|
||||
const homeButton = mainWindow.getByRole('button', { name: /^home$/i }).first();
|
||||
await expect(homeButton).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await homeButton.click();
|
||||
}
|
||||
|
||||
async function openExtensions(mainWindow: Page) {
|
||||
const extensionsButton = mainWindow.getByRole('button', { name: /^extensions$/i }).first();
|
||||
await expect(extensionsButton).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await extensionsButton.click();
|
||||
}
|
||||
|
||||
test.describe('Goose App', () => {
|
||||
|
||||
test.describe('General UI', () => {
|
||||
@@ -117,7 +135,7 @@ test.describe('Goose App', () => {
|
||||
await expect(mainWindow.getByTestId('chat-input')).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
|
||||
// Navigate to Settings via sidebar
|
||||
await mainWindow.getByTestId('sidebar-settings-button').click();
|
||||
await openSettings(mainWindow);
|
||||
|
||||
// Navigate to App tab
|
||||
await mainWindow.getByTestId('settings-app-tab').click();
|
||||
@@ -147,7 +165,7 @@ test.describe('Goose App', () => {
|
||||
await lightModeButton.click();
|
||||
|
||||
// Navigate back to home
|
||||
await mainWindow.getByTestId('sidebar-home-button').click();
|
||||
await openHome(mainWindow);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -207,7 +225,7 @@ test.describe('Goose App', () => {
|
||||
|
||||
test('MCP integration - add extension and use tool', async () => {
|
||||
// Navigate to Extensions via sidebar
|
||||
await mainWindow.getByTestId('sidebar-extensions-button').click();
|
||||
await openExtensions(mainWindow);
|
||||
|
||||
// Add custom extension
|
||||
await mainWindow.getByRole('button', { name: 'Add custom extension' }).click();
|
||||
@@ -228,7 +246,7 @@ test.describe('Goose App', () => {
|
||||
).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
|
||||
// Navigate back to home
|
||||
await mainWindow.getByTestId('sidebar-home-button').click();
|
||||
await openHome(mainWindow);
|
||||
|
||||
// Send a message requesting a running quote
|
||||
const chatInput = mainWindow.getByTestId('chat-input');
|
||||
|
||||
@@ -157,16 +157,25 @@ function withVisualDelayPage(page: Page): Page {
|
||||
|
||||
export const test = base.extend<GooseTestFixtures>({
|
||||
goosePage: async ({}, use, testInfo) => {
|
||||
testInfo.setTimeout(Math.max(testInfo.timeout, 120000));
|
||||
|
||||
let electronApp: Awaited<ReturnType<typeof electron.launch>> | null = null;
|
||||
let page: Page | null = null;
|
||||
let videoDir: string | undefined;
|
||||
let videoTrimStartMs = 0;
|
||||
const appRoot = join(__dirname, '../..');
|
||||
const executablePath = resolvePackagedExecutable(appRoot);
|
||||
const executablePath = resolveDirectElectronExecutable(appRoot);
|
||||
|
||||
if (!fs.existsSync(executablePath)) {
|
||||
throw new Error(
|
||||
`Packaged app executable not found at ${executablePath}. Build it first (e.g. "cd ui/desktop && npm run package"), or set GOOSE_PACKAGED_EXECUTABLE.`
|
||||
`Electron executable not found at ${executablePath}. Install dependencies in ui/desktop to ensure node_modules/electron exists.`
|
||||
);
|
||||
}
|
||||
|
||||
const viteMainPath = join(appRoot, '.vite', 'build', 'main.js');
|
||||
if (!fs.existsSync(viteMainPath)) {
|
||||
throw new Error(
|
||||
`Direct Electron mode requires Vite build output at ${viteMainPath}. Run "cd ui/desktop && npm run package" (or another build step that generates .vite/build) first.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,18 +184,21 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
try {
|
||||
videoDir = isVideoRecording() ? testInfo.outputPath('videos') : undefined;
|
||||
const launchOptions = buildLaunchOptions(executablePath, tempDir, videoDir);
|
||||
debugLog(`Launching packaged Electron for test: ${testInfo.title}`);
|
||||
debugLog(`Using packaged executable: ${executablePath}`);
|
||||
debugLog(`Launching direct Electron for test: ${testInfo.title}`);
|
||||
debugLog(`Using Electron executable: ${executablePath}`);
|
||||
|
||||
electronApp = await electron.launch(launchOptions);
|
||||
attachAppDebugLogs(electronApp);
|
||||
|
||||
await electronApp.firstWindow({ timeout: 30000 });
|
||||
await electronApp.firstWindow({ timeout: 20000 });
|
||||
const recordingStartMs = Date.now();
|
||||
await waitForRootWindow(electronApp, 60000);
|
||||
const rootWindow = await waitForRootWindow(electronApp, 8000).catch(async () => {
|
||||
debugLog('Root-ready window not found quickly; falling back to first window.');
|
||||
return await electronApp!.firstWindow({ timeout: 5000 });
|
||||
});
|
||||
const rootReadyElapsedMs = Date.now() - recordingStartMs;
|
||||
page = await waitForReadyAppWindow(electronApp, 60000);
|
||||
await page.waitForLoadState('domcontentloaded', { timeout: 10000 }).catch(() => {});
|
||||
page = rootWindow;
|
||||
await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => {});
|
||||
debugLog(`Selected app window URL: ${page.url()}`);
|
||||
attachPageDebugLogs(page);
|
||||
if (isVideoRecording()) {
|
||||
@@ -218,37 +230,6 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
},
|
||||
});
|
||||
|
||||
async function waitForReadyAppWindow(
|
||||
electronApp: Awaited<ReturnType<typeof electron.launch>>,
|
||||
timeoutMs: number
|
||||
): Promise<Page> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const windows = electronApp.windows();
|
||||
for (const page of windows) {
|
||||
try {
|
||||
const isReady = await page.evaluate(() => {
|
||||
const root = document.getElementById('root');
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
const chatInput = document.querySelector('[data-testid="chat-input"]');
|
||||
return !!chatInput;
|
||||
});
|
||||
if (isReady) {
|
||||
return page;
|
||||
}
|
||||
} catch {
|
||||
// Window may not be ready for evaluation yet.
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
const urls = electronApp.windows().map((w) => w.url() || '<empty>');
|
||||
throw new Error(`No app-ready window found within ${timeoutMs}ms. Window URLs: ${urls.join(', ')}`);
|
||||
}
|
||||
|
||||
async function waitForRootWindow(
|
||||
electronApp: Awaited<ReturnType<typeof electron.launch>>,
|
||||
timeoutMs: number
|
||||
@@ -273,20 +254,14 @@ async function waitForRootWindow(
|
||||
throw new Error(`No root-ready window found within ${timeoutMs}ms. Window URLs: ${urls.join(', ')}`);
|
||||
}
|
||||
|
||||
function resolvePackagedExecutable(appRoot: string): string {
|
||||
if (process.env.GOOSE_PACKAGED_EXECUTABLE) {
|
||||
return process.env.GOOSE_PACKAGED_EXECUTABLE;
|
||||
}
|
||||
|
||||
function resolveDirectElectronExecutable(appRoot: string): string {
|
||||
if (process.platform === 'darwin') {
|
||||
return join(appRoot, 'out', 'Goose-darwin-arm64', 'Goose.app', 'Contents', 'MacOS', 'Goose');
|
||||
return join(appRoot, 'node_modules', 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron');
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return join(appRoot, 'out', 'Goose-win32-x64', 'Goose.exe');
|
||||
return join(appRoot, 'node_modules', 'electron', 'dist', 'electron.exe');
|
||||
}
|
||||
|
||||
return join(appRoot, 'out', 'goose-linux-x64', 'goose');
|
||||
return join(appRoot, 'node_modules', 'electron', 'dist', 'electron');
|
||||
}
|
||||
|
||||
function createIsolatedGoosePathRoot(): string {
|
||||
@@ -319,10 +294,11 @@ function buildLaunchOptions(
|
||||
tempDir: string,
|
||||
videoDir?: string
|
||||
): Parameters<typeof electron.launch>[0] {
|
||||
const appRoot = join(__dirname, '../..');
|
||||
const launchOptions: Parameters<typeof electron.launch>[0] = {
|
||||
executablePath,
|
||||
args: [],
|
||||
timeout: 60000,
|
||||
args: [appRoot],
|
||||
timeout: 30000,
|
||||
env: {
|
||||
...process.env,
|
||||
GOOSE_ALLOWLIST_BYPASS: 'true',
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { test, expect } from './fixtures.electron.packaged';
|
||||
|
||||
const DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
test.describe('Goose App', () => {
|
||||
test('history is empty before first session', async ({ goosePage }) => {
|
||||
const mainWindow = goosePage;
|
||||
|
||||
const showAll = mainWindow.getByText('Show All', { exact: true }).first();
|
||||
if (!(await showAll.isVisible().catch(() => false))) {
|
||||
const chatButton = mainWindow.getByRole('button', { name: /^chat$/i }).first();
|
||||
await expect(chatButton).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await chatButton.click();
|
||||
}
|
||||
|
||||
await expect(mainWindow.getByTestId('chat-sessions-list')).toBeVisible({
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
});
|
||||
await expect(mainWindow.getByTestId('chat-start-new')).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await expect(mainWindow.getByTestId('chat-show-all')).toHaveCount(0);
|
||||
|
||||
const chatInput = mainWindow.getByTestId('chat-input');
|
||||
if (!(await chatInput.isVisible().catch(() => false))) {
|
||||
const sidebarHomeButton = mainWindow.getByTestId('sidebar-home-button');
|
||||
if (await sidebarHomeButton.isVisible().catch(() => false)) {
|
||||
await sidebarHomeButton.click();
|
||||
} else {
|
||||
const homeButton = mainWindow.getByRole('button', { name: /^home$/i }).first();
|
||||
await expect(homeButton).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await homeButton.click();
|
||||
}
|
||||
}
|
||||
await expect(chatInput).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
|
||||
const alertTrigger = mainWindow.getByTestId('bottom-menu-alert-trigger');
|
||||
await expect(alertTrigger).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await alertTrigger.click();
|
||||
|
||||
const contextWindowAlert = mainWindow
|
||||
.getByTestId('alert-box')
|
||||
.filter({ has: mainWindow.getByText('Context window', { exact: true }) })
|
||||
.first();
|
||||
await expect(contextWindowAlert).toBeVisible({ timeout: DEFAULT_TIMEOUT });
|
||||
await expect(contextWindowAlert.getByTestId('alert-progress-current')).toHaveText(/^0$/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user