JavaScript Editor (Test Step)
The JavaScript test step (see JavaScript Step →) hosts a dedicated code editor built on AvaloniaEdit. This page documents the editor's capabilities and how they are implemented, for developers extending or embedding the editor.
Editor Architecture
The editor is implemented by JavaScriptEditor (a subclass of DefaultTextEditor) and hosted inside JavaScriptPanel. Key subsystems:
| Subsystem | Source | Responsibility |
|---|---|---|
| Semantic tokenizer | JsTokenColorizer.cs | Real-time syntax highlighting via Esprima tokenization |
| Completion contexts | JsCompletion/*.cs | API-aware completion lists (host objects, locals, step parameters) |
| Error markers | JavaScriptEditor.MarkError / TextMarkerService | Squiggly-underline diagnostics with hover tooltips |
| Debug session | JsDebugSession.cs + JsCdpClient.cs | CDP (Chrome DevTools Protocol) debugging of the V8 engine |
| Locals panel | DebugLocalsPanel.axaml | Scope variable tree during a debug session |
Syntax Highlighting
JsTokenColorizer performs semantic tokenization using the Esprima parser:
- Token colors are resolved through a lazy palette initialization for thread safety — the palette is built once on first use, not in the static constructor.
- Incomplete code (the usual state while typing) is handled by auto-closing brackets so the parser can tokenize partial expressions.
- Tokenization runs on the thread pool with a 60 ms debounce — rapid keystrokes never block the UI thread; parse+redraw storms are prevented.
Code Completion
Completion is context-driven through the IJsCompletionContext interface:
DefaultJsCompletionContext— the standalone baseline: JS keywords, built-in prototypes (JsBuiltInPrototypes.cs), and the host sandbox objects (aethrix,pm,insomnia,console,project,testParameters,host).StepJsCompletionContext— used when a JavaScript test step is loaded; extends the default context with:- Step parameters — the step's local variables, shown with their current values.
- Script-local symbols — variables/functions declared in the document before the caret, found by
ScriptLocalScanner(a lightweight declaration scanner, not a full semantic model). - Typed member completion — for local variables whose type can be inferred (
var x = aethrix.response), typingx.completes the members of the inferred host type.
The API model (JsApiModel.cs) is generated by reflection from the actual runtime host types (AethrixJsHost, PmSandbox, InsomniaSandbox, …), so completion cannot drift from runtime reality. Return-type overrides in JsApiModel fix cases where the C# declaration is broader than the runtime value (e.g. PmSandbox.expect → PmExpect, AethrixJsHost.assertFor → AethrixJsAssert).
Completion and Insight Triggers
| Trigger | Behavior |
|---|---|
| Typing an identifier prefix | Opens/updates the completion window; matches are filtered by prefix (case-insensitive) |
Typing . after an object | Lists the object's members (GetMembersOf) |
Typing ( after a function | Opens a parameter insight popup showing the full signature; commits the selected completion |
Ctrl+Space (or equivalent) | Manual completion trigger at the caret |
Error Marking and Diagnostics
Errors are surfaced in three coordinated ways:
- Editor markers —
MarkError(line, column, message)places a red squiggly underline. The marker carries the message as its tooltip. - Hover tooltip — hovering a marked error shows the message in a themed popup (auto-hides after ~2 s for errors, ~3 s for info). A 200 ms delayed close on mouse-leave prevents flicker, and a dismissed-offset guard prevents re-show loops when the pointer is stationary.
- Errors tab integration —
JavaScriptEditor.DiagnosticsUpdatedraises on every diagnostics change;JavaScriptPanelforwards them toJavaScriptTestStep.UpdateCompileDiagnostics(), which populates the shared bottom Errors tab inAutomationItemPresenter. Selecting a row callsGoToLine(line)— the caret moves to the line and focus returns to the editor viaDispatcher.Post(deferred so the DataGrid doesn't steal focus back).
Live diagnostics (from typing) and compile diagnostics (from the Build button) flow through the same pipeline.
Debugging
The editor integrates a full debug session backed by the Chrome DevTools Protocol (JsCdpClient + JsDebugSession), driving the same V8 engine used at runtime:
- Breakpoints — set/remove via the editor's left gutter (
BreakpointMargin) or the Toggle Breakpoint context menu item. Breakpoints are queued while the script context is still being parsed and installed once the script ID is known (InstallBreakpointsByScriptIdAsync). - Session controls — Continue, Step Into, Step Over, Step Out, Stop (toolbar in
JavaScriptPanel, enabled only while a session is active). - Current-line highlight —
ShowCurrentLine(line)/ClearCurrentLine()render the executing line and auto-scroll to it. - Locals panel —
DebugLocalsPanelshows a variable tree for the paused scope, plus a status row ("Running…", "Paused at line N", "Session ended."). - Console output —
console.log/info/warn/errorandaethrix.info/warnmessages are captured via CDP (OutputReceived) and surfaced in the step's diagnostics.
Debug Session Lifecycle
Start Debug → Compile check → Launch V8 engine → Load script source
→ Wait for "scriptParsed" (queued breakpoints installed)
→ Pause on first statement (Debugger.pause after runIfWaitingForDebugger)
→ User drives: Continue / Step Into / Step Over / Step Out
→ Stop or script completion → Session ends, UI resets
The CDP client handles the tricky ordering requirements of the V8 debug protocol: Runtime.runIfWaitingForDebugger must be called before Debugger.pause is effective, and breakpoints installed before the script URL is known must be flushed once Debugger.scriptParsed fires.
Editing Commands
The editor toolbar provides:
- Build — compile the script (populates the Errors tab; does not execute).
- Run — execute the step normally.
- Debug — start the CDP debug session.
- Format Document / Format Selection — re-indent code.
- Comment Selection / Uncomment Selection — toggle
//comments.
Extending the Editor
To add a new host object to completion:
- Add the type to
JsApiModel.ReflectedTypes()(or a curated entry for non-reflected types likeJavaScriptTestStep). - If a member's runtime return type is broader than its C# declaration, add an entry to
OverrideReturnType. JsApiModelis built once from the actual host types — completion updates automatically.