fix: chat loading-state model placeholder (#8431)
Signed-off-by: sunilkumarvalmiki <g.sunilkumarvalmiki@gmail.com> Co-authored-by: jh-block <jhugo@block.xyz>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { test as base, Page, Browser, chromium } from '@playwright/test';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { exec, spawn, ChildProcess } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(require('child_process').exec);
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
type GooseTestFixtures = {
|
||||
goosePage: Page;
|
||||
@@ -28,7 +28,8 @@ type GooseTestFixtures = {
|
||||
*/
|
||||
export const test = base.extend<GooseTestFixtures>({
|
||||
// Test-scoped fixture: launches a fresh Electron app for each test
|
||||
goosePage: async ({}, use, testInfo) => {
|
||||
goosePage: async ({ browserName }, providePage, testInfo) => {
|
||||
void browserName;
|
||||
console.log(`Launching fresh Electron app for test: ${testInfo.title}`);
|
||||
|
||||
let appProcess: ChildProcess | null = null;
|
||||
@@ -80,8 +81,9 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
console.log(`Connected to Electron app on attempt ${attempt} (~${(attempt * retryDelay) / 1000}s)`);
|
||||
break;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (attempt === maxRetries) {
|
||||
throw new Error(`Failed to connect to Electron app after ${maxRetries} attempts (${(maxRetries * retryDelay) / 1000}s). Last error: ${error.message}`);
|
||||
throw new Error(`Failed to connect to Electron app after ${maxRetries} attempts (${(maxRetries * retryDelay) / 1000}s). Last error: ${errorMessage}`);
|
||||
}
|
||||
// Wait before next retry
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
@@ -92,26 +94,28 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
throw new Error('Browser connection failed unexpectedly');
|
||||
}
|
||||
|
||||
// Get the electron app context and first page
|
||||
const contexts = browser.contexts();
|
||||
if (contexts.length === 0) {
|
||||
throw new Error('No browser contexts found');
|
||||
// Wait for Electron to create its first window after the CDP endpoint is up.
|
||||
let page: Page | null = null;
|
||||
for (let attempt = 1; attempt <= 100; attempt++) {
|
||||
const contexts = browser.contexts();
|
||||
page = contexts.flatMap((context) => context.pages())[0] ?? null;
|
||||
if (page) {
|
||||
break;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
const pages = contexts[0].pages();
|
||||
if (pages.length === 0) {
|
||||
if (!page) {
|
||||
throw new Error('No windows/pages found');
|
||||
}
|
||||
|
||||
const page = pages[0];
|
||||
|
||||
// Wait for page to be ready
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Try to wait for networkidle
|
||||
try {
|
||||
await page.waitForLoadState('networkidle', { timeout: 10000 });
|
||||
} catch (error) {
|
||||
} catch {
|
||||
console.log('NetworkIdle timeout (likely due to MCP activity), continuing...');
|
||||
}
|
||||
|
||||
@@ -124,7 +128,7 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
console.log('App ready, starting test...');
|
||||
|
||||
// Provide the page to the test
|
||||
await use(page);
|
||||
await providePage(page);
|
||||
|
||||
} finally {
|
||||
console.log('Cleaning up Electron app for this test...');
|
||||
@@ -146,19 +150,23 @@ export const test = base.extend<GooseTestFixtures>({
|
||||
// First try SIGTERM for graceful shutdown
|
||||
process.kill(-appProcess.pid, 'SIGTERM');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Process might already be dead
|
||||
}
|
||||
// Then SIGKILL if still running
|
||||
try {
|
||||
process.kill(-appProcess.pid, 'SIGKILL');
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Process already exited
|
||||
}
|
||||
}
|
||||
console.log('Cleaned up app process');
|
||||
} catch (error) {
|
||||
if (error.code !== 'ESRCH' && !error.message?.includes('No such process')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
!('code' in error && error.code === 'ESRCH') &&
|
||||
!error.message.includes('No such process')
|
||||
) {
|
||||
console.error('Error killing app process:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test, expect } from './fixtures';
|
||||
|
||||
test.describe('Loading State', () => {
|
||||
test('shows a model placeholder while creating a new chat session', async ({ goosePage }) => {
|
||||
await goosePage.waitForSelector('[data-testid="chat-input"]', { timeout: 30000 });
|
||||
|
||||
const chatInput = await goosePage.waitForSelector('[data-testid="chat-input"]');
|
||||
await chatInput.fill('Respond with the single word hello.');
|
||||
await chatInput.press('Enter');
|
||||
|
||||
await goosePage.waitForSelector('[data-testid="loading-indicator"]', {
|
||||
state: 'visible',
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const loadingModel = goosePage.locator('[data-testid="model-loading-state"]');
|
||||
await expect(loadingModel).toHaveText(/loading model/i, { timeout: 10000 });
|
||||
|
||||
await goosePage.screenshot({
|
||||
path: test.info().outputPath('loading-state-fresh-session.png'),
|
||||
fullPage: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user