mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
chore: bump version to 1.0.0-beta.6 build 152 (#161)
Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
31a48e7a65
commit
4bdb21da41
11 changed files with 103 additions and 87 deletions
|
|
@ -33,20 +33,20 @@ struct VibeTunnelApp: App {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var colorScheme: ColorScheme? {
|
private var colorScheme: ColorScheme? {
|
||||||
switch colorSchemePreferenceRaw {
|
switch colorSchemePreferenceRaw {
|
||||||
case "light": return .light
|
case "light": return .light
|
||||||
case "dark": return .dark
|
case "dark": return .dark
|
||||||
default: return nil // System default
|
default: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
private func getStoredWindowStyle() -> MacWindowStyle {
|
private func getStoredWindowStyle() -> MacWindowStyle {
|
||||||
let styleRaw = UserDefaults.standard.string(forKey: "macWindowStyle") ?? "standard"
|
let styleRaw = UserDefaults.standard.string(forKey: "macWindowStyle") ?? "standard"
|
||||||
return styleRaw == "inline" ? .inline : .standard
|
return styleRaw == "inline" ? .inline : .standard
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private func handleURL(_ url: URL) {
|
private func handleURL(_ url: URL) {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ struct TerminalInput: Codable {
|
||||||
case ctrlE = "\u{0005}"
|
case ctrlE = "\u{0005}"
|
||||||
|
|
||||||
// MARK: - Function Keys
|
// MARK: - Function Keys
|
||||||
|
|
||||||
/// F1 key.
|
/// F1 key.
|
||||||
case f1 = "\u{001B}OP"
|
case f1 = "\u{001B}OP"
|
||||||
/// F2 key.
|
/// F2 key.
|
||||||
|
|
@ -156,9 +156,9 @@ struct TerminalInput: Codable {
|
||||||
case f11 = "\u{001B}[23~"
|
case f11 = "\u{001B}[23~"
|
||||||
/// F12 key.
|
/// F12 key.
|
||||||
case f12 = "\u{001B}[24~"
|
case f12 = "\u{001B}[24~"
|
||||||
|
|
||||||
// MARK: - Additional Special Characters
|
// MARK: - Additional Special Characters
|
||||||
|
|
||||||
/// Backslash character.
|
/// Backslash character.
|
||||||
case backslash = "\\"
|
case backslash = "\\"
|
||||||
/// Pipe character.
|
/// Pipe character.
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ struct TerminalToolbar: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ struct TerminalToolbar: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +212,7 @@ struct TerminalToolbar: View {
|
||||||
// Send Ctrl+E for end
|
// Send Ctrl+E for end
|
||||||
onSpecialKey(.ctrlE)
|
onSpecialKey(.ctrlE)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// VibeTunnel Version Configuration
|
// VibeTunnel Version Configuration
|
||||||
// This file contains the version and build number for the app
|
// This file contains the version and build number for the app
|
||||||
|
|
||||||
MARKETING_VERSION = 1.0.0-beta.4
|
MARKETING_VERSION = 1.0.0-beta.6
|
||||||
CURRENT_PROJECT_VERSION = 109
|
CURRENT_PROJECT_VERSION = 152
|
||||||
|
|
||||||
// Domain and GitHub configuration
|
// Domain and GitHub configuration
|
||||||
APP_DOMAIN = vibetunnel.sh
|
APP_DOMAIN = vibetunnel.sh
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// This file contains the version and build number for the app
|
// This file contains the version and build number for the app
|
||||||
|
|
||||||
MARKETING_VERSION = 1.0.0-beta.6
|
MARKETING_VERSION = 1.0.0-beta.6
|
||||||
CURRENT_PROJECT_VERSION = 151
|
CURRENT_PROJECT_VERSION = 152
|
||||||
|
|
||||||
// Domain and GitHub configuration
|
// Domain and GitHub configuration
|
||||||
APP_DOMAIN = vibetunnel.sh
|
APP_DOMAIN = vibetunnel.sh
|
||||||
|
|
|
||||||
|
|
@ -59,18 +59,18 @@ const SHELL_SPECIFIC_PATTERNS = {
|
||||||
withEscape: /[$>#%❯➜]\s*\x1b\[/,
|
withEscape: /[$>#%❯➜]\s*\x1b\[/,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PromptDetector {
|
export namespace PromptDetector {
|
||||||
// Cache for regex test results to avoid repeated tests
|
// Cache for regex test results to avoid repeated tests
|
||||||
private static endPromptCache = new Map<string, boolean>();
|
const endPromptCache = new Map<string, boolean>();
|
||||||
private static onlyPromptCache = new Map<string, boolean>();
|
const onlyPromptCache = new Map<string, boolean>();
|
||||||
private static cacheSize = 0;
|
let cacheSize = 0;
|
||||||
private static readonly MAX_CACHE_SIZE = 1000;
|
const MAX_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the entire output is just a prompt (no other content)
|
* Check if the entire output is just a prompt (no other content)
|
||||||
* Used by activity detector to determine if output is meaningful
|
* Used by activity detector to determine if output is meaningful
|
||||||
*/
|
*/
|
||||||
static isPromptOnly(data: string): boolean {
|
export function isPromptOnly(data: string): boolean {
|
||||||
// Input validation
|
// Input validation
|
||||||
if (data.length > 10000) {
|
if (data.length > 10000) {
|
||||||
logger.warn('Unusually long prompt input detected', { length: data.length });
|
logger.warn('Unusually long prompt input detected', { length: data.length });
|
||||||
|
|
@ -80,14 +80,15 @@ export class PromptDetector {
|
||||||
const trimmed = data.trim();
|
const trimmed = data.trim();
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
if (PromptDetector.onlyPromptCache.has(trimmed)) {
|
if (onlyPromptCache.has(trimmed)) {
|
||||||
return PromptDetector.onlyPromptCache.get(trimmed) ?? false;
|
const cachedResult = onlyPromptCache.get(trimmed);
|
||||||
|
return cachedResult ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = PROMPT_ONLY_REGEX.test(trimmed);
|
const result = PROMPT_ONLY_REGEX.test(trimmed);
|
||||||
|
|
||||||
// Cache result
|
// Cache result
|
||||||
PromptDetector.cacheResult(PromptDetector.onlyPromptCache, trimmed, result);
|
cacheResult(onlyPromptCache, trimmed, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -96,14 +97,15 @@ export class PromptDetector {
|
||||||
* Check if output ends with a prompt (for title injection)
|
* Check if output ends with a prompt (for title injection)
|
||||||
* This is used to determine when to inject terminal title sequences
|
* This is used to determine when to inject terminal title sequences
|
||||||
*/
|
*/
|
||||||
static endsWithPrompt(data: string): boolean {
|
export function endsWithPrompt(data: string): boolean {
|
||||||
// For title injection, we need to check the last part of the output
|
// For title injection, we need to check the last part of the output
|
||||||
// Use last 100 chars as cache key to balance cache efficiency and accuracy
|
// Use last 100 chars as cache key to balance cache efficiency and accuracy
|
||||||
const cacheKey = data.slice(-100);
|
const cacheKey = data.slice(-100);
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
if (PromptDetector.endPromptCache.has(cacheKey)) {
|
if (endPromptCache.has(cacheKey)) {
|
||||||
return PromptDetector.endPromptCache.get(cacheKey) ?? false;
|
const cachedResult = endPromptCache.get(cacheKey);
|
||||||
|
return cachedResult ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip ANSI codes for more reliable detection
|
// Strip ANSI codes for more reliable detection
|
||||||
|
|
@ -111,7 +113,7 @@ export class PromptDetector {
|
||||||
const result = UNIFIED_PROMPT_END_REGEX.test(cleanData);
|
const result = UNIFIED_PROMPT_END_REGEX.test(cleanData);
|
||||||
|
|
||||||
// Cache result
|
// Cache result
|
||||||
PromptDetector.cacheResult(PromptDetector.endPromptCache, cacheKey, result);
|
cacheResult(endPromptCache, cacheKey, result);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
logger.debug('Detected prompt at end of output');
|
logger.debug('Detected prompt at end of output');
|
||||||
|
|
@ -124,7 +126,7 @@ export class PromptDetector {
|
||||||
* Get specific shell type based on prompt pattern
|
* Get specific shell type based on prompt pattern
|
||||||
* This can be used for shell-specific optimizations in the future
|
* This can be used for shell-specific optimizations in the future
|
||||||
*/
|
*/
|
||||||
static getShellType(data: string): keyof typeof SHELL_SPECIFIC_PATTERNS | null {
|
export function getShellType(data: string): keyof typeof SHELL_SPECIFIC_PATTERNS | null {
|
||||||
const trimmed = data.trim();
|
const trimmed = data.trim();
|
||||||
|
|
||||||
// Check each shell pattern
|
// Check each shell pattern
|
||||||
|
|
@ -140,53 +142,53 @@ export class PromptDetector {
|
||||||
/**
|
/**
|
||||||
* Helper to cache results with size limit
|
* Helper to cache results with size limit
|
||||||
*/
|
*/
|
||||||
private static cacheResult(cache: Map<string, boolean>, key: string, value: boolean): void {
|
function cacheResult(cache: Map<string, boolean>, key: string, value: boolean): void {
|
||||||
if (PromptDetector.cacheSize >= PromptDetector.MAX_CACHE_SIZE) {
|
if (cacheSize >= MAX_CACHE_SIZE) {
|
||||||
// Clear oldest entries when cache is full
|
// Clear oldest entries when cache is full
|
||||||
const entriesToDelete = Math.floor(PromptDetector.MAX_CACHE_SIZE * 0.2); // Clear 20%
|
const entriesToDelete = Math.floor(MAX_CACHE_SIZE * 0.2); // Clear 20%
|
||||||
const iterator = cache.keys();
|
const iterator = cache.keys();
|
||||||
for (let i = 0; i < entriesToDelete; i++) {
|
for (let i = 0; i < entriesToDelete; i++) {
|
||||||
const keyToDelete = iterator.next().value;
|
const keyToDelete = iterator.next().value;
|
||||||
if (keyToDelete) {
|
if (keyToDelete) {
|
||||||
cache.delete(keyToDelete);
|
cache.delete(keyToDelete);
|
||||||
PromptDetector.cacheSize--;
|
cacheSize--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.set(key, value);
|
cache.set(key, value);
|
||||||
PromptDetector.cacheSize++;
|
cacheSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all caches (useful for tests or memory management)
|
* Clear all caches (useful for tests or memory management)
|
||||||
*/
|
*/
|
||||||
static clearCache(): void {
|
export function clearCache(): void {
|
||||||
PromptDetector.endPromptCache.clear();
|
endPromptCache.clear();
|
||||||
PromptDetector.onlyPromptCache.clear();
|
onlyPromptCache.clear();
|
||||||
PromptDetector.cacheSize = 0;
|
cacheSize = 0;
|
||||||
logger.debug('Prompt pattern caches cleared');
|
logger.debug('Prompt pattern caches cleared');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cache statistics for monitoring
|
* Get cache statistics for monitoring
|
||||||
*/
|
*/
|
||||||
static getCacheStats(): {
|
export function getCacheStats(): {
|
||||||
size: number;
|
size: number;
|
||||||
maxSize: number;
|
maxSize: number;
|
||||||
hitRate: { end: number; only: number };
|
hitRate: { end: number; only: number };
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
size: PromptDetector.cacheSize,
|
size: cacheSize,
|
||||||
maxSize: PromptDetector.MAX_CACHE_SIZE,
|
maxSize: MAX_CACHE_SIZE,
|
||||||
hitRate: {
|
hitRate: {
|
||||||
end: PromptDetector.endPromptCache.size,
|
end: endPromptCache.size,
|
||||||
only: PromptDetector.onlyPromptCache.size,
|
only: onlyPromptCache.size,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export for backward compatibility
|
// Export for backward compatibility
|
||||||
export const isPromptOnly = PromptDetector.isPromptOnly.bind(PromptDetector);
|
export const isPromptOnly = PromptDetector.isPromptOnly;
|
||||||
export const endsWithPrompt = PromptDetector.endsWithPrompt.bind(PromptDetector);
|
export const endsWithPrompt = PromptDetector.endsWithPrompt;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
// This file is updated during the build process
|
// This file is updated during the build process
|
||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import packageJson from '../../package.json';
|
||||||
import { createLogger } from './utils/logger.js';
|
import { createLogger } from './utils/logger.js';
|
||||||
|
|
||||||
const logger = createLogger('version');
|
const logger = createLogger('version');
|
||||||
|
|
||||||
export const VERSION = '1.0.0-beta.3';
|
export const VERSION = packageJson.version;
|
||||||
// BUILD_DATE will be replaced by build script, fallback to current time in dev
|
// BUILD_DATE will be replaced by build script, fallback to current time in dev
|
||||||
export const BUILD_DATE = process.env.BUILD_DATE || new Date().toISOString();
|
export const BUILD_DATE = process.env.BUILD_DATE || new Date().toISOString();
|
||||||
export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP || Date.now();
|
export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP || Date.now();
|
||||||
|
|
|
||||||
|
|
@ -178,31 +178,31 @@ export class TestSessionManager {
|
||||||
/**
|
/**
|
||||||
* Creates test data factory for consistent test data generation
|
* Creates test data factory for consistent test data generation
|
||||||
*/
|
*/
|
||||||
export class TestDataFactory {
|
export namespace TestDataFactory {
|
||||||
private static counters: Map<string, number> = new Map();
|
const counters: Map<string, number> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates sequential IDs for a given prefix
|
* Generates sequential IDs for a given prefix
|
||||||
*/
|
*/
|
||||||
static sequentialId(prefix: string): string {
|
export function sequentialId(prefix: string): string {
|
||||||
const current = TestDataFactory.counters.get(prefix) || 0;
|
const current = counters.get(prefix) || 0;
|
||||||
TestDataFactory.counters.set(prefix, current + 1);
|
counters.set(prefix, current + 1);
|
||||||
return `${prefix}-${current + 1}`;
|
return `${prefix}-${current + 1}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates session name with optional prefix
|
* Generates session name with optional prefix
|
||||||
*/
|
*/
|
||||||
static sessionName(prefix = 'session'): string {
|
export function sessionName(prefix = 'session'): string {
|
||||||
const timestamp = new Date().toISOString().slice(11, 19).replace(/:/g, '');
|
const timestamp = new Date().toISOString().slice(11, 19).replace(/:/g, '');
|
||||||
const counter = TestDataFactory.sequentialId(prefix);
|
const counter = sequentialId(prefix);
|
||||||
return `${counter}-${timestamp}`;
|
return `${counter}-${timestamp}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates command for testing
|
* Generates command for testing
|
||||||
*/
|
*/
|
||||||
static command(type: 'echo' | 'sleep' | 'env' | 'file' = 'echo'): string {
|
export function command(type: 'echo' | 'sleep' | 'env' | 'file' = 'echo'): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'echo':
|
case 'echo':
|
||||||
return `echo "Test output ${Date.now()}"`;
|
return `echo "Test output ${Date.now()}"`;
|
||||||
|
|
@ -218,8 +218,8 @@ export class TestDataFactory {
|
||||||
/**
|
/**
|
||||||
* Reset all counters
|
* Reset all counters
|
||||||
*/
|
*/
|
||||||
static reset(): void {
|
export function reset(): void {
|
||||||
TestDataFactory.counters.clear();
|
counters.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ test.describe('Debug Session Tests', () => {
|
||||||
|
|
||||||
// Check the app component's state
|
// Check the app component's state
|
||||||
const appHideExited = await page.evaluate(() => {
|
const appHideExited = await page.evaluate(() => {
|
||||||
const app = document.querySelector('vibetunnel-app') as any;
|
const app = document.querySelector('vibetunnel-app') as HTMLElement & {
|
||||||
|
hideExited?: boolean;
|
||||||
|
};
|
||||||
return app?.hideExited;
|
return app?.hideExited;
|
||||||
});
|
});
|
||||||
console.log('App component hideExited:', appHideExited);
|
console.log('App component hideExited:', appHideExited);
|
||||||
|
|
@ -140,7 +142,7 @@ test.describe('Debug Session Tests', () => {
|
||||||
|
|
||||||
// Check if all sessions are exited
|
// Check if all sessions are exited
|
||||||
const exitedCount = sessionsResponse.sessions.filter(
|
const exitedCount = sessionsResponse.sessions.filter(
|
||||||
(s: any) => s.status === 'exited'
|
(s: { status: string }) => s.status === 'exited'
|
||||||
).length;
|
).length;
|
||||||
console.log(`Exited sessions: ${exitedCount} out of ${sessionsResponse.count}`);
|
console.log(`Exited sessions: ${exitedCount} out of ${sessionsResponse.count}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,7 @@ test.describe.skip('Terminal Interaction', () => {
|
||||||
|
|
||||||
test('should execute multiple commands in sequence', async ({ page }) => {
|
test('should execute multiple commands in sequence', async ({ page }) => {
|
||||||
// Execute sequence with expected outputs
|
// Execute sequence with expected outputs
|
||||||
await executeCommandSequence(page, [
|
await executeCommandSequence(page, ['echo "Test 1"', 'echo "Test 2"']);
|
||||||
{ command: 'echo "Test 1"', expectedOutput: 'Test 1' },
|
|
||||||
{ command: 'echo "Test 2"', expectedOutput: 'Test 2', waitBetween: 500 },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Both outputs should be visible
|
// Both outputs should be visible
|
||||||
await assertTerminalContains(page, 'Test 1');
|
await assertTerminalContains(page, 'Test 1');
|
||||||
|
|
@ -56,9 +53,7 @@ test.describe.skip('Terminal Interaction', () => {
|
||||||
|
|
||||||
test('should handle long-running commands', async ({ page }) => {
|
test('should handle long-running commands', async ({ page }) => {
|
||||||
// Execute and wait for completion
|
// Execute and wait for completion
|
||||||
await executeAndVerifyCommand(page, 'sleep 1 && echo "Done sleeping"', 'Done sleeping', {
|
await executeAndVerifyCommand(page, 'sleep 1 && echo "Done sleeping"', 'Done sleeping');
|
||||||
timeout: 3000,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle command interruption', async ({ page }) => {
|
test('should handle command interruption', async ({ page }) => {
|
||||||
|
|
@ -134,7 +129,12 @@ test.describe.skip('Terminal Interaction', () => {
|
||||||
test('should handle terminal resize', async ({ page }) => {
|
test('should handle terminal resize', async ({ page }) => {
|
||||||
// Get initial terminal dimensions
|
// Get initial terminal dimensions
|
||||||
const initialDimensions = await page.evaluate(() => {
|
const initialDimensions = await page.evaluate(() => {
|
||||||
const terminal = document.querySelector('vibe-terminal') as any;
|
const terminal = document.querySelector('vibe-terminal') as HTMLElement & {
|
||||||
|
cols?: number;
|
||||||
|
rows?: number;
|
||||||
|
actualCols?: number;
|
||||||
|
actualRows?: number;
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
cols: terminal?.cols || 80,
|
cols: terminal?.cols || 80,
|
||||||
rows: terminal?.rows || 24,
|
rows: terminal?.rows || 24,
|
||||||
|
|
@ -159,7 +159,12 @@ test.describe.skip('Terminal Interaction', () => {
|
||||||
// Wait for terminal-resize event or dimension change
|
// Wait for terminal-resize event or dimension change
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
({ initial }) => {
|
({ initial }) => {
|
||||||
const terminal = document.querySelector('vibe-terminal') as any;
|
const terminal = document.querySelector('vibe-terminal') as HTMLElement & {
|
||||||
|
cols?: number;
|
||||||
|
rows?: number;
|
||||||
|
actualCols?: number;
|
||||||
|
actualRows?: number;
|
||||||
|
};
|
||||||
const currentCols = terminal?.cols || 80;
|
const currentCols = terminal?.cols || 80;
|
||||||
const currentRows = terminal?.rows || 24;
|
const currentRows = terminal?.rows || 24;
|
||||||
const currentActualCols = terminal?.actualCols || currentCols;
|
const currentActualCols = terminal?.actualCols || currentCols;
|
||||||
|
|
@ -179,7 +184,12 @@ test.describe.skip('Terminal Interaction', () => {
|
||||||
|
|
||||||
// Verify terminal dimensions changed
|
// Verify terminal dimensions changed
|
||||||
const newDimensions = await page.evaluate(() => {
|
const newDimensions = await page.evaluate(() => {
|
||||||
const terminal = document.querySelector('vibe-terminal') as any;
|
const terminal = document.querySelector('vibe-terminal') as HTMLElement & {
|
||||||
|
cols?: number;
|
||||||
|
rows?: number;
|
||||||
|
actualCols?: number;
|
||||||
|
actualRows?: number;
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
cols: terminal?.cols || 80,
|
cols: terminal?.cols || 80,
|
||||||
rows: terminal?.rows || 24,
|
rows: terminal?.rows || 24,
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,34 @@ import { logger } from './logger';
|
||||||
/**
|
/**
|
||||||
* Optimized wait utilities with reduced timeouts and smarter strategies
|
* Optimized wait utilities with reduced timeouts and smarter strategies
|
||||||
*/
|
*/
|
||||||
export class OptimizedWaitUtils {
|
export namespace OptimizedWaitUtils {
|
||||||
// Reduced default timeouts for faster test execution
|
// Reduced default timeouts for faster test execution
|
||||||
private static readonly QUICK_TIMEOUT = TEST_TIMEOUTS.QUICK;
|
const QUICK_TIMEOUT = TEST_TIMEOUTS.QUICK;
|
||||||
private static readonly DEFAULT_TIMEOUT = TEST_TIMEOUTS.DEFAULT;
|
const DEFAULT_TIMEOUT = TEST_TIMEOUTS.DEFAULT;
|
||||||
private static readonly LONG_TIMEOUT = TEST_TIMEOUTS.LONG;
|
|
||||||
private static readonly logger = logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for app initialization with optimized checks
|
* Wait for app initialization with optimized checks
|
||||||
*/
|
*/
|
||||||
static async waitForAppReady(page: Page): Promise<void> {
|
export async function waitForAppReady(page: Page): Promise<void> {
|
||||||
// Wait for app element to attach (reduced timeout)
|
// Wait for app element to attach (reduced timeout)
|
||||||
await page.waitForSelector('vibetunnel-app', {
|
await page.waitForSelector('vibetunnel-app', {
|
||||||
state: 'attached',
|
state: 'attached',
|
||||||
timeout: OptimizedWaitUtils.DEFAULT_TIMEOUT,
|
timeout: DEFAULT_TIMEOUT,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use Promise.allSettled for better reliability
|
// Use Promise.allSettled for better reliability
|
||||||
const results = await Promise.allSettled([
|
const results = await Promise.allSettled([
|
||||||
page.waitForSelector('button[title="Create New Session"]', {
|
page.waitForSelector('button[title="Create New Session"]', {
|
||||||
state: 'visible',
|
state: 'visible',
|
||||||
timeout: OptimizedWaitUtils.QUICK_TIMEOUT,
|
timeout: QUICK_TIMEOUT,
|
||||||
}),
|
}),
|
||||||
page.waitForSelector('session-card', {
|
page.waitForSelector('session-card', {
|
||||||
state: 'visible',
|
state: 'visible',
|
||||||
timeout: OptimizedWaitUtils.QUICK_TIMEOUT,
|
timeout: QUICK_TIMEOUT,
|
||||||
}),
|
}),
|
||||||
page.waitForSelector('auth-login', {
|
page.waitForSelector('auth-login', {
|
||||||
state: 'visible',
|
state: 'visible',
|
||||||
timeout: OptimizedWaitUtils.QUICK_TIMEOUT,
|
timeout: QUICK_TIMEOUT,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -52,7 +50,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for session card with specific name
|
* Wait for session card with specific name
|
||||||
*/
|
*/
|
||||||
static async waitForSessionCard(
|
export async function waitForSessionCard(
|
||||||
page: Page,
|
page: Page,
|
||||||
sessionName: string,
|
sessionName: string,
|
||||||
timeout = 3000
|
timeout = 3000
|
||||||
|
|
@ -65,7 +63,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for terminal to be ready (optimized)
|
* Wait for terminal to be ready (optimized)
|
||||||
*/
|
*/
|
||||||
static async waitForTerminalReady(page: Page, timeout = 3000): Promise<void> {
|
export async function waitForTerminalReady(page: Page, timeout = 3000): Promise<void> {
|
||||||
// Wait for xterm element
|
// Wait for xterm element
|
||||||
await page.waitForSelector('.xterm', { state: 'visible', timeout });
|
await page.waitForSelector('.xterm', { state: 'visible', timeout });
|
||||||
|
|
||||||
|
|
@ -84,7 +82,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for session state change
|
* Wait for session state change
|
||||||
*/
|
*/
|
||||||
static async waitForSessionState(
|
export async function waitForSessionState(
|
||||||
page: Page,
|
page: Page,
|
||||||
sessionName: string,
|
sessionName: string,
|
||||||
expectedState: 'RUNNING' | 'EXITED',
|
expectedState: 'RUNNING' | 'EXITED',
|
||||||
|
|
@ -117,7 +115,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Smart wait for navigation with fallback
|
* Smart wait for navigation with fallback
|
||||||
*/
|
*/
|
||||||
static async waitForNavigation(page: Page, url: string, timeout = 3000): Promise<void> {
|
export async function waitForNavigation(page: Page, url: string, timeout = 3000): Promise<void> {
|
||||||
// Try to wait for URL change
|
// Try to wait for URL change
|
||||||
try {
|
try {
|
||||||
await page.waitForURL(url, { timeout });
|
await page.waitForURL(url, { timeout });
|
||||||
|
|
@ -133,13 +131,13 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for element count with early exit
|
* Wait for element count with early exit
|
||||||
*/
|
*/
|
||||||
static async waitForElementCount(
|
export async function waitForElementCount(
|
||||||
page: Page,
|
page: Page,
|
||||||
selector: string,
|
selector: string,
|
||||||
expectedCount: number,
|
expectedCount: number,
|
||||||
options?: { operator?: 'exact' | 'minimum' | 'maximum'; timeout?: number }
|
options?: { operator?: 'exact' | 'minimum' | 'maximum'; timeout?: number }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { operator = 'exact', timeout = OptimizedWaitUtils.DEFAULT_TIMEOUT } = options || {};
|
const { operator = 'exact', timeout = DEFAULT_TIMEOUT } = options || {};
|
||||||
const pollInterval = 100;
|
const pollInterval = 100;
|
||||||
const maxAttempts = timeout / pollInterval;
|
const maxAttempts = timeout / pollInterval;
|
||||||
|
|
||||||
|
|
@ -164,7 +162,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for any text content (useful for terminal output)
|
* Wait for any text content (useful for terminal output)
|
||||||
*/
|
*/
|
||||||
static async waitForAnyText(locator: Locator, timeout = 2000): Promise<string> {
|
export async function waitForAnyText(locator: Locator, timeout = 2000): Promise<string> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
while (Date.now() - startTime < timeout) {
|
while (Date.now() - startTime < timeout) {
|
||||||
|
|
@ -181,7 +179,7 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Fast visibility check with retry
|
* Fast visibility check with retry
|
||||||
*/
|
*/
|
||||||
static async isEventuallyVisible(locator: Locator, timeout = 1000): Promise<boolean> {
|
export async function isEventuallyVisible(locator: Locator, timeout = 1000): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await locator.waitFor({ state: 'visible', timeout });
|
await locator.waitFor({ state: 'visible', timeout });
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -194,7 +192,10 @@ export class OptimizedWaitUtils {
|
||||||
/**
|
/**
|
||||||
* Wait for network idle with early exit
|
* Wait for network idle with early exit
|
||||||
*/
|
*/
|
||||||
static async waitForNetworkQuiet(page: Page, options?: { timeout?: number }): Promise<void> {
|
export async function waitForNetworkQuiet(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number }
|
||||||
|
): Promise<void> {
|
||||||
const { timeout = TEST_TIMEOUTS.NETWORK_QUIET } = options || {};
|
const { timeout = TEST_TIMEOUTS.NETWORK_QUIET } = options || {};
|
||||||
|
|
||||||
// Track pending requests
|
// Track pending requests
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue