Skip to content

Commit e11dddf

Browse files
committed
Seperate test suite rerun fix
1 parent a9fe90b commit e11dddf

File tree

5 files changed

+269
-25
lines changed

5 files changed

+269
-25
lines changed

packages/app/src/components/sidebar/explorer.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ interface TestEntry {
3030
type: 'suite' | 'test'
3131
specFile?: string
3232
fullTitle?: string
33+
featureFile?: string
34+
featureLine?: number
35+
suiteType?: string
3336
}
3437

3538
interface RunCapabilities {
@@ -123,13 +126,15 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
123126
// Clear execution data before triggering rerun
124127
this.dispatchEvent(new CustomEvent('clear-execution-data', { bubbles: true, composed: true }))
125128

126-
await this.#postToBackend('/api/tests/run', {
129+
const payload = {
127130
...detail,
128131
runAll: detail.uid === '*',
129132
framework: this.#getFramework(),
130133
specFile: detail.specFile || this.#deriveSpecFile(detail),
131134
configFile: this.#getConfigPath()
132-
})
135+
}
136+
console.log('[Explorer] Sending test run request:', payload)
137+
await this.#postToBackend('/api/tests/run', payload)
133138
}
134139

135140
async #handleTestStop(event: Event) {
@@ -289,6 +294,9 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
289294
spec-file="${entry.specFile || ''}"
290295
full-title="${entry.fullTitle || ''}"
291296
label-text="${entry.label}"
297+
feature-file="${entry.featureFile || ''}"
298+
feature-line="${entry.featureLine ?? ''}"
299+
suite-type="${entry.suiteType || ''}"
292300
.runDisabled=${this.#isRunDisabled(entry)}
293301
.runDisabledReason=${this.#getRunDisabledReason(entry)}
294302
>
@@ -345,6 +353,9 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
345353
callSource: (entry as any).callSource,
346354
specFile: (entry as any).file,
347355
fullTitle: entry.title,
356+
featureFile: (entry as any).featureFile,
357+
featureLine: (entry as any).featureLine,
358+
suiteType: (entry as any).type,
348359
children: Object.values(entries)
349360
.map(this.#getTestEntry.bind(this))
350361
.filter(this.#filterEntry.bind(this))
@@ -362,6 +373,8 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
362373
callSource: (entry as any).callSource,
363374
specFile: (entry as any).file,
364375
fullTitle: (entry as any).fullTitle || entry.title,
376+
featureFile: (entry as any).featureFile,
377+
featureLine: (entry as any).featureLine,
365378
children: []
366379
}
367380
}

packages/app/src/components/sidebar/test-suite.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export interface TestRunDetail {
2626
label?: string
2727
callSource?: string
2828
configFile?: string
29+
featureFile?: string
30+
featureLine?: number
31+
suiteType?: string
2932
}
3033

3134
@customElement(TEST_SUITE)
@@ -86,6 +89,15 @@ export class ExplorerTestEntry extends CollapseableEntry {
8689
@property({ type: String, attribute: 'run-disabled-reason' })
8790
runDisabledReason?: string
8891

92+
@property({ type: String, attribute: 'feature-file' })
93+
featureFile?: string
94+
95+
@property({ type: Number, attribute: 'feature-line' })
96+
featureLine?: number
97+
98+
@property({ type: String, attribute: 'suite-type' })
99+
suiteType?: string
100+
89101
static styles = [
90102
...Element.styles,
91103
css`
@@ -136,7 +148,10 @@ export class ExplorerTestEntry extends CollapseableEntry {
136148
specFile: this.specFile,
137149
fullTitle: this.fullTitle,
138150
label: this.labelText,
139-
callSource: this.callSource
151+
callSource: this.callSource,
152+
featureFile: this.featureFile,
153+
featureLine: this.featureLine,
154+
suiteType: this.suiteType
140155
}
141156
this.dispatchEvent(
142157
new CustomEvent<TestRunDetail>('app-test-run', {

packages/app/src/controller/DataManager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,31 +264,39 @@ export class DataManagerController implements ReactiveController {
264264

265265
const suiteMap = new Map<string, SuiteStatsFragment>()
266266

267+
console.log('[DataManager] Suites update - existing suites:', this.suitesContextProvider.value?.length || 0)
268+
267269
// Populate with existing suites (keeps test list visible)
268270
;(this.suitesContextProvider.value || []).forEach((chunk) => {
269271
Object.entries(chunk as Record<string, SuiteStatsFragment>).forEach(
270272
([uid, suite]) => {
271273
if (suite?.uid) {
272274
suiteMap.set(uid, suite)
275+
console.log('[DataManager] Added existing suite to map:', uid, suite.title)
273276
}
274277
}
275278
)
276279
})
277280

281+
console.log('[DataManager] Incoming payloads:', payloads.length)
282+
278283
// Process incoming payloads
279284
payloads.forEach((chunk) => {
280285
if (!chunk) return
281286

282287
Object.entries(chunk).forEach(([uid, suite]) => {
283288
if (!suite?.uid) return
284289

290+
console.log('[DataManager] Processing incoming suite:', uid, suite.title)
285291
const existing = suiteMap.get(uid)
286292

287293
// Always merge to preserve all tests in the suite
288294
suiteMap.set(uid, existing ? this.#mergeSuite(existing, suite) : suite)
289295
})
290296
})
291297

298+
console.log('[DataManager] Final suite map size:', suiteMap.size)
299+
292300
this.suitesContextProvider.setValue(
293301
Array.from(suiteMap.entries()).map(([uid, suite]) => ({ [uid]: suite }))
294302
)

packages/backend/src/runner.ts

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ const WDIO_CONFIG_FILENAMES = [
1414
'wdio.conf.mjs'
1515
]
1616

17+
/**
18+
* Escape special regex characters in a string
19+
*/
20+
function escapeRegex(str: string): string {
21+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
22+
}
23+
1724
export interface RunnerRequestBody {
1825
uid: string
1926
entryType: 'suite' | 'test'
@@ -27,6 +34,9 @@ export interface RunnerRequestBody {
2734
lineNumber?: number
2835
devtoolsHost?: string
2936
devtoolsPort?: number
37+
featureFile?: string
38+
featureLine?: number
39+
suiteType?: string
3040
}
3141

3242
const FRAMEWORK_FILTERS: Record<
@@ -35,23 +45,69 @@ const FRAMEWORK_FILTERS: Record<
3545
> = {
3646
cucumber: ({ specArg, payload }) => {
3747
const filters: string[] = []
48+
console.log('[Runner] Cucumber filter - payload:', {
49+
entryType: payload.entryType,
50+
suiteType: payload.suiteType,
51+
featureFile: payload.featureFile,
52+
featureLine: payload.featureLine,
53+
fullTitle: payload.fullTitle,
54+
specArg
55+
})
56+
57+
// For feature-level suites, run the entire feature file
58+
if (payload.suiteType === 'feature' && specArg) {
59+
// Remove any line number from specArg for feature-level execution
60+
const featureFile = specArg.split(':')[0]
61+
filters.push('--spec', featureFile)
62+
console.log('[Runner] Feature-level execution - running entire file:', featureFile)
63+
return filters
64+
}
65+
66+
// Priority 1: Use feature file with line number for exact scenario targeting (works for examples)
67+
// Note: Cucumber scenarios are type 'suite', not 'test'
68+
if (payload.featureFile && payload.featureLine) {
69+
filters.push('--spec', `${payload.featureFile}:${payload.featureLine}`)
70+
console.log('[Runner] Using feature file:line filter:', filters)
71+
return filters
72+
}
73+
74+
// Priority 2: For specific test reruns with example row number, use exact regex match
75+
if (payload.entryType === 'test' && payload.fullTitle) {
76+
// Cucumber fullTitle format: "1: Scenario name" or "2: Scenario name"
77+
// Extract the row number and scenario name
78+
const rowMatch = payload.fullTitle.match(/^(\d+):\s*(.+)$/)
79+
if (rowMatch) {
80+
const [, rowNumber, scenarioName] = rowMatch
81+
// Use spec file filter
82+
if (specArg) {
83+
filters.push('--spec', specArg)
84+
}
85+
// Use regex to match the exact "rowNumber: scenarioName" pattern
86+
// This ensures we only run that specific example row
87+
filters.push('--cucumberOpts.name', `^${rowNumber}:\\s*${escapeRegex(scenarioName.trim())}$`)
88+
return filters
89+
}
90+
// No row number - use plain name filter
91+
if (specArg) {
92+
filters.push('--spec', specArg)
93+
}
94+
filters.push('--cucumberOpts.name', payload.fullTitle.trim())
95+
return filters
96+
}
97+
98+
// Suite-level rerun
3899
if (specArg) {
39100
filters.push('--spec', specArg)
40101
}
41-
const scenarioName = payload.fullTitle
42-
? payload.fullTitle.replace(/^\s*\d+:\s*/, '').trim()
43-
: undefined
44-
if (payload.entryType === 'test' && scenarioName) {
45-
filters.push('--cucumberOpts.name', scenarioName)
46-
}
47102
return filters
48103
},
49104
mocha: ({ specArg, payload }) => {
50105
const filters: string[] = []
51106
if (specArg) {
52107
filters.push('--spec', specArg)
53108
}
54-
if (payload.entryType === 'test' && payload.fullTitle) {
109+
// For both tests and suites, use grep to filter
110+
if (payload.fullTitle) {
55111
filters.push('--mochaOpts.grep', payload.fullTitle)
56112
}
57113
return filters
@@ -61,7 +117,8 @@ const FRAMEWORK_FILTERS: Record<
61117
if (specArg) {
62118
filters.push('--spec', specArg)
63119
}
64-
if (payload.entryType === 'test' && payload.fullTitle) {
120+
// For both tests and suites, use grep to filter
121+
if (payload.fullTitle) {
65122
filters.push('--jasmineOpts.grep', payload.fullTitle)
66123
}
67124
return filters

0 commit comments

Comments
 (0)