@@ -11,9 +11,10 @@ import {
1111 NotificationType ,
1212 ViewItem ,
1313 ViewSection ,
14+ VSBrowser ,
1415 WebDriver ,
1516 WelcomeContentSection ,
16- Workbench ,
17+ Workbench
1718} from 'vscode-extension-tester' ;
1819
1920export async function waitForInputUpdate (
@@ -97,6 +98,110 @@ export async function itemExists(
9798 ) ;
9899}
99100
101+ export async function stabilizeComponentsView ( getSection : ( ) => Promise < ViewSection > , timeout = 20000 ) : Promise < ViewSection > {
102+ const result = await VSBrowser . instance . driver . wait ( async ( ) => {
103+ try {
104+ let section = await getSection ( ) ;
105+
106+ await section . expand ( ) ;
107+ await new Promise ( res => setTimeout ( res , 300 ) ) ;
108+
109+ section = await getSection ( ) ;
110+
111+ // ---- try tree mode ----
112+ try {
113+ const items = await section . getVisibleItems ( ) ;
114+ await Promise . all ( items . map ( i => i . getText ( ) ) ) ;
115+ return section ;
116+ } catch {
117+ // ---- fallback: welcome content mode ----
118+ try {
119+ const welcome = await section . findWelcomeContent ( ) ;
120+ const buttons = await welcome . getButtons ( ) ;
121+ if ( buttons ) {
122+ return section ;
123+ }
124+ } catch {
125+ return false ;
126+ }
127+ }
128+
129+ return false ;
130+
131+ } catch {
132+ return false ;
133+ }
134+ } , timeout , 'Components view did not stabilize' ) ;
135+
136+ return result as ViewSection ;
137+ }
138+
139+ export async function findItemFuzzy ( section : ViewSection , label : string ) : Promise < ViewItem | undefined > {
140+ const items = await section . getVisibleItems ( ) ;
141+ for ( const item of items ) {
142+ try {
143+ const text = await item . getText ( ) ;
144+ if ( text . includes ( label ) ) {
145+ return item ;
146+ }
147+ } catch {
148+ // ignore
149+ }
150+ }
151+
152+ return undefined ;
153+ }
154+
155+ export async function waitForItem ( getSection : ( ) => Promise < ViewSection > , label : string , strict : boolean = true , timeout = 15000 ) {
156+ return VSBrowser . instance . driver . wait ( async ( ) => {
157+ try {
158+ const section = await getSection ( ) ;
159+ const item = strict ? await section . findItem ( label ) : await findItemFuzzy ( section , label ) ;
160+ return item ?? false ;
161+ } catch {
162+ return false ;
163+ }
164+ } , timeout , `Item "${ label } " not found within ${ timeout } ms` ) as Promise < ViewItem > ;
165+ }
166+
167+ export async function waitForItemStable ( getSection : ( ) => Promise < ViewSection > , label : string , strict : boolean = true , timeout = 15000 ) {
168+ await stabilizeComponentsView ( getSection ) ;
169+ return waitForItem ( getSection , label , strict , timeout ) ;
170+ }
171+
172+
173+ export async function waitForItemToDisappear ( getSection : ( ) => Promise < ViewSection > , label : string , timeout = 15000 ) : Promise < boolean > {
174+ return VSBrowser . instance . driver . wait ( async ( ) => {
175+ try {
176+ const section = await getSection ( ) ;
177+ const item = await section . findItem ( label ) ;
178+ return ! item ;
179+ } catch {
180+ return true ;
181+ }
182+ } , timeout , `Item "${ label } " still exists after ${ timeout } ms` ) ;
183+ }
184+
185+ export async function waitForItemToDisappearStable ( getSection : ( ) => Promise < ViewSection > , label : string , timeout = 15000 ) : Promise < boolean > {
186+ await stabilizeComponentsView ( getSection ) ;
187+ return waitForItemToDisappear ( getSection , label , timeout ) ;
188+ }
189+
190+ export async function withStableItem ( getSection : ( ) => Promise < ViewSection > , label : string , action : ( item : ViewItem ) => Promise < void > , timeout = 10_000 ) {
191+ await stabilizeComponentsView ( getSection ) ;
192+ return VSBrowser . instance . driver . wait ( async ( ) => {
193+ try {
194+ const section = await getSection ( ) ;
195+ const item = await section . findItem ( label ) ;
196+ if ( ! item ) return false ;
197+ await action ( item ) ;
198+ return true ;
199+ } catch {
200+ return false ;
201+ }
202+ } , timeout , `Action on "${ label } " could not be performed` ) ;
203+ }
204+
100205export async function itemDoesNotExist (
101206 title : string ,
102207 view : ViewSection ,
@@ -191,3 +296,100 @@ export async function webViewIsOpened(name: string, driver: WebDriver, timeout =
191296 }
192297 } , timeout ) ;
193298}
299+
300+ export async function step < T > ( name : string , fn : ( ) => Promise < T > ) : Promise < T > {
301+ const callSiteStack = new Error ( `Step: ${ name } ` ) . stack ;
302+ try {
303+ return await fn ( ) ;
304+ } catch ( err ) {
305+ const error = err as Error ;
306+ error . message = `Step failed: ${ name } \n${ error . message } ` ;
307+ if ( error . stack && callSiteStack ) {
308+ error . stack =
309+ `${ error . stack } \n\n--- Step call site ---\n${ callSiteStack } ` ;
310+ }
311+ throw error ;
312+ }
313+ }
314+
315+ export async function debugClick ( element , name : string ) {
316+ try {
317+ await element . click ( ) ;
318+ } catch ( e ) {
319+ /* eslint-disable no-console */
320+ error ( `Failed to click: ${ name } ` ) ;
321+ error ( await element . getAttribute ( 'outerHTML' ) ) ;
322+ /* eslint-enable no-console */
323+ throw e ;
324+ }
325+ }
326+
327+ /* eslint-disable no-console */
328+ export async function listItems ( getSection : ( ) => Promise < ViewSection > , title = undefined ) : Promise < void > {
329+ const t = title ? `[${ title } ]: ` : '' ;
330+ log ( `${ t } Current components in view: >>>` ) ;
331+
332+ try {
333+ await VSBrowser . instance . driver . wait ( async ( ) => {
334+ try {
335+ const section = await getSection ( ) ;
336+
337+ // Expand the section
338+ await section . expand ( ) ;
339+
340+ // First try: list actual components
341+ try {
342+ const items = await VSBrowser . instance . driver . wait ( async ( ) => {
343+ try {
344+ return await section . getVisibleItems ( ) ;
345+ } catch {
346+ return null ;
347+ }
348+ } , 10000 ) ;
349+
350+ if ( items && items . length ) {
351+ const labels = [ ] ;
352+ for ( const item of items ) {
353+ try {
354+ labels . push ( await item . getText ( ) ) ;
355+ } catch {
356+ labels . push ( '<error>' ) ;
357+ }
358+ }
359+ log ( `[${ labels . join ( ', ' ) } ]` ) ;
360+ return true ;
361+ }
362+ } catch {
363+ // fallback to welcome content
364+ }
365+
366+ // Second try: welcome content (empty section)
367+ try {
368+ const welcome = await section . findWelcomeContent ( ) ;
369+ const buttons = await welcome . getButtons ( ) ;
370+ if ( buttons && buttons . length ) {
371+ log ( '[<empty: welcome content>]' ) ;
372+ return true ;
373+ }
374+ } catch {
375+ // still nothing visible, retry
376+ }
377+
378+ return false ;
379+
380+ } catch {
381+ return false ;
382+ }
383+ } , 10000 , 'Could not list items' ) ;
384+
385+ } catch ( err ) {
386+ log ( 'Error while getting the current components: ' , err ) ;
387+ } finally {
388+ log ( '<<<' ) ;
389+ }
390+ }
391+
392+ export const log = console . log ;
393+ export const warn = console . warn ;
394+ export const error = console . error ;
395+ /* eslint-enable no-console */
0 commit comments