--json flag placement | Place before positional args: cases get --json <id> ✓ |
cases clone semantics | Fetches the full TestCaseV2, strips generated item/value IDs (so the API re-assigns), preserves module/attr refs so steps still resolve. Default new name is AI Copilot – <original> unless --name given. Waits up to 15 s for inventory indexing before copying tags. Always follow with inventory move testCase <newId> --folder-id <folderId> — clone alone doesn't place. |
inventory delete-folder childBehavior | Body is {"childBehavior": "moveToParent"|"deleteRecursively"|"abort"}. CLI default is moveToParent (same as "ungroup" in the portal). --delete-children maps to deleteRecursively. Use --force to skip the confirmation prompt. |
| JUnit results are JSON | GET /_playlists/api/v2/runs/{id}/junit returns TestSuitesV1 as JSON, not XML, despite the endpoint name. playlists results <runId> already handles this — don't try to parse the response as XML. |
| Published swagger occasionally lies | Inventory v3 search filter is documented PascalCase but only lowercase works; Inventory v3 PATCH uses a different wrapper/casing from MBT PATCH; some "JUnit" endpoints return JSON. When CLI output disagrees with swagger, trust the CLI — cross-check via the live Swagger UI at /<surface>/apiDocs/swagger on your tenant. |
| Inventory indexing delay | After cases create via MBT, the Inventory index takes 3–10 s to reflect the new record. The CLI retries automatically; if you hit inventory search programmatically, build in the same retry. |
| MCP server wiring | For the personal-agent flow below you need ToscaCloudMcpServer configured via mcp-remote + PKCE OAuth. Full recipe in scripts/README.md § MCP wiring. |
| Block IDs ≠ Module entity IDs | Get block IDs from cases get --json <caseId> → testCaseItems[].reusableTestStepBlockId where $type == "TestStepFolderReferenceV2" |
parameterLayerId missing | Each TestStepFolderReferenceV2 must have a fresh ULID parameterLayerId or all parameter values are silently ignored |
| Entity ID truncation in table | Always use --json to get full IDs before passing to commands |
Html module root Engine param | Manually created Html modules must have {"name":"Engine","value":"Html","type":"Configuration"} in the root-level parameters array. Without it: XModules and XModuleAttributes have to provide the configuration param "Engine" |
| Duplicate page elements | Modern pages render the same nav link in mobile + desktop. Tag+InnerText+HREF alone matches all copies. Use browser_evaluate to count matches; add ClassName to discriminate. |
| Leftover browser tab | Start Precondition with CloseBrowser Title="*" before OpenUrl to avoid "More than one matching tab" |
| MBT PATCH ops | Lowercase: replace, add, remove. Response is 204 No Content. Unsupported ops (deep JSON-pointer paths into nested step trees, remove on array elements, move) are silently dropped by the server — but the CLI catches this: _confirm_version_bump runs a GET after every PATCH and exits 1 with Error: … version unchanged when the op was a no-op. On that exit, fall back to full PUT (cases update/modules update/blocks update). |
| Inventory v3 PATCH body | Wrapper: {"operations": [{"op": "Replace", ...}]} — PascalCase op. An MBT-shape body (bare array, lowercase op) is accepted and 204'd but applies no changes. The CLI's confirm-GET catches this shape-mismatch the same way it catches unsupported-op no-ops. |
| CLI enforces confirm-GET on every write | cases update / cases patch / modules update / blocks update / inventory patch all run a follow-up GET and exit non-zero if version didn't bump or the target field didn't change. Green ✓ confirmed = diff landed. Red Error: … version unchanged / … did not apply = silent no-op; fall back to full PUT. --no-confirm exists for debugging transport shape only — never use on production writes. |
| Inventory search filter | Despite swagger, only lowercase works: contains, and |
| SAP standard modules | Not in inventory. SAP Logon, SAP Login, T-code — use IDs directly from SAP guide |
| TSU export field | reusableTestStepBlockIds (no double-e) |
version in PUT body | Omit — rejected by case, block, and module PUT endpoints. CLI's update_case/update_block/update_module strip it automatically |
MBT test case ID = Inventory entityId | cases get/steps/update accept only the Inventory entityId. Playlist item id and inventory attributes.surrogate both 404. Resolve via inventory search … --type TestCase --json → id.entityId |
Failed playlist run with <failure /> only | Playlists v2 has no step-level log endpoint, but E2G does. Use playlists logs <runId> — it walks /_e2g/api/executions/{executionId} units → /units/{unitId}/attachments → SAS-signed Azure Blob downloads (logs.txt, JUnit.xml, TBoxResults.tas, TestSteps.json, Recording.mp4). Works under Tricentis_Cloud_API. The endpoint keys on PlaylistRunV1.executionId, not the playlist run's id — the CLI resolves this via playlists status automatically; pass --execution-id / -e to skip the lookup. SAS TTL ≈ 30 min; the blob GET must NOT carry an Authorization header. |
| Personal-agent runs need MCP, not CLI | Tricentis_Cloud_API (CLI service token) cannot dispatch to or read a developer's personal Local Runner — _e2g/api/agents/<personalAgentName> returns 403, and playlists status <runId> on a private run returns 403. Use mcp__ToscaCloudMcpServer__RunPlaylist(playlistId, runOnAPersonalAgent=true) to trigger and GetRecentRuns + GetFailedTestSteps to inspect — MCP carries the developer's user identity (PKCE OAuth via mcp-remote). |
cases delete / modules delete / blocks delete → 403 | The Tricentis_Cloud_API client-credentials role has create/read/update/patch on MBT artifacts but no delete privilege on this tenant. Symptom: DELETE /_mbt/api/v2/builder/testCases/{id} returns 403 regardless of query-string tweaks (?force=true, ?permanent=true), and all bulk variants (testCases/bulkDelete, testCases/delete, DELETE /testCases with body) are 405 — no such route. Inventory-side DELETE routes (_inventory/api/v3/artifacts/testCase/{id}, v1 equivalents) are 404/405. The MCP tool set carries the user's identity but doesn't expose a delete-test-case / delete-module / delete-block tool — only DeletePlaylistById. Workarounds: (a) delete via the Portal UI (the logged-in user's browser token has delete); (b) ask tenant admin to grant the Cloud-API role testCases:delete / modules:delete / reuseableTestStepBlocks:delete. Always run inventory search + playlist-reference scan before delete either way — dangling sourceId references in playlists are a harder cleanup than keeping a stale "(Copy)" case around. |
| Local Runner preflight | Before triggering on a personal agent: install Tosca Local Runner / Cloud Agent on the developer's machine; install + enable Tricentis Automation Extension in Chrome and/or Edge; keep the target browser maximized (minimized windows cause coordinate-out-of-bounds and silent click misses). |
| Html "More than one matching tab" | Agent shares user's Chrome profile. Add module-level Url=https://<host>* TechnicalId to scope document matching to one tab. Also prepend a ControlFlowItemV2 If to Precondition: condition = Verify always-visible app element Visible=True, then = CloseBrowser Title="*<AppName>*" |
| Click operation values | Uppercase in braces: {CLICK}, {DOUBLECLICK}, {RIGHTCLICK}, {ALTCLICK}, {CTRLCLICK}, {SHIFTCLICK}, {LONGCLICK}, {MOUSEOVER}, {DRAG}, {DROP}. For hover use {MOUSEOVER} — not {Hover} (fails with "No suitable value found for command Hover"). Add {MOUSEOVER} to the Link's valueRange. Synthetic JS events don't fire CSS :hover; TOSCA's {MOUSEOVER} emits a real mouse move |
{Click} reports Succeeded but browser doesn't navigate | Drupal / SPA mega-menu links sometimes log [Succeeded] Click '…' while the tab URL never changes — the next module's Url= scope then can't find the tab. Per Tricentis best-practices KB5 #12, replace value: "{Click}" with value: "X" (direct click — invokes the DOM click handler without mouse emulation). Do not try {LEFTCLICK} — not a registered Html-engine keyword, throws [Exception] with ~0.07 s duration. |
| Html scanner is viewport-scoped, not document-scoped | A Verify on a below-the-fold <h2>/<div> fails with Could not find … even though browser_evaluate('document.querySelectorAll(sel).length') ≥ 1. ScrollToFindElement=True steering does not reliably help. Fixes in order: (1) prepend a {SENDKEYS[{PAGEDOWN}]} on the page body, or OpenUrl to a fragment anchor, to bring the element into the viewport; (2) pivot to Verify JavaScript Result — CDP Runtime.evaluate sees the whole DOM regardless of scroll. Distinct from the "scanner observer disabled" case in standard-modules.md; check viewport first: browser_evaluate('document.querySelector(sel).getBoundingClientRect().y') vs window.innerHeight. |
| Attaching cases to a playlist | The items list discriminator is $type: "InputTestCaseV1" (field: sourceId, not id). Folders use InputFolderV1. Using TestCaseV1 / TestCase returns "InputItemV1 $type must be either InputFolderV1 or InputTestCaseV1". |
Module-level Url/Title must be parameterType: "TechnicalId" | Not "Configuration". Set as Configuration and the Html engine silently ignores them for tab scoping — symptom is persistent "More than one matching tab was found" regardless of pattern. Verify with modules get --json <id> → parameters[].parameterType. Fix in-place via modules update. |
UseActiveTab = True alone rejected on some tenants | A Verify JavaScript Result step with UseActiveTab=True and no other criteria raises "Specify at least one of the Search Criteria.". Always pair with Title=*<AppName>* or Url=https://<host>*, or switch to UseActiveTab=False + Title/Url. Reliably working shape: UseActiveTab=False + Title=*<AppName>*. |
| Container nesting does NOT scope attribute matching | Nesting a Button inside a Container in the module tree affects only Steering-param inheritance — it does not scope DOM resolution. moduleAttributeReference.id=<Button> resolves globally; two matching buttons in different page regions still give "Found multiple controls for Button ''". Discriminate in the child's own selector (combine ancestor class + child class in ClassName), or scope via Verify JavaScript Result + document.querySelector('.region-header button.lang-switch'). |
GetRecentRuns UUID-sorted cap | Returns ~10 executionIds sorted alphabetically by UUID (not by time). A newly dispatched run whose UUID sorts past the cap is invisible regardless of wait time. If two consecutive polls with identical nameFilter return the same pre-existing set, stop polling — pivot to mcp__ToscaCloudMcpServer__GetRecentPlaylistRunLogs(playlistId) (authoritative per-playlist pass/fail). |
| Don't swap service-token clientIds chasing 403s | The CLI's service account only uses Tricentis_Cloud_API. Do NOT substitute E2G_Agents / Tosca_Server / Tricentis_Hosted_E2G_Agents — those are engine-internal identities with a different scope set (no delete, no private-agent dispatch, no log attachments). A 403 on the Cloud-API role is either a Portal-UI action needed (user identity) or a tenant-admin role grant — not a clientId swap. |
| Keyboard command values | All uppercase-braced: {ENTER} {TAB} {ESC} {F1}..{F24} {UP} {DOWN} {LEFT} {RIGHT} {BACKSPACE} {DEL} {HOME} {END} {SHIFT} {CTRL} {ALT}. Advanced: {SENDKEYS["..."]}, {KEYPRESS[code]}, {KEYDOWN/KEYUP[code]}, {TEXTINPUT["..."]}. Ref: keyboard_operations |
| Action mode cheat-sheet | Input write; Insert (API modules); Verify + actionProperty assert; Buffer/Output capture into {B[name]}; WaitOn dynamic wait; Select pick a specific child; Constraint/Exclude narrow tables. Ref: action_types |
| Dynamic expressions | {CP[Param]} config param; {B[Var]} buffer (case-sensitive, test-case-scoped — does NOT cross cases); {MATH[...]} arithmetic with Abs/Ceiling/Floor/Max/Min/Pow/Round/Sign/Sqrt/Truncate; string ops {STRINGLENGTH} {STRINGTOLOWER} {STRINGTOUPPER} {TRIM} {STRINGREPLACE} {STRINGSEARCH} {BASE64} {NUMBEROFOCCURRENCES} |
InnerText exact-match | TOSCA's InnerText TechnicalId matches the full element innerText exactly, including text of nested children. A card link wrapping an <h2> will have innerText="<caption>\n<heading>" and will not match a short caption. Drop InnerText; use Tag + HREF + ClassName or a Title attribute |
Parent visibility:hidden propagates | Closed mega-menus hide children via parent styling; TOSCA's default IgnoreInvisibleHtmlElements=True filters them out. Open the parent before looking up the child, or set IgnoreInvisibleHtmlElements=False as a Steering module param |
| Html "The Browser could not be found" | Tricentis Chrome extension not attached to the agent's Chrome. Fix on the agent (install/enable extension), not in the test case |
ControlFlowItemV2 for optional elements | Works cleanly when the module-level selector (Title/Url) can produce a clean no-match. Verify steps inside the condition evaluate false on hidden elements; they hard-fail when the document itself can't be found. Narrow the module-level selector before relying on If |
Test case PUT requires id in body | The full PUT body must include "id": "<caseId>" — API rejects bodies without it |
| New case not in inventory immediately | After cases create, wait 3–10 s before searching — CLI retries automatically |
| Placing a case after create/clone | Always run inventory move testCase <newId> --folder-id <folderId> — creation alone doesn't place it |
| Finding a folder's entity ID | Use inventory folder-tree --folder-ids "<parentId>" or read the UUID from the portal URL |
inventory search --folder-id | Filters client-side by matching the folderKey suffix — pass --folder-ids with parent IDs |
modules update returns {} | A 200/204 with empty body is normal — verify with modules get <id> --json afterwards |
Block params need id | Every businessParameters entry needs a ULID id — always use blocks add-param which generates one |
referencedParameterId | Each parameter value entry must match a businessParameter.id from the block — get IDs via blocks get <blockId> --json |
{CP[ParamName]} syntax | Reference test config params in step values: {CP[Username]}, {CP[Password]} |
ProcessOperations subValues | The Arguments step uses actionMode: "Select" with each CLI arg as a separate item in subValues[] — multiple args in one value string won't work |
Standard modules invisible in inventory search | Intentional. Discover via GET /_mbt/api/v2/builder/packages + packages/{pkg}/modules/{moduleId}. Top-level module GUIDs appear stable; attribute GUIDs are NOT confirmed stable — re-discover per tenant. See standard-modules.md |
{SCRIPT[...]} / {XP[...]} dynamic-value expansion | Not registered on Tosca Cloud. To run JS from a test step, use the Execute JavaScript / Verify JavaScript Result Standard modules — see standard-modules.md |
| Html scanner blind to body content (not iframe / not shadow DOM / not CSS-hidden) | Module Steering flags won't fix it. Pivot to Verify JavaScript Result (CDP-based, bypasses the scanner). Full diagnostic playbook + anti-patterns in standard-modules.md |