1+ /* eslint-disable no-console */
12import {
23 ActivityBar ,
34 By ,
@@ -45,9 +46,12 @@ export async function closeActivityBarView(name: string): Promise<void> {
4546 * this more flexible in the future. Also, there needs to be an active editor
4647 * for the command to work, so ensure a file is opened before calling this
4748 * function.
49+ * @param isCreateQueryIframeSupported Whether the server requires the "Create
50+ * Connection" webview form. Pass the result of `isCreateQueryIframeServer()`.
4851 */
49- export async function connectToServer ( ) : Promise < void > {
50- // eslint-disable-next-line no-console
52+ export async function connectToServer (
53+ isCreateQueryIframeSupported = false
54+ ) : Promise < void > {
5155 console . log ( 'Connecting to Deephaven server...' ) ;
5256
5357 await executeCommandWithRetry ( 'Deephaven: Select Connection' ) ;
@@ -58,7 +62,7 @@ export async function connectToServer(): Promise<void> {
5862 const input = await InputBox . create ( ) ;
5963 await input . selectQuickPick ( 0 ) ;
6064
61- await waitForServerConnection ( ) ;
65+ await waitForServerConnection ( isCreateQueryIframeSupported ) ;
6266}
6367
6468/**
@@ -169,15 +173,13 @@ export async function executeWithRetry<T>(
169173 ) ;
170174
171175 if ( ! isRetryableError ) {
172- // eslint-disable-next-line no-console
173176 console . error ( 'Non-retryable error encountered:' , error ) ;
174177 throw error ;
175178 }
176179
177180 const hasRetryAttemptsRemaining = retryAttempt < maxRetries ;
178181
179182 if ( ! hasRetryAttemptsRemaining ) {
180- // eslint-disable-next-line no-console
181183 console . error ( 'Max retry attempts reached. Last error:' , error ) ;
182184 throw error ;
183185 }
@@ -379,7 +381,6 @@ export async function openFileResources(
379381 return ;
380382 }
381383
382- // eslint-disable-next-line no-console
383384 console . log ( 'Opening filePaths:' , filePaths ) ;
384385
385386 // In CI environment, openResources doesn't work on Linux. Using the quick open
@@ -399,8 +400,10 @@ export async function openFileResources(
399400 * @param editor Editor to execute code lens in
400401 */
401402export async function runDhFileCodeLens ( editor : TextEditor ) : Promise < void > {
402- const runDhFileCodeLens = await getCodeLens ( editor , 'Run Deephaven File' ) ;
403- await runDhFileCodeLens . click ( ) ;
403+ await executeWithRetry ( async ( ) => {
404+ const runDhFileCodeLens = await getCodeLens ( editor , 'Run Deephaven File' ) ;
405+ await runDhFileCodeLens . click ( ) ;
406+ } ) ;
404407}
405408
406409/**
@@ -422,7 +425,6 @@ export async function step<TResult>(
422425 fn : ( stepLabel : string ) => Promise < TResult >
423426) : Promise < TResult > {
424427 const stepLabel = `Step ${ n } : ${ label } ` ;
425- // eslint-disable-next-line no-console
426428 console . log ( stepLabel ) ;
427429 return fn ( stepLabel ) ;
428430}
@@ -485,7 +487,6 @@ export async function switchToFrame(
485487 throw err ;
486488 }
487489
488- // eslint-disable-next-line no-console
489490 console . log ( `Retrying after '${ errorType } ' error` ) ;
490491
491492 // Try retrieving the iframe and switching again
@@ -497,21 +498,131 @@ export async function switchToFrame(
497498 try {
498499 await driver . switchTo ( ) . frame ( iframe ) ;
499500 } catch ( err ) {
500- // eslint-disable-next-line no-console
501501 console . log ( `Failed to switch to frame: ${ iframeOrIdentifier } ` , err ) ;
502502 throw err ;
503503 }
504504 }
505505 }
506506}
507507
508+ /**
509+ * Check if the given server URL requires the "Create Connection" webview form
510+ * to create a worker. Mirrors the getDheFeatures() check in src/dh/dhe.ts using
511+ * the same endpoint and response validation.
512+ */
513+ export async function getIsCreateQueryIframeSupported (
514+ serverUrl : string | undefined
515+ ) : Promise < boolean > {
516+ if ( serverUrl == null ) {
517+ return false ;
518+ }
519+
520+ try {
521+ const res = await fetch ( new URL ( '/iriside/features.json' , serverUrl ) ) ;
522+ if ( ! res . ok || res . headers . get ( 'content-type' ) !== 'application/json' ) {
523+ console . log (
524+ `[getIsCreateQueryIframe] Unsupported. Status: ${ res . status } , Content-Type: ${ res . headers . get ( 'content-type' ) } `
525+ ) ;
526+ return false ;
527+ }
528+ const json = await res . json ( ) ;
529+ return json ?. features ?. createQueryIframe === true ;
530+ } catch ( err ) {
531+ console . error ( `[getIsCreateQueryIframe] Error at ${ serverUrl } :` , err ) ;
532+ return false ;
533+ }
534+ }
535+
536+ /**
537+ * Fill in and submit the "Create Connection" webview form that appears for
538+ * servers with the `createQueryIframe` feature. Sets Heap Size to 0.5 GB and
539+ * Language to Python, then clicks Connect.
540+ */
541+ async function handleCreateQueryForm ( ) : Promise < void > {
542+ const { driver } = VSBrowser . instance ;
543+
544+ // The Create Connection panel is a VS Code WebviewView rendered as an iframe
545+ // with src containing "purpose=webviewView".
546+ // Navigate: outer sidebar iframe → #active-frame → #content-iframe.
547+ await switchToFrame ( [
548+ 'iframe[src*="purpose=webviewView"]' ,
549+ '#active-frame' ,
550+ '#content-iframe' ,
551+ ] ) ;
552+
553+ console . log ( '[handleCreateQueryForm] Waiting for form to load...' ) ;
554+ // Wait for the form to finish loading (blank panel / spinner may appear first)
555+ const heapInput = await driver . wait < WebElement > ( async ( ) => {
556+ const [ el ] = await driver . findElements (
557+ By . css ( '.form-control.inputHeapSize' )
558+ ) ;
559+ return el ;
560+ } ) ;
561+
562+ console . log ( '[handleCreateQueryForm] Form loaded. Setting heap size...' ) ;
563+ await heapInput . clear ( ) ;
564+ await heapInput . sendKeys ( '0.5' ) ;
565+
566+ const langSelect = await driver . findElement (
567+ By . css ( '.custom-select.inputLanguage' )
568+ ) ;
569+ for ( const option of await langSelect . findElements ( By . css ( 'option' ) ) ) {
570+ if ( ( await option . getText ( ) ) === 'Python' ) {
571+ await option . click ( ) ;
572+ break ;
573+ }
574+ }
575+
576+ console . log ( '[handleCreateQueryForm] Language set. Clicking Connect...' ) ;
577+ const connectBtn = await driver . findElement (
578+ By . css ( 'button[type="submit"].btn-primary' )
579+ ) ;
580+ await connectBtn . click ( ) ;
581+
582+ console . log (
583+ '[handleCreateQueryForm] Connect clicked. Waiting for panel to finish...'
584+ ) ;
585+
586+ console . log ( '[handleCreateQueryForm] Returning to default content.' ) ;
587+ await driver . switchTo ( ) . defaultContent ( ) ;
588+
589+ // Wait for the outer sidebar iframe to become hidden. VS Code keeps the iframe
590+ // in the DOM when a WebviewView panel closes but hides it via CSS — so check
591+ // isDisplayed() rather than DOM presence. The panel hides once the worker is
592+ // fully created, which is the tightest signal before the notification fires.
593+ console . log ( '[handleCreateQueryForm] Waiting for panel to close...' ) ;
594+ try {
595+ await driver . wait ( async ( ) => {
596+ const [ frame ] = await driver . findElements (
597+ By . css ( 'iframe[src*="purpose=webviewView"]' )
598+ ) ;
599+
600+ try {
601+ return ! ( await frame . isDisplayed ( ) ) ;
602+ } catch {
603+ return true ; // stale
604+ }
605+ } ) ;
606+ } catch ( err ) {
607+ throw new Error (
608+ `[handleCreateQueryForm] TIMEOUT waiting for panel to close: ${ err } `
609+ ) ;
610+ }
611+
612+ console . log ( '[handleCreateQueryForm] Panel closed.' ) ;
613+ }
614+
508615/**
509616 * Wait for Deephaven server connection to complete, handling username/password
510617 * input if prompted. Also handles authentication method selection if the server
511618 * supports both SAML and Basic authentication. Closes the "Created Deephaven
512619 * session" notification when connection is established.
620+ * @param isCreateQueryIframeSupported Whether the server requires the "Create
621+ * Connection" webview form
513622 */
514- export async function waitForServerConnection ( ) : Promise < void > {
623+ export async function waitForServerConnection (
624+ isCreateQueryIframeSupported : boolean
625+ ) : Promise < void > {
515626 let firstInputBox : InputBox | null = null ;
516627 try {
517628 firstInputBox = await InputBox . create ( ) ;
@@ -561,6 +672,11 @@ export async function waitForServerConnection(): Promise<void> {
561672 await passwordInputBox . confirm ( ) ;
562673 }
563674
675+ // Handle "Create Connection" form for servers with the createQueryIframe feature
676+ if ( isCreateQueryIframeSupported ) {
677+ await handleCreateQueryForm ( ) ;
678+ }
679+
564680 // Wait for connection to be active
565681 const notification = await VSBrowser . instance . driver . wait < Notification > (
566682 getServerConnectedNotification
0 commit comments