Skip to main content

Recording

Click Record on an empty session (or use Resume to extend an existing one). While recording, Trq captures:

  • Clicks (with button + modifiers), inputs (text, selects, checkboxes), and keystrokes (Enter, Tab, shortcuts).
  • Navigations — full page loads and in-document (SPA) route changes.
  • File uploads — the chosen files are content-addressed and stored with the session, so uploads replay byte-for-byte.
  • Tabs — opening a new tab and switching between tabs (Studio follows along; see below).
  • Assertions you author inline (next).
Passwords

Passwords are recorded as their actual value (stored in the session bundle) so replay needs no prompts. Treat .trq files that contain credentials accordingly.

Author an assertion while recording​

While recording, click the ⌖ Assert button in the top toolbar (between Stop and Capture). It turns blue to show assert mode is on, and the page enters pick mode — hovering outlines each element. Click the element you want to check.

Assert mode active — click the Assert button, then click an element on the page

A small menu opens, titled Assert about <tag>, offering:

  • Is visible — the element is present and visible.
  • Has text — the element's text matches (pre-filled from the element).
  • Has value — an input's/select's value matches (pre-filled, shown only for form fields).

Pick one and click Confirm. The assertion is added as a step — it shows up in the Events panel as an assert row (pink badge) like assert text contains "…" on <selector>.

The assert menu — choose an assertion type, set the match mode, and click Confirm

Assertions inside same-origin iframes are supported — the picker reaches into the frame and the menu renders in the main window.

Match modes — exact, contains, regex​

For Has text and Has value, a Match dropdown controls how the recorded string is compared against the live value at replay time. The default is exact (and any older recording with no mode set stays exact), so behavior is unchanged unless you opt in. You can set the mode while authoring or later in the Edit drawer (handy for relaxing an assertion that failed because the live text had extra characters).

  • exact — the value must equal the expected string character-for-character.

    Has text · match: exact · "Submitted"
    passes → "Submitted"
    fails → "Form Submitted", "submitted"
  • contains — passes if the expected string appears anywhere inside the live value (substring). Use this when the element wraps your text in extra labels/punctuation.

    Has text · match: contains · "Destination Screen"
    passes → "Destination Screen", "Destination Screen : Thank You"
    fails → "Source Screen"
  • regex — the expected string is a JavaScript regular expression (new RegExp(pattern), case-sensitive, no /…/flags wrapper) tested against the live value. Use anchors/character classes for precise structural checks.

    Has text · match: regex · "^Order #\d+$"
    passes → "Order #7742", "Order #1"
    fails → "Order #", "Pending Order #12"

    An invalid pattern fails the step with invalid regex "…". {{var.NAME}} templates are resolved before the match, so contains/regex can reference captured values too.

Failure messages name the mode, e.g. expected text to contain "Destination Screen" but found "Source Screen" (after 5000ms).

Table assertions​

For HTML <table> content, Trq has table-aware assertions that address rows and columns logically — no brittle nth-child cell selectors. While recording, turn on ⌖ Assert and click any cell inside a table: the menu detects the table and offers a Table cell (and Row count) option, pre-filled from the cell you clicked.

The assert menu detects a table and offers a pre-filled table-cell assertion with a row lookup

The star of the show is the row lookup: instead of “row 3, column 6”, you say “the row where Email = ada@example.com, check its Status”. The email identifies the row; the status is checked in that same row.

The three kinds​

  • table · cell — assert one cell's text. The row is addressed by body index or, more robustly, by a key cell (“the row where Name = Alice”); the column by its header or index.

    table · cell · row where "Email" = "ada@example.com" · column "Status" · exact · "Active"
  • table · row count — assert the number of body rows with =, ≥, or ≤. Add a filter to count only matching rows — including = 0 to assert no row matches (a deleted record is gone).

    table · rows = 0 where "Name" = "Bob" # Bob's row is gone
  • table · column — assert a whole column contains a set of values, or equals an ordered list.

Columns addressed by header survive column reordering. Header matching is case-insensitive and whitespace-tolerant. Expected values and lookup keys accept {{var.NAME}} templates, and the same match modes (exact / contains / regex) apply.

Refine it in the Edit drawer​

Any assert can be authored or refined in the Edit drawer — set kind to a table · … and fill the row/column fields:

The Edit drawer showing a table-cell assertion: row lookup by Email, target column Status equals Active

How it resolves​

A table assert resolves in two layers. The selector ladder finds one stable thing — the <table> (structural selectors, no cell data). Then, at replay time, Trq reads the live table and matches the row and column against the data: it finds the row whose key cell matches, and reads the target column in that same row. Because both come from the same <tr>, the two values stay bound together — and the check survives status changes, re-renders, and column reordering.

Two-layer resolution: the selector ladder locates the table, then row lookup and column addressing find the cell in the live data

