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