Skip to content

Commit 6855708

Browse files
committed
feat: add debugging functionality
This change made to be able to debug the testing process from the vscode debugging UI.
1 parent cde7850 commit 6855708

29 files changed

Lines changed: 895 additions & 112 deletions

CODE_OF_CONDUCT.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Code of Conduct
2+
3+
## Our Pledge
4+
5+
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6+
7+
## Our Standards
8+
9+
Examples of behavior that contributes to creating a positive environment include:
10+
11+
- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
12+
- Exercise consideration and respect in your speech and actions.
13+
- Attempt collaboration before conflict.
14+
- Refrain from demeaning, discriminatory, or harassing behavior and speech.
15+
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
16+
- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
17+
18+
Examples of unacceptable behavior by participants include:
19+
20+
- Violence, threats of violence or violent language directed against another person.
21+
- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
22+
- Posting or displaying sexually explicit or violent material.
23+
- Posting or threatening to post other people’s personally identifying information ("doxing").
24+
- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
25+
- Inappropriate photography or recording.
26+
- Inappropriate physical contact. You should have someone’s consent before touching them.
27+
- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
28+
- Deliberate intimidation, stalking or following (online or in person).
29+
- Advocating for, or encouraging, any of the above behavior.
30+
- Sustained disruption of community events, including talks and presentations.
31+
32+
## Our Responsibilities
33+
34+
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
35+
36+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
37+
38+
## Scope
39+
40+
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
41+
42+
## Enforcement
43+
44+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
45+
46+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
47+
48+
## Reporting
49+
50+
### Project Spaces
51+
52+
For reporting issues in spaces related to WebdriverIO please contact any of the members of the [Technical Steering Committee (TSC)](/AUTHORS.md). WebdriverIO handles CoC issues related to the spaces that it maintains. Projects maintainers commit to:
53+
54+
- maintain confidentiality with regard to the reporter of an incident
55+
- to participate in the path for escalation as outlined in the section on Escalation when required.
56+
57+
### Foundation Spaces
58+
59+
For reporting issues in spaces managed by the OpenJS Foundation, for example, repositories within the OpenJS organization, use the email `[email protected]`. The Cross Project Council (CPC) is responsible for managing these reports and commits to:
60+
61+
- maintain confidentiality with regard to the reporter of an incident
62+
- to participate in the path for escalation as outlined in
63+
the section on Escalation when required.
64+
65+
## Escalation
66+
67+
The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a foundation-wide team established to manage escalation when a reporter believes that a report to a member project or the CPC has not been properly handled. In order to escalate to the CoCP send an email to `[email protected]`.
68+
69+
For more information, refer to the full [Code of Conduct governance document](https://github.com/openjs-foundation/cross-project-council/blob/master/CODE_OF_CONDUCT.md).
70+
71+
## Attribution
72+
73+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org/), version 1.4, available at [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4/) and also inspired among other things by:
74+
75+
- [Citizen Code of Conduct](http://citizencodeofconduct.org/)
76+
- [Appium](https://github.com/appium/appium/blob/master/CONDUCT.md)

CONTRIBUTION.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Development
2+
3+
How to develop and build this extension:
4+
5+
- Fork the project.
6+
- Clone the project somewhere on your computer
7+
8+
```bash
9+
$ git clone [email protected]:<your-username>/vscode-webdriverio.git
10+
```
11+
12+
- Set up the project via:
13+
14+
```bash
15+
# if you have not installed the PNPM, Please install globally
16+
$ npm install -g pnpm
17+
```
18+
19+
```bash
20+
$ pnpm install
21+
```
22+
23+
- Implement the code which you want to achieve.
24+
- Run the unit test via:
25+
26+
```bash
27+
$ pnpm test:unit
28+
```
29+
30+
We are using 2 testing tools `vitest` and `vscode-test-cli`. Please see the [README.md](./tests/README.md) of the unit test.
31+
32+
- And run the extension via press F5
33+
34+
- Get the coverage report via:
35+
36+
```bash
37+
$ pnpm coverage
38+
```
39+
40+
and see the following files.
41+
42+
- `coverage/merge/coverage-merge.json` (JSON format)
43+
- `coverage/report/index.html` (HTML format)

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 mato533
3+
Copyright (c) 2025 WebdriverIO Team
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ You can read the official guides about how to run the tests in the VSCode Docume
2929
The toolbar at the top provides various commands to manage test execution:
3030

3131
- **Refresh Tests**: To reload your test suite, reflecting any new changes.
32-
- **Run All Tests**: To start testing all cases that are currently visible.
32+
- **Run Tests**: To start testing all cases that are currently visible.
33+
- **Debug Tests**: To begin a debugging session for the tests.
3334
- **Show Output**: To display detailed logs from test executions.
3435
- **Miscellaneous Settings**: To customize the Testing view, such as sorting and grouping tests.
3536

@@ -52,6 +53,7 @@ You can do the following actions:
5253
- **Run a Single Test:** Click the test icon next to a test case to run that specific test.
5354
- **More Options:** Right-click the test icon to open a context menu with additional options:
5455
- `Run Test`: Execute the selected test case.
56+
- `Debug Test`: Execute the selected test case with a debugging session.
5557
- `Reveal in Test Explorer`: Locate and highlight the test in the centralized Testing view.
5658

5759
##  Extension Settings

assets/testing-file.png

7.21 KB
Loading

assets/testing-view.png

739 Bytes
Loading

src/api/debug.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { resolve } from 'node:path'
2+
3+
import * as vscode from 'vscode'
4+
5+
import { TestRunner } from './run.js'
6+
import { WdioExtensionWorker } from './worker.js'
7+
import { log } from '../utils/logger.js'
8+
9+
let debuggerId = 0
10+
const DEBUGGER_NAME = 'wdio-debugger'
11+
const WORKER_PATH = resolve(__dirname, 'worker/index.cjs')
12+
13+
export class DebugSessionTerminatedError extends Error {
14+
constructor(message = 'Debug session was terminated during test execution') {
15+
super(message)
16+
this.name = 'DebugSessionTerminatedError'
17+
}
18+
}
19+
20+
export class DebugRunner extends TestRunner {
21+
private _runController: AbortController | null = null
22+
23+
constructor(
24+
workspaceFolder: vscode.WorkspaceFolder | undefined,
25+
token: vscode.CancellationToken,
26+
workerCwd: string,
27+
worker = new WdioExtensionDebugWorker(`#DEBUGGER${debuggerId++}`, workerCwd, workspaceFolder, token)
28+
) {
29+
super(worker)
30+
31+
worker.setDebugTerminationCallback(() => {
32+
if (this._runController) {
33+
this._runController.abort(new DebugSessionTerminatedError())
34+
}
35+
})
36+
}
37+
38+
public override async run(test: vscode.TestItem) {
39+
this._runController = new AbortController()
40+
41+
try {
42+
await this.worker.start()
43+
await this.worker.waitForStart()
44+
45+
const runPromise = Promise.race([
46+
super.run(test),
47+
new Promise<never>((_, reject) => {
48+
this._runController!.signal.addEventListener('abort', () => {
49+
const reason = this._runController!.signal.reason || new DebugSessionTerminatedError()
50+
reject(reason)
51+
})
52+
}),
53+
])
54+
55+
return await runPromise
56+
} catch (error) {
57+
if (error instanceof DebugSessionTerminatedError) {
58+
log.error('[DEBUG] Test execution interrupted: debug session terminated')
59+
}
60+
throw error
61+
} finally {
62+
this._runController = null
63+
}
64+
}
65+
66+
public async dispose(): Promise<void> {
67+
await this.worker.stop()
68+
}
69+
}
70+
71+
export class WdioExtensionDebugWorker extends WdioExtensionWorker {
72+
private _deferredPromise = promiseWithResolvers<void>()
73+
private _session: vscode.DebugSession | undefined = undefined
74+
private _debugTerminationCallback: (() => void) | null = null
75+
76+
constructor(
77+
cid: string = '#0',
78+
cwd: string,
79+
private _workspaceFolder: vscode.WorkspaceFolder | undefined,
80+
private _token: vscode.CancellationToken
81+
) {
82+
super(cid, cwd)
83+
}
84+
85+
/**
86+
* Set a callback to be called when the debug session terminates
87+
*/
88+
public setDebugTerminationCallback(callback: () => void): void {
89+
this._debugTerminationCallback = callback
90+
}
91+
92+
/**
93+
* Start the worker process
94+
*/
95+
public override async start(): Promise<void> {
96+
try {
97+
const wsUrl = await this.getServer()
98+
99+
vscode.debug
100+
.startDebugging(this._workspaceFolder, {
101+
__name: DEBUGGER_NAME,
102+
name: 'Debug Tests',
103+
type: 'node',
104+
request: 'launch',
105+
cwd: this.cwd,
106+
program: WORKER_PATH,
107+
autoAttachChildProcesses: true,
108+
env: {
109+
...process.env,
110+
WDIO_EXTENSION_WORKER_CID: this.cid,
111+
WDIO_EXTENSION_WORKER_WS_URL: wsUrl,
112+
FORCE_COLOR: '1',
113+
ELECTRON_RUN_AS_NODE: undefined,
114+
},
115+
})
116+
.then(
117+
(resolved) => {
118+
if (resolved) {
119+
log.info('[DEBUG] Debugging started.')
120+
} else {
121+
this._deferredPromise.reject(
122+
new Error('Failed to start debugging. See output for more information.')
123+
)
124+
log.error('[DEBUG] Debugging failed')
125+
}
126+
},
127+
(error) => {
128+
this._deferredPromise.reject(new Error('Failed to start debugging', { cause: error }))
129+
log.error('[DEBUG] Start debugging failed')
130+
log.error(error.toString())
131+
}
132+
)
133+
134+
const onDidStart = vscode.debug.onDidStartDebugSession(async (session) => {
135+
if (session.configuration.__name !== DEBUGGER_NAME) {
136+
return
137+
}
138+
if (this._token.isCancellationRequested) {
139+
log.info('[DEBUG] Debugging cancel requested.')
140+
vscode.debug.stopDebugging(session)
141+
return
142+
}
143+
this._session = session
144+
this._token.onCancellationRequested(async () => {
145+
log.info('[DEBUG] Debugging cancel requested.')
146+
await this.stop()
147+
await vscode.debug.stopDebugging(session)
148+
})
149+
150+
log.debug('[DEBUG] Debugging started completely.')
151+
this._deferredPromise.resolve()
152+
})
153+
154+
const onDidTerminate = vscode.debug.onDidTerminateDebugSession((session) => {
155+
if (session.configuration.__name !== DEBUGGER_NAME) {
156+
return
157+
}
158+
159+
if (this._debugTerminationCallback) {
160+
log.debug('[DEBUG] Debug session terminated, calling termination callback.')
161+
this._debugTerminationCallback()
162+
}
163+
164+
this.disposables.reverse().forEach((d) => d.dispose())
165+
})
166+
167+
this.disposables.push(onDidStart, onDidTerminate)
168+
} catch (error) {
169+
log.debug(`Failed to start worker: ${error instanceof Error ? error.message : String(error)}`)
170+
this.stop()
171+
throw error
172+
}
173+
}
174+
175+
public override async waitForStart(): Promise<void> {
176+
log.debug('[DEBUG] Wait for connecting worker process.')
177+
await super.waitForStart()
178+
await this._deferredPromise.promise
179+
log.debug('[DEBUG] The worker process connected.')
180+
}
181+
182+
public override async stop() {
183+
await super.stop()
184+
if (this._session) {
185+
await vscode.debug.stopDebugging()
186+
this._session = undefined
187+
}
188+
}
189+
}
190+
191+
export function promiseWithResolvers<T>() {
192+
let resolve!: (value: T | PromiseLike<T>) => void
193+
let reject!: (reason?: any) => void
194+
const promise = new Promise<T>((res, rej) => {
195+
resolve = res
196+
reject = rej
197+
})
198+
return { promise, resolve, reject }
199+
}

src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { ServerManager } from './manager.js'
22
export { TestRunner } from './run.js'
3+
export { DebugRunner } from './debug.js'
34
export type * from './types.js'

0 commit comments

Comments
 (0)