Counting and attribute assertions​

Two assertion kinds ask about something other than an element's text or state. Both are authored in the Edit drawer → Assertion section (set kind):

  • element count — how many elements the selector matches, with exactly / at least / at most. This is the only assertion where zero is a valid answer, so it's also how you assert that something is absent: count of .error-row is exactly 0. It counts across every reachable document (including same-origin iframes) and never reports "selector did not resolve".
  • attribute equals — the value of a named attribute on the resolved element, with the usual exact / contains / regex match modes. Use it for state carried in markup rather than text: aria-expanded, data-status, an href, a graph node's data-id. The expected value is templatable, so it can be compared against a captured variable.

count deliberately skips ambiguous rungs (text/, aria/, chained and xpath rungs), which resolve to a single element by construction and would always answer 1. It counts the first CSS rung that matches anything.

Node graphs and canvas UIs​

Node-graph editors built on React Flow record and replay without anything special:

  • Connecting nodes — dragging from one connection handle to another is captured as a drag step and re-driven as a real mouse gesture, so the edge is genuinely created on replay.
  • Selecting a node — recorded as an ordinary click. React Flow stamps data-testid="rf__node-<id>" on every node, which Trq already prefers, so node selectors are stable by default.
  • Handles get an order-independent selector built from their own attributes ([data-nodeid='x'][data-handlepos='top']) rather than a positional path — so a recording survives nodes being added or reordered.
  • Repositioning a node — dragging a node to a new spot is recorded as a drag with an offset destination (drag <node> by (110, 55)) rather than a drop target, because there isn't one.
  • Panning and zooming — a canvas pan is an offset drag on the pane; wheel and pinch (ctrl+wheel) zoom are recorded as a wheel step.
  • Asserting the graph — use element count for .react-flow__node / .react-flow__edge, and attribute equals for a node's own state.

Free-form drags and zoom​

Two things make a canvas different from ordinary drag-and-drop, and Trq handles both:

There is no drop target. Dropping a node somewhere on open canvas can't be described as "released over element X". Such a drag is recorded as a delta instead — and the delta is stored in the canvas's own coordinate units, not screen pixels. Trq divides by the surface's current zoom when recording and multiplies by the live zoom when replaying, so a move recorded at 100% lands on the same world position when the test runs against a canvas sitting at 60%.

The delta is measured from how far the element actually travelled, not how far the mouse did. Drag libraries swallow the first few pixels as a movement threshold, so the two differ — and on replay Trq primes the gesture with a small nudge before measuring, so the node lands exactly where it was recorded rather than consistently short.

A drag that ends where it began is ambiguous. Dragging a node keeps it under the cursor the whole way, so press and release land on the same element — which is also what selecting a paragraph of text looks like. Trq separates them by checking whether anything actually moved between press and release. Text selections are still ignored; node moves and canvas pans are recorded.

Canvas zoom is a real gesture, not a setting

A wheel step records the summed scroll of one gesture (the browser fires dozens of events for a single swipe) along with whether ctrl was held — browsers synthesize ctrl+wheel for a trackpad pinch, and canvas libraries read exactly that to tell zoom from scroll. Replaying without the modifier would scroll the page instead of zooming the canvas.

Multi-tab​

In Studio, recording and replay follow into new tabs and windows — tab-open and tab-switch are captured and re-driven automatically.

You can also author a new-tab navigation instead of recording one. Add a navigate step (or edit an existing one — see Editing a test), enter the URL, and tick open in new tab. On replay Trq opens that URL in a fresh tab, switches to it, and runs the following steps there. The option is only available for a full-document load (an in-document SPA route can't happen in a new tab), and single-tab headless runs (the CLI) fall back to navigating in place.

Closing a tab​

A test that opens a tab and never closes it leaves it open for the rest of the run. Close the tab in Trq's tab bar while recording and the close is captured like any other action:

Closing a tab during recording — the ✕ on the tab bar, and the resulting tab-close step in the Events panel

You can also add one by hand: right-click a step → Insert step above/below ▸ ✕ Close tab…, or use + Add step. Its one field is a picker of the tabs opened before that step — a tab opened later can't be closed there, so it isn't offered.

Tabs are addressed by identity, not by position, so inserting or removing a tab elsewhere in the session never makes a close land on the wrong one.

Three behaviours worth knowing:

  • Closing the last remaining tab clears its page rather than removing it — Trq always keeps one view. From your side the page closes and the tab resets to blank.
  • A tab that has already closed itself is skipped, not failed. Plenty of pages call window.close() on their own; a step that failed because the thing it wanted gone is already gone would be noise.
  • Closing works the same on Play, Resume and Debug, and inside a called flow — where the flow shares the caller's tabs, so a flow that opens a tab should close it too.