@@ -32,6 +32,27 @@ interface TestEntry {
3232 fullTitle ?: string
3333}
3434
35+ interface RunCapabilities {
36+ canRunSuites : boolean
37+ canRunTests : boolean
38+ }
39+
40+ type RunnerOptions = {
41+ framework ?: string
42+ configFile ?: string
43+ configFilePath ?: string
44+ runCapabilities ?: Partial < RunCapabilities >
45+ }
46+
47+ const DEFAULT_CAPABILITIES : RunCapabilities = {
48+ canRunSuites : true ,
49+ canRunTests : true
50+ }
51+
52+ const FRAMEWORK_CAPABILITIES : Record < string , RunCapabilities > = {
53+ cucumber : { canRunSuites : true , canRunTests : false }
54+ }
55+
3556@customElement ( EXPLORER )
3657export class DevtoolsSidebarExplorer extends CollapseableEntry {
3758 #testFilter: DevtoolsSidebarFilter | undefined
@@ -92,9 +113,13 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
92113 }
93114
94115 async #handleTestRun( event : Event ) {
95- console . log ( 'handleTestRun' , event )
96116 event . stopPropagation ( )
97117 const detail = ( event as CustomEvent < TestRunDetail > ) . detail
118+ if ( this . #isRunDisabledDetail( detail ) ) {
119+ this . #surfaceCapabilityWarning( detail )
120+ return
121+ }
122+
98123 await this . #postToBackend( '/api/tests/run' , {
99124 ...detail ,
100125 runAll : detail . uid === '*' ,
@@ -157,7 +182,14 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
157182 }
158183
159184 #runAllSuites( ) {
160- console . log ( 'runAllSuites' )
185+ if ( ! this . #getRunCapabilities( ) . canRunSuites ) {
186+ this . #surfaceCapabilityWarning( {
187+ entryType : 'suite' ,
188+ uid : '*'
189+ } as TestRunDetail )
190+ return
191+ }
192+
161193 void this . #postToBackend( '/api/tests/run' , {
162194 uid : '*' ,
163195 entryType : 'suite' ,
@@ -174,18 +206,70 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
174206 }
175207
176208 #getFramework( ) : string | undefined {
177- const options = this . metadata ?. options as { framework ?: string } | undefined
178- return options ?. framework
209+ return this . #getRunnerOptions( ) ?. framework
210+ }
211+
212+ #getRunnerOptions( ) : RunnerOptions | undefined {
213+ return this . metadata ?. options as RunnerOptions | undefined
214+ }
215+
216+ #getRunCapabilities( ) : RunCapabilities {
217+ const options = this . #getRunnerOptions( )
218+ if ( options ?. runCapabilities ) {
219+ return {
220+ ...DEFAULT_CAPABILITIES ,
221+ ...options . runCapabilities
222+ }
223+ }
224+ const framework = options ?. framework ?. toLowerCase ( ) ?? ''
225+ return FRAMEWORK_CAPABILITIES [ framework ] || DEFAULT_CAPABILITIES
226+ }
227+
228+ #isRunDisabled( entry : TestEntry ) {
229+ const caps = this . #getRunCapabilities( )
230+ if ( entry . type === 'test' && ! caps . canRunTests ) {
231+ return true
232+ }
233+ if ( entry . type === 'suite' && ! caps . canRunSuites ) {
234+ return true
235+ }
236+ return false
237+ }
238+
239+ #isRunDisabledDetail( detail : TestRunDetail ) {
240+ const caps = this . #getRunCapabilities( )
241+ if ( detail . entryType === 'test' && ! caps . canRunTests ) {
242+ return true
243+ }
244+ if ( detail . entryType === 'suite' && ! caps . canRunSuites ) {
245+ return true
246+ }
247+ return false
248+ }
249+
250+ #surfaceCapabilityWarning( detail : TestRunDetail ) {
251+ const message =
252+ detail . entryType === 'test'
253+ ? 'Single-test execution is not supported by this framework.'
254+ : 'Suite execution is disabled by this framework.'
255+ window . dispatchEvent (
256+ new CustomEvent ( 'app-logs' , {
257+ detail : message
258+ } )
259+ )
260+ }
261+
262+ #getRunDisabledReason( entry : TestEntry ) {
263+ if ( ! this . #isRunDisabled( entry ) ) {
264+ return undefined
265+ }
266+ return entry . type === 'test'
267+ ? 'Single-test execution is not supported by this framework.'
268+ : 'Suite execution is not supported by this framework.'
179269 }
180270
181271 #getConfigPath( ) : string | undefined {
182- const options = this . metadata ?. options as
183- | {
184- configFile ?: string
185- configFilePath ?: string
186- }
187- | undefined
188- console . log ( 'getConfigPath' , options ?. configFilePath , options ?. configFile )
272+ const options = this . #getRunnerOptions( )
189273 return options ?. configFilePath || options ?. configFile
190274 }
191275
@@ -199,6 +283,8 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
199283 spec-file ="${ entry . specFile || '' } "
200284 full-title ="${ entry . fullTitle || '' } "
201285 label-text ="${ entry . label } "
286+ .runDisabled =${ this . #isRunDisabled( entry ) }
287+ .runDisabledReason =${ this . #getRunDisabledReason( entry ) }
202288 >
203289 < label slot ="label "> ${ entry . label } </ label >
204290 ${ entry . children && entry . children . length
@@ -281,12 +367,10 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
281367 return
282368 }
283369
284- // ✅ Only root suites (no parent = true top-level suite)
285370 const rootSuites = this . suites
286371 . flatMap ( ( s ) => Object . values ( s ) )
287372 . filter ( ( suite ) => ! suite . parent )
288373
289- // Deduplicate by uid (in case some frameworks still push duplicates)
290374 const uniqueSuites = Array . from (
291375 new Map ( rootSuites . map ( ( suite ) => [ suite . uid , suite ] ) ) . values ( )
292376 )
0 commit comments