Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/VALIDATION_RULE_REGISTRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Provar test-case validation runs in two layers. This registry is the single cano

**The validity bridge (PDX-509):** a `critical` best-practice violation is surfaced into `issues[]` as an `ERROR` and therefore gates `is_valid` — EXCEPT where a Layer-1 check already owns the concept (then it is suppressed to avoid double-reporting). `major`/`minor`/`info` affect `quality_score` (and the `needs_improvement` status) only. The `status` field is tri-state: `invalid` (a critical) / `needs_improvement` (loads but `quality_score < quality_threshold`) / `valid`.

**Counts:** Layer 1 — 23 rules (18 gating). Layer 2 — 178 rules (critical 64 / major 67 / minor 29 / info 18; 58 bridged to `is_valid`).
**Counts:** Layer 1 — 23 rules (18 gating). Layer 2 — 179 rules (critical 64 / major 68 / minor 29 / info 18; 58 bridged to `is_valid`).

## Layer 1 — Structural validity rules

Expand Down Expand Up @@ -194,6 +194,7 @@ Provar test-case validation runs in two layers. This registry is the single cano
| `UI-LOCATOR-SAVE-001` | TestCaseDesign | major | 5 | No | Save button locator must use correct pattern. |
| `UI-LOOKUP-ID-001` | TestCaseDesign | major | 6 | No | UiDoAction lookup fields should use Name values, not IDs. |
| `UI-NAVIGATE-PREFER-SCREEN-001` | TestCaseDesign | info | 1 | No | Prefer UiWithScreen over UiNavigate for Salesforce. |
| `UI-SCREEN-CONTEXT-001` | TestCaseDesign | major | 5 | No | UI verification or post-save step left under the wrong screen context. |
| `UI-SCREEN-NAV-001` | TestCaseDesign | major | 5 | No | First UiWithScreen must use navigate=Always or IfNeccessary. |
| `UI-SCREEN-NAV-002` | TestCaseDesign | minor | 2 | No | First UiWithScreen should prefer navigate=Always over IfNeccessary. |
| `UI-SCREEN-OBJID-001` | TestCaseDesign | major | 5 | No | UiWithScreen with navigate=Always for Edit/View must have sfUiTargetObjectId. |
Expand Down
47 changes: 38 additions & 9 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,7 @@ Validates an XML test case for schema correctness (validity score) and best prac
- **`CONN-DB-002`** (`dbConnectResultNameMismatch`) — a DB operation (`SqlQuery`/`DbRead`/`DbInsert`/`DbUpdate`/`DbDelete`) sets a `dbConnectionName` that does not match any `DbConnect` `resultName` in the test, so the open connection can't be found. Defers to `CONN-DB-001` when there is no `DbConnect` at all.
- **`UI-LOCATOR-BUTTON-CASING-001`** (`uiLocatorButtonCasing`) — a `UiDoAction` locator uses a wrong-cased standard-button name: `name=Cancel` (use lowercase `name=cancel`) or `name=Continue`/`name=continue` on the record-type screen (use `name=save&path=selectRecordType`).
- **`UI-LOCATOR-RECORDTYPE-001`** (`uiLocatorRecordTypeField`) — a `UiDoAction` Record Type picker locator uses `name=recordTypeId`/`name=recordType` instead of `name=RecordType` with `field=RecordTypeId` in the binding.
- **`UI-SCREEN-CONTEXT-001`** (`uiAssertScreenContext`) — a `UiAssert`/`UiRead` left nested under a create screen (`UiWithScreen` whose target URI carries `action=New`), or any UI action placed **after a plain Save** inside the same `action=New` `<clause name="substeps">` block. Save is detected by parsing the `UiDoAction` locator binding's decoded `action` token and matching it **exactly** to `save` (case-insensitive), so continuation buttons like **Save & New** (`action=saveAndNew`) are not treated as a Save. Salesforce tears down the create form on Save and navigates to the saved record's View screen, so a verification still under `action=New` asserts against a page that no longer exists, and steps following the Save in the same create wrapper run in stale context. **Both checks are scoped to `action=New`** — post-save steps/asserts under `action=Edit`/`View` are a valid same-record pattern and do **not** fire. The test still loads and may score 100, so this is a runtime/logic correctness issue. Fix: close the create screen at the Save step and open a new `UiWithScreen` targeting `action=View` (capturing the new record id via `sfUiTargetResultName`) for the verification or follow-up action. A step that trips both checks (assert placed after Save under New) is **de-duped to one violation per offending step** inside the local engine before scoring, so it is penalised once (major × weight 5 → score 96.25), not twice — this matters because the local engine sums every violation and has no `test_item_id` field of its own. **Coverage limit:** this is a structural heuristic — a passing run confirms the create/verify split is structured correctly, not that the assertion logic itself is correct.
- **Structural correctness rules** — Checks on element shape / required structure that the Provar IDE depends on to render and run a step. Mostly critical; like the rules above they run offline / in `local_fallback` and keep score parity with the Quality Hub back-end. Currently enforced:
- **`SETVALUES-STRUCTURE-001`** (`setValuesStructure`, critical) — a `SetValues` step has no `<namedValues>` container.
- **`SETVALUES-NAME-001`** (`namedValueName`, critical) — a `<namedValue>` inside a `SetValues` step is missing its `name` attribute.
Expand Down Expand Up @@ -2029,13 +2030,13 @@ The constraint is also referenced in the [`provar_testcase_generate`](#provar_te

Tools that surface Salesforce org metadata to authoring tools without making a live API call. These read from data that has already been written to disk by the Provar IDE — they do **not** trigger metadata downloads themselves and they do **not** require an authenticated session.

> **Distinct from `.provarCaches`:** the runtime cache used by `provar_automation_testrun` lives at `<resultsPath>/.provarCaches/` and is regenerated per run. The cache read by `provar_org_describe` lives in the Provar IDE **workspace** (`<workspace>/.metadata/<connection_name>/`) and is updated when a user opens the project and loads a connection in the IDE.
> **Distinct from `.provarCaches`:** the runtime cache used by `provar_automation_testrun` lives at `<resultsPath>/.provarCaches/` and is regenerated per run. The cache read by `provar_org_describe` lives in the Provar IDE **workspace** `.metadata` directory and is updated when a user opens the project and loads a connection in the IDE.

### `provar_org_describe`

Read cached Salesforce describe data for one connection from the Provar workspace `.metadata` cache. Returns the object list, required-field schema, and a cache age. Use this before calling `provar_testcase_generate` so the generator can produce steps with correctly-typed field values.

**Prerequisite:** the project must have been opened in Provar IDE at least once with the named connection loaded. If the cache is missing, the tool returns a structured response with `details.suggestion` rather than an error.
**Prerequisite:** the project must have been opened in Provar IDE at least once with the named connection loaded, and (for the chosen test environment) the relevant objects expanded so their metadata is written to disk. If the cache is missing, the tool returns a structured response with `details.suggestion` rather than an error.

**Workspace discovery heuristic** — the tool walks candidate directories in this order and uses the first one that exists:

Expand All @@ -2045,12 +2046,20 @@ Read cached Salesforce describe data for one connection from the Provar workspac

`<name-dashes>` is the project's basename with whitespace collapsed to single dashes and lowercased: `"My Project"` → `"my-project"`.

| Input | Type | Required | Default | Description |
| ----------------- | ----------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `project_path` | string | yes | — | Absolute path to the Provar test project root (the directory containing `.testproject`). Must be within `--allowed-paths`. |
| `connection_name` | string | yes | — | Connection name as defined in `.testproject` (e.g. `"MyOrg"`). Must match the `.metadata` subdirectory exactly. Path separators in this value are rejected (`PATH_TRAVERSAL`). |
| `objects` | string[] | no | all | Filter — only return data for these object API names. When omitted, lists every object cached under the connection directory. |
| `field_filter` | `'required'` \| `'all'` | no | `'required'` | Which fields to return. `'required'` includes only fields with `nillable=false`; `'all'` returns every cached field. |
**Cache layout resolution (within the discovered workspace).** The tool prefers the Provar IDE SfObject cache and falls back to the legacy/native layout:

1. **Provar IDE SfObject layout (preferred):** `<workspace>/.metadata/.plugins/com.provar.eclipse.ui/<connection_name>/<environment>/SfObject/<Object>.xml` — one XML file per object, written by the IDE when a connection's objects are expanded. `<environment>` is the test environment name (e.g. `default`, `UAT`). The tool tries the requested `environment` first; if that environment has no `SfObject` directory, it falls back to any environment that does, preferring `default`.
2. **Legacy / native layout (fallback):** `<workspace>/.metadata/<connection_name>/<Object>.{json,xml,object}` — used only when the IDE SfObject layout is absent.

`cache_age_ms` reflects the `mtime` of whichever cache directory was used (the `SfObject` directory for the IDE layout).

| Input | Type | Required | Default | Description |
| ----------------- | ----------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `project_path` | string | yes | — | Absolute path to the Provar test project root (the directory containing `.testproject`). Must be within `--allowed-paths`. |
| `connection_name` | string | yes | — | Connection name as defined in `.testproject` (e.g. `"MyOrg"`). Must match the cache subdirectory exactly. String identifier, **not** a file path — path separators or `..` in this value are rejected (`PATH_TRAVERSAL`). |
| `environment` | string | no | `'default'` | Test environment name whose cached metadata to read in the Provar IDE SfObject layout (e.g. `"default"`, `"UAT"`). If the requested environment has no cached metadata the tool falls back to any environment that does, preferring `default`. Ignored for the legacy layout. String identifier, **not** a file path — path separators or `..` are rejected (`PATH_TRAVERSAL`). |
| `objects` | string[] | no | all | Filter — only return data for these object API names. When omitted, lists every object cached under the resolved cache directory. |
| `field_filter` | `'required'` \| `'all'` | no | `'required'` | Which fields to return. `'required'` includes only fields with `nillable=false`; `'all'` returns every cached field. |

| Output field | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -2133,7 +2142,27 @@ Read cached Salesforce describe data for one connection from the Provar workspac
}
```

**On-disk cache schema (one file per object).** The tool reads `.json` first, then `.xml`, then `.object` as a fallback:
**On-disk cache schema (one file per object).**

The **preferred** format is the Provar IDE SfObject XML at `<workspace>/.metadata/.plugins/com.provar.eclipse.ui/<connection_name>/<environment>/SfObject/<Object>.xml`. Fields live under `<sfObject><fields><sfField .../></fields>`:

```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<sfObject n="provar__Person__c" t="Person" label="Person" keyPrefix="a0D">
<fields>
<sfField n="Id" type="id"/>
<sfField n="provar__Email__c" type="email" required="true"/> <!-- required="true" → nillable=false -->
<sfField n="Name" length="80"/> <!-- no type attribute → type "unknown" -->
<sfField n="OwnerId" type="reference" relationshipName="Owner">
<referenceTos><string>User</string></referenceTos> <!-- container field; children are NOT counted -->
</sfField>
</fields>
</sfObject>
```

Field mapping: `name` ← `n` (a field with no `n` is skipped); `type` ← `type` (absent → `"unknown"`; Salesforce booleans appear as `_boolean`); `nillable` ← `NOT(required="true")`; `default_value` is always `null` (this format carries no default). The object display name comes from the `sfObject` `t` attribute, else `n`. A **stub** file (`detailsLoaded="false"` with no `<fields>`) is reported as `exists: true, field_count: 0` — not an error.

The **legacy/native** format (read only when the SfObject layout is absent) lives at `<workspace>/.metadata/<connection_name>/<Object>.{json,xml,object}`. JSON is tried first, then `.xml` / `.object` (CustomObject / toolingObjectInfo metadata):

```jsonc
// <workspace>/.metadata/<connection_name>/Account.json
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@provartesting/provardx-cli",
"description": "A plugin for the Salesforce CLI to orchestrate testing activities and report quality metrics to Provar Quality Hub",
"version": "1.6.2",
"version": "1.6.3",
"mcpName": "io.github.ProvarTesting/provar",
"license": "BSD-3-Clause",
"plugins": [
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
"url": "https://github.com/ProvarTesting/provardx-cli",
"source": "github"
},
"version": "1.6.2",
"version": "1.6.3",
"packages": [
{
"registryType": "npm",
"identifier": "@provartesting/provardx-cli",
"version": "1.6.2",
"version": "1.6.3",
"transport": {
"type": "stdio"
},
Expand Down
16 changes: 16 additions & 0 deletions src/mcp/rules/provar_best_practices_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -2959,6 +2959,22 @@
"notes": "Mirrors QH UiActionNestingStructureValidator (lambda/src/validator/best_practices_engine.py). Emits one violation per offending step so that (rule_id, test_item_id) de-dups against the API. UiWithRow plays a dual role: itself a UI action that must be nested, and a container whose substeps clause satisfies the rule for its descendants.",
"source": "Field observation: flat-emitted UI actions (sibling of UiWithScreen) fail to render in Provar IDE despite scoring 100/94 in earlier local validation"
},
{
"id": "UI-SCREEN-CONTEXT-001",
"category": "TestCaseDesign",
"name": "UI verification or post-save step left under the wrong screen context",
"description": "A UiAssert or UiRead nested under a create screen (UiWithScreen target action=New), or any UI action placed after a plain Save (action=save) inside the same action=New substeps block, is almost certainly bound to the wrong screen. Salesforce tears down the create form on Save and navigates to the saved record's View screen, so a verification still nested under action=New asserts against a page that no longer exists, and steps following the Save in the same create wrapper run in stale context. Both checks are scoped to action=New: post-save steps under action=Edit/View are a valid same-record pattern and do not fire, and continuation buttons like Save & New (action=saveAndNew) are not treated as a Save. The test case still loads and may even score 100, so this is a runtime/logic correctness issue (major), not a load failure.",
"appliesTo": ["Step", "TestCase"],
"severity": "major",
"weight": 5,
"recommendation": "Close the create UiWithScreen at the Save step and open a new UiWithScreen targeting the post-save screen for any verification or follow-up action. For Salesforce record creation, assert the saved record under a UiWithScreen with target sf:ui:target?object=<Object>&action=View&recordId={<idVar>}, capturing the created record id via sfUiTargetResultName on the create screen. Do not place UiAssert/UiRead steps under an action=New screen, and do not leave steps after a Save inside the create screen's substeps block.",
"check": {
"type": "uiAssertScreenContext",
"target": "step"
},
"notes": "Two heuristics over data already in the XML, both scoped to UiWithScreen target action=New: (1) UiAssert/UiRead nested directly under the create screen; (2) any UI action that appears after a plain Save UiDoAction within the same <clause name=\"substeps\"> block. Save is detected by parsing the locator binding's decoded action token and comparing it EXACTLY to 'save' (case-insensitive), so saveAndNew / savedSearch / savepoint never match. A step that trips both heuristics (assert placed after Save under New) is de-duped to a single violation per offending testItemId inside the local engine before scoring, so it is penalised once (major x weight 5); this matters because the local score sums every BPViolation and has no test_item_id field of its own. Coverage limit: this is a structural heuristic — a passing run confirms the create/verify split is structured correctly, not that the assertion logic itself is correct.",
"source": "Field observation: AI-generated create-and-verify tests leave the UiAssert under the action=New screen, scoring 100 while asserting against the wrong page at runtime"
},
{
"id": "VAR-STRING-LITERAL-001",
"category": "TestCaseDesign",
Expand Down
Loading