From 2a302c859ecd3c6899220d2579449ea14438f8cf Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 11 Aug 2025 12:58:05 +0530 Subject: [PATCH 1/5] feat: check browser status during run execution --- server/src/pgboss-worker.ts | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 2c3ae1ac..5f3bc692 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -215,7 +215,8 @@ async function triggerIntegrationUpdates(runId: string, robotMetaId: string): Pr * Modified processRunExecution function - only add browser reset */ async function processRunExecution(job: Job) { - const BROWSER_INIT_TIMEOUT = 30000; + const BROWSER_INIT_TIMEOUT = 60000; + const BROWSER_PAGE_TIMEOUT = 45000; const data = job.data; logger.log('info', `Processing run execution job for runId: ${data.runId}, browserId: ${data.browserId}`); @@ -244,15 +245,28 @@ async function processRunExecution(job: Job) { let browser = browserPool.getRemoteBrowser(browserId); const browserWaitStart = Date.now(); + let lastLogTime = 0; while (!browser && (Date.now() - browserWaitStart) < BROWSER_INIT_TIMEOUT) { - logger.log('debug', `Browser ${browserId} not ready yet, waiting...`); - await new Promise(resolve => setTimeout(resolve, 1000)); + const currentTime = Date.now(); + + const browserStatus = browserPool.getBrowserStatus(browserId); + if (browserStatus === null) { + throw new Error(`Browser slot ${browserId} does not exist in pool`); + } + + if (currentTime - lastLogTime > 10000) { + logger.log('info', `Browser ${browserId} not ready yet (status: ${browserStatus}), waiting... (${Math.round((currentTime - browserWaitStart) / 1000)}s elapsed)`); + lastLogTime = currentTime; + } + + await new Promise(resolve => setTimeout(resolve, 2000)); browser = browserPool.getRemoteBrowser(browserId); } if (!browser) { - throw new Error(`Browser ${browserId} not found in pool after timeout`); + const finalStatus = browserPool.getBrowserStatus(browserId); + throw new Error(`Browser ${browserId} not found in pool after ${BROWSER_INIT_TIMEOUT/1000}s timeout (final status: ${finalStatus})`); } logger.log('info', `Browser ${browserId} found and ready for execution`); @@ -273,14 +287,22 @@ async function processRunExecution(job: Job) { let currentPage = browser.getCurrentPage(); const pageWaitStart = Date.now(); - while (!currentPage && (Date.now() - pageWaitStart) < 30000) { - logger.log('debug', `Page not ready for browser ${browserId}, waiting...`); + let lastPageLogTime = 0; + + while (!currentPage && (Date.now() - pageWaitStart) < BROWSER_PAGE_TIMEOUT) { + const currentTime = Date.now(); + + if (currentTime - lastPageLogTime > 5000) { + logger.log('info', `Page not ready for browser ${browserId}, waiting... (${Math.round((currentTime - pageWaitStart) / 1000)}s elapsed)`); + lastPageLogTime = currentTime; + } + await new Promise(resolve => setTimeout(resolve, 1000)); currentPage = browser.getCurrentPage(); } if (!currentPage) { - throw new Error(`No current page available for browser ${browserId} after timeout`); + throw new Error(`No current page available for browser ${browserId} after ${BROWSER_PAGE_TIMEOUT/1000}s timeout`); } logger.log('info', `Starting workflow execution for run ${data.runId}`); From 6d75520cdff8b879116c2cab8c400812e0b5ad91 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 11 Aug 2025 13:01:31 +0530 Subject: [PATCH 2/5] feat: add browser status method --- .../browser-management/classes/BrowserPool.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/server/src/browser-management/classes/BrowserPool.ts b/server/src/browser-management/classes/BrowserPool.ts index e6dcf6b8..eb99f2df 100644 --- a/server/src/browser-management/classes/BrowserPool.ts +++ b/server/src/browser-management/classes/BrowserPool.ts @@ -221,6 +221,12 @@ export class BrowserPool { return undefined; } + // Return undefined for failed slots + if (poolInfo.status === "failed") { + logger.log('debug', `Browser ${id} has failed status`); + return undefined; + } + return poolInfo.browser || undefined; }; @@ -607,8 +613,13 @@ export class BrowserPool { * @returns true if successful, false if slot wasn't reserved */ public upgradeBrowserSlot = (id: string, browser: RemoteBrowser): boolean => { - if (!this.pool[id] || this.pool[id].status !== "reserved") { - logger.log('warn', `Cannot upgrade browser ${id}: slot not reserved`); + if (!this.pool[id]) { + logger.log('warn', `Cannot upgrade browser ${id}: slot does not exist in pool`); + return false; + } + + if (this.pool[id].status !== "reserved") { + logger.log('warn', `Cannot upgrade browser ${id}: slot not in reserved state (current: ${this.pool[id].status})`); return false; } @@ -629,4 +640,17 @@ export class BrowserPool { this.deleteRemoteBrowser(id); } }; + + /** + * Gets the current status of a browser slot. + * + * @param id browser ID to check + * @returns the status or null if browser doesn't exist + */ + public getBrowserStatus = (id: string): "reserved" | "initializing" | "ready" | "failed" | null => { + if (!this.pool[id]) { + return null; + } + return this.pool[id].status || null; + }; } \ No newline at end of file From 96339f556820fc5093ebd90ceec86470e5ee3580 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 11 Aug 2025 13:02:20 +0530 Subject: [PATCH 3/5] feat: error handling browser init --- server/src/browser-management/controller.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/src/browser-management/controller.ts b/server/src/browser-management/controller.ts index 3a2c31e3..1c6ecb5c 100644 --- a/server/src/browser-management/controller.ts +++ b/server/src/browser-management/controller.ts @@ -242,7 +242,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { logger.log('warn', `No client connected to browser ${id} within timeout, proceeding with dummy socket`); resolve(null); } - }, 10000); + }, 15000); }); namespace.on('error', (error: any) => { @@ -272,21 +272,25 @@ const initializeBrowserAsync = async (id: string, userId: string) => { browserSession = new RemoteBrowser(dummySocket, userId, id); } + logger.log('debug', `Starting browser initialization for ${id}`); await browserSession.initialize(userId); + logger.log('debug', `Browser initialization completed for ${id}`); const upgraded = browserPool.upgradeBrowserSlot(id, browserSession); if (!upgraded) { throw new Error('Failed to upgrade reserved browser slot'); } + await new Promise(resolve => setTimeout(resolve, 500)); + if (socket) { socket.emit('ready-for-run'); } else { setTimeout(async () => { try { - logger.log('info', `Starting execution for browser ${id} with dummy socket`); + logger.log('info', `Browser ${id} with dummy socket is ready for execution`); } catch (error: any) { - logger.log('error', `Error executing run for browser ${id}: ${error.message}`); + logger.log('error', `Error with dummy socket browser ${id}: ${error.message}`); } }, 100); } @@ -299,10 +303,12 @@ const initializeBrowserAsync = async (id: string, userId: string) => { if (socket) { socket.emit('error', { message: error.message }); } + throw error; } } catch (error: any) { logger.log('error', `Error setting up browser ${id}: ${error.message}`); browserPool.failBrowserSlot(id); + throw error; } }; From b2f6211c22f77c15f6a9af75e141f491e0ffaebb Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 11 Aug 2025 16:03:28 +0530 Subject: [PATCH 4/5] feat: add depth limitations --- src/helpers/clientSelectorGenerator.ts | 98 ++++++++++---------------- 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/src/helpers/clientSelectorGenerator.ts b/src/helpers/clientSelectorGenerator.ts index cb782d44..9ec690b7 100644 --- a/src/helpers/clientSelectorGenerator.ts +++ b/src/helpers/clientSelectorGenerator.ts @@ -2494,70 +2494,38 @@ class ClientSelectorGenerator { }; private getAllDescendantsIncludingShadow( - parentElement: HTMLElement + parentElement: HTMLElement, + maxDepth: number = 20 ): HTMLElement[] { const allDescendants: HTMLElement[] = []; const visited = new Set(); - const shadowRootsSeen = new Set(); - const traverseShadowRoot = (shadowRoot: ShadowRoot, depth: number = 0) => { - if (depth > 10) return; + const traverse = (element: HTMLElement, currentDepth: number) => { + if (currentDepth >= maxDepth || visited.has(element)) { + return; + } + visited.add(element); - try { - const shadowElements = Array.from( - shadowRoot.querySelectorAll("*") - ) as HTMLElement[]; + if (element !== parentElement) { + allDescendants.push(element); + } - shadowElements.forEach((shadowElement) => { - if (!visited.has(shadowElement)) { - visited.add(shadowElement); - allDescendants.push(shadowElement); + // Traverse light DOM children + const children = Array.from(element.children) as HTMLElement[]; + for (const child of children) { + traverse(child, currentDepth + 1); + } - if ( - shadowElement.shadowRoot && - !shadowRootsSeen.has(shadowElement.shadowRoot) - ) { - shadowRootsSeen.add(shadowElement.shadowRoot); - traverseShadowRoot(shadowElement.shadowRoot, depth + 1); - } - } - }); - - Array.from(shadowRoot.children).forEach((child) => { - const htmlChild = child as HTMLElement; - if ( - htmlChild.shadowRoot && - !shadowRootsSeen.has(htmlChild.shadowRoot) - ) { - shadowRootsSeen.add(htmlChild.shadowRoot); - traverseShadowRoot(htmlChild.shadowRoot, depth + 1); - } - }); - } catch (error) { - console.warn(`Error traversing shadow root:`, error); + // Traverse shadow DOM if it exists + if (element.shadowRoot) { + const shadowChildren = Array.from(element.shadowRoot.children) as HTMLElement[]; + for (const shadowChild of shadowChildren) { + traverse(shadowChild, currentDepth + 1); + } } }; - const regularDescendants = Array.from( - parentElement.querySelectorAll("*") - ) as HTMLElement[]; - regularDescendants.forEach((descendant) => { - if (!visited.has(descendant)) { - visited.add(descendant); - allDescendants.push(descendant); - } - }); - - const elementsWithShadow = [parentElement, ...regularDescendants].filter( - (el) => el.shadowRoot - ); - elementsWithShadow.forEach((element) => { - if (!shadowRootsSeen.has(element.shadowRoot!)) { - shadowRootsSeen.add(element.shadowRoot!); - traverseShadowRoot(element.shadowRoot!, 0); - } - }); - + traverse(parentElement, 0); return allDescendants; } @@ -2577,6 +2545,8 @@ class ClientSelectorGenerator { if (processedElements.has(descendant)) return; processedElements.add(descendant); + if (!this.isMeaningfulElement(descendant)) return; + const absolutePath = this.buildOptimizedAbsoluteXPath( descendant, listSelector, @@ -2766,16 +2736,20 @@ class ClientSelectorGenerator { rootElement: HTMLElement, otherListElements: HTMLElement[] = [] ): string | null { - if (!this.elementContains(rootElement, targetElement) || targetElement === rootElement) { + if ( + !this.elementContains(rootElement, targetElement) || + targetElement === rootElement + ) { return null; } const pathParts: string[] = []; let current: HTMLElement | null = targetElement; + let pathDepth = 0; + const MAX_PATH_DEPTH = 20; // Build path from target up to root - while (current && current !== rootElement) { - // Calculate conflicts for each element in the path + while (current && current !== rootElement && pathDepth < MAX_PATH_DEPTH) { const classes = this.getCommonClassesAcrossLists( current, otherListElements @@ -2806,11 +2780,15 @@ class ClientSelectorGenerator { pathParts.unshift(pathPart); } - // Move to parent (either regular parent or shadow host) - current = current.parentElement || + current = + current.parentElement || ((current.getRootNode() as ShadowRoot).host as HTMLElement | null); + + pathDepth++; + } - if (!current) break; + if (current !== rootElement) { + return null; } return pathParts.length > 0 ? "/" + pathParts.join("/") : null; From fe4a12cfe48bdcbc65cb1a965a58d68850681c4e Mon Sep 17 00:00:00 2001 From: Rohit Date: Tue, 12 Aug 2025 22:19:30 +0530 Subject: [PATCH 5/5] feat: add interval for queue checks --- server/src/pgboss-worker.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 5f3bc692..c8baa9c0 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -797,6 +797,10 @@ async function registerRunExecutionWorker() { }; await checkForNewUserQueues(); + + setInterval(async () => { + await checkForNewUserQueues(); + }, 10000); logger.log('info', 'Run execution worker registered successfully'); } catch (error: unknown) { @@ -843,6 +847,10 @@ async function registerAbortRunWorker() { }; await checkForNewAbortQueues(); + + setInterval(async () => { + await checkForNewAbortQueues(); + }, 10000); logger.log('info', 'Abort run worker registration system initialized'); } catch (error: unknown) {