Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/app/src/components/sidebar/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface TestEntry {
uid: string
state?: string
label: string
callSource?: string
children: TestEntry[]
}

Expand Down Expand Up @@ -66,7 +67,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {

#renderEntry (entry: TestEntry): TemplateResult {
return html`
<wdio-test-entry state="${entry.state as any}">
<wdio-test-entry state="${entry.state as any}" call-source="${entry.callSource || ''}">
<label slot="label">${entry.label}</label>
${entry.children && entry.children.length
? html`
Expand Down Expand Up @@ -116,6 +117,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
: entry.tests.find((t) => t.state === 'failed')
? TestState.FAILED
: TestState.PASSED,
callSource: (entry as any).callSource,
children: Object.values(entries)
.map(this.#getTestEntry.bind(this))
.filter(this.#filterEntry.bind(this))
Expand All @@ -129,6 +131,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
: entry.state === 'failed'
? TestState.FAILED
: TestState.PASSED,
callSource: (entry as any).callSource,
children: []
}
}
Expand Down
12 changes: 11 additions & 1 deletion packages/app/src/components/sidebar/test-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export class ExplorerTestEntry extends CollapseableEntry {
@property({ type: String })
state?: TestState

@property({ type: String, attribute: 'call-source' })
callSource?: string

static styles = [...Element.styles, css`
:host {
display: block;
Expand All @@ -71,6 +74,13 @@ export class ExplorerTestEntry extends CollapseableEntry {
this.requestUpdate()
}

#viewSource() {
if (!this.callSource) return
window.dispatchEvent(new CustomEvent('app-source-highlight', {
detail: this.callSource
}))
}

get hasPassed () {
return this.state === TestState.PASSED
}
Expand Down Expand Up @@ -126,7 +136,7 @@ export class ExplorerTestEntry extends CollapseableEntry {
</button>
`
}
<button class="p-1 rounded hover:bg-toolbarHoverBackground my-1 group">
<button class="p-1 rounded hover:bg-toolbarHoverBackground my-1 group" @click="${() => this.#viewSource()}">
<icon-mdi-eye class="group-hover:text-chartsYellow"></icon-mdi-eye>
</button>
${!hasNoChildren ? html`
Expand Down
23 changes: 14 additions & 9 deletions packages/app/src/components/workbench/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class DevtoolsSource extends Element {

.cm-editor {
width: 100%;
height: 100%;
padding: 10px 0px;
}
.cm-content {
Expand Down Expand Up @@ -63,20 +64,24 @@ export class DevtoolsSource extends Element {
const editorView = new EditorView(opts)
container.replaceWith(editorView.dom)

/**
* highlight line of call source
*/
const lines = [...(this.shadowRoot?.querySelectorAll('.cm-line') || [])]
if (highlightLine && lines.length && highlightLine < lines.length) {
setTimeout(() => {
lines[highlightLine].classList.add('cm-activeLine')
}, 100)
// Use CodeMirror API to select and scroll to the target line (1-based index)
if (highlightLine && highlightLine > 0) {
try {
const lineInfo = editorView.state.doc.line(highlightLine) // 1-based
requestAnimationFrame(() => {
editorView.dispatch({
selection: { anchor: lineInfo.from },
effects: EditorView.scrollIntoView(lineInfo.from, { y: 'center' }) // center the line
})
})
} catch { /* ignore */ }
}
}

#highlightCallSource (ev: CustomEvent<string>) {
const [filePath, line] = ev.detail.split(':')
this.#renderEditor(filePath, parseInt(line, 10) + 1)
this.#renderEditor(filePath, parseInt(line, 10))
this.closest('wdio-devtools-tabs')?.activateTab('Source')
}

render() {
Expand Down
7 changes: 6 additions & 1 deletion packages/service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,26 @@
"test": "eslint ."
},
"dependencies": {
"@babel/types": "^7.28.4",
"@wdio/devtools-backend": "workspace:^",
"@wdio/devtools-script": "workspace:^",
"@wdio/logger": "9.18.0",
"@wdio/reporter": "9.18.0",
"@wdio/types": "9.16.2",
"import-meta-resolve": "^4.1.0",
"stack-trace": "1.0.0-pre2",
"ws": "^8.18.3"
},
"license": "MIT",
"devDependencies": {
"@babel/parser": "^7.28.4",
"@babel/traverse": "^7.28.4",
"@types/babel__core": "^7.20.5",
"@types/babel__traverse": "^7.28.0",
"@types/stack-trace": "^0.0.33",
"@types/ws": "^8.18.1",
"@wdio/globals": "9.17.0",
"@wdio/protocols": "9.16.2",
"stack-trace": "1.0.0-pre2",
"vite-plugin-dts": "^4.5.4"
},
"peerDependencies": {
Expand Down
52 changes: 50 additions & 2 deletions packages/service/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export const DEFAULT_LAUNCH_CAPS: WebdriverIO.Capabilities = {
browserName: 'chrome',
'goog:chromeOptions': {
// production:
// args: ['--window-size=1200,800']
// development:
args: ['--window-size=1600,1200']
// development:
// args: ['--window-size=1600,1200', '--auto-open-devtools-for-tabs']
}
}

Expand All @@ -25,4 +25,52 @@ export const CONTEXT_CHANGE_COMMANDS = [
'url', 'back', 'forward', 'refresh', 'switchFrame', 'newWindow', 'createWindow', 'closeWindow'
]

/**
* Existing pattern (kept for any external consumers)
*/
export const SPEC_FILE_PATTERN = /(test|spec|features)[\\/].*\.(js|ts)$/i

/**
* Parser options
*/
export const PARSE_PLUGINS = [
'typescript',
'jsx',
'decorators-legacy',
'classProperties',
'dynamicImport'
] as const

/**
* Test framework identifiers
*/
export const TEST_FN_NAMES = ['it', 'test', 'specify', 'fit', 'xit'] as const
export const SUITE_FN_NAMES = ['describe', 'context', 'suite'] as const
export const STEP_FN_NAMES = ['Given', 'When', 'Then', 'And', 'But', 'defineStep'] as const

/**
* File/type recognizers
*/
export const STEP_FILE_RE = /\.(?:steps?)\.[cm]?[jt]sx?$/i
export const STEP_DIR_RE = /(?:^|\/)(?:step[-_]?definitions|steps)\/.+\.[cm]?[jt]sx?$/i
export const SPEC_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/i
export const FEATURE_FILE_RE = /\.feature$/i
export const SOURCE_FILE_EXT_RE = /\.(?:[cm]?js|[cm]?ts)x?$/

/**
* Gherkin Feature/Scenario line
*/
export const FEATURE_OR_SCENARIO_LINE_RE = /^\s*(Feature|Scenario(?: Outline)?):\s*(.+)\s*$/i

/**
* Step definition textual scan regexes
*/
export const STEP_DEF_REGEX_LITERAL_RE = /\b(Given|When|Then|And|But)\s*\(\s*(\/(?:\\.|[^/\\])+\/[gimsuy]*)/
export const STEP_DEF_STRING_RE = /\b(Given|When|Then|And|But)\s*\(\s*(['`])([^'`\\]*(?:\\.[^'`\\]*)*)\2/

/**
* Step directories discovery
*/
export const STEPS_DIR_CANDIDATES = ['step-definitions', 'step_definitions', 'steps'] as const
export const STEPS_DIR_ASCENT_MAX = 6
export const STEPS_GLOBAL_SEARCH_MAX_DEPTH = 5
29 changes: 29 additions & 0 deletions packages/service/src/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import WebdriverIOReporter, { type SuiteStats, type TestStats } from '@wdio/reporter'
import { mapTestToSource, setCurrentSpecFile, mapSuiteToSource } from './utils.js'

export class TestReporter extends WebdriverIOReporter {
#report: (data: any) => void
#currentSpecFile?: string
#suitePath: string[] = []

constructor (options: any, report: (data: any) => void) {
super(options)
Expand All @@ -10,10 +13,27 @@ export class TestReporter extends WebdriverIOReporter {

onSuiteStart(suiteStats: SuiteStats): void {
super.onSuiteStart(suiteStats)
this.#currentSpecFile = suiteStats.file
setCurrentSpecFile(suiteStats.file)

// Push title if non-empty
if (suiteStats.title) this.#suitePath.push(suiteStats.title)

// Enrich and set callSource for suites
mapSuiteToSource(suiteStats as any, this.#currentSpecFile, this.#suitePath)
if ((suiteStats as any).file && (suiteStats as any).line != null) {
;(suiteStats as any).callSource = `${(suiteStats as any).file}:${(suiteStats as any).line}`
}

this.#sendUpstream()
}

onTestStart(testStats: TestStats): void {
// Enrich testStats with callSource info
mapTestToSource(testStats, this.#currentSpecFile)
if ((testStats as any).file && (testStats as any).line != null) {
;(testStats as any).callSource = `${(testStats as any).file}:${(testStats as any).line}`
}
super.onTestStart(testStats)
this.#sendUpstream()
}
Expand All @@ -25,6 +45,15 @@ export class TestReporter extends WebdriverIOReporter {

onSuiteEnd(suiteStats: SuiteStats): void {
super.onSuiteEnd(suiteStats)
// Pop the suite we pushed on start
if (suiteStats.title && this.#suitePath[this.#suitePath.length - 1] === suiteStats.title) {
this.#suitePath.pop()
}
// Only clear when the last suite ends
if (this.#suitePath.length === 0) {
this.#currentSpecFile = undefined
setCurrentSpecFile(undefined)
}
this.#sendUpstream()
}

Expand Down
12 changes: 12 additions & 0 deletions packages/service/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ declare module WebdriverIO {
interface ServiceOption extends ServiceOptions {}
interface Capabilities {}
}

declare module '@wdio/reporter' {
interface TestStats {
file?: string
line?: number
column?: number
}

interface SuiteStats {
line?: string
}
}
Loading
Loading