@uicontract/skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/SKILL.md +223 -0
- package/package.json +37 -0
- package/references/browser-tool-bridge.md +81 -0
- package/references/manifest-schema.md +75 -0
- package/references/workflow-patterns.md +234 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UIC Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @uicontract/skill
|
|
2
|
+
|
|
3
|
+
Agent skill files for [UI Contracts](https://github.com/sherifkozman/uicontract).
|
|
4
|
+
|
|
5
|
+
Gives AI coding agents a structured way to discover, target, and interact with UI elements using stable `data-agent-id` selectors instead of fragile CSS classes or text matches.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What's Included
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|------|---------|
|
|
13
|
+
| [`SKILL.md`](./SKILL.md) | Full skill definition - commands, patterns, rules |
|
|
14
|
+
| [`references/manifest-schema.md`](./references/manifest-schema.md) | `manifest.json` structure and field reference |
|
|
15
|
+
| [`references/browser-tool-bridge.md`](./references/browser-tool-bridge.md) | Targeting syntax for Playwright, Cypress, agent-browser, Chrome MCP |
|
|
16
|
+
| [`references/workflow-patterns.md`](./references/workflow-patterns.md) | Multi-step automation recipes |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation by Agent Type
|
|
21
|
+
|
|
22
|
+
### Claude (claude.ai / Claude Code)
|
|
23
|
+
|
|
24
|
+
Add to your project's `CLAUDE.md` or global agent instructions:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
@uicontract/skill
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or copy `SKILL.md` into your project root and reference it:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx --yes @uicontract/skill install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For Claude Code, add to `.claude/skills/`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g @uicontract/skill
|
|
40
|
+
# Then in your project:
|
|
41
|
+
cp $(npm root -g)/@uicontract/skill/SKILL.md .claude/skills/uic.md
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Cursor
|
|
45
|
+
|
|
46
|
+
Add to `.cursor/rules/uic.mdc`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @uicontract/skill
|
|
50
|
+
cp node_modules/@uicontract/skill/SKILL.md .cursor/rules/uic.mdc
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### GitHub Copilot / VS Code
|
|
54
|
+
|
|
55
|
+
Add to `.github/copilot-instructions.md`:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install @uicontract/skill
|
|
59
|
+
cat node_modules/@uicontract/skill/SKILL.md >> .github/copilot-instructions.md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Any AI coding tool
|
|
63
|
+
|
|
64
|
+
The skill is plain Markdown. Copy `SKILL.md` wherever your tool reads agent instructions:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install @uicontract/skill
|
|
68
|
+
cat node_modules/@uicontract/skill/SKILL.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## How It Works
|
|
74
|
+
|
|
75
|
+
Once loaded, the skill teaches the agent to:
|
|
76
|
+
|
|
77
|
+
1. **Check for an existing `manifest.json`** before scanning
|
|
78
|
+
2. **Use `npx uicontract find`** to locate elements by label or purpose
|
|
79
|
+
3. **Target elements by `data-agent-id`** instead of CSS selectors or text
|
|
80
|
+
4. **Run `npx uicontract diff`** to detect breaking UI changes
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Agent finds the element:
|
|
84
|
+
npx uicontract find "pause subscription" --json
|
|
85
|
+
# => { "agentId": "settings.billing.pause-subscription.button", ... }
|
|
86
|
+
|
|
87
|
+
# Agent targets it:
|
|
88
|
+
[data-agent-id="settings.billing.pause-subscription.button"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
See [`SKILL.md`](./SKILL.md) for the full command reference and workflow patterns.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
[MIT](../../LICENSE)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uic
|
|
3
|
+
description: Use when automating browser interactions with a web app that has a manifest.json or data-agent-id attributes. Use when the agent needs to find, target, or interact with specific UI elements by name, label, or purpose.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UI Contracts for Agent Automation
|
|
7
|
+
|
|
8
|
+
UI Contracts makes web app UIs machine-readable with stable hierarchical IDs, so agents can find, target, and interact with any interactive element by name instead of fragile selectors.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx uicontract scan ./src -o manifest.json
|
|
14
|
+
npx uicontract name manifest.json -o manifest.json
|
|
15
|
+
npx uicontract find "login" --json
|
|
16
|
+
npx uicontract describe <agent-id> --json
|
|
17
|
+
npx uicontract list --type button --json
|
|
18
|
+
npx uicontract diff old.json new.json --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Core Workflow
|
|
22
|
+
|
|
23
|
+
### 1. Discover
|
|
24
|
+
|
|
25
|
+
Check for an existing `manifest.json` in the project root. If none exists, generate one:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx uicontract scan ./src -o manifest.json
|
|
29
|
+
npx uicontract name manifest.json -o manifest.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Find
|
|
33
|
+
|
|
34
|
+
Search for elements by description. Fuzzy matching is enabled by default:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx uicontract find "pause subscription" --json
|
|
38
|
+
npx uicontract find "pase subscribtion" --json # fuzzy match still finds it
|
|
39
|
+
npx uicontract find "billing" --type button --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Target
|
|
43
|
+
|
|
44
|
+
Use the `agentId` from the find result to target the element in the browser.
|
|
45
|
+
|
|
46
|
+
**agent-browser:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agent-browser find testid "settings.billing.pause-subscription.button" click
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**CSS selector (any tool):**
|
|
53
|
+
|
|
54
|
+
```css
|
|
55
|
+
[data-agent-id="settings.billing.pause-subscription.button"]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Playwright MCP:** Launch with `--test-id-attribute=data-agent-id`, then use `ref` values from the accessibility snapshot.
|
|
59
|
+
|
|
60
|
+
See [references/browser-tool-bridge.md](references/browser-tool-bridge.md) for all supported tools.
|
|
61
|
+
|
|
62
|
+
### 4. Verify
|
|
63
|
+
|
|
64
|
+
Compare the current manifest against a baseline to detect breaking changes:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx uicontract diff baseline.json current.json --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Exit code 1 means breaking changes were found.
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
### Discovery
|
|
75
|
+
|
|
76
|
+
**scan** -- Discover interactive elements in source code.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx uicontract scan <directory> -o manifest.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Flag | Description |
|
|
83
|
+
|------|-------------|
|
|
84
|
+
| `-o, --output <path>` | Output file path (default: `manifest.json`) |
|
|
85
|
+
| `--framework <name>` | Force a specific framework parser |
|
|
86
|
+
| `--json` | Write raw JSON to stdout |
|
|
87
|
+
| `--verbose` | Enable debug logging |
|
|
88
|
+
|
|
89
|
+
**name** -- Assign stable hierarchical IDs to discovered elements.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx uicontract name manifest.json -o manifest.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Flag | Description |
|
|
96
|
+
|------|-------------|
|
|
97
|
+
| `-o, --output <path>` | Output file path |
|
|
98
|
+
| `--ai` | Use AI-assisted naming for ambiguous elements |
|
|
99
|
+
| `--ai-timeout <ms>` | Timeout for AI naming requests |
|
|
100
|
+
|
|
101
|
+
**annotate** -- Insert `data-agent-id` attributes into source files.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx uicontract annotate manifest.json --dry-run
|
|
105
|
+
npx uicontract annotate manifest.json --write
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Flag | Description |
|
|
109
|
+
|------|-------------|
|
|
110
|
+
| `--dry-run` | Preview patches without modifying files |
|
|
111
|
+
| `--write` | Apply patches to source files |
|
|
112
|
+
| `--backup-dir <path>` | Directory for pre-annotation backups (default: `.uic-backup/`) |
|
|
113
|
+
|
|
114
|
+
### Query
|
|
115
|
+
|
|
116
|
+
**find** -- Search for elements by description.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx uicontract find <query> --json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Flag | Description |
|
|
123
|
+
|------|-------------|
|
|
124
|
+
| `--manifest <path>` | Path to manifest file (default: `manifest.json`) |
|
|
125
|
+
| `--type <type>` | Filter by element type (`button`, `input`, `a`, etc.) |
|
|
126
|
+
| `--exact` | Disable fuzzy matching, require exact substring |
|
|
127
|
+
| `--json` | Output as JSON array |
|
|
128
|
+
|
|
129
|
+
**describe** -- Show detailed info for one element.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npx uicontract describe <agent-id> --json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Flag | Description |
|
|
136
|
+
|------|-------------|
|
|
137
|
+
| `--manifest <path>` | Path to manifest file (default: `manifest.json`) |
|
|
138
|
+
| `--json` | Output as JSON object |
|
|
139
|
+
|
|
140
|
+
**list** -- List elements with optional filters.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx uicontract list --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| Flag | Description |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `--manifest <path>` | Path to manifest file (default: `manifest.json`) |
|
|
149
|
+
| `--type <type>` | Filter by element type |
|
|
150
|
+
| `--route <route>` | Filter by route prefix |
|
|
151
|
+
| `--component <name>` | Filter by component name |
|
|
152
|
+
| `--routes` | List all unique routes |
|
|
153
|
+
| `--components` | List all unique component names |
|
|
154
|
+
| `--json` | Output as JSON array |
|
|
155
|
+
|
|
156
|
+
### Governance
|
|
157
|
+
|
|
158
|
+
**diff** -- Compare two manifests for breaking changes.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx uicontract diff <old-manifest> <new-manifest> --json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Flag | Description |
|
|
165
|
+
|------|-------------|
|
|
166
|
+
| `--allow-breaking` | Exit 0 even when breaking changes are found |
|
|
167
|
+
| `--json` | Output diff as JSON object |
|
|
168
|
+
|
|
169
|
+
Change categories:
|
|
170
|
+
- **Breaking**: removed elements, renamed agent IDs, changed element types.
|
|
171
|
+
- **Informational**: added elements, changed labels, moved source locations.
|
|
172
|
+
|
|
173
|
+
## Selector Patterns
|
|
174
|
+
|
|
175
|
+
Use these CSS attribute selectors to target `data-agent-id` values:
|
|
176
|
+
|
|
177
|
+
| Pattern | Syntax | Use Case |
|
|
178
|
+
|---------|--------|----------|
|
|
179
|
+
| Exact | `[data-agent-id="id"]` | Target one specific element |
|
|
180
|
+
| Prefix | `[data-agent-id^="settings.billing."]` | All elements in a section |
|
|
181
|
+
| Substring | `[data-agent-id*="billing"]` | Any element mentioning "billing" |
|
|
182
|
+
| Suffix | `[data-agent-id$=".button"]` | All buttons |
|
|
183
|
+
| Presence | `[data-agent-id]` | Any UI Contracts-annotated element |
|
|
184
|
+
|
|
185
|
+
## Integration Example
|
|
186
|
+
|
|
187
|
+
UI Contracts + agent-browser, three steps:
|
|
188
|
+
|
|
189
|
+
**Step 1 -- Find the element:**
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npx uicontract find "pause subscription" --json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Step 2 -- Navigate to the page:**
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
agent-browser open http://localhost:3000/settings/billing
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Step 3 -- Interact:**
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
agent-browser find testid "settings.billing.pause-subscription.button" click
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Key Rules
|
|
208
|
+
|
|
209
|
+
- Always use `--json` for machine parsing. The JSON format is stable across versions.
|
|
210
|
+
- Never hardcode CSS selectors when an agent ID is available.
|
|
211
|
+
- Check `conditional: true` elements -- they may not be in the DOM without the right app state.
|
|
212
|
+
- Check `dynamic: true` elements -- their count depends on runtime data.
|
|
213
|
+
- Run `npx uicontract diff` before and after changes to catch regressions.
|
|
214
|
+
- Run `npx uicontract scan` after UI changes to keep the manifest current.
|
|
215
|
+
- Commit the baseline manifest to version control for CI diffing.
|
|
216
|
+
|
|
217
|
+
## References
|
|
218
|
+
|
|
219
|
+
| File | Contents |
|
|
220
|
+
|------|----------|
|
|
221
|
+
| [references/browser-tool-bridge.md](references/browser-tool-bridge.md) | Tool-specific targeting (agent-browser, Playwright MCP, Chrome MCP, Cypress) |
|
|
222
|
+
| [references/workflow-patterns.md](references/workflow-patterns.md) | Multi-step automation recipes (form fill, nav test, CI regression, annotation pipeline) |
|
|
223
|
+
| [references/manifest-schema.md](references/manifest-schema.md) | Full manifest.json structure, element fields, agent ID format |
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uicontract/skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent skill files for UIC - makes web app UIs machine-readable for AI coding agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "UIC Contributors",
|
|
7
|
+
"homepage": "https://github.com/sherifkozman/uicontract",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/sherifkozman/uicontract.git",
|
|
11
|
+
"directory": "packages/skill"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sherifkozman/uicontract/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"uic",
|
|
18
|
+
"ui-contracts",
|
|
19
|
+
"agent",
|
|
20
|
+
"skill",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"data-agent-id",
|
|
23
|
+
"manifest",
|
|
24
|
+
"automation",
|
|
25
|
+
"claude",
|
|
26
|
+
"cursor",
|
|
27
|
+
"copilot"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"SKILL.md",
|
|
34
|
+
"references",
|
|
35
|
+
"README.md"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Browser Tool Bridge
|
|
2
|
+
|
|
3
|
+
UI Contracts annotates source code with `data-agent-id` attributes. This reference documents how to target those attributes from each browser automation tool, bridging the gap between UI Contracts' manifest output and actual browser interactions.
|
|
4
|
+
|
|
5
|
+
## Tool-Specific Targeting
|
|
6
|
+
|
|
7
|
+
### agent-browser
|
|
8
|
+
|
|
9
|
+
No configuration needed. Use the `find testid` locator to target elements by their agent ID directly.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Click an element
|
|
13
|
+
agent-browser find testid "settings.billing.pause-subscription.button" click
|
|
14
|
+
|
|
15
|
+
# Fill a form field
|
|
16
|
+
agent-browser find testid "login.login-form.email.input" fill "user@example.com"
|
|
17
|
+
|
|
18
|
+
# Check visibility
|
|
19
|
+
agent-browser find testid "settings.billing.pause-subscription.button" visible
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Playwright MCP
|
|
23
|
+
|
|
24
|
+
Configure the `--test-id-attribute` flag at launch so Playwright recognizes `data-agent-id` as the test ID attribute:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx playwright --test-id-attribute=data-agent-id
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
After this configuration, agent IDs appear in accessibility snapshots. Use the `ref` values from the snapshot to interact with elements.
|
|
31
|
+
|
|
32
|
+
### Chrome MCP
|
|
33
|
+
|
|
34
|
+
Use the `find` tool with a natural-language description, or use `javascript_tool` with a CSS selector for precise targeting:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
document.querySelector('[data-agent-id="settings.billing.pause-subscription.button"]')
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Cypress
|
|
41
|
+
|
|
42
|
+
Target elements with the standard attribute selector:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
cy.get('[data-agent-id="settings.billing.pause-subscription.button"]').click()
|
|
46
|
+
cy.get('[data-agent-id="login.login-form.email.input"]').type('user@example.com')
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Any CSS-Based Tool
|
|
50
|
+
|
|
51
|
+
The universal selector pattern works with any tool that supports CSS selectors:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
[data-agent-id="settings.billing.pause-subscription.button"]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Selector Patterns
|
|
58
|
+
|
|
59
|
+
| Pattern | Syntax | Use Case |
|
|
60
|
+
|-----------|-------------------------------------------|---------------------------------------|
|
|
61
|
+
| Exact | `[data-agent-id="id"]` | Target one specific element |
|
|
62
|
+
| Prefix | `[data-agent-id^="settings.billing."]` | All elements in billing section |
|
|
63
|
+
| Substring | `[data-agent-id*="billing"]` | Any element mentioning "billing" |
|
|
64
|
+
| Suffix | `[data-agent-id$=".button"]` | All buttons |
|
|
65
|
+
| Presence | `[data-agent-id]` | Any UI Contracts-annotated element |
|
|
66
|
+
|
|
67
|
+
## Handling Conditional Elements
|
|
68
|
+
|
|
69
|
+
Elements with `"conditional": true` in the manifest may not be present in the DOM at all times. Before targeting a conditional element:
|
|
70
|
+
|
|
71
|
+
1. **Check the route.** The manifest's `route` field tells you which page the element lives on. Navigate there first.
|
|
72
|
+
2. **Wait for render.** After navigation, use the tool's wait or retry mechanism to allow the element to appear.
|
|
73
|
+
3. **Verify visibility.** Confirm the element is present before interacting. If it is not, the required application state (e.g., a modal being open, a feature flag being enabled) may not be met.
|
|
74
|
+
|
|
75
|
+
## Handling Dynamic Elements
|
|
76
|
+
|
|
77
|
+
Elements with `"dynamic": true` in the manifest are rendered from data (e.g., a list of items from an API response). The exact set of elements depends on runtime data. To work with dynamic elements:
|
|
78
|
+
|
|
79
|
+
1. **Use prefix selectors** to discover all instances. For example, `[data-agent-id^="dashboard.task-list.task-"]` matches every task item regardless of how many exist.
|
|
80
|
+
2. **Enumerate first.** Query all matching elements, then filter or iterate over the results.
|
|
81
|
+
3. **Expect variability.** The count of dynamic elements will differ between environments and over time as data changes.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Manifest Schema Reference
|
|
2
|
+
|
|
3
|
+
The manifest is a JSON file produced by running `npx uicontract scan <dir>` followed by `npx uicontract name`. It describes every interactive element in a web application, giving each a stable hierarchical ID that browser automation tools can target.
|
|
4
|
+
|
|
5
|
+
## Top-Level Structure
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
{
|
|
9
|
+
"schemaVersion": "1.0",
|
|
10
|
+
"generatedAt": "2026-02-21T00:00:00Z",
|
|
11
|
+
"generator": { "name": "uicontract", "version": "0.1.0" },
|
|
12
|
+
"metadata": {
|
|
13
|
+
"framework": "react",
|
|
14
|
+
"projectRoot": "/path/to/project",
|
|
15
|
+
"filesScanned": 42,
|
|
16
|
+
"elementsDiscovered": 87,
|
|
17
|
+
"warnings": 3
|
|
18
|
+
},
|
|
19
|
+
"elements": [
|
|
20
|
+
{ /* ... element objects ... */ }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Field | Type | Description |
|
|
26
|
+
|-----------------|--------|------------------------------------------|
|
|
27
|
+
| schemaVersion | string | Schema version, currently `"1.0"` |
|
|
28
|
+
| generatedAt | string | ISO 8601 datetime of manifest generation |
|
|
29
|
+
| generator | object | Tool name and version that produced this |
|
|
30
|
+
| metadata | object | Summary statistics about the scan |
|
|
31
|
+
| elements | array | List of discovered interactive elements |
|
|
32
|
+
|
|
33
|
+
## Metadata
|
|
34
|
+
|
|
35
|
+
| Field | Type | Example | Description |
|
|
36
|
+
|--------------------|--------|------------|------------------------------------|
|
|
37
|
+
| framework | string | `"react"` | Detected framework (`react`, `vue`) |
|
|
38
|
+
| projectRoot | string | `"/app"` | Absolute path to project root |
|
|
39
|
+
| filesScanned | number | `42` | Number of source files scanned |
|
|
40
|
+
| elementsDiscovered | number | `87` | Total interactive elements found |
|
|
41
|
+
| warnings | number | `3` | Count of parser warnings |
|
|
42
|
+
|
|
43
|
+
## Element Fields
|
|
44
|
+
|
|
45
|
+
Each object in the `elements` array describes one interactive element:
|
|
46
|
+
|
|
47
|
+
| Field | Type | Description |
|
|
48
|
+
|---------------|----------------|----------------------------------------------------|
|
|
49
|
+
| agentId | string | Stable hierarchical ID -- the primary selector target |
|
|
50
|
+
| type | string | Element kind: `button`, `input`, `select`, `a`, `form`, `textarea` |
|
|
51
|
+
| label | string\|null | Human-readable description of the element |
|
|
52
|
+
| route | string\|null | URL path where the element appears |
|
|
53
|
+
| handler | string\|null | Event handler function name (e.g. `handleSubmit`) |
|
|
54
|
+
| conditional | boolean | `true` if the element may not always be visible |
|
|
55
|
+
| dynamic | boolean | `true` if rendered from dynamic data |
|
|
56
|
+
| filePath | string | Source file path relative to project root |
|
|
57
|
+
| line | number | Line number in source file (1-indexed) |
|
|
58
|
+
| column | number | Column number in source file (1-indexed) |
|
|
59
|
+
| componentName | string\|null | Name of the React/Vue component containing this element |
|
|
60
|
+
|
|
61
|
+
## Agent ID Format
|
|
62
|
+
|
|
63
|
+
Agent IDs follow the pattern:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
route.component.element-name.type
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
IDs use dot-separated segments with kebab-case within each segment. Examples:
|
|
70
|
+
|
|
71
|
+
- `settings.billing.pause-subscription.button` -- a button in the BillingSettings component on the /settings/billing route
|
|
72
|
+
- `login.login-form.email.input` -- an email input in the LoginForm component on the /login route
|
|
73
|
+
- `dashboard.header.notifications.a` -- a notifications link in the Header component on the /dashboard route
|
|
74
|
+
|
|
75
|
+
The hierarchical structure enables prefix-based selectors. For example, `settings.billing.*` matches every interactive element on the billing settings page, useful for scoping automation tasks to a specific section of the UI.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Workflow Patterns
|
|
2
|
+
|
|
3
|
+
Multi-step automation recipes that chain UI Contracts CLI commands with browser tools. Each recipe is a concrete bash sequence you can adapt to your application.
|
|
4
|
+
|
|
5
|
+
## Recipe 1: Form Fill
|
|
6
|
+
|
|
7
|
+
Scan the manifest for input fields on a specific route, fill each one, then submit.
|
|
8
|
+
|
|
9
|
+
### Step 1 -- Discover inputs on the target route
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx uicontract list --route "/checkout" --type input --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Expected output:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
[
|
|
19
|
+
{ "agentId": "checkout.shipping-form.first-name.input", "type": "input", "label": "First name" },
|
|
20
|
+
{ "agentId": "checkout.shipping-form.last-name.input", "type": "input", "label": "Last name" },
|
|
21
|
+
{ "agentId": "checkout.shipping-form.address.input", "type": "input", "label": "Address" },
|
|
22
|
+
{ "agentId": "checkout.shipping-form.city.input", "type": "input", "label": "City" },
|
|
23
|
+
{ "agentId": "checkout.shipping-form.zip.input", "type": "input", "label": "Zip code" }
|
|
24
|
+
]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Step 2 -- Find the submit button
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx uicontract find "submit order" --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Expected output:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
[
|
|
37
|
+
{ "agentId": "checkout.shipping-form.submit-order.button", "type": "button", "label": "Submit order" }
|
|
38
|
+
]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Step 3 -- Navigate and fill
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Navigate to the checkout page
|
|
45
|
+
agent-browser navigate "https://localhost:3000/checkout"
|
|
46
|
+
|
|
47
|
+
# Fill each field using the agent IDs from Step 1
|
|
48
|
+
agent-browser find testid "checkout.shipping-form.first-name.input" fill "Jane"
|
|
49
|
+
agent-browser find testid "checkout.shipping-form.last-name.input" fill "Doe"
|
|
50
|
+
agent-browser find testid "checkout.shipping-form.address.input" fill "123 Main St"
|
|
51
|
+
agent-browser find testid "checkout.shipping-form.city.input" fill "Springfield"
|
|
52
|
+
agent-browser find testid "checkout.shipping-form.zip.input" fill "62704"
|
|
53
|
+
|
|
54
|
+
# Submit
|
|
55
|
+
agent-browser find testid "checkout.shipping-form.submit-order.button" click
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Recipe 2: Navigation Test
|
|
61
|
+
|
|
62
|
+
Verify that every route in the application has the expected interactive elements present and visible.
|
|
63
|
+
|
|
64
|
+
### Step 1 -- Get all routes
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx uicontract list --routes --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Expected output:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
[
|
|
74
|
+
"/",
|
|
75
|
+
"/login",
|
|
76
|
+
"/dashboard",
|
|
77
|
+
"/settings/billing",
|
|
78
|
+
"/checkout"
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 2 -- Get elements per route
|
|
83
|
+
|
|
84
|
+
For each route, list the elements expected on that page:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx uicontract list --route "/login" --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Expected output:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
[
|
|
94
|
+
{ "agentId": "login.login-form.email.input", "type": "input", "label": "Email" },
|
|
95
|
+
{ "agentId": "login.login-form.password.input", "type": "input", "label": "Password" },
|
|
96
|
+
{ "agentId": "login.login-form.sign-in.button", "type": "button", "label": "Sign in" }
|
|
97
|
+
]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 3 -- Navigate and verify each route
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# For each route, navigate and confirm every non-conditional element is visible
|
|
104
|
+
agent-browser navigate "https://localhost:3000/login"
|
|
105
|
+
|
|
106
|
+
agent-browser find testid "login.login-form.email.input" visible
|
|
107
|
+
agent-browser find testid "login.login-form.password.input" visible
|
|
108
|
+
agent-browser find testid "login.login-form.sign-in.button" visible
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Repeat for every route. Skip elements with `"conditional": true` unless you set up the required application state first (see Browser Tool Bridge for conditional element handling).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Recipe 3: Regression Check (CI)
|
|
116
|
+
|
|
117
|
+
Compare a baseline manifest against the current source to detect breaking changes. Intended for CI pipelines.
|
|
118
|
+
|
|
119
|
+
### Step 1 -- Generate the current manifest
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx uicontract scan ./src -o current.json && npx uicontract name current.json -o current.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Step 2 -- Diff against baseline
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx uicontract diff baseline.json current.json --json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Expected output when there are no breaking changes:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"breaking": [],
|
|
136
|
+
"nonBreaking": [
|
|
137
|
+
{ "type": "added", "agentId": "settings.profile.avatar-upload.input" }
|
|
138
|
+
],
|
|
139
|
+
"summary": { "breaking": 0, "nonBreaking": 1 }
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Expected output when there are breaking changes:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"breaking": [
|
|
148
|
+
{ "type": "removed", "agentId": "checkout.shipping-form.submit-order.button" },
|
|
149
|
+
{ "type": "renamed", "oldAgentId": "login.login-form.sign-in.button", "newAgentId": "login.auth-form.log-in.button" }
|
|
150
|
+
],
|
|
151
|
+
"nonBreaking": [],
|
|
152
|
+
"summary": { "breaking": 2, "nonBreaking": 0 }
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Step 3 -- Fail CI on breaking changes
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx uicontract diff baseline.json current.json --json
|
|
160
|
+
EXIT_CODE=$?
|
|
161
|
+
|
|
162
|
+
if [ $EXIT_CODE -ne 0 ]; then
|
|
163
|
+
echo "Breaking manifest changes detected. Review the diff output above."
|
|
164
|
+
echo "If intentional, update baseline.json: cp current.json baseline.json"
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`uicontract diff` exits with code 1 when breaking changes are found, making it a direct CI gate.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Recipe 4: Full Annotation Pipeline
|
|
174
|
+
|
|
175
|
+
Scan the source, generate stable names, annotate source files with `data-agent-id` attributes, then verify the round-trip produces a stable manifest.
|
|
176
|
+
|
|
177
|
+
### Step 1 -- Scan
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx uicontract scan ./src -o manifest.json
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Step 2 -- Name
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npx uicontract name manifest.json -o manifest.json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Step 3 -- Preview annotations (dry run)
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npx uicontract annotate manifest.json --dry-run
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Expected output shows the patches that would be applied:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
src/components/LoginForm.tsx:12:8
|
|
199
|
+
- <input type="email" />
|
|
200
|
+
+ <input type="email" data-agent-id="login.login-form.email.input" />
|
|
201
|
+
|
|
202
|
+
src/components/LoginForm.tsx:18:8
|
|
203
|
+
- <button onClick={handleSubmit}>Sign in</button>
|
|
204
|
+
+ <button onClick={handleSubmit} data-agent-id="login.login-form.sign-in.button">Sign in</button>
|
|
205
|
+
|
|
206
|
+
2 files, 5 elements to annotate.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Step 4 -- Write annotations
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx uicontract annotate manifest.json --write
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
This modifies source files in place. A backup is created in `.uic-backup/` before any changes are written.
|
|
216
|
+
|
|
217
|
+
### Step 5 -- Re-scan and verify round-trip
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npx uicontract scan ./src -o manifest-after.json && npx uicontract name manifest-after.json -o manifest-after.json
|
|
221
|
+
npx uicontract diff manifest.json manifest-after.json --json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Expected output for a successful round-trip (zero changes):
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"breaking": [],
|
|
229
|
+
"nonBreaking": [],
|
|
230
|
+
"summary": { "breaking": 0, "nonBreaking": 0 }
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A zero-change diff confirms that annotations were inserted correctly and the re-scan produces the same manifest. If the diff reports changes, the annotator may have altered element positions or the parser may be interpreting annotated source differently than unannotated source -- both are bugs worth investigating.
|