qiksy-mcp 1.2.0 → 1.4.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/AGENT-GUIDE.md +143 -9
- package/README.md +77 -11
- package/package.json +7 -2
- package/server.mjs +3766 -208
- package/widgets.mjs +604 -0
package/AGENT-GUIDE.md
CHANGED
|
@@ -61,31 +61,165 @@ inside the existing (even backgrounded) tab.
|
|
|
61
61
|
consent in the popup.** A refusal there is the human's to clear; report it in one
|
|
62
62
|
sentence rather than working around it.
|
|
63
63
|
|
|
64
|
-
`qa_click` · `qa_type` · `qa_type_many` · `
|
|
65
|
-
`
|
|
64
|
+
`qa_click` · `qa_type` · `qa_type_many` · `qa_fill_json` · `qa_pick_date` · `qa_pick_path` ·
|
|
65
|
+
`qa_pick_tags` · `qa_pick_range` · `qa_pick_time` · `qa_probe` · `qa_upload` · `qa_press` · `qa_wait_ready` ·
|
|
66
|
+
`qa_navigate` · `qa_open_isolated` · `qa_close_tab` · `qa_recipe_apply`
|
|
67
|
+
|
|
68
|
+
**Local, no browser involved.** `qa_recipe_save` · `qa_recipe_list` — the saved flows on
|
|
69
|
+
this machine (see *Forms* below).
|
|
66
70
|
|
|
67
71
|
---
|
|
68
72
|
|
|
73
|
+
## Filling a form — one call per step, not one per field
|
|
74
|
+
|
|
75
|
+
This is where the most time gets lost here, so it is worth stating plainly.
|
|
76
|
+
|
|
77
|
+
`qa_type` is **not** a keystroke emulator. It is the extension's own fill engine, so one
|
|
78
|
+
call handles a text input, a native `<select>`, a checkbox, a slider, rich text — **and**
|
|
79
|
+
the controls that look like they need clicking: Radix/shadcn, MUI, Ant and react-select
|
|
80
|
+
dropdowns, typeahead comboboxes, popup date and time pickers. It opens the popover, waits
|
|
81
|
+
for the options, picks the one matching your value and verifies the trigger changed.
|
|
82
|
+
`qa_type_many` takes an array of exactly those; a batch where half the entries are
|
|
83
|
+
dropdowns and dates is the normal use, not an edge case.
|
|
84
|
+
|
|
85
|
+
So one step of a wizard is about three calls:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
qa_snapshot { fields_only: true } → the step's controls, not the app
|
|
89
|
+
qa_type_many { fields: [ …every field of the step… ] } → widgets and all, in order
|
|
90
|
+
qa_click { name: "Next" } → its `changed` says it advanced
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Opening a dropdown, snapshotting its options and clicking one costs three round trips and
|
|
94
|
+
two settle windows **per field**. Keep it for an entry the batch reported `ok: false`.
|
|
95
|
+
Uploads are the one exception to "one call": `qa_upload` takes `files[]` for the whole step.
|
|
96
|
+
|
|
97
|
+
**Five widget shapes need their own verb, because one value takes several interactions.**
|
|
98
|
+
`qa_pick_date { name, date: "14/03/1987" }` sets an exact date whatever library owns the calendar —
|
|
99
|
+
it types first and steers the picker only if that fails (MUI X year view, Ant's header year,
|
|
100
|
+
react-datepicker's dropdowns, bare arrows). `qa_pick_path { name, path: ["Ukraine","Kyiv oblast","Kyiv"] }`
|
|
101
|
+
walks a cascader or a drill-down level by level. `qa_pick_tags { name, tags: [...] }` adds chips to
|
|
102
|
+
a control that CREATES its values (Ant `mode="tags"`): it types, **verifies the text is still in the
|
|
103
|
+
field**, then presses Enter — because a commit clears the component's own search input, and Enter on
|
|
104
|
+
a wiped field re-submits the previous text, which toggles that tag back OFF. `qa_pick_range { name,
|
|
105
|
+
from, to }` sets both ends of a range that lives in one control, walking start → end panel without
|
|
106
|
+
re-opening. `qa_pick_time { name, time: "14:30" }` drives a column picker whose cells have no role at
|
|
107
|
+
all. All five are ONE call and all five re-snapshot between steps, which is what makes them work
|
|
108
|
+
where a batch cannot.
|
|
109
|
+
|
|
110
|
+
**If you already hold the data, skip the mapping.** `qa_fill_json { fields: { "Initiative
|
|
111
|
+
name": "Test run", "Country": "Ukraine", "I agree": true } }` — labels instead of refs, one
|
|
112
|
+
call, and it snapshots and resolves for you. That is the same JSON shape the panel's `{ }`
|
|
113
|
+
editor and its ⚡Fill button use, so a dataset you build reads the way a human would edit it.
|
|
114
|
+
Keys that matched nothing come back with the page's real field names, so the next call fixes
|
|
115
|
+
itself. Use `qa_snapshot` + `qa_type_many` instead when you must pick one specific control
|
|
116
|
+
among duplicates.
|
|
117
|
+
|
|
118
|
+
Custom widgets are still filled strictly one at a time — two open popovers break each
|
|
119
|
+
other — so a step with several dropdowns takes a few seconds of genuine waiting. That part
|
|
120
|
+
is the widgets, not the bridge.
|
|
121
|
+
|
|
122
|
+
## The fast route: put the value straight in
|
|
123
|
+
|
|
124
|
+
`qa_type { raw: true }` writes the string VERBATIM into the control's own text input — through the
|
|
125
|
+
native value setter, which is what React's tracked `value` property requires — and commits it with a
|
|
126
|
+
key. Measured on a live stand: a date **0.30s this way against 26s** driven through its calendar, a
|
|
127
|
+
time 0.30 against 14, a range 0.60 against 35.
|
|
128
|
+
|
|
129
|
+
It is for controls that **parse** text: dates, times, range ends, tags, a creatable or async select.
|
|
130
|
+
Pass the value in the format the control DISPLAYS (`06/11/2024`, `14:30`, the option text), not ISO.
|
|
131
|
+
|
|
132
|
+
- `commit` — the key that commits, default `Enter`. Pass `""` for a plain input, where Enter submits
|
|
133
|
+
the form.
|
|
134
|
+
- `blur: true` — a tags field keeps its value only once focus LEAVES.
|
|
135
|
+
- `pick: true` — write the text to filter, then CLICK the option that matches. For a select the text
|
|
136
|
+
alone never chooses; this is the other half.
|
|
137
|
+
- `open: true` — click the control first. A select filters only while its list is open.
|
|
138
|
+
|
|
139
|
+
A select that must **choose a row from its own list** is not a raw case: leave the flag off and let
|
|
140
|
+
the fill engine do it (it opens the list, waits, picks the match, checks the trigger changed). Ant
|
|
141
|
+
Design in particular commits its selection its own way and ignores an option click from outside the
|
|
142
|
+
page's own context.
|
|
143
|
+
|
|
144
|
+
`qa_type_many` and `qa_fill_json` take these per entry and set them for you: raw for what parses,
|
|
145
|
+
the engine for what chooses. Raw entries go in ONE pass — they open nothing, so they cannot collide.
|
|
146
|
+
|
|
147
|
+
**And when the widget is what you are testing, do not use this.** Driving the popup the way a user
|
|
148
|
+
does is the honest thing then; the visual side belongs to a real browser driver that can see pixels.
|
|
149
|
+
|
|
150
|
+
## You are told what a widget IS — don't work it out by trial and error
|
|
151
|
+
|
|
152
|
+
Every session before this one re-derived the same facts by failing at them. They are in the
|
|
153
|
+
replies now, in three places, all free:
|
|
154
|
+
|
|
155
|
+
- **`qa_snapshot`** → `componentLibraries` + `hints` (which UI library draws this page, and what
|
|
156
|
+
that means: an Ant day is a role-less `<td title="2026-06-28">`, a MUI date field cannot be
|
|
157
|
+
written to at all, a react-select value must be the option TEXT) and **`widgets`** — the notable
|
|
158
|
+
kinds on the page, each with the verb that drives it and the fields it applies to.
|
|
159
|
+
- **`qa_click` / `qa_press`** → `widget`, the verdict on what your click just opened, read off the
|
|
160
|
+
diff: `calendar-popup` → `qa_pick_date` · `option-list` → it is a select, so pass a value instead
|
|
161
|
+
of clicking · `tree-popup` → click the CHECKBOX via `qa_pick_path` · `modal` → snapshot `within`
|
|
162
|
+
the dialog · `navigated` → the step swapped, every ref you hold is void ·
|
|
163
|
+
**`portalled-invisible`** → it opened and the tree gained NOTHING, so the popup is role-less
|
|
164
|
+
markup in a portal: address its parts by CSS selector, which every verb accepts.
|
|
165
|
+
- **`qa_probe { name }`** → for the ONE control that refused a value, and the only route into a
|
|
166
|
+
widget hand-rolled from `<div>`s (role `generic`, no name, no state). Shape first — a sectioned
|
|
167
|
+
date field, a file input, a switch and a slider are settled with **no click at all** — then one
|
|
168
|
+
click, read the diff, Escape. Do not probe every field: the snapshot already classified the page.
|
|
169
|
+
|
|
170
|
+
**Then save what you learned.** A long flow discovered once should not be discovered twice:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
qa_recipe_save { name: "New initiative" } → ~/.qiksy/recipes/new-initiative.json
|
|
174
|
+
qa_recipe_list → what is already saved for this host
|
|
175
|
+
qa_recipe_apply { name: "New initiative", step: 1, dry_run: true } → does it still fit?
|
|
176
|
+
qa_recipe_apply { name: "New initiative", all: true, values: {...} } → replay it
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The recipe stores field NAMES, not refs (a ref lives for one snapshot; a name survives a
|
|
180
|
+
re-render and a new session), and the replay re-matches them against the page as it is now.
|
|
181
|
+
`values` swaps the data, so one saved flow becomes many runs. `dry_run` reads only. With
|
|
182
|
+
`all` it clicks each advance control — including a final Submit, so keep it stepwise on
|
|
183
|
+
anything irreversible. Tell the human where the file is: it is theirs to edit or delete.
|
|
184
|
+
|
|
69
185
|
## How to drive it well
|
|
70
186
|
|
|
71
187
|
- **Target by `ref` from a fresh snapshot.** A `ref` is unique where a CSS selector is
|
|
72
188
|
ambiguous. After anything that navigates or re-renders, call `qa_wait_ready` and take
|
|
73
189
|
a **new** snapshot — old refs go stale.
|
|
190
|
+
- **A ref belongs to ONE snapshot.** Taking another renumbers them: `e12` may now be a
|
|
191
|
+
different control, and clicking it will *succeed* — on the wrong thing. Discard the old
|
|
192
|
+
refs, or target by `name` (`qa_click { name: "Next" }`, `qa_fill_json`), which survives
|
|
193
|
+
both a re-render and a renumbering. This pressed **Back instead of Next** the first time
|
|
194
|
+
it ran against a real wizard.
|
|
195
|
+
- **Narrow the snapshot on a big app.** `fields_only`, `roles`, `match`, `within` (the
|
|
196
|
+
selector of a container node, for one dialog or step), `format: "tree"`. Pulling the whole
|
|
197
|
+
tree on every step of a form is the other half of a slow run. Filtering never invalidates
|
|
198
|
+
a ref.
|
|
74
199
|
- **Address a specific tab** with `tabId` from `qa_tabs`; omit it to act on the active
|
|
75
200
|
one. Tabs from `qa_open_isolated` are separate logins on the same site — that is how
|
|
76
201
|
you drive several accounts at once.
|
|
77
202
|
- **After a submit**, `qa_wait_ready` before the next call: the old document (and Qiksy
|
|
78
203
|
inside it) is gone, and the next call would land on a page still being replaced.
|
|
79
|
-
- **Autocomplete
|
|
80
|
-
|
|
81
|
-
the
|
|
204
|
+
- **Autocomplete: give `qa_type` the option text first.** It waits for the suggestion list
|
|
205
|
+
itself (they arrive asynchronously, ~0.4s on travel sites) and picks the match. Only when
|
|
206
|
+
it answers `ok: false` do you fall back to type → wait → snapshot → click the option, and
|
|
207
|
+
even then check the trigger's text before assuming the choice committed.
|
|
82
208
|
|
|
83
209
|
## What it cannot do — say so, don't substitute
|
|
84
210
|
|
|
85
|
-
- **No screenshots.** `qa_snapshot` is a tree, not pixels.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
211
|
+
- **No screenshots.** `qa_snapshot` is a tree, not pixels. A judgement that genuinely needs
|
|
212
|
+
to SEE the rendering — is this the right shade, does that shadow look right — cannot be
|
|
213
|
+
made from here.
|
|
214
|
+
- **No arbitrary JavaScript.** No `evaluate`, by design (a Web Store requirement). Every verb
|
|
215
|
+
is a closed vocabulary: you name an element, never a program.
|
|
216
|
+
- **Styles, geometry and web storage ARE available** — `qa_styles` (computed styles, the box,
|
|
217
|
+
and the CSS custom properties in scope), `qa_snapshot({geometry:true})` (every node's box in
|
|
218
|
+
CSS pixels), `qa_storage` (localStorage / sessionStorage, read free, write behind Agent
|
|
219
|
+
control). This guide used to list all three as impossible. That was a wrong inference from
|
|
220
|
+
the no-eval rule, and it cost real capability: an agent told a thing cannot be done does not
|
|
221
|
+
try it. So: no pixels, but plenty of numbers — "this button renders #3B82F6 at 15px with
|
|
222
|
+
22px padding, and its box overlaps the one next to it" needs no screenshot at all.
|
|
89
223
|
- **No `<iframe>` contents.** Payment forms (Stripe, 3-D Secure) live in frames the
|
|
90
224
|
content script does not enter. Fill up to them, not inside them.
|
|
91
225
|
- **No captcha.** Stop and hand it back.
|
package/README.md
CHANGED
|
@@ -22,16 +22,19 @@ unless the licence (or the 14-day trial) is valid, so once a trial lapses *every
|
|
|
22
22
|
tool fails — `qa_export` included — with a message saying so. Do not plan a
|
|
23
23
|
free-tier integration around read-only tools.
|
|
24
24
|
|
|
25
|
-
**npm carries `qiksy-mcp@1.0
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`
|
|
30
|
-
`
|
|
25
|
+
**npm carries `qiksy-mcp@1.3.0`** — the current build, with all thirty-two tools
|
|
26
|
+
including a filterable full-page `qa_snapshot`, `qa_type_many` (a whole form —
|
|
27
|
+
custom dropdowns and date pickers included — in one call), recipes that make the
|
|
28
|
+
second run cheap (`qa_recipe_save` / `qa_recipe_list` / `qa_recipe_apply`), and
|
|
29
|
+
Agent control (`qa_click`, `qa_type`, `qa_press`, `qa_navigate`,
|
|
30
|
+
`qa_open_isolated`, `qa_close_tab`). Just `npx qiksy-mcp` — no clone, no local
|
|
31
|
+
path, no version to pin (it always fetches `latest`). The 1.3.0 additions are all
|
|
32
|
+
server-side, so they work with the extension already installed. (For hacking on
|
|
33
|
+
the bridge itself, see **Local run** at the bottom.)
|
|
31
34
|
|
|
32
35
|
## Tools
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
Thirty-two in total. **Read** — sense the page (all accept an optional `tabId` from `qa_tabs`; default = the tabs you attached to the agent session, or the active tab if you attached none):
|
|
35
38
|
|
|
36
39
|
| Tool | What it returns |
|
|
37
40
|
|---------------|-----------------|
|
|
@@ -39,7 +42,7 @@ Eighteen in total. **Read** — sense the page (all accept an optional `tabId` f
|
|
|
39
42
|
| `qa_status` | URL + error/warning/form counts + isolated-login name. |
|
|
40
43
|
| `qa_findings` | Findings (with selectors + detail); `severity` = `all` \| `error` \| `warning`. |
|
|
41
44
|
| `qa_export` | The full `qa-export/v1` bundle: findings, failed requests + bodies, form structure, repro steps, env. |
|
|
42
|
-
| `qa_snapshot` | A full-page accessibility snapshot — interactive elements + landmarks with their roles, labels and a
|
|
45
|
+
| `qa_snapshot` | A full-page accessibility snapshot — interactive elements + landmarks with their roles, labels and a `ref` you can pass to `qa_click`/`qa_type`/`qa_press` instead of a CSS selector. Read-only. **One snapshot owns its refs**: taking a new one renumbers them, so `e12` from an earlier snapshot may now be a different control and the click will succeed on the wrong thing — keep only the latest snapshot's refs, or target by `name`. **Narrow it** on a component-heavy app: `fields_only` (form controls only), `roles: ["button"]`, `match` on the accessible name, `within: "<selector of a container>"` for one dialog or wizard step, `limit`, `format: "tree"` for about half the bytes. Filtering happens on the way out, so the refs you get back are as valid as the full tree's. |
|
|
43
46
|
|
|
44
47
|
**Command** — drive Qiksy's own UI / analysis (never the app under test; all accept `tabId`):
|
|
45
48
|
|
|
@@ -57,10 +60,66 @@ Because tools accept a `tabId`, an agent can read **several logins at once**: ca
|
|
|
57
60
|
|
|
58
61
|
| Tool | What it does |
|
|
59
62
|
|------------|--------------|
|
|
60
|
-
| `qa_click` | Click an element by CSS selector. Sends the full pointer sequence (`pointerdown` → `mouseup` → `click`), so Radix/shadcn/MUI components that listen for `pointerdown` react like they would to a real click. Scrolls into view first; refuses a selector that matches nothing or has zero size instead of silently doing nothing. |
|
|
61
|
-
| `qa_type` | Set
|
|
62
|
-
| `
|
|
63
|
+
| `qa_click` | Click an element by `ref`, by CSS selector, or by the control's accessible **`name`** (`{ name: "Next", role: "button" }`). Use the name right after a fill: a fill re-renders the step, and a ref from before it either gets refused as stale or — worse, after any new snapshot — silently means a different control. The bridge resolves a name against a snapshot it takes at that moment. Sends the full pointer sequence (`pointerdown` → `mouseup` → `click`), so Radix/shadcn/MUI components that listen for `pointerdown` react like they would to a real click. Scrolls into view first; refuses a selector that matches nothing or has zero size instead of silently doing nothing. |
|
|
64
|
+
| `qa_type` | Set ONE field of **any kind** to a value. This is the extension's own fill engine, not a keystroke emulator, so the same call covers text/number/date inputs and textareas (native setter + `InputEvent`, so React/Vue controlled inputs keep the value), native `<select>` (matched against the option labels), checkboxes and radios, sliders, contenteditable rich text — **and** the widgets that look like they need clicking: Radix/shadcn, MUI, Ant, react-select dropdowns, typeahead comboboxes, popup date & time pickers. It opens the popover, waits for the options, picks the match and verifies the trigger changed. Reports `stuck: false` when the field rejected the value — masked, read-only or controlled. |
|
|
65
|
+
| `qa_type` + `raw` | **Put the value straight in.** Writes the string verbatim through the native value setter (React swaps `value` for its own tracked property, so a plain assignment is reverted on the next render) and commits with a key. Measured: a date **0.30s against 26s** through its calendar, a time 0.30 against 14, a range 0.60 against 35. For controls that PARSE text — dates, times, range ends, tags — in the format the control DISPLAYS. `pick: true` adds the other half for a select: write to filter, then click the matching option. `open: true` opens the list first (a select filters only while it is up); `blur: true` for a tags field, which keeps its value only once focus leaves. A select that must choose from its own list keeps the ordinary engine path. |
|
|
66
|
+
| `qa_type_many` | Fill a WHOLE form, or one step of a wizard, in ONE call: an array of `{ ref \| selector, value, action? }`. **Every widget kind `qa_type` drives goes in the same batch** — a batch where half the entries are dropdowns and dates is the normal use, not an edge case. Prefer `ref`: a form built from a repeated component gives several inputs the identical cssPath, and a batch of selectors would pour every value into the first match. Returns a per-field result list + filled/total; only an entry that came back `ok:false` is worth a follow-up call. |
|
|
67
|
+
| `qa_fill_json` | Fill a form from a plain `{ "Field label": "value" }` object — a value of **`"click"`** presses the thing instead (a chip, a custom radio, a switch, any control that takes no value), and **`"Label#2"`, `"Label#3"`** address the 2nd and 3rd control sharing one label, which is how a form built from a repeated component is filled — **the same shape as the JSON behind the panel's `{ }` editor and its ⚡Fill button**. It snapshots for you, resolves the labels (exact → truncated → contains) and hands the whole lot to the batch filler, so the agent needs no refs and no snapshot of its own: one call for a form it has never seen. Keys that matched nothing come back **with the page's real field names**, so the next call fixes its own key. Keys are read in order, so on repeated labels the Nth key takes the Nth control. `dry_run` resolves without filling. |
|
|
68
|
+
| `qa_upload` | Attach a file to an `<input type=file>`, including the hidden one behind a styled dropzone: `path` (this process reads it, so the bytes never enter the agent's context), `content` as base64, or a hostile `fixture` built in the page. `files: [...]` attaches several in one round trip. |
|
|
69
|
+
| `qa_pick_date` | An EXACT date in one call, whatever library owns the calendar. Types it first (two calls, and correct for most inputs); if the value does not stick, opens the picker and steers it — MUI X's year view, Ant's clickable header year, react-datepicker's month/year dropdowns, plain arrows otherwise — stepping until the header agrees, then clicking the day. Reports `via` and a `trail`, so an unrecognised picker is visible rather than silent. **Use it for MUI X date fields**: they are three contenteditable sections the component rebuilds from its own state, so a written "14" can land in the YEAR and the day stay empty. |
|
|
70
|
+
| `qa_pick_path` | A value that lives across several levels, in one call: an Ant Cascader (country → region → city), a submenu chain, a drill-down. Opens the control and clicks each level on a FRESH snapshot, because opening a level is what re-renders the popup. A level that never appears is reported with the ones that did. |
|
|
71
|
+
| `qa_pick_tags` | A whole list of tags on a control that CREATES its values — Ant's Select in `mode="tags"`, and every chips input built the same way. Per tag it types, **verifies the text is still in the field**, then presses the key, on a ref re-resolved each time. The verification is the whole point: when a tag commits the component clears its own search input, the re-render can wipe a value written a moment earlier, and the key then re-submits the PREVIOUS text — which in tags mode toggles that tag back **off**, so the form loses the tag you added first. It also never clicks the field first (a click moves the caret out of the search input). |
|
|
72
|
+
| `qa_pick_time` | A time on a column-based picker (Ant's TimePicker). Needed because the cells are bare list items with NO role — invisible to the snapshot — and the ordinary fill path misreads the control as a date field, types an ISO date into it and leaves it empty *while reporting success*. This reads each column's text, finds which entry is your value, clicks that child, then presses the panel's own OK — which is not optional: without it the value stays provisional. |
|
|
73
|
+
| `qa_pick_range` | Both ends of a date range that live in ONE control (Ant's RangePicker). Not two `qa_pick_date` calls: the control walks from the start panel to the end panel itself, and approaching it cold a second time restarts the range on the start input. Says so plainly when the panel closes after the start — then the end is its own field. |
|
|
63
74
|
| `qa_press` | Dispatch a key (`Enter`, `Escape`, `Tab`, …). Without a selector it goes to the focused element, like a real keyboard. |
|
|
75
|
+
| `qa_probe` | **What kind of widget is this, and how do I drive it?** — for the one control that refused a value. Judges by SHAPE first (role, tag, name, state, neighbours), which settles a sectioned date field, a file input, a switch or a slider **without touching the page**; when the shape says nothing — the case of a widget hand-rolled from `<div>`s, where role is `generic` and there is no name and no state — it clicks once, reads what the page DID, and presses Escape to put it back. Returns the `kind`, the `verb` that drives it, and `why`. The verdict worth the most is `portalled-invisible`: it opened and the accessibility tree gained NOTHING, which means role-less markup in a portal (Ant Design) and the parts must be addressed by CSS selector. |
|
|
76
|
+
|
|
77
|
+
**One step of a multi-step form is about three calls**, and this is the single biggest
|
|
78
|
+
difference between a fast run and a slow one:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
qa_snapshot { fields_only: true, roles: ["button"] } → the step's controls, not the whole app
|
|
82
|
+
qa_type_many { fields: [ …the entire step… ] } → dropdowns, dates and inputs together
|
|
83
|
+
qa_click { ref: <Next> } → its `changed` says the step advanced
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**When you already hold the data**, skip the mapping entirely — `qa_fill_json { fields: { "Initiative
|
|
87
|
+
name": "Test run", "Country": "Ukraine", "Start date": "2026-08-01", "I agree": true } }` is the whole
|
|
88
|
+
step in one call, labels instead of refs. Keep `qa_snapshot` + `qa_type_many` for when you must pick a
|
|
89
|
+
specific control among duplicates.
|
|
90
|
+
|
|
91
|
+
Opening a dropdown, snapshotting to read its options and clicking one works, but it pays
|
|
92
|
+
a settle window twice per field and costs three round trips where the batch costs a share
|
|
93
|
+
of one. Keep it for an entry the batch reported `ok:false`.
|
|
94
|
+
|
|
95
|
+
**Recipes** — so the second run is the cheap one. Refs live for one snapshot and a new
|
|
96
|
+
session starts with nothing, which is why a form that took twenty calls to work out used to
|
|
97
|
+
take twenty again. These three keep the flow on your machine, in field NAMES:
|
|
98
|
+
|
|
99
|
+
| Tool | What it does |
|
|
100
|
+
|------|--------------|
|
|
101
|
+
| `qa_recipe_save` | Writes what the agent just filled, uploaded and clicked to `~/.qiksy/recipes/<name>.json`, folded into steps (a click after a group of fills is what ends a step of a wizard). Pass `steps` to write or correct one by hand. Local only — no browser, no Pro. |
|
|
102
|
+
| `qa_recipe_list` | The saved flows, newest first: host, step count, the first step's field names. Worth one call before starting anything form-heavy. |
|
|
103
|
+
| `qa_recipe_apply` | Replays a recipe against the page as it is **now** — per step it snapshots, matches every field by accessible name (exact → truncated → contains → the stored cssPath), fills the step in one batch, attaches its uploads and clicks the advance control. `values` overrides the stored data (one flow, many datasets); `dry_run: true` resolves everything and touches nothing; `all: true` walks the remaining steps — which means it will click a final Submit, so keep it stepwise on anything irreversible. A target the page no longer has comes back under `missing` instead of failing silently. |
|
|
104
|
+
|
|
105
|
+
The recipe is a plain JSON file you own — editable, deletable, never uploaded.
|
|
106
|
+
`QIKSY_RECIPES_DIR` moves the folder (e.g. into the project, to share flows with a team).
|
|
107
|
+
|
|
108
|
+
### Qiksy talking to you
|
|
109
|
+
|
|
110
|
+
Now and then a tool result carries `qiksySays` — a line for the **human**, which the agent is asked
|
|
111
|
+
to pass on in your language. It says hello the first time, and after that it speaks only when
|
|
112
|
+
something happened: your agent went quiet for a long time, the same control got clicked three times,
|
|
113
|
+
a couple of fields refused their values in a row, or a long form is worth saving as a recipe. It is
|
|
114
|
+
blunt and it swears mildly, and it is rationed on purpose — an 8-minute floor, a 10–30 minute gap
|
|
115
|
+
per kind of remark, five lines per session, so 40-odd calls produce at most one.
|
|
116
|
+
|
|
117
|
+
| Variable | Effect |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `QIKSY_MCP_VOICE=rude` | Default. The character, as described above. |
|
|
120
|
+
| `QIKSY_MCP_VOICE=nice` | Same moments, polite wording. **Chosen automatically in CI** (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `TEAMCITY_VERSION`, `JENKINS_URL`) so a pipeline log and a customer demo stay strictly business. |
|
|
121
|
+
| `QIKSY_MCP_VOICE=off` | Nothing addressed to the human at all. |
|
|
122
|
+
| `QIKSY_MCP_QUIPS=0` | Silences the voice *and* the decorative `qx …` line on every result. |
|
|
64
123
|
|
|
65
124
|
**Chaining** — what makes a multi-page, multi-role run reliable:
|
|
66
125
|
|
|
@@ -253,3 +312,10 @@ Logs go to **stderr** (stdout is the JSON-RPC channel). You should see `bridge l
|
|
|
253
312
|
- **One tab at a time**: tools read the *active* tab in the last-focused Chrome window. Focus the app under test.
|
|
254
313
|
- **Service-worker sleep**: if Chrome idles the extension's worker, the socket drops and reconnects automatically (≤~30 s). If a tool times out, retry.
|
|
255
314
|
- **Security**: bound to `127.0.0.1` only; connections without the matching token (or from a non-extension origin) are closed before any data is sent.
|
|
315
|
+
|
|
316
|
+
## Follow the build
|
|
317
|
+
|
|
318
|
+
We build Qiksy in the open — what shipped, what broke, what's next.
|
|
319
|
+
|
|
320
|
+
- **LinkedIn** — [linkedin.com/company/qiksy](https://www.linkedin.com/company/qiksy)
|
|
321
|
+
- **Site** — [qiksy.app](https://qiksy.app)
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qiksy-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "MCP bridge for the Qiksy browser extension — expose live QA findings, forms, network and session to any MCP-capable coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"qiksy-mcp": "server.mjs"
|
|
8
8
|
},
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"server.mjs",
|
|
11
|
+
"widgets.mjs",
|
|
12
|
+
"README.md",
|
|
13
|
+
"AGENT-GUIDE.md"
|
|
14
|
+
],
|
|
10
15
|
"engines": {
|
|
11
16
|
"node": ">=18"
|
|
12
17
|
},
|