Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>RuvioltaNew to Visual Studio Code? Get it now.
Ruviolta

Ruviolta

Ruviolta

|
7 installs
| (1) | Free
All-in-one Web, API, and Android language support for Ruviolta .ut (User Testing) files, including real and virtual device workflows.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Ruviolta

All-in-one Web, API, and Android language support for Ruviolta .ut (User Testing) files in Visual Studio Code, including workflows for virtual and real Android devices.

Features

  • Syntax highlighting for Web and Android UI commands, API requests, expectations, protocol helpers, flow commands, and runtime response values
  • Full JavaScript expression highlighting after let
  • Embedded JavaScript highlighting inside script { } blocks
  • Ruviolta syntax diagnostics
  • JavaScript syntax and undefined identifier diagnostics
  • Multiline variable declarations
  • Code snippets
  • Structural document formatting with a Ruviolta editor-title Format button
  • Lightweight automatic indentation while entering new DSL lines
  • Hover information for Ruviolta commands
  • Run the current Ruviolta file with its own project configuration
  • Debug UI, API, and mixed flows step by step with editable Retry and Apply to code
  • Highlight, format, diagnose, and document reusable run() flows with explicit inputs and -> output capture
  • Multiline API command diagnostics and formatting
  • API, GraphQL, SOAP, OAuth 2.0, polling, WebSocket, SSE, and Cleanup: hover/snippet support
  • Optional Cases: tables, safe literal datasets, and project-bound JSON/CSV case sources with highlighting, diagnostics, formatting, hover, and snippets
  • Open the latest report for the active project
  • Project-aware Run, Debug, and Stop editor-title controls for Web and Android
  • Android-only native APK selection and installation from the editor title
  • Android diagnostics, completion, formatting, and hover support for native selectors, input, selection, switches, checkboxes, file upload, and gestures
  • Ruviolta Dark and Ruviolta Light color themes

Android: virtual and real devices

Ruviolta is one platform for Web, API, and Android testing. The extension uses the active project's configuration to provide the same Run, Debug, Stop, formatting, diagnostics, and .ut authoring workflow for all three targets.

Provision Android support and create the editable example project from a terminal in the workspace:

npx ruviolta android init

For a Ruviolta-managed virtual Android device:

npx ruviolta mobile start android
npx ruviolta mobile app install android projects/android/demoData/Ruviolta-Demo-1.1.2.apk
npx ruviolta run projects/android "@demoTest"
npx ruviolta mobile stop android

Virtual mode manages the emulator lifecycle through the Ruviolta CLI. Real mode runs the same native .ut tests on a supported connected physical Android device with USB debugging authorized. Select real mode with android.device.type: "real" in projects/android/config/ruviolta.config.mjs; an explicit serial is supported for unattended or multi-device environments. The extension's Select and Install Android APK action is available in the editor title for configured Android projects.

@androidRequest
Scenario: Submit a request from Android
  Variables:
            let attachmentPath = "../demoData/DemoData.pdf"

        waitFor("FORM").click()
        waitFor("First name").replaceInput("Georgi")
        waitFor("Country select").select("Bulgaria")
        upload("Choose a file", attachmentPath)
        waitFor("Accept demo terms checkbox").checkbox(ON)
        waitFor("Enable notifications switch").switch(ON)
        waitFor("Submit request").click()

Multi-project layout

projects/
├── customer-portal/
│   ├── config/
│   │   └── ruviolta.config.mjs
│   ├── tests/
│   │   └── login.ut
│   └── reports/
│
└── admin-portal/
    ├── config/
    │   └── ruviolta.config.mjs
    ├── tests/
    │   └── search.ut
    └── reports/

The extension locates the closest project configuration for the active .ut file. It recomputes the owning project after editor, file, workspace, and configuration changes. An android: {} marker selects Android behavior; otherwise the Web runtime remains active. Files without a resolvable project are not guessed.

File example

File Description: Login-related tests
  Global Variables:
            let username = "test-user"

Scenario: User logs in successfully
  Variables:
            let message = `Logging in as ${username}`

        visit("/login")
        input("#username", username)
        click("#login")

        script {
          await Promise.resolve()
          console.log(message)
        }

Formatting

Use Ruviolta: Format File, the blue Format button in the editor title, or VS Code's standard Format Document action. All three use the same structural formatter:

  • tags, File Description:, and Scenario: use 0 spaces;
  • section headings use 2 spaces;
  • declarations inside Global Variables: and Variables: use 12 spaces;
  • scenario, Verify, Cases, and Cleanup content uses 8 spaces.

