Aller au contenu principal

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:

SubsystemSourceResponsibility
Semantic tokenizerJsTokenColorizer.csReal-time syntax highlighting via Esprima tokenization
Completion contextsJsCompletion/*.csAPI-aware completion lists (host objects, locals, step parameters)
Error markersJavaScriptEditor.MarkError / TextMarkerServiceSquiggly-underline diagnostics with hover tooltips
Debug sessionJsDebugSession.cs + JsCdpClient.csCDP (Chrome DevTools Protocol) debugging of the V8 engine
Locals panelDebugLocalsPanel.axamlScope 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), typing x. 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​

TriggerBehavior
Typing an identifier prefixOpens/updates the completion window; matches are filtered by prefix (case-insensitive)
Typing . after an objectLists the object's members (GetMembersOf)
Typing ( after a functionOpens 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:

  1. Editor markers — MarkError(line, column, message) places a red squiggly underline. The marker carries the message as its tooltip.
  2. 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.
  3. Errors tab integration — JavaScriptEditor.DiagnosticsUpdated raises on every diagnostics change; JavaScriptPanel forwards them to JavaScriptTestStep.UpdateCompileDiagnostics(), which populates the shared bottom Errors tab in AutomationItemPresenter. Selecting a row calls GoToLine(line) — the caret moves to the line and focus returns to the editor via Dispatcher.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 — DebugLocalsPanel shows a variable tree for the paused scope, plus a status row ("Running…", "Paused at line N", "Session ended.").
  • Console output — console.log/info/warn/error and aethrix.info/warn messages 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:

  1. Add the type to JsApiModel.ReflectedTypes() (or a curated entry for non-reflected types like JavaScriptTestStep).
  2. If a member's runtime return type is broader than its C# declaration, add an entry to OverrideReturnType.
  3. JsApiModel is built once from the actual host types — completion updates automatically.

See Also​