The editor adjusts only the active line after Enter or completion of a structural token such as a section heading, let, command call, Cases row, or script {. It does not reformat the document after ordinary character input. Strings, XPath, CSS, JavaScript expressions, inline comments, and command chains are preserved.

JavaScript expressions

let total = price * quantity
let status = total >= 100 ? "large" : "small"

let activeUsers = users
  .filter(user => user.active)
  .map(user => user.name)

let response = await loadData()

Full JavaScript statements such as if, for, while, and try belong inside a script { } block.

External files

let text = readText("data/message.txt")
let data = readJson("data/user.json")
let rows = readCsv("data/users.csv")
let bytes = readBytes("data/file.bin")
let value = env("VARIABLE_NAME")
let loginEmail = data("login", "email")
let helpers = await importJs("helpers/functions.js")

Relative file paths are resolved from the current .ut file. env() reads only the active project's .env; data() reads the requested field from the active project's config/data.mjs and must be the complete value of a let declaration.

Data cases

Variables:, Cases:, and Cleanup: are optional. Cases: runs the complete scenario independently for each row or object, and Cleanup runs once per case.

Scenario: Verify roles
Cases:
| role    | expectedStatus |
| admin   | 200            |
| student | 403            |

api("GET", "/permissions", { query: { role } })
expectStatus(expectedStatus)

The extension also supports Cases: readJson("data/roles.json"), Cases: readCsv("data/roles.csv"), and safe literal object arrays without converting one form into another. The formatter preserves the document line ending and never inserts an absent optional section.

API commands

The extension understands the Ruviolta API DSL in the same .ut files used for browser automation. Multiline request objects are highlighted, formatted, and syntax-checked as JavaScript expressions.

api("POST", "/users", {
  auth: { type: "bearer", token },
  json: { name: userName }
})
expectStatus(201)
expectJson("$.name", userName)
expectExists("$.id")
expectContainsOnly("$.roles", ["admin", "teacher"])
expectSchema("$.user", { id: "integer", name: "string" })

The response runtime values recognized by highlighting and diagnostics are:

response
responseStatus
responseHeaders
responseBody
responseText
responseTime
request

Supported API and protocol commands include:

api()
expectStatus()
expectHeader()
expectCookie()
expectJson()
expectJsonNotEqual()
expectExists()
expectMissing()
expectContains()
expectNotContains()
expectContainsAny()
expectContainsOnly()
expectCount()
expectSchema()
expectEach()
expectResponseTime()
expectText()
graphql()
soap()
poll()
oauth2()
websocket()
wsSend()
wsReceive()
wsClose()
sse()
sseReceive()
sseClose()
output()

wsReceive() and sseReceive() can capture values with ->:

wsReceive("feed", { match: /update/, count: 3 }) -> updates
sseReceive("events", { match: { event: "status" }, count: 2 }) -> statuses

Mixed API + UI

API and UI steps can share variables in one scenario:

Scenario: Create through API and verify in UI
Variables:

api("POST", "/users", { json: { name: "Test User" } })
expectStatus(201)
let userId = responseBody.id

visit("/users/" + userId)
waitForText("h1", "Test User")

Cleanup:
api("DELETE", "/users/{id}", { path: { id: userId } })
expectStatus(204)

Cleanup: is highlighted, formatted, diagnosed, and documented. It belongs to the scenario and is executed by Ruviolta even when a main API or UI step fails.

For authentication bridges, request options can use browserCookies: true to send the active browser session cookies and syncCookiesToBrowser: true to apply response cookies to the browser.

API-only tests can also be debugged. Ruviolta uses its standalone Stepflow Debugger window while no tested page exists; when a mixed flow reaches a browser step, the normal injected debugger panel continues in the tested page. API request/response and protocol details are shown in redacted form.

Browser commands

visit("/")
refresh()
scrollTo("#save")
upload("#avatar", "data/photo.png")

check("#terms")
uncheck("#newsletter")
select("important option text")
select("//select[@name='country']", "Canada")
selectRepeated(
  "//tr[@id='row-{{current}}-0']//select[contains(@class, 'courseSelect')]",
  [1, 2, 3, 8],
  ["86", "26", "27", "125436"]
)
inputRepeated(
  "//tr[@id='row-{{current}}-0']//input[contains(@class, 'weeks1')]",
  [1, 2, 3, 8],
  "18"
)

waitForText("#status", "completed", 10000)
waitUntilMissing(".loading", 10000)
waitForEnabled("#submit", 8000)
waitForDisabled("#locked", 8000)
waitForHidden(".overlay", 8000)
waitForChecked("#terms", 8000)
waitForUnchecked("#newsletter", 8000)

waitForUrl("/dashboard", 15000)
waitForUrlContains("/students/", 15000)
waitForTitle("Student profile", 15000)

hover("#profile-menu")
radio("input[name='role'][value='teacher']")

inputByLabel("Name", firstName)
inputByLabel("Phone", phoneNumber)
clickByLabel("Account:", "//button[normalize-space()='Select']")

pageDown()
pageUP()

clickRepeated("//button[.='Save and continue']", 5)
waitFor("#save").clickRepeated(3)
waitForLink("Add").clickRepeated(5)
waitForButton("Save").clickRepeated(5)
clickLink("read more")
clickButton("save")

waitForLink("Add").click()
waitForLink("//a", "Add", [2], 5000).click()
waitForButton("Save", [2], 3000).click()
waitForButton("button.action", "Save", [4], 3000).click()

click("#dialog-button")
waitForDialog(10000)
dialog(accept)

cookies(accept)
waitToastMessage("success", 10000)
closeToastMessage("success")
closeAllToasts()

For projects marked with android: {}, the shared command names route to the native Android runtime. Common semantic forms are:

waitFor("Дневник")
waitFor("Вход", 2)
waitFor(contains["ученик"])
waitFor(contains["ученик"], 2)
waitFor("А", nextTo["7 Клас"])
waitFor(symbol["U+F100"], nextTo[contains["Асен Иванов"]]).click()
waitFor(contains["ученик"], after["Заглавие"])
click(contains["ученик"])
click(contains["ученик"], 2)
click("А", nextTo["7 Клас"])
click(symbol["U+F100"], nextTo[contains["Асен Иванов"]])
click("Запази", before["Редакция"])
click("Запази", after["Редакция"])
switch("Известия", ON)
switch(contains["извест"], 2, OFF)
switch("Известия", nextTo["Профил"], ON)
checkbox("Съгласен съм", ON)
checkbox("Активен", after["Профил"], OFF)
radio("Жена")
radio(contains["Друг"])
radio("Да", 2)
longPress("Ученик", 2)
longPress("А", nextTo["7 Клас"], 2)
longPress(symbol["U+F100"], 2)
dragAndDrop("Ученик", "Друг клас")
dragAndDrop(contains["Иван"], contains["7 Клас"])
dragAndDrop("А", nextTo["7 Клас"], "Избрани класове")
select("Първи Срок", "Втори Срок")
scrollTo("Иван Петров Иванов")
scrollTo(contains["ученик"])
scrollTo(contains["ученик"], 2)
scrollTo(symbol["U+F100"])
scrollTo("А", nextTo["7 Клас"])
scrollTo(contains["ученик"], after["Заглавие"])
replaceInput("Съобщение", "New text")
replaceInput(contains["съобщ"], nextTo[contains["Ученик"]], "New text")
upload("Choose a file", attachmentPath)
swipe("up")
swipe("left")
back()

Android select() opens native Spinner or accessible custom dropdown controls, refreshes the live hierarchy, and resolves the option using normalized full text or an unambiguous partial match. scrollTo() uses visible scrollable containers and bounded progress detection. swipe() supports up, down, left, and right; back() sends the Android system Back action. Android upload() transfers the local file to the active device and completes the system file-picker flow. Its file path may be a quoted path or a declared Ruviolta variable. Android exact quoted targets use normalized semantic equality. contains[...] adds case-insensitive partial semantic matching with an optional 1-based occurrence. For click(), waitFor(), and scrollTo(), nextTo[...], before[...], and after[...] select a matching target in the anchor's nearest meaningful shared hierarchy scope. Their anchors may use partial matching as nextTo[contains["text"]], before[contains["text"]], or after[contains["text"]]; this is the only supported nested contains[] form. Contextual waits poll until that relationship exists, while contextual scrolling preserves bounded progress detection across refreshed hierarchies. These forms do not replace structured selectors, bounds, or paths. symbol["U+F100"] matches the actual Unicode code point exposed in Android hierarchy text, regardless of how the local font renders the glyph. It accepts the same occurrence and contextual qualifiers as other semantic targets. Android waitFor() supports one chained target action: click, input, replaceInput, clear, select, longPress, switch, checkbox, or radio. The original semantic selector is freshly resolved for that action. Standalone Android replaceInput() accepts exact, partial, occurrence, and contextual targets and uses the existing verified editable-field behavior. Android switch() and checkbox() set and verify ON/OFF state instead of blindly toggling. radio() selects and verifies one option. longPress() uses one continuous native hold for the requested seconds, while Android dragAndDrop() resolves semantic source and destination elements and performs one continuous native drag gesture. These commands reuse the same exact, contains[...], occurrence, contextual, and structured selector model.

The Android inspector shows icon-font text together with a reusable symbol identifier, for example text="" symbol=U+F100. Image and image-like nodes show their available accessibility properties and hierarchy context. This does not introduce image recognition, visual matching, or an image[] selector.

CSS selectors and XPath expressions are supported where a locator is expected. inputByLabel() finds an editable input or textarea by exact visible label text. clickByLabel() uses the label as a readable anchor and scopes the supplied target locator to nearby containers. closeAllToasts() is best effort and never fails when no toast can be closed.

waitForLink() and waitForButton() accept exact whitespace-normalized text or a recognized CSS/XPath locator. The optional [n] occurrence is 1-based ([1] is the first match) and must appear before an optional millisecond timeout. A locator plus exact text is also supported, and both commands provide fresh locator context to chained actions.

All other conditional UI waits also accept an optional final positive integer timeout in milliseconds. Without it they use the project timeout; with it only that wait is overridden. This includes waitFor(), waitForVisible(), text/value/attribute/property/count waits, missing/state waits, URL/title waits, waitForDialog(), and waitToastMessage(). In Android projects, waitFor() preserves its existing second occurrence/context argument and accepts the timeout as a final third argument. Timeout variables and expressions are validated after evaluation. wait(milliseconds) remains an unconditional delay and is unchanged.

clickRepeated(locator, count) performs intentional repeated clicks using the existing between-click page-change waiting behavior. In a command chain, clickRepeated(count) inherits the current locator, while clickRepeated(locator, count) uses the explicit locator instead.

selectRepeated(locatorExpression, positions, values) performs one normal select() operation for every item in positions and remains one Ruviolta step. {{current}} means the current value from the second argument, not the array index or a normal Ruviolta variable. Every occurrence is replaced in the generated CSS/XPath locator. An array third argument uses strict one-to-one positional mapping and must have the same length as positions. A scalar third argument reuses the same selection value for every position.

let rows = [1, 2, 3, 8]
let courses = ["86", "26", "27", "125436"]

selectRepeated(
  "#row-{{current}}-0 select.courseSelect",
  rows,
  courses
)

selectRepeated(
  "#row-{{current}}-0 select.teacherSelect",
  rows,
  "John"
)

["John"] is an array and therefore does not mean repeat mode; use the scalar value "John" to select it for every position. {{current}} is local to the first argument of selectRepeated() and does not reserve or shadow a variable named current.

inputRepeated(locatorExpression, positions, values) uses the same explicit position and {{current}} model while executing the normal input() behavior for each generated locator. The second argument alone determines which positions are changed. A scalar third argument repeats one input value, while an array uses strict one-to-one positional mapping and must have the same length as positions.

inputRepeated(
  "//tr[@id='row-{{current}}-0']//input[contains(@class, 'weeks1')]",
  [1, 2, 3, 4],
  "18"
)

inputRepeated(
  "//tr[@id='row-{{current}}-0']//input[contains(@class, 'weeks1')]",
  [1, 2, 3, 4],
  ["18", "20", "22", "24"]
)

["18"] remains positional array mode and is not repeat mode; use the scalar "18" to enter the same value for every position. {{current}} uses the same dedicated placeholder highlighting as selectRepeated() and remains separate from normal variables and concatenated locator expressions.

Reusable test flows

Variables:
let userName = param("Test User")
let role = param("student")

run("tests/users.ut@createUser")
run("tests/users.ut@createUser") { "Peter" }
run("tests/users.ut@createUser") { "Peter", role: selectedRole } -> created

run() executes a tagged scenario or all scenarios from another .ut file. param(defaultExpression) marks the bindable let slots in source order. Invocation entries may be positional, named, or mixed; a named value wins when it targets a slot already filled positionally. Expressions are evaluated when the run step executes, and missing arguments use the called flow's defaults while explicit falsy and null values remain supplied values.

The same flow mechanism works for UI-only, API-only, and mixed flows. References may target the current project or a sibling project under the same projects/ directory:

run("identity/tests/users.ut@createUser") { userName: "Test User" } -> created

A cross-project flow temporarily uses the called project's UI and API environment settings, including api.baseUrl, and restores the caller context afterward. When a browser already exists it is reused; API-only nested flows stay browser-free. Each concrete scenario and Case still receives a fresh local variable scope, and nested calls receive only values explicitly forwarded by their direct caller.

let in a variable section is initialization; let among scenario or Cleanup steps is chronological executable content and may create or update a scenario-local variable. The formatter keeps section declarations at 12 spaces and runtime lets at 8 spaces.

output(value) returns an explicit result from the called scenario, and -> name captures it in the caller. The extension highlights, validates, formats, and documents runtime lets, param(...), and invocation blocks. The legacy second-argument input object and ruviolta.accept() remain supported for existing tests.

runRepeated("tests/users.ut@createUser")[N] reuses the same run() engine sequentially. It accepts zero datasets, one dataset reused N times, or exactly N positional datasets. A different count is diagnosed before execution.

Advanced parallel definitions under tests/multiTestRunner/ use named Worker: blocks containing only ordered run() or runRepeated() calls. Worker blocks execute concurrently without work stealing or automatic dependency/data management.

runRepeated("tests/classDiary.ut@createClassDiary")[2] {"11", "R"}, {"12", "A"} binds each dataset positionally to the called Scenario's param() declarations. One dataset is reused for every iteration; zero datasets preserve called-flow defaults.

Web Scenarios can declare let multiWindows = N and use openWindow(n) as a synchronous switch between isolated browser sessions. Browser network rules use mockNetwork(urlPattern, options) and are automatically Scenario-scoped; clearNetworkMocks() removes them early.

Project reports

Reports are written to the active project only:

projects/<project-name>/reports/YYYY-MM-DD_HH-mm-ss-SSS/report.html
projects/<project-name>/reports/latest.html

Ruviolta: Open Latest Report prefers the active .ut file's project. When no project is active and several projects exist, the extension asks which project report should open.

After Ruviolta: Run Current File finishes, the notification offers an Open Report button for the same project.

Ruviolta: Debug Current File starts the root project through the interactive Stepflow Debugger. UI steps keep the injected Shadow DOM panel, while API-only execution uses the standalone Stepflow window until a browser is needed. Every supported single-line step has a pencil editor; Retry executes an edited command and a successful retry can be written back with Apply to code. Same-project and cross-project called flows are shown in one compact colored row, API steps show redacted request/response details, and source updates are written to the correct root or called .ut file.

Browser selection

The browser is selected in the active project's file:

projects/<project-name>/config/ruviolta.config.mjs

Supported values are "chrome", "edge", and "firefox".

Ruviolta controls Chrome and Edge through CDP and Firefox through WebDriver BiDi.

Commands

  • Ruviolta: Format File
  • Ruviolta: Run Ruviolta Test
  • Ruviolta: Debug Ruviolta Test
  • Ruviolta: Stop Ruviolta Test
  • Ruviolta: Select and Install Android APK (Android projects only)
  • Ruviolta: Open Latest Report

Keyboard shortcut

Run the current Ruviolta file with:

Ctrl+Alt+R

Debug the current Ruviolta file with:

Ctrl+Alt+D

Terminal examples

npx ruviolta run
npx ruviolta run projects/customer-portal
npx ruviolta run projects/customer-portal/tests/login.ut
npx ruviolta run "@loginTest"
npx ruviolta run projects/customer-portal "@smoke,@critical"
npx ruviolta run projects/customer-portal --env production
npx ruviolta debug projects/customer-portal "@loginTest"
npx ruviolta android init
npx ruviolta mobile start android
npx ruviolta mobile app install android projects/android/demoData/Ruviolta-Demo-1.1.2.apk
npx ruviolta run projects/android "@demoTest"
npx ruviolta mobile stop android

In PowerShell, quote arguments beginning with @.

Extension configuration

ruviolta.nodePath

Path to the Node.js executable used to run Ruviolta tests.

Default: node

ruviolta.runScript

Optional path to the Ruviolta CLI script. Leave this empty to auto-detect the Ruviolta installation in the workspace.

Default: auto-detect

ruviolta.format.enable

Enables formatting for Ruviolta files.

ruviolta.diagnostics.enable

Enables syntax diagnostics for Ruviolta files.

Color themes

  • Ruviolta Dark
  • Ruviolta Light

Package transparency

The VSIX contains the compiled extension runtime, language definitions, snippets, themes, and documentation. The bundled JavaScript dependencies and their license texts are identified in THIRD_PARTY_NOTICES.md. The Ruviolta extension itself remains governed by the extension license included in the VSIX.

Author

Georgi Todorov

License

Ruviolta VS Code Extension License.

Free to use, modify, and redistribute at no charge. Selling, sublicensing for a fee, or commercially redistributing the extension is not permitted without prior written permission.

See the LICENSE file included with the extension for the full license terms.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft