qiksy-mcp
Browser MCP server for the Chrome tab you already have open — your session, your logins. Gives Claude Code, Cursor, Codex and VS Code the live page: findings, forms, failed requests with server bodies.
npm · fingerprint 0f291f808c205d90cb8a3568 · repository · RSS
What changed
Changed 2026-10-17 10:47 UTC
31 descriptions rewritten, 1 schema or annotation change with the description left byte-identical.
--- pinned/qa_click +++ observed/qa_click { - "description": "Click an element on the page under test — by a qa_snapshot ref, by a CSS selector, or by the control's accessible `name` (the one to use right after a fill, since filling re-renders the step and staleness is what makes a ref refuse). Sends the full pointer sequence (pointerdown → mouseup → click), so component libraries that listen for pointerdown — Radix, shadcn, MUI — react to it like a real click. Scrolls the element into view first, and refuses a target that matches nothing or has zero size rather than silently doing nothing. Returns what the click CHANGED — what appeared, disappeared or updated once the page settled — so you do not need a follow-up qa_snapshot to find out whether it did anything. `changed: null` means the page did not move, which is itself the answer. On a recent extension build the reply also carries `announced` — what the page's live regions (aria-live / role=status / role=alert) actually spoke during the settle window, or null if they stayed silent: a toast that appeared in `changed` but not in `announced` is invisible to a screen reader (WCAG 4.1.3). AND WHEN THE PRESS NAVIGATED there is no diff to give: the reply carries `navigated: <the new url>` instead. That IS what the press did — do not press again, a second one would land on the NEXT page rather than repeat this one. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Click an element on the page under test — by a qa_snapshot ref, by a CSS selector, or by the control's accessible `name` (the one to use right after a fill, since filling re-renders the step and staleness is what makes a ref refuse). Sends the full pointer sequence (pointerdown → mouseup → click), so component libraries that listen for pointerdown — Radix, shadcn, MUI — react to it like a real click. Scrolls the element into view first, and refuses a target that matches nothing or has zero size rather than silently doing nothing. Returns what the click CHANGED — what appeared, disappeared or updated once the page settled — so you do not need a follow-up qa_snapshot to find out whether it did anything. `changed: null` means the page did not move, which is itself the answer. On a recent extension build the reply also carries `announced` — what the page's live regions (aria-live / role=status / role=alert) actually spoke during the settle window, or null if they stayed silent: a toast that appeared in `changed` but not in `announced` is invisible to a screen reader (WCAG 4.1.3). AND WHEN THE PRESS NAVIGATED there is no diff to give: the reply carries `navigated: <the new url>` instead. That IS what the press did — do not press again, a second one would land on the NEXT page rather than repeat this one. BEFORE READING AN EMPTY DIFF AS A DEAD CONTROL, look for `tab` in the reply: it is there only when this tab was not in front, and `state: \"hidden\"` means Chrome had stopped painting it, so a popover could not render and nothing on an animation frame ran. Call qa_focus_tab and click again rather than filing anything. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Click an element (Pro)" + "title": "Click an element" }
--- pinned/qa_click_point +++ observed/qa_click_point { - "description": "Click at an x,y POINT instead of at an element — for applications that draw their interface inside a canvas, where there is nothing to name: Figma, Miro, maps, charts, diagram and image editors. Everywhere else use qa_click: it names what it pressed, survives a re-render and tells you when it missed, none of which a coordinate can do. Coordinates are CSS pixels from the top-left of the VIEWPORT, not of the page — the same frame a qa_screenshot is taken in, so reading a position off a screenshot works; scroll the target into view first. The reply names the element the browser found under the point, and carries the usual `changed` diff, so you can tell a press that did something from one that landed on empty canvas. HONEST LIMIT, and it is not theoretical: these events carry `isTrusted: false`. Anything that checks it will ignore the click and say nothing — measured on npmjs, where a React checkbox took the synthetic click and reverted it on the next render while a real one held. Canvas applications do not check it, which is why this verb is for them. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Click at an x,y POINT instead of at an element — for applications that draw their interface inside a canvas, where there is nothing to name: Figma, Miro, maps, charts, diagram and image editors. Everywhere else use qa_click: it names what it pressed, survives a re-render and tells you when it missed, none of which a coordinate can do. Coordinates are CSS pixels from the top-left of the VIEWPORT, not of the page — the same frame a qa_screenshot is taken in, so reading a position off a screenshot works; scroll the target into view first. The reply names the element the browser found under the point, and carries the usual `changed` diff, so you can tell a press that did something from one that landed on empty canvas. HONEST LIMIT, and it is not theoretical: these events carry `isTrusted: false`. Anything that checks it will ignore the click and say nothing — measured on npmjs, where a React checkbox took the synthetic click and reverted it on the next render while a real one held. Canvas applications do not check it, which is why this verb is for them. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Click a point on a canvas (Pro)" + "title": "Click a point on a canvas" }
--- pinned/qa_close_tab +++ observed/qa_close_tab { - "description": "Close one tab or a list of them — how you tidy up the isolated logins when a matrix run is finished. Returns which ids closed and which were already gone. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Close one tab or a list of them — how you tidy up the isolated logins when a matrix run is finished. Returns which ids closed and which were already gone. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Close tabs (Pro)" + "title": "Close tabs" }
--- pinned/qa_dialogs +++ observed/qa_dialogs { - "description": "The browser's OWN dialogs — “Leave site? Changes you made may not be saved”, confirm(), alert(), prompt() — are drawn outside the document, so no click of any kind can reach their buttons. Nothing on this bridge can press them and nothing in any page can. What IS reachable is the page's REQUEST for them, and that is what this changes: `beforeunload:true` stops the app from asking, so the navigation simply proceeds; `confirm:\"accept\"|\"dismiss\"` decides what confirm() returns; `alert:true` swallows alerts; `prompt` supplies the string (or null to cancel). op:'list' is free and reads the policy in force; setting one needs Pro + the Agent control consent, because an auto-accepted confirm can be the one in front of Delete. SCOPE: this tab, until it closes; it survives reloads of it. EVERY suppressed dialog is logged as a finding carrying the question the page asked and the answer given — so never report that the app stopped asking. It asked; Qiksy answered. Clear it (op:'clear') as soon as the flow is through.", + "description": "The browser's OWN dialogs — “Leave site? Changes you made may not be saved”, confirm(), alert(), prompt() — are drawn outside the document, so no click of any kind can reach their buttons. Nothing on this bridge can press them and nothing in any page can. What IS reachable is the page's REQUEST for them, and that is what this changes: `beforeunload:true` stops the app from asking, so the navigation simply proceeds; `confirm:\"accept\"|\"dismiss\"` decides what confirm() returns; `alert:true` swallows alerts; `prompt` supplies the string (or null to cancel). op:'list' is free and reads the policy in force; setting one needs the Agent control consent, because an auto-accepted confirm can be the one in front of Delete. SCOPE: this tab, until it closes; it survives reloads of it. EVERY suppressed dialog is logged as a finding carrying the question the page asked and the answer given — so never report that the app stopped asking. It asked; Qiksy answered. Clear it (op:'clear') as soon as the flow is through.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_drag_point +++ observed/qa_drag_point { - "description": "Press at one point, move, release at another — the other half of canvas work, and the half a click cannot express: moving a shape, marqueeing a selection, panning, drawing a box, dragging a handle. Without it an agent fakes a drag out of two clicks and changes something nobody asked for. The pointer is moved in STEPS between the two points (12 by default), because a single jump from start to end is not a drag to most canvas code — it tracks the pointer while the button is down, and one giant delta reads as a glitch or as nothing at all. The moves go to the element the drag STARTED on, which is what a browser does once a button is down. Same coordinates and the same honest limit as qa_click_point: CSS pixels from the top-left of the viewport, and `isTrusted: false`. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Press at one point, move, release at another — the other half of canvas work, and the half a click cannot express: moving a shape, marqueeing a selection, panning, drawing a box, dragging a handle. Without it an agent fakes a drag out of two clicks and changes something nobody asked for. The pointer is moved in STEPS between the two points (12 by default), because a single jump from start to end is not a drag to most canvas code — it tracks the pointer while the button is down, and one giant delta reads as a glitch or as nothing at all. The moves go to the element the drag STARTED on, which is what a browser does once a button is down. Same coordinates and the same honest limit as qa_click_point: CSS pixels from the top-left of the viewport, and `isTrusted: false`. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Drag from one point to another on a canvas (Pro)" + "title": "Drag from one point to another on a canvas" }
--- pinned/qa_failure_gallery +++ observed/qa_failure_gallery { - "description": "THE PICTURES NOBODY EVER TAKES: the empty list, the 500 with the server's own words, the spinner that never resolves, the 403, the dropped connection. Every other capture tool can only photograph a failure that happens to occur — this one CAUSES it, because the response mocks and the camera are the same product. It walks the states one at a time: sets a rule, reloads, waits for the page, photographs it, removes the rule, moves on — and writes one self-contained HTML file with every picture embedded, which opens in any browser without this extension. That file is the point: the designer and the product owner who need to see the error states do not have Qiksy and never will. The rules are ALWAYS removed at the end, including when something throws — a rule left behind outlives the reason for it and makes the next measurement on the tab a lie. Scope is the mocks' own: the page's fetch/XHR in the top frame, so an app fed by a WebSocket will show a gallery of healthy screens and say so. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "THE PICTURES NOBODY EVER TAKES: the empty list, the 500 with the server's own words, the spinner that never resolves, the 403, the dropped connection. Every other capture tool can only photograph a failure that happens to occur — this one CAUSES it, because the response mocks and the camera are the same product. It walks the states one at a time: sets a rule, reloads, waits for the page, photographs it, removes the rule, moves on — and writes one self-contained HTML file with every picture embedded, which opens in any browser without this extension. That file is the point: the designer and the product owner who need to see the error states do not have Qiksy and never will. The rules are ALWAYS removed at the end, including when something throws — a rule left behind outlives the reason for it and makes the next measurement on the tab a lie. Scope is the mocks' own: the page's fetch/XHR in the top frame, so an app fed by a WebSocket will show a gallery of healthy screens and say so. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_fill_json +++ observed/qa_fill_json { - "description": "Hand the page a plain JSON object — { \"Field label\": \"value\" } — and it fills the form in ONE call. No refs, no snapshot of your own: this takes its own snapshot, matches each key against the fields' accessible names (exact, then truncated, then contains) and passes the whole lot to the batch filler, so every control kind is covered — text, native and CUSTOM selects, typeahead comboboxes, date pickers, checkboxes (true/false), radios, sliders, rich text. This is the shape of the JSON behind the panel's own `{ }` editor and its ⚡Fill button, so a dataset you write here reads the same way a human would edit it. Reach for this when you ALREADY know the data — from a ticket, a persona, a CSV row — and for a form you have not mapped: it is one call instead of snapshot-then-batch. Use qa_snapshot + qa_type_many instead when you need to choose targets deliberately (duplicate labels, one specific control among many). Keys that match nothing come back as `unmatched`, together with the field names the page actually has, so the next call can use the right key without another round trip. DATE FIELDS BUILT FROM SECTIONS (MUI X DateField/DatePicker: separate Day / Month / Year spans the component owns) cannot be written to at all — the answer will say so under `verified`, and the way in is the calendar: click the \"Choose …\" button, switch to the year view, pick the year, step the month, click the day. A value of \"click\" PRESSES the thing instead of filling it — for the parts of a step that take no value: a chip, a custom radio (div role=radio), a switch, a toggle button. { \"Climate\": \"click\", \"Medium\": \"click\" } goes in the same batch as the text fields. REPEATED LABELS: a form built from a repeated component gives several controls the identical label — address them as \"Full name\", \"Full name#2\", \"Full name#3\", which is the same #N notation the panel's own dataset uses, so a dataset copied out of the `{ }` editor works as written. ONE CALL PER WIZARD STEP: pass `advance: \"Next\"` and this presses that control after the fill — but only when the fill was clean. It is NOT pressed if an entry came back ok:false, if a key matched nothing, or if a value is still wrong after the repair, and the reply says which of those it was. Whether the page really advanced is decided by the DIFF the click produced (a step swap makes many nodes appear AND disappear), never by the click answering ok: a Next that is disabled until the step validates accepts a click and does nothing — that case is caught before the click, from the control's own state, and comes back as `advanced.ok:false` with `stillNeeded`. When it did advance, `next` carries the FIELD NAMES of the step you are now on, so the following step costs no qa_snapshot of your own; pass next:false to leave them out. dry_run:true resolves the keys and fills nothing — read-only, and the cheap way to check a dataset against a page (with `advance` set it reports `wouldAdvance` and presses nothing). Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Hand the page a plain JSON object — { \"Field label\": \"value\" } — and it fills the form in ONE call. No refs, no snapshot of your own: this takes its own snapshot, matches each key against the fields' accessible names (exact, then truncated, then contains) and passes the whole lot to the batch filler, so every control kind is covered — text, native and CUSTOM selects, typeahead comboboxes, date pickers, checkboxes (true/false), radios, sliders, rich text. This is the shape of the JSON behind the panel's own `{ }` editor and its ⚡Fill button, so a dataset you write here reads the same way a human would edit it. Reach for this when you ALREADY know the data — from a ticket, a persona, a CSV row — and for a form you have not mapped: it is one call instead of snapshot-then-batch. Use qa_snapshot + qa_type_many instead when you need to choose targets deliberately (duplicate labels, one specific control among many). Keys that match nothing come back as `unmatched`, together with the field names the page actually has, so the next call can use the right key without another round trip. DATE FIELDS BUILT FROM SECTIONS (MUI X DateField/DatePicker: separate Day / Month / Year spans the component owns) cannot be written to at all — the answer will say so under `verified`, and the way in is the calendar: click the \"Choose …\" button, switch to the year view, pick the year, step the month, click the day. A value of \"click\" PRESSES the thing instead of filling it — for the parts of a step that take no value: a chip, a custom radio (div role=radio), a switch, a toggle button. { \"Climate\": \"click\", \"Medium\": \"click\" } goes in the same batch as the text fields. REPEATED LABELS: a form built from a repeated component gives several controls the identical label — address them as \"Full name\", \"Full name#2\", \"Full name#3\", which is the same #N notation the panel's own dataset uses, so a dataset copied out of the `{ }` editor works as written. ONE CALL PER WIZARD STEP: pass `advance: \"Next\"` and this presses that control after the fill — but only when the fill was clean. It is NOT pressed if an entry came back ok:false, if a key matched nothing, or if a value is still wrong after the repair, and the reply says which of those it was. Whether the page really advanced is decided by the DIFF the click produced (a step swap makes many nodes appear AND disappear), never by the click answering ok: a Next that is disabled until the step validates accepts a click and does nothing — that case is caught before the click, from the control's own state, and comes back as `advanced.ok:false` with `stillNeeded`. When it did advance, `next` carries the FIELD NAMES of the step you are now on, so the following step costs no qa_snapshot of your own; pass next:false to leave them out. dry_run:true resolves the keys and fills nothing — read-only, and the cheap way to check a dataset against a page (with `advance` set it reports `wouldAdvance` and presses nothing). Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Fill a form from a JSON of labels and values (Pro)" + "title": "Fill a form from a JSON of labels and values" }
--- pinned/qa_focus_walk +++ observed/qa_focus_walk { - "description": "The keyboard route as ONE call: presses Tab repeatedly and records where focus actually lands, then compares that trail against the interactive elements the accessibility tree claims. This is the accessibility question a click can never answer — Qiksy clicks teleport to an element, a keyboard user has to REACH it. Reports: the focus trail; interactive elements that never got focus (the div-button a Tab user can never reach); a suspected focus TRAP when the same short cycle of names repeats without the walk closing; and order-vs-geometry suspects — consecutive stops where focus jumps far UP the page, the classic broken tab order. Honest limits, stated because this tool feeds audits: matching is by accessible NAME (the press reply carries no ref), so duplicate names can mask a missed element; focus VISIBILITY is not judged here (that is qa_styles on a stop that interests you); and the walk changes the page's focus for real, so run it before filling, not mid-form. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "The keyboard route as ONE call: presses Tab repeatedly and records where focus actually lands, then compares that trail against the interactive elements the accessibility tree claims. This is the accessibility question a click can never answer — Qiksy clicks teleport to an element, a keyboard user has to REACH it. Reports: the focus trail; interactive elements that never got focus (the div-button a Tab user can never reach); a suspected focus TRAP when the same short cycle of names repeats without the walk closing; and order-vs-geometry suspects — consecutive stops where focus jumps far UP the page, the classic broken tab order. Honest limits, stated because this tool feeds audits: matching is by accessible NAME (the press reply carries no ref), so duplicate names can mask a missed element; focus VISIBILITY is not judged here (that is qa_styles on a stop that interests you); and the walk changes the page's focus for real, so run it before filling, not mid-form. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Walk the page the way a keyboard user does — Tab by Tab (Pro)" + "title": "Walk the page the way a keyboard user does — Tab by Tab" }
--- pinned/qa_hover +++ observed/qa_hover { - "description": "Put the pointer on an element AND LEAVE IT THERE, then report what that revealed: a menu that opens on hover, a tooltip, the actions a row only shows under the pointer, the value a chart reads out beneath it. qa_click cannot answer any of those — it presses a millisecond after it hovers, and the press closes what the hover opened. It holds for `holdMs` (500 by default) before measuring, because tooltips wait — measuring at once reports \"no tooltip\" about one that was always going to appear. The reply carries the usual `changed` diff plus `tooltip` (role=tooltip, aria-describedby or title, whichever exists) and `expanded` when the control changed its aria-expanded. ONE FACT TO READ CAREFULLY: `cssHover` says whether the browser's own `:hover` matched. Dispatched events run the page's JavaScript handlers — every menu and tooltip library — but they do not move the real cursor, so an effect written PURELY in CSS does not appear and `cssHover` comes back false. That is the tool being honest, not the page being broken. With trusted input switched on the real pointer moves, `cssHover` is true, and CSS-only hovers light up too. Call it again with `off: true` to take the pointer away — \"the tooltip disappears\" is the other half of the check, and it needs its own call. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Put the pointer on an element AND LEAVE IT THERE, then report what that revealed: a menu that opens on hover, a tooltip, the actions a row only shows under the pointer, the value a chart reads out beneath it. qa_click cannot answer any of those — it presses a millisecond after it hovers, and the press closes what the hover opened. It holds for `holdMs` (500 by default) before measuring, because tooltips wait — measuring at once reports \"no tooltip\" about one that was always going to appear. The reply carries the usual `changed` diff plus `tooltip` (role=tooltip, aria-describedby or title, whichever exists) and `expanded` when the control changed its aria-expanded. ONE FACT TO READ CAREFULLY: `cssHover` says whether the browser's own `:hover` matched. Dispatched events run the page's JavaScript handlers — every menu and tooltip library — but they do not move the real cursor, so an effect written PURELY in CSS does not appear and `cssHover` comes back false. That is the tool being honest, not the page being broken. With trusted input switched on the real pointer moves, `cssHover` is true, and CSS-only hovers light up too. Call it again with `off: true` to take the pointer away — \"the tooltip disappears\" is the other half of the check, and it needs its own call. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Hover over an element and hold (Pro)" + "title": "Hover over an element and hold" }
--- pinned/qa_mock +++ observed/qa_mock { - "description": "Override what the app RECEIVES from the network — the thing DevTools calls a local override, driven from here. Set a rule and the page's next matching fetch/XHR is answered by it: your status, your body, optionally after a delay, or as an outright network failure. This is how you test the paths a server will not produce on demand: an empty list, a 500, a 403 that should log the user out, a 30-second response that should show a spinner, a malformed payload, a field the API does not return yet. op:'list' is free and READS the rules in force; set / remove / toggle / clear change what the app sees and need Pro + the Agent control consent, because an app fed an invented 200 can persist it. MATCHING: `url` is a substring of the full URL, or `/regex/flags` when you wrap it in slashes; `method` defaults to any. First matching enabled rule wins. `times` spends the rule after N answers (leave it out for unlimited) — that is how you make the FIRST call fail and the retry succeed. LIFETIME: a rule belongs to ONE TAB, survives reloads of it, and is gone when it closes. It is not in force in any other tab. WHAT IT DOES NOT CATCH, so you do not debug a rule that was never consulted: only the page's own fetch/XHR in the top frame. Not the HTML document itself, not images/scripts/stylesheets, not WebSocket or EventSource, not sendBeacon, not requests made inside an iframe or from the page's own service worker. Mocked responses are logged as `(mocked)` in the session, the HAR and the report — a value you invented is never presented as the server's. Remember to clear rules when you are done; a forgotten mock is the most expensive thing here.", + "description": "Override what the app RECEIVES from the network — the thing DevTools calls a local override, driven from here. Set a rule and the page's next matching fetch/XHR is answered by it: your status, your body, optionally after a delay, or as an outright network failure. This is how you test the paths a server will not produce on demand: an empty list, a 500, a 403 that should log the user out, a 30-second response that should show a spinner, a malformed payload, a field the API does not return yet. op:'list' is free and READS the rules in force; set / remove / toggle / clear change what the app sees and need the Agent control consent, because an app fed an invented 200 can persist it. MATCHING: `url` is a substring of the full URL, or `/regex/flags` when you wrap it in slashes; `method` defaults to any. First matching enabled rule wins. `times` spends the rule after N answers (leave it out for unlimited) — that is how you make the FIRST call fail and the retry succeed. LIFETIME: a rule belongs to ONE TAB, survives reloads of it, and is gone when it closes. It is not in force in any other tab. WHAT IT DOES NOT CATCH, so you do not debug a rule that was never consulted: only the page's own fetch/XHR in the top frame. Not the HTML document itself, not images/scripts/stylesheets, not WebSocket or EventSource, not sendBeacon, not requests made inside an iframe or from the page's own service worker. Mocked responses are logged as `(mocked)` in the session, the HAR and the report — a value you invented is never presented as the server's. Remember to clear rules when you are done; a forgotten mock is the most expensive thing here.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_navigate +++ observed/qa_navigate { - "description": "Point a tab at a URL and wait until it is loaded and Qiksy is live again — the ready-wait is built in, so the next step can run straight after. Use it to jump between steps of a flow that are not reachable by clicking. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Point a tab at a URL and wait until it is loaded and Qiksy is live again — the ready-wait is built in, so the next step can run straight after. Use it to jump between steps of a flow that are not reachable by clicking. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Navigate a tab (Pro)" + "title": "Navigate a tab" }
--- pinned/qa_open_isolated +++ observed/qa_open_isolated { - "description": "Open a tab with its OWN private session — separate cookies, localStorage, IndexedDB — so a second (or third) user can be logged in at the same time in the same browser. Returns its `tabId`; pass that to any tool to run the whole flow as that user, while the first login keeps working untouched. This is how you test a hand-off: submit as one role here, then verify it as another role there, side by side. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Open a tab with its OWN private session — separate cookies, localStorage, IndexedDB — so a second (or third) user can be logged in at the same time in the same browser. Returns its `tabId`; pass that to any tool to run the whole flow as that user, while the first login keeps working untouched. This is how you test a hand-off: submit as one role here, then verify it as another role there, side by side. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Open an isolated login tab (Pro)" + "title": "Open an isolated login tab" }
--- pinned/qa_pick_date +++ observed/qa_pick_date { - "description": "Put an EXACT date into a date field in ONE call — including the ones a value cannot be written to. It tries the cheap way first (type the date, press Enter, check it stuck), and only if that fails does it open the picker and steer it: the year view for MUI X, the clickable year in the header for Ant Design, the month/year dropdowns for react-datepicker, and plain arrows for anything else — stepping until the calendar header agrees, then clicking the day. Use this instead of writing to a date field whose value never sticks: MUI X DateField is three contenteditable sections the component rebuilds from its own state, so a written \"14\" can land in the YEAR and the day stay empty. Returns `via` (typed or calendar) and a `trail` of what it actually had to do, so a picker this does not recognise is visible rather than silent. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Put an EXACT date into a date field in ONE call — including the ones a value cannot be written to. It tries the cheap way first (type the date, press Enter, check it stuck), and only if that fails does it open the picker and steer it: the year view for MUI X, the clickable year in the header for Ant Design, the month/year dropdowns for react-datepicker, and plain arrows for anything else — stepping until the calendar header agrees, then clicking the day. Use this instead of writing to a date field whose value never sticks: MUI X DateField is three contenteditable sections the component rebuilds from its own state, so a written \"14\" can land in the YEAR and the day stay empty. Returns `via` (typed or calendar) and a `trail` of what it actually had to do, so a picker this does not recognise is visible rather than silent. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Set a date, whatever library owns the calendar (Pro)" + "title": "Set a date, whatever library owns the calendar" }
--- pinned/qa_pick_path +++ observed/qa_pick_path { - "description": "One call for the widgets where a single value takes several interactions: an Ant Design Cascader (country → region → city, a column per level), a TreeSelect with collapsed branches, a menu of submenus, any drill-down. Pass the path as it reads on screen and it opens the control, clicks each level on a FRESH snapshot (opening a level is exactly what re-renders the popup), expands a branch that hides its children, and closes up afterwards. A level that never appears is reported with the levels that did, so you can see where the path diverged from the page instead of guessing. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "One call for the widgets where a single value takes several interactions: an Ant Design Cascader (country → region → city, a column per level), a TreeSelect with collapsed branches, a menu of submenus, any drill-down. Pass the path as it reads on screen and it opens the control, clicks each level on a FRESH snapshot (opening a level is exactly what re-renders the popup), expands a branch that hides its children, and closes up afterwards. A level that never appears is reported with the levels that did, so you can see where the path diverged from the page instead of guessing. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Choose a value that lives across several levels (Pro)" + "title": "Choose a value that lives across several levels" }
--- pinned/qa_pick_range +++ observed/qa_pick_range { - "description": "Both ends of a date range in one call, for a control that owns two inputs — Ant Design's RangePicker and its equivalents. Not the same as two qa_pick_date calls: the control walks from the start panel to the end panel by itself, and approaching it a second time from cold restarts the range on the start input, which is how a range ends up half-set. The start goes in through the normal single-date route (so every library it knows still applies), then the panel — still open — is steered to the end date by the same recipe with the opening click dropped. Reports a `trail` of what it clicked, and if the panel closed after the start it says so instead of pretending: some pickers do, and then the end date is its own field (\"End date\") for a plain qa_pick_date. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Both ends of a date range in one call, for a control that owns two inputs — Ant Design's RangePicker and its equivalents. Not the same as two qa_pick_date calls: the control walks from the start panel to the end panel by itself, and approaching it a second time from cold restarts the range on the start input, which is how a range ends up half-set. The start goes in through the normal single-date route (so every library it knows still applies), then the panel — still open — is steered to the end date by the same recipe with the opening click dropped. Reports a `trail` of what it clicked, and if the panel closed after the start it says so instead of pretending: some pickers do, and then the end date is its own field (\"End date\") for a plain qa_pick_date. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Set a date RANGE that lives in one control (Pro)" + "title": "Set a date RANGE that lives in one control" }
--- pinned/qa_pick_tags +++ observed/qa_pick_tags { - "description": "One call for a whole list of tags on a control that has no option list to pick from — Ant Design's Select in `mode=\"tags\"`, and every chips input built the same way: the value is typed and committed with a key. 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 point and it is not defensive coding: 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. That is how a form ends up with the tag you added first missing. It also never clicks the field first: a click moves the caret out of the component's search input and the text goes nowhere. Returns which tags went in and, for any that did not, what the field was holding instead. If a control refuses every tag it is probably NOT in tags mode — it only accepts values from its list, and qa_type with the option text is the right verb. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "One call for a whole list of tags on a control that has no option list to pick from — Ant Design's Select in `mode=\"tags\"`, and every chips input built the same way: the value is typed and committed with a key. 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 point and it is not defensive coding: 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. That is how a form ends up with the tag you added first missing. It also never clicks the field first: a click moves the caret out of the component's search input and the text goes nowhere. Returns which tags went in and, for any that did not, what the field was holding instead. If a control refuses every tag it is probably NOT in tags mode — it only accepts values from its list, and qa_type with the option text is the right verb. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Add tags / chips to a field that CREATES its values (Pro)" + "title": "Add tags / chips to a field that CREATES its values" }
--- pinned/qa_pick_time +++ observed/qa_pick_time { - "description": "A time in one call on a column-based picker — Ant Design's TimePicker and its equivalents. Needed because the cells are bare list items with NO accessibility role, so the snapshot cannot see them, and the ordinary fill path misreads the control as a date field (\"calendar did not open — typed an ISO date instead\") and leaves it empty while reporting success. This reads each column's text, finds which entry is your value and clicks THAT child, then presses the panel's own OK — which is not optional: without it the value stays provisional and the field ends up empty. Reading the column instead of computing an index is deliberate: the minute step is configurable and guessing it would be a bug. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "A time in one call on a column-based picker — Ant Design's TimePicker and its equivalents. Needed because the cells are bare list items with NO accessibility role, so the snapshot cannot see them, and the ordinary fill path misreads the control as a date field (\"calendar did not open — typed an ISO date instead\") and leaves it empty while reporting success. This reads each column's text, finds which entry is your value and clicks THAT child, then presses the panel's own OK — which is not optional: without it the value stays provisional and the field ends up empty. Reading the column instead of computing an index is deliberate: the minute step is configurable and guessing it would be a bug. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Set a time on a picker whose cells have no role (Pro)" + "title": "Set a time on a picker whose cells have no role" }
--- pinned/qa_press +++ observed/qa_press { - "description": "Dispatch a keyboard key (keydown/keypress/keyup) — Enter to submit, Escape to close, Tab to move focus. Without a selector it goes to whatever currently has focus, like a real keyboard. Like qa_click, it returns what changed once the page settled. On a recent extension build the reply also carries `focus` — the element that ACTUALLY holds focus after the key (name, role, selector, box), which is how you follow a Tab route or catch focus landing somewhere unnamed — and `announced`, what the live regions spoke (null = they stayed silent). Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Dispatch a keyboard key (keydown/keypress/keyup) — Enter to submit, Escape to close, Tab to move focus. Without a selector it goes to whatever currently has focus, like a real keyboard. Like qa_click, it returns what changed once the page settled. On a recent extension build the reply also carries `focus` — the element that ACTUALLY holds focus after the key (name, role, selector, box), which is how you follow a Tab route or catch focus landing somewhere unnamed — and `announced`, what the live regions spoke (null = they stayed silent). Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Press a key (Pro)" + "title": "Press a key" }
--- pinned/qa_probe +++ observed/qa_probe { - "description": "Identify ONE unfamiliar control and get back the verb that drives it. Judges it first by shape — role, tag, name, state and its neighbours — which settles a sectioned date field, a file input, a switch, a slider or a plain field without touching the page at all. When the shape says nothing, which is what happens to a widget hand-rolled from <div>s (role \"generic\", no name, no state), it clicks once and reads what the page DID: day numbers or a grid appeared → a calendar, two options → a select however it is built, checkboxes → a tree, a menu, a modal, or the most useful verdict of the set — it went `expanded` and the tree gained NOTHING, which means the popup is portalled markup with no roles and must be addressed by CSS selector (Ant Design). Then it presses Escape so the form is left as it was found. Use it on the ONE field that just refused a value, not on every field: a snapshot already classifies the whole page (`componentLibraries` + `hints`), and driving is what the other verbs are for. Do not aim it at a Submit or Next — that click advances the page and Escape cannot take it back; the reply says `pageMoved` when it happened. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Identify ONE unfamiliar control and get back the verb that drives it. Judges it first by shape — role, tag, name, state and its neighbours — which settles a sectioned date field, a file input, a switch, a slider or a plain field without touching the page at all. When the shape says nothing, which is what happens to a widget hand-rolled from <div>s (role \"generic\", no name, no state), it clicks once and reads what the page DID: day numbers or a grid appeared → a calendar, two options → a select however it is built, checkboxes → a tree, a menu, a modal, or the most useful verdict of the set — it went `expanded` and the tree gained NOTHING, which means the popup is portalled markup with no roles and must be addressed by CSS selector (Ant Design). Then it presses Escape so the form is left as it was found. Use it on the ONE field that just refused a value, not on every field: a snapshot already classifies the whole page (`componentLibraries` + `hints`), and driving is what the other verbs are for. Do not aim it at a Submit or Next — that click advances the page and Escape cannot take it back; the reply says `pageMoved` when it happened. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "What kind of widget is this, and how do I drive it? (Pro)" + "title": "What kind of widget is this, and how do I drive it?" }
--- pinned/qa_recipe_apply +++ observed/qa_recipe_apply { - "description": "Replay a saved flow on the page as it is NOW. Per step it takes its own snapshot, matches every field by accessible name (exact, then truncated, then contains, then the stored cssPath), fills the whole step in ONE batch, attaches the step's uploads, and clicks the advance control — so a step costs you one call instead of a snapshot plus a call per field. Refs are never reused across runs; the names are. A field the page no longer has comes back under `missing` instead of failing the step, so you can see what drifted and fix that one field by hand. Override any value with `values` — same recipe, different data (that is how one saved flow becomes a data-driven test). START WITH dry_run:true: it resolves everything and changes nothing, needs neither Pro nor consent, and tells you whether the recipe still fits. IT ACTS: with `all` it walks every remaining step and clicks each advance control, so if the last step's advance is Submit, it submits. On anything irreversible, run the steps one at a time. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Replay a saved flow on the page as it is NOW. Per step it takes its own snapshot, matches every field by accessible name (exact, then truncated, then contains, then the stored cssPath), fills the whole step in ONE batch, attaches the step's uploads, and clicks the advance control — so a step costs you one call instead of a snapshot plus a call per field. Refs are never reused across runs; the names are. A field the page no longer has comes back under `missing` instead of failing the step, so you can see what drifted and fix that one field by hand. Override any value with `values` — same recipe, different data (that is how one saved flow becomes a data-driven test). START WITH dry_run:true: it resolves everything and changes nothing, needs no consent at all, and tells you whether the recipe still fits. IT ACTS: with `all` it walks every remaining step and clicks each advance control, so if the last step's advance is Submit, it submits. On anything irreversible, run the steps one at a time. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Replay a saved recipe, a step at a time (Pro)" + "title": "Replay a saved recipe, a step at a time" }
--- pinned/qa_recipe_list +++ observed/qa_recipe_list { - "description": "The flows already saved on this machine, newest first: name, host, step count, and the fields of the first step so you can tell them apart. Worth ONE call at the start of any form-heavy task — if a recipe for this host exists, replaying it beats rediscovering the form. Local only, no browser, no Pro.", + "description": "The flows already saved on this machine, newest first: name, host, step count, and the fields of the first step so you can tell them apart. Worth ONE call at the start of any form-heavy task — if a recipe for this host exists, replaying it beats rediscovering the form. Local only, no browser, no consent.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_recipe_save +++ observed/qa_recipe_save { - "description": "Write what you just did on this page to a JSON recipe on this machine, so the NEXT run replays it instead of rediscovering it. Call it with just a name and it builds the recipe from what this session actually filled: the fields (by accessible name, with the cssPath as fallback), the uploads, and the control you clicked to move on — folded into steps, since a click after a group of fills is what ends a step of a wizard. Pass `steps` yourself to write a recipe by hand, or to correct one. Do this at the END of any flow that took more than a few calls — a long multi-step form, a signup, a wizard — and say so in one line to the human: the recipe is a file they own, at the path this returns, editable and deletable by hand. Local only: writes a file, touches no browser, needs no Pro.", + "description": "Write what you just did on this page to a JSON recipe on this machine, so the NEXT run replays it instead of rediscovering it. Call it with just a name and it builds the recipe from what this session actually filled: the fields (by accessible name, with the cssPath as fallback), the uploads, and the control you clicked to move on — folded into steps, since a click after a group of fills is what ends a step of a wizard. Pass `steps` yourself to write a recipe by hand, or to correct one. Do this at the END of any flow that took more than a few calls — a long multi-step form, a signup, a wizard — and say so in one line to the human: the recipe is a file they own, at the path this returns, editable and deletable by hand. Local only: writes a file, touches no browser, needs no consent.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_reload +++ observed/qa_reload { - "description": "Reload the tab as it stands — same address, no URL to pass — and wait until it has loaded AND Qiksy is live in the new document, so the next call needs no sleep. Use it after anything a fresh load fixes: the extension updated under an open tab, a mock was cleared, storage was written by hand, the app wedged. The browser performs it, not the page's own script, so it works even when that script is already dead — which is exactly the case a reload is for. Pass bypassCache for the hard reload (Ctrl/Cmd+Shift+R) when you suspect a stale bundle. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Reload the tab as it stands — same address, no URL to pass — and wait until it has loaded AND Qiksy is live in the new document, so the next call needs no sleep. Use it after anything a fresh load fixes: the extension updated under an open tab, a mock was cleared, storage was written by hand, the app wedged. The browser performs it, not the page's own script, so it works even when that script is already dead — which is exactly the case a reload is for. Pass bypassCache for the hard reload (Ctrl/Cmd+Shift+R) when you suspect a stale bundle. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Reload a tab (Pro)" + "title": "Reload a tab" }
--- pinned/qa_scenario_run +++ observed/qa_scenario_run { - "description": "Replay a flow recorded in the panel, on the page as it is now, with YOUR data. Give `vars` for its placeholders ({ \"summary\": \"Login 500s\" }) and/or `values` to override any field by its label. This is how a form somebody fills weekly — a bug in Jira, a task, a story — becomes one call: the route was recorded once by a human who knows the form, and you supply the text. Each step is filled through the same engine as the panel's Fill, and the run STOPS at the first field the page no longer has rather than filling on and pressing Next over an invalid form. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Replay a flow recorded in the panel, on the page as it is now, with YOUR data. Give `vars` for its placeholders ({ \"summary\": \"Login 500s\" }) and/or `values` to override any field by its label. This is how a form somebody fills weekly — a bug in Jira, a task, a story — becomes one call: the route was recorded once by a human who knows the form, and you supply the text. Each step is filled through the same engine as the panel's Fill, and the run STOPS at the first field the page no longer has rather than filling on and pressing Next over an invalid form. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Run a scenario the human recorded (Pro)" + "title": "Run a scenario the human recorded" }
--- pinned/qa_scenario_save +++ observed/qa_scenario_save { - "description": "Turn what you just drove into a scenario in Qiksy's Scenarios tab — the same list the human records into by hand, on the same machine. Call it with a name and it builds the flow from what this session actually filled: fields by accessible name, uploads, and the control you clicked to move on, folded into steps. Pass `steps` to write or correct one by hand. THIS IS THE ONE THE HUMAN CAN PRESS. qa_recipe_save writes a file for you to replay; this puts it in front of the person, with Run, Data and Random beside it, and it can be part of a project suite. Do it at the end of any flow that took more than a few calls, and tell them in one line that it is there — a scenario nobody knows about is one nobody runs. Needs Pro (scenarios are a Pro feature); it stores data and touches no page, so it needs no Agent-control consent.", + "description": "Turn what you just drove into a scenario in Qiksy's Scenarios tab — the same list the human records into by hand, on the same machine. Call it with a name and it builds the flow from what this session actually filled: fields by accessible name, uploads, and the control you clicked to move on, folded into steps. Pass `steps` to write or correct one by hand. THIS IS THE ONE THE HUMAN CAN PRESS. qa_recipe_save writes a file for you to replay; this puts it in front of the person, with Run, Data and Random beside it, and it can be part of a project suite. Do it at the end of any flow that took more than a few calls, and tell them in one line that it is there — a scenario nobody knows about is one nobody runs. Stores data and touches no page, so it needs no Agent-control consent and no licence — scenarios are free, like everything else.", "execution": { "taskSupport": "forbidden" }, - "title": "Save this flow into the panel, so the human can run it (Pro)" + "title": "Save this flow into the panel, so the human can run it" }
--- pinned/qa_scenarios +++ observed/qa_scenarios { - "description": "The flows the human recorded in Qiksy's Scenarios tab, on this machine: name, project, tags, step and field counts, and — the part you need — the PLACEHOLDERS each one asks for. A scenario stores its route, not its content: a bug report or a task keeps the same fields every time and different text, so the values are named holes ({summary}, {steps}) that you fill per run. Worth one call before any long form on a host the human works on. Free, no Pro, no browser action.", + "description": "The flows the human recorded in Qiksy's Scenarios tab, on this machine: name, project, tags, step and field counts, and — the part you need — the PLACEHOLDERS each one asks for. A scenario stores its route, not its content: a bug report or a task keeps the same fields every time and different text, so the values are named holes ({summary}, {steps}) that you fill per run. Worth one call before any long form on a host the human works on. Free, no consent, no browser action.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_storage +++ observed/qa_storage { - "description": "Read and write the app's own localStorage / sessionStorage — flip a feature flag, expire a token to test the re-auth path, or prove what the app actually persisted after a save. Without `op` (or with op:'list'/'get') it READS and is free like every other read on this bridge; op:'set'|'remove'|'clear' WRITES and needs Pro + the Agent control consent, because changing a stored token or flag changes what the app does just as surely as clicking does. Closed vocabulary — it executes nothing you supply, it names a key. Listing truncates long values at 300 chars; ask for one key by name to get it whole.", + "description": "Read and write the app's own localStorage / sessionStorage — flip a feature flag, expire a token to test the re-auth path, or prove what the app actually persisted after a save. Without `op` (or with op:'list'/'get') it READS and is free like every other read on this bridge; op:'set'|'remove'|'clear' WRITES and needs the Agent control consent, because changing a stored token or flag changes what the app does just as surely as clicking does. Closed vocabulary — it executes nothing you supply, it names a key. Listing truncates long values at 300 chars; ask for one key by name to get it whole.", "execution": { "taskSupport": "forbidden" },
--- pinned/qa_suite_run +++ observed/qa_suite_run { - "description": "Replay a GROUP of saved flows back to back and answer about the whole set: which passed, which stopped, at what step and on what. Pick the set one of three ways — `recipes` by name, `project` (everything filed under that product or client), or `tag` ([\"smoke\"]). BOTH STORES ARE IN THE SET: the flows you saved here and the ones the human recorded in the panel's Scenarios tab (qa_scenarios), so a hand-recorded flow can be part of a project run. A name held by both is run from the panel — that is the copy the person edits. Each recipe is opened at its own start URL first, so recipe two does not begin on recipe one's confirmation screen. It does NOT stop at the first failure — a set is only useful when you learn the state of all of it — but each recipe still stops at its own first unresolved step, because filling on through a broken flow is worse than a short report. START WITH dry_run:true: it resolves every step of every recipe, changes nothing, and needs neither Pro nor consent. That is the honest way to ask \"do these still fit\" before running them for real. IT ACTS: a suite multiplies what one recipe does, submits included. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Replay a GROUP of saved flows back to back and answer about the whole set: which passed, which stopped, at what step and on what. Pick the set one of three ways — `recipes` by name, `project` (everything filed under that product or client), or `tag` ([\"smoke\"]). BOTH STORES ARE IN THE SET: the flows you saved here and the ones the human recorded in the panel's Scenarios tab (qa_scenarios), so a hand-recorded flow can be part of a project run. A name held by both is run from the panel — that is the copy the person edits. Each recipe is opened at its own start URL first, so recipe two does not begin on recipe one's confirmation screen. It does NOT stop at the first failure — a set is only useful when you learn the state of all of it — but each recipe still stops at its own first unresolved step, because filling on through a broken flow is worse than a short report. START WITH dry_run:true: it resolves every step of every recipe, changes nothing, and needs no consent at all. That is the honest way to ask \"do these still fit\" before running them for real. IT ACTS: a suite multiplies what one recipe does, submits included. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Run several saved recipes as one set (Pro)" + "title": "Run several saved recipes as one set" }
--- pinned/qa_type +++ observed/qa_type { - "description": "Put a value into ONE control, whatever kind it is — target it by a qa_snapshot ref (preferred) or a CSS selector. This is not a keystroke emulator, it is the extension's own fill engine, the same one behind the ⚡Fill button, so \"typing\" works on far more than text boxes: text/number/date inputs and textareas (native value setter + InputEvent, so React/Vue controlled inputs keep the value), native <select> (your text is matched against the option labels), checkboxes and radios (a truthy value checks it, with a real click so the app's listeners run), CUSTOM SELECTS and typeahead comboboxes (Radix/shadcn, MUI, Ant, react-select, Downshift — it opens the popover, waits for the options, picks the one matching your value and verifies the trigger actually changed), POPUP DATE & TIME PICKERS (opens the calendar and picks a day), sliders, and contenteditable rich text. A file field takes the neutral Qiksy test file matched to its `accept` — use qa_upload when the bytes matter. So do NOT click a dropdown open, snapshot to read its options and click one: name the field and the value you want. Only fall back to the click path when this answers ok:false. Reports back whether the value stuck, so a masked or read-only field is visible to you rather than silently ignored. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Put a value into ONE control, whatever kind it is — target it by a qa_snapshot ref (preferred) or a CSS selector. This is not a keystroke emulator, it is the extension's own fill engine, the same one behind the ⚡Fill button, so \"typing\" works on far more than text boxes: text/number/date inputs and textareas (native value setter + InputEvent, so React/Vue controlled inputs keep the value), native <select> (your text is matched against the option labels), checkboxes and radios (a truthy value checks it, with a real click so the app's listeners run), CUSTOM SELECTS and typeahead comboboxes (Radix/shadcn, MUI, Ant, react-select, Downshift — it opens the popover, waits for the options, picks the one matching your value and verifies the trigger actually changed), POPUP DATE & TIME PICKERS (opens the calendar and picks a day), sliders, and contenteditable rich text. A file field takes the neutral Qiksy test file matched to its `accept` — use qa_upload when the bytes matter. So do NOT click a dropdown open, snapshot to read its options and click one: name the field and the value you want. Only fall back to the click path when this answers ok:false. Reports back whether the value stuck, so a masked or read-only field is visible to you rather than silently ignored. WHAT `stuck: true` DOES NOT PROMISE: it says the DOM kept the value, not that the app took it. When the tab is not in front the reply therefore also carries `tab` — `state: \"hidden\"` (another tab is active, so Chrome is not painting it and a popover will not render) or `state: \"unfocused\"` (the window is behind another application, so anything that commits on real focus or blur never gets it). It is a caveat and not a refusal: the fill still happened. If the answer matters, call qa_focus_tab and do it again. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Set a field to a value — any kind of field (Pro)" + "title": "Set a field to a value — any kind of field" }
--- pinned/qa_type_many +++ observed/qa_type_many { - "description": "Fill an ENTIRE form (or one step of a wizard) in ONE call instead of a round-trip per field: pass an array of { ref | selector, value, action? }. EVERY control kind qa_type drives goes in the same batch — custom selects and typeahead comboboxes (Radix/shadcn, MUI, Ant, react-select), popup date & time pickers, native selects, checkboxes, radios, sliders, rich text, plain inputs. A batch of ten fields where six are dropdowns and dates is the NORMAL use of this tool, not an edge case, and it is the difference between one call and thirty. They are filled in order and strictly one at a time (two open popovers break each other), so a step with several custom widgets still takes a few seconds of real waiting — that part is the widgets, not the round trip. PREFER `ref` from qa_snapshot: a form built from a repeated component gives several inputs the identical cssPath, and a batch of selectors would then pour every value into the first match. Returns a per-field result list plus filled/total; a field that came back ok:false is the only one worth a follow-up call. File fields are the one thing that belongs elsewhere — qa_upload takes files[] and attaches several in one round trip. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Fill an ENTIRE form (or one step of a wizard) in ONE call instead of a round-trip per field: pass an array of { ref | selector, value, action? }. EVERY control kind qa_type drives goes in the same batch — custom selects and typeahead comboboxes (Radix/shadcn, MUI, Ant, react-select), popup date & time pickers, native selects, checkboxes, radios, sliders, rich text, plain inputs. A batch of ten fields where six are dropdowns and dates is the NORMAL use of this tool, not an edge case, and it is the difference between one call and thirty. They are filled in order and strictly one at a time (two open popovers break each other), so a step with several custom widgets still takes a few seconds of real waiting — that part is the widgets, not the round trip. PREFER `ref` from qa_snapshot: a form built from a repeated component gives several inputs the identical cssPath, and a batch of selectors would then pour every value into the first match. Returns a per-field result list plus filled/total; a field that came back ok:false is the only one worth a follow-up call. File fields are the one thing that belongs elsewhere — qa_upload takes files[] and attaches several in one round trip. Requires the one-time “Agent control” confirmation in the panel; without it the extension answers consent-required and does nothing.", "execution": { "taskSupport": "forbidden" }, - "title": "Fill a whole step in one call — every kind of field (Pro)" + "title": "Fill a whole step in one call — every kind of field" }
--- pinned/qa_upload +++ observed/qa_upload { "description": "Put a file into an <input type=file> — including the hidden ones behind a styled dropzone, which a click can never reach. Three ways to choose what goes in: `fixture` for a hostile edge-case file (the upload counterpart of naughty strings — empty, oversize, wrong-type, corrupt, unicode name, unsanitised SVG…), `path` for a file on this machine, or `content` as base64. Pass `content` as base64 to attach a file YOU generated (a PDF fixture, a malformed CSV, an oversized image); omit it and Qiksy attaches a neutral test file matched to the field's `accept`. A content script has no filesystem access and the OS file picker is a modal an agent must never open, so bytes over this bridge are the only way a file from outside the browser gets in. AND WHERE THERE IS NO FILE INPUT AT ALL — a button that calls the native picker straight away, as YouTube Studio's audio-language dialog does — name that button with `field` and the file is handed to the picker it opens, exactly as a person choosing it would. The reply says `via` when that route was used, and it says so only when the page actually took the file. Delivery fires both paths uploaders use — assigning `input.files` + change, and a synthetic drop on the nearest dropzone — because react-dropzone-style components listen only for the second. SEVERAL FIELDS IN ONE CALL: pass `files` — an array of the same arguments — and they are attached in order, one round trip for the whole step instead of one per file.", "execution": { - "title": "Attach a file to an upload field (Pro)" + "title": "Attach a file to an upload field" }
--- pinned/qa_wait_for +++ observed/qa_wait_for { - "description": "Wait until the page satisfies ONE named condition, then return — instead of polling qa_snapshot, which renumbers every ref each time it is called. Conditions: `appears` / `disappears` / `enabled` (each takes an accessible name OR a CSS selector) · `text` / `textGone` (anywhere on the page) · `countOf` a selector with `atLeast` or `exactly` · `announced` (what the page said through a live region — pass a string to wait for one containing it, or true for any) · `urlContains`. Use it after anything whose answer arrives later than a click settles: an order confirmation, a slow save, a list that repopulates, a button that ungreys when validation finishes. A condition already true when you call returns immediately. ON TIMEOUT IT SAYS WHAT IT SAW — how many matched, how many were visible, the nearest names on the page, and anything announced — so a wait that failed on a typo tells you so instead of just running out. A SIGN-IN IS WHAT THIS IS FOR, MORE THAN ANYTHING ELSE. When the site hands over to a password, a code from a phone or a hardware key, that step is the person's — do not drive it and do not call it a failure. Tell them to sign in, then `qa_wait_for { urlContains: \"<the site you came from>\", timeoutMs: 180000 }`: it is answered by the extension, so it survives the navigation, and if the tab never moves at all it says THAT instead of just running out. Read-only: waiting is not acting, so this needs no Pro and no consent — the same footing as qa_wait_ready. There is no \"until quiet\" (that is what every action already does) and no \"until the network is idle\" (the interceptor sees only the page's own fetch/XHR in the top frame, so the number would not mean what its name says).", + "description": "Wait until the page satisfies ONE named condition, then return — instead of polling qa_snapshot, which renumbers every ref each time it is called. Conditions: `appears` / `disappears` / `enabled` (each takes an accessible name OR a CSS selector) · `text` / `textGone` (anywhere on the page) · `countOf` a selector with `atLeast` or `exactly` · `announced` (what the page said through a live region — pass a string to wait for one containing it, or true for any) · `urlContains`. Use it after anything whose answer arrives later than a click settles: an order confirmation, a slow save, a list that repopulates, a button that ungreys when validation finishes. A condition already true when you call returns immediately. ON TIMEOUT IT SAYS WHAT IT SAW — how many matched, how many were visible, the nearest names on the page, and anything announced — so a wait that failed on a typo tells you so instead of just running out. A SIGN-IN IS WHAT THIS IS FOR, MORE THAN ANYTHING ELSE. When the site hands over to a password, a code from a phone or a hardware key, that step is the person's — do not drive it and do not call it a failure. Tell them to sign in, then `qa_wait_for { urlContains: \"<the site you came from>\", timeoutMs: 180000 }`: it is answered by the extension, so it survives the navigation, and if the tab never moves at all it says THAT instead of just running out. Read-only: waiting is not acting, so this needs no consent — the same footing as qa_wait_ready. There is no \"until quiet\" (that is what every action already does) and no \"until the network is idle\" (the interceptor sees only the page's own fetch/XHR in the top frame, so the number would not mean what its name says).", "execution": { "taskSupport": "forbidden" },
Changed 2026-10-16 11:29 UTC
1 tool added.
+ tool added: qa_steps
Changed 2026-10-06 13:26 UTC
8 schema or annotation changes with the description left byte-identical, 2 tools added.
--- pinned/qa_click +++ observed/qa_click { "description": "Click an element on the page under test — by a qa_snapshot ref, by a CSS selector, or by the control's accessible `name` (the one to use right after a fill, since filling re-renders the step and staleness is what makes a ref refuse). Sends the full pointer sequence (pointerdown → mouseup → click), so component libraries that listen for pointerdown — Radix, shadcn, MUI — react to it like a real click. Scrolls the element into view first, and refuses a target that matches nothing or has zero size rather than silently doing nothing. Returns what the click CHANGED — what appeared, disappeared or updated once the page settled — so you do not need a follow-up qa_snapshot to find out whether it did anything. `changed: null` means the page did not move, which is itself the answer. On a recent extension build the reply also carries `announced` — what the page's live regions (aria-live / role=status / role=alert) actually spoke during the settle window, or null if they stayed silent: a toast that appeared in `changed` but not in `announced` is invisible to a screen reader (WCAG 4.1.3). AND WHEN THE PRESS NAVIGATED there is no diff to give: the reply carries `navigated: <the new url>` instead. That IS what the press did — do not press again, a second one would land on the NEXT page rather than repeat this one. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "name": { "description": "Click the control with THIS accessible name — \"Next\", \"Submit application\", \"Add member\". Use it instead of a ref right after filling: a fill re-renders the page, which makes the ref you took before it stale (the extension refuses a stale ref rather than clicking the wrong thing). This resolves the name against a fresh snapshot, so it cannot go stale.", "type": "string"
--- pinned/qa_collect +++ observed/qa_collect { "description": "ASK FOR A TABLE INSTEAD OF A PAGE. Name WHICH nodes (a CSS selector, a role, a name fragment — any combination, scoped with `within`) and WHICH of their properties to take, and get back exactly that, once. «Every row in the list with its name and price», «every link in the nav and where it points», «all the inputs with what they currently hold» are one call here and a dozen with qa_snapshot plus qa_read. Take any of: text · value (what the control HOLDS, read the way the fill engine reads it) · name (accessible name) · role · tag · selector · href · state (disabled/required/invalid/expanded/checked/hidden) · geometry — plus attr:<name> for an attribute and style:<css-property> for a computed style. A property nobody can supply is named back rather than dropped, so a missing column is never mistaken for a missing feature. Read-only, and the vocabulary is closed: this is a different projection of the page, never a program the page runs.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "limit": { "description": "Cap the rows (max 300). A capped answer says so rather than looking complete.", "type": "number"
--- pinned/qa_fill_json +++ observed/qa_fill_json { "description": "Hand the page a plain JSON object — { \"Field label\": \"value\" } — and it fills the form in ONE call. No refs, no snapshot of your own: this takes its own snapshot, matches each key against the fields' accessible names (exact, then truncated, then contains) and passes the whole lot to the batch filler, so every control kind is covered — text, native and CUSTOM selects, typeahead comboboxes, date pickers, checkboxes (true/false), radios, sliders, rich text. This is the shape of the JSON behind the panel's own `{ }` editor and its ⚡Fill button, so a dataset you write here reads the same way a human would edit it. Reach for this when you ALREADY know the data — from a ticket, a persona, a CSV row — and for a form you have not mapped: it is one call instead of snapshot-then-batch. Use qa_snapshot + qa_type_many instead when you need to choose targets deliberately (duplicate labels, one specific control among many). Keys that match nothing come back as `unmatched`, together with the field names the page actually has, so the next call can use the right key without another round trip. DATE FIELDS BUILT FROM SECTIONS (MUI X DateField/DatePicker: separate Day / Month / Year spans the component owns) cannot be written to at all — the answer will say so under `verified`, and the way in is the calendar: click the \"Choose …\" button, switch to the year view, pick the year, step the month, click the day. A value of \"click\" PRESSES the thing instead of filling it — for the parts of a step that take no value: a chip, a custom radio (div role=radio), a switch, a toggle button. { \"Climate\": \"click\", \"Medium\": \"click\" } goes in the same batch as the text fields. REPEATED LABELS: a form built from a repeated component gives several controls the identical label — address them as \"Full name\", \"Full name#2\", \"Full name#3\", which is the same #N notation the panel's own dataset uses, so a dataset copied out of the `{ }` editor works as written. ONE CALL PER WIZARD STEP: pass `advance: \"Next\"` and this presses that control after the fill — but only when the fill was clean. It is NOT pressed if an entry came back ok:false, if a key matched nothing, or if a value is still wrong after the repair, and the reply says which of those it was. Whether the page really advanced is decided by the DIFF the click produced (a step swap makes many nodes appear AND disappear), never by the click answering ok: a Next that is disabled until the step validates accepts a click and does nothing — that case is caught before the click, from the control's own state, and comes back as `advanced.ok:false` with `stillNeeded`. When it did advance, `next` carries the FIELD NAMES of the step you are now on, so the following step costs no qa_snapshot of your own; pass next:false to leave them out. dry_run:true resolves the keys and fills nothing — read-only, and the cheap way to check a dataset against a page (with `advance` set it reports `wouldAdvance` and presses nothing). Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "next": { "description": "Default true when `advance` is set: ride the NEXT step's field names back in the same reply (`next.fields`, plus its title and what its forward control is called), so the step after this one costs you no qa_snapshot. Pass false when you do not need them.", "type": "boolean"
+ tool added: qa_frames
+ tool added: qa_odd_one_out
--- pinned/qa_press +++ observed/qa_press { "description": "Dispatch a keyboard key (keydown/keypress/keyup) — Enter to submit, Escape to close, Tab to move focus. Without a selector it goes to whatever currently has focus, like a real keyboard. Like qa_click, it returns what changed once the page settled. On a recent extension build the reply also carries `focus` — the element that ACTUALLY holds focus after the key (name, role, selector, box), which is how you follow a Tab route or catch focus landing somewhere unnamed — and `announced`, what the live regions spoke (null = they stayed silent). Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "key": { "description": "Key value, e.g. \"Enter\", \"Escape\", \"Tab\", \"a\"", "type": "string"
--- pinned/qa_read +++ observed/qa_read { "description": "Return the RENDERED text of the page under test, or of a single element named by a qa_snapshot ref or a CSS selector. This is the companion to qa_snapshot: that one maps what you can ACT on (roles, names, refs) and caps every name at 80 characters, so anything longer — a one-time code in an email, an error paragraph, a generated invoice number — is invisible to it. Uses innerText, so hidden branches and a mail thread's collapsed quoted history stay out. Read-only: it executes nothing and returns text, capped (default 4000 chars, max 20000).", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "maxChars": { "description": "Cap the returned text (default 4000, max 20000)", "type": "number"
--- pinned/qa_snapshot +++ observed/qa_snapshot { "description": "A compact accessibility / interaction snapshot of the WHOLE page: every interactive control, landmark and heading with a role, an accessible name, its state, and a STABLE ref like [ref=e12]. Unlike qa_export — which lists only the FORM fields Qiksy detects and can miss inputs, file uploads, date pickers, selects and buttons — this \"sees everything\". Pass a ref straight back to qa_click / qa_type / qa_spotlight: a ref is unique and unambiguous, so it works where a CSS selector is duplicated across fields. Built in the live, already-authenticated page in ~milliseconds — no browser launch, no CDP. Re-run it after the page changes to refresh the refs. ONE SNAPSHOT OWNS ITS REFS: taking a new one RENUMBERS them, so every ref from an earlier snapshot is void even when its element is still on the page — e12 may now be a different control, and nothing will refuse the click. Keep only the refs of your latest snapshot; where you cannot, target by `name` (qa_click, qa_fill_json), which survives both a re-render and a renumbering. On a big app, NARROW IT: `fields_only` for the form controls, `roles` for exactly the kinds you want, `match` for a name, `within` for one dialog or step panel. Filtering happens on the way out and does NOT invalidate anything — the refs you get back work like any other. One step of a wizard usually needs `fields_only:true, roles:[\"button\"]`, not the whole page.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "geometry": { "description": "Add each node's box as \"x,y,w,h\" in CSS pixels — for overlap, clipping, off-screen and tap-target-size checks. Off by default: it is bulk on every node.", "type": "boolean"
--- pinned/qa_styles +++ observed/qa_styles { "description": "The COMPUTED styles of one element — colour, type, spacing, border, shadow, layout — plus its box in CSS pixels, and the CSS custom properties (design tokens) in scope for it. This is how you check a built page against a design: it answers \"this button renders #3B82F6 at 15px with 22px padding\" without a screenshot and without measuring anything by eye. Target it by a qa_snapshot ref (preferred) or a CSS selector. Read-only and executes NOTHING: the property list is fixed in the extension, so you name an element, never a program. Pair it with a design source (a Figma file, a token JSON) to report the diff, and with qa_spotlight to show the human which element you mean.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "ref": { "description": "A ref from qa_snapshot (e.g. \"e12\") — preferred over selector", "type": "string"
--- pinned/qa_type +++ observed/qa_type { "description": "Put a value into ONE control, whatever kind it is — target it by a qa_snapshot ref (preferred) or a CSS selector. This is not a keystroke emulator, it is the extension's own fill engine, the same one behind the ⚡Fill button, so \"typing\" works on far more than text boxes: text/number/date inputs and textareas (native value setter + InputEvent, so React/Vue controlled inputs keep the value), native <select> (your text is matched against the option labels), checkboxes and radios (a truthy value checks it, with a real click so the app's listeners run), CUSTOM SELECTS and typeahead comboboxes (Radix/shadcn, MUI, Ant, react-select, Downshift — it opens the popover, waits for the options, picks the one matching your value and verifies the trigger actually changed), POPUP DATE & TIME PICKERS (opens the calendar and picks a day), sliders, and contenteditable rich text. A file field takes the neutral Qiksy test file matched to its `accept` — use qa_upload when the bytes matter. So do NOT click a dropdown open, snapshot to read its options and click one: name the field and the value you want. Only fall back to the click path when this answers ok:false. Reports back whether the value stuck, so a masked or read-only field is visible to you rather than silently ignored. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "frameId": { + "description": "Which frame to ask, from qa_frames. Omit for the page itself. A payment form, an SSO step or an embedded widget usually lives in a frame of its own, and a cross-origin one can only be read from inside it.", + "type": "integer" + }, "name": { "description": "The field's accessible name — resolved against a snapshot taken right now, so it survives the re-render that invalidates a ref. Use it for a field you are re-filling after something else changed.", "type": "string"
Changed 2026-10-05 14:28 UTC
2 descriptions rewritten, 7 schema or annotation changes with the description left byte-identical, 32 tools added.
+ tool added: qa_annotate
--- pinned/qa_axe +++ observed/qa_axe { "description": "The industry-standard accessibility rule engine (axe-core — the one under Lighthouse), bundled INSIDE the extension and run locally on the live DOM: nothing leaves the page, no remote code. Hundreds of WCAG A/AA checks on top of qa_run_audit's own set, narrowed to WCAG-tagged rules so every violation maps to a criterion. Returns violations with impact, WCAG tags, help URL and up to 10 offending selectors each. Read-only. Needs an extension build that carries the engine — an older build answers \"unknown command: axe\" (reload the extension after updating).", "execution": { + "omit": { + "description": "Drop these field paths and keep the rest — the cheap way to lose one huge field ([\"html\"], [\"steps\"]) without listing everything you want.", + "items": { + "type": "string" + }, + "type": "array" + }, + "select": { + "description": "Keep only these field paths, e.g. [\"ok\",\"fields[].name\"]. Dots walk into objects, and a path through a list applies to every entry, keeping it a list. A path that matches nothing is reported back with the field names that ARE there.", + "items": { + "type": "string" + }, + "type": "array" + }, "tabId": { "description": "Target tab (from qa_tabs); omit for the active tab", "type": "integer"
+ tool added: qa_click_point
+ tool added: qa_collect
+ tool added: qa_diff
+ tool added: qa_done
+ tool added: qa_draft
+ tool added: qa_draft_add
+ tool added: qa_draft_build
+ tool added: qa_draft_clear
+ tool added: qa_draft_drop
+ tool added: qa_draft_edit
+ tool added: qa_draft_meta
+ tool added: qa_draft_move
+ tool added: qa_drag_point
--- pinned/qa_export +++ observed/qa_export { "description": "Full qa-export/v1 bundle for a page: findings (with selectors), failed requests (with server error bodies), detected form structure, recorded repro steps, env, and isolated-login name. Read this, then fix the underlying issues. Pass tabId (from qa_tabs) to target a specific tab / isolated login.", "execution": { + "omit": { + "description": "Drop these field paths and keep the rest — the cheap way to lose one huge field ([\"html\"], [\"steps\"]) without listing everything you want.", + "items": { + "type": "string" + }, + "type": "array" + }, + "select": { + "description": "Keep only these field paths, e.g. [\"ok\",\"fields[].name\"]. Dots walk into objects, and a path through a list applies to every entry, keeping it a list. A path that matches nothing is reported back with the field names that ARE there.", + "items": { + "type": "string" + }, + "type": "array" + }, "tabId": { "description": "Target tab (from qa_tabs); omit for the active tab", "type": "integer"
+ tool added: qa_failure_gallery
--- pinned/qa_findings +++ observed/qa_findings { "description": "Findings on a page (console/JS/network/a11y/markup/submit) — each with a CSS selector and detail. Filter by severity; pass tabId (from qa_tabs) to read a specific tab / isolated login.", "execution": { + "omit": { + "description": "Drop these field paths and keep the rest — the cheap way to lose one huge field ([\"html\"], [\"steps\"]) without listing everything you want.", + "items": { + "type": "string" + }, + "type": "array" + }, + "select": { + "description": "Keep only these field paths, e.g. [\"ok\",\"fields[].name\"]. Dots walk into objects, and a path through a list applies to every entry, keeping it a list. A path that matches nothing is reported back with the field names that ARE there.", + "items": { + "type": "string" + }, + "type": "array" + }, "severity": { "default": "all", "description": "Severity filter",
+ tool added: qa_focus_proof
--- pinned/qa_focus_walk +++ observed/qa_focus_walk { "description": "The keyboard route as ONE call: presses Tab repeatedly and records where focus actually lands, then compares that trail against the interactive elements the accessibility tree claims. This is the accessibility question a click can never answer — Qiksy clicks teleport to an element, a keyboard user has to REACH it. Reports: the focus trail; interactive elements that never got focus (the div-button a Tab user can never reach); a suspected focus TRAP when the same short cycle of names repeats without the walk closing; and order-vs-geometry suspects — consecutive stops where focus jumps far UP the page, the classic broken tab order. Honest limits, stated because this tool feeds audits: matching is by accessible NAME (the press reply carries no ref), so duplicate names can mask a missed element; focus VISIBILITY is not judged here (that is qa_styles on a stop that interests you); and the walk changes the page's focus for real, so run it before filling, not mid-form. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "sheet": { + "description": "Also DRAW the route: write a PNG here with a numbered pin at every stop and a line between consecutive ones. A tab order that zigzags across the page is an argument nobody wins with a table of names — it is obvious in one picture, and that is what gets shown to the person who has to fix it.", + "type": "string" + }, "tabId": { "description": "Target tab (from qa_tabs); omit for the active tab", "type": "integer"
+ tool added: qa_gap
+ tool added: qa_hover
+ tool added: qa_open_tab
--- pinned/qa_probe +++ observed/qa_probe { "description": "Identify ONE unfamiliar control and get back the verb that drives it. Judges it first by shape — role, tag, name, state and its neighbours — which settles a sectioned date field, a file input, a switch, a slider or a plain field without touching the page at all. When the shape says nothing, which is what happens to a widget hand-rolled from <div>s (role \"generic\", no name, no state), it clicks once and reads what the page DID: day numbers or a grid appeared → a calendar, two options → a select however it is built, checkboxes → a tree, a menu, a modal, or the most useful verdict of the set — it went `expanded` and the tree gained NOTHING, which means the popup is portalled markup with no roles and must be addressed by CSS selector (Ant Design). Then it presses Escape so the form is left as it was found. Use it on the ONE field that just refused a value, not on every field: a snapshot already classifies the whole page (`componentLibraries` + `hints`), and driving is what the other verbs are for. Do not aim it at a Submit or Next — that click advances the page and Escape cannot take it back; the reply says `pageMoved` when it happened. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { + "ref": { + "description": "A ref from the last qa_snapshot of this tab. Resolved against THAT snapshot and re-found on the fresh one, so it survives the renumbering.", + "type": "number" + }, "selector": { "description": "CSS selector — for a control with no accessible name at all, which is the case this tool exists for", "type": "string"
+ tool added: qa_report_build
+ tool added: qa_report_drop
+ tool added: qa_report_edit
+ tool added: qa_report_get
+ tool added: qa_reports
--- pinned/qa_run_audit +++ observed/qa_run_audit { "description": "Run Qiksy's a11y/markup audit on the current DOM (unlabeled controls, missing names, WCAG AA contrast, broken images, duplicate ids, positive tabindex, …) and return the findings. Also refreshes the panel. Static, non-destructive — reads the DOM only.", "execution": { + "omit": { + "description": "Drop these field paths and keep the rest — the cheap way to lose one huge field ([\"html\"], [\"steps\"]) without listing everything you want.", + "items": { + "type": "string" + }, + "type": "array" + }, + "select": { + "description": "Keep only these field paths, e.g. [\"ok\",\"fields[].name\"]. Dots walk into objects, and a path through a list applies to every entry, keeping it a list. A path that matches nothing is reported back with the field names that ARE there.", + "items": { + "type": "string" + }, + "type": "array" + }, "tabId": { "description": "Target tab (from qa_tabs); omit for the active tab", "type": "integer"
--- pinned/qa_screenshot +++ observed/qa_screenshot { - "description": "Take a real picture of the page under test, or of ONE element, and hand it back as an image you can look at — not as measurements. Use it for the questions geometry cannot answer: does this look right, is the shadow there, is the text legible on that background, what does the tester actually see. Pass `ref` or `selector` to crop to one element (with a little padding); omit both for the visible page. The Qiksy panel is hidden and password and card fields are covered BEFORE the frame is taken, so a screenshot pasted into a ticket carries no secret. THE TAB MUST BE IN FRONT: the browser photographs the window's active tab, and Qiksy refuses to photograph a different one rather than hand you a page you did not ask about — call qa_focus_tab first if you have been driving a background tab. Downscaled to `maxWidth` (default 1200) because a retina frame is megabytes of context for pixels nobody needed; pass `path` to write the PNG to a file instead of spending context on it at all. Read-only: it changes nothing on the page.", + "description": "Take a real picture of the page under test, or of ONE element, and hand it back as an image you can look at — not as measurements. Use it for the questions geometry cannot answer: does this look right, is the shadow there, is the text legible on that background, what does the tester actually see. Pass `ref` or `selector` to crop to one element (with a little padding); omit both for the visible page. Pass `full: true` for THE WHOLE PAGE top to bottom, however long it is — the page is scrolled, photographed a screenful at a time and stitched into one picture, with a sticky header kept once rather than printed down the side. That is usually what a human means by \"show me the page\", and the visible-area shot answers a different question. It takes a second or so per screenful and cannot be combined with `ref`/`selector`. The Qiksy panel is hidden and password and card fields are covered BEFORE the frame is taken, so a screenshot pasted into a ticket carries no secret. THE TAB MUST BE IN FRONT: the browser photographs the window's active tab, and Qiksy refuses to photograph a different one rather than hand you a page you did not ask about — call qa_focus_tab first if you have been driving a background tab. EVERY SHOT CARRIES TWO THINGS A PICTURE CANNOT SAY ABOUT ITSELF: `build` — which build was actually running (the script files the page fetched, whatever version it declares, viewport, DPR, colour scheme, language), so nobody has to ask «which build was this»; and `receipt` — what was covered before the frame and what was never collected at all, which is what makes a picture of somebody else's application sendable. Downscaled to `maxWidth` (default 1200) because a retina frame is megabytes of context for pixels nobody needed. PASS `path` FOR A LONG PAGE: the file is written at FULL resolution — the downscale exists to protect this conversation, and a file is not this conversation. An inline thirty-screen shot is unreadable by arithmetic, not by accident. Read-only: it changes nothing on the page.", "execution": { "taskSupport": "forbidden" }, + "full": { + "description": "Photograph the WHOLE page, not just the visible part — scrolled and stitched. Ignored when ref/selector is given.", + "type": "boolean" + }, + "map": { + "description": "Return the element map with the picture: every visible control with its accessible name, role, CSS selector and box IN IMAGE PIXELS. This is what makes a screenshot something you can point INTO — anchor an annotation to a control, or draw a finding onto the shot without aiming. Written automatically beside the file when `path` is given.", + "type": "boolean" + }, "maxWidth": { "description": "Downscale to at most this wide (default 1200, max 2000)", "type": "number" - "description": "Write the PNG here instead of returning it inline — an absolute path, or one relative to where the bridge runs. You get back the path and the size.", + "description": "Write the PNG here instead of returning it inline — an absolute path, or one relative to where the bridge runs. You get back the path and the size, at FULL resolution, plus a `<name>.map.json` beside it.", "type": "string" }, "ref": { + "searchable": { + "description": "Also write a searchable SVG beside the PNG: the same picture with every string the page was showing written invisibly over its own pixels, so Ctrl+F works INSIDE the screenshot and the text can be selected and copied. Not OCR — the strings were read from the live document, so there is no recognition to be wrong about. Text inside <canvas> or a frame cannot be included, and the file says so.", + "type": "boolean" + }, "selector": { "description": "Crop to the first element matching this CSS selector", "type": "string"
+ tool added: qa_shot_crop
+ tool added: qa_shot_drop
+ tool added: qa_shot_list
+ tool added: qa_shot_mark
+ tool added: qa_shot_read
--- pinned/qa_snapshot +++ observed/qa_snapshot { "description": "A compact accessibility / interaction snapshot of the WHOLE page: every interactive control, landmark and heading with a role, an accessible name, its state, and a STABLE ref like [ref=e12]. Unlike qa_export — which lists only the FORM fields Qiksy detects and can miss inputs, file uploads, date pickers, selects and buttons — this \"sees everything\". Pass a ref straight back to qa_click / qa_type / qa_spotlight: a ref is unique and unambiguous, so it works where a CSS selector is duplicated across fields. Built in the live, already-authenticated page in ~milliseconds — no browser launch, no CDP. Re-run it after the page changes to refresh the refs. ONE SNAPSHOT OWNS ITS REFS: taking a new one RENUMBERS them, so every ref from an earlier snapshot is void even when its element is still on the page — e12 may now be a different control, and nothing will refuse the click. Keep only the refs of your latest snapshot; where you cannot, target by `name` (qa_click, qa_fill_json), which survives both a re-render and a renumbering. On a big app, NARROW IT: `fields_only` for the form controls, `roles` for exactly the kinds you want, `match` for a name, `within` for one dialog or step panel. Filtering happens on the way out and does NOT invalidate anything — the refs you get back work like any other. One step of a wizard usually needs `fields_only:true, roles:[\"button\"]`, not the whole page.", "execution": { + "omit": { + "description": "Drop these field paths and keep the rest — the cheap way to lose one huge field ([\"html\"], [\"steps\"]) without listing everything you want.", + "items": { + "type": "string" + }, + "type": "array" + }, "roles": { "description": "Keep only these roles, e.g. [\"button\",\"link\"] or [\"combobox\"]. Unioned with fields_only when both are given.", "items": { + "select": { + "description": "Keep only these field paths, e.g. [\"ok\",\"fields[].name\"]. Dots walk into objects, and a path through a list applies to every entry, keeping it a list. A path that matches nothing is reported back with the field names that ARE there.", + "items": { + "type": "string" + }, + "type": "array" + }, "tabId": { "description": "Target tab (from qa_tabs); omit for the active tab", "type": "integer"
--- pinned/qa_wait_for +++ observed/qa_wait_for { - "description": "Wait until the page satisfies ONE named condition, then return — instead of polling qa_snapshot, which renumbers every ref each time it is called. Conditions: `appears` / `disappears` / `enabled` (each takes an accessible name OR a CSS selector) · `text` / `textGone` (anywhere on the page) · `countOf` a selector with `atLeast` or `exactly` · `announced` (what the page said through a live region — pass a string to wait for one containing it, or true for any) · `urlContains`. Use it after anything whose answer arrives later than a click settles: an order confirmation, a slow save, a list that repopulates, a button that ungreys when validation finishes. A condition already true when you call returns immediately. ON TIMEOUT IT SAYS WHAT IT SAW — how many matched, how many were visible, the nearest names on the page, and anything announced — so a wait that failed on a typo tells you so instead of just running out. Read-only: waiting is not acting, so this needs no Pro and no consent — the same footing as qa_wait_ready. There is no \"until quiet\" (that is what every action already does) and no \"until the network is idle\" (the interceptor sees only the page's own fetch/XHR in the top frame, so the number would not mean what its name says).", + "description": "Wait until the page satisfies ONE named condition, then return — instead of polling qa_snapshot, which renumbers every ref each time it is called. Conditions: `appears` / `disappears` / `enabled` (each takes an accessible name OR a CSS selector) · `text` / `textGone` (anywhere on the page) · `countOf` a selector with `atLeast` or `exactly` · `announced` (what the page said through a live region — pass a string to wait for one containing it, or true for any) · `urlContains`. Use it after anything whose answer arrives later than a click settles: an order confirmation, a slow save, a list that repopulates, a button that ungreys when validation finishes. A condition already true when you call returns immediately. ON TIMEOUT IT SAYS WHAT IT SAW — how many matched, how many were visible, the nearest names on the page, and anything announced — so a wait that failed on a typo tells you so instead of just running out. A SIGN-IN IS WHAT THIS IS FOR, MORE THAN ANYTHING ELSE. When the site hands over to a password, a code from a phone or a hardware key, that step is the person's — do not drive it and do not call it a failure. Tell them to sign in, then `qa_wait_for { urlContains: \"<the site you came from>\", timeoutMs: 180000 }`: it is answered by the extension, so it survives the navigation, and if the tab never moves at all it says THAT instead of just running out. Read-only: waiting is not acting, so this needs no Pro and no consent — the same footing as qa_wait_ready. There is no \"until quiet\" (that is what every action already does) and no \"until the network is idle\" (the interceptor sees only the page's own fetch/XHR in the top frame, so the number would not mean what its name says).", "execution": { "taskSupport": "forbidden" }, - "description": "Give up after this long (default 15000, max 60000)", + "description": "Give up after this long (default 15000, max 60000 — or up to 300000 with urlContains, which is how you wait for a person to sign in)", "type": "number" }, "urlContains": {
+ tool added: qa_watch
+ tool added: qa_width_law
+ tool added: qa_wiring
Changed 2026-09-03 10:50 UTC
2 descriptions rewritten, 2 tools added.
--- pinned/qa_click +++ observed/qa_click { - "description": "Click an element on the page under test — by a qa_snapshot ref, by a CSS selector, or by the control's accessible `name` (the one to use right after a fill, since filling re-renders the step and staleness is what makes a ref refuse). Sends the full pointer sequence (pointerdown → mouseup → click), so component libraries that listen for pointerdown — Radix, shadcn, MUI — react to it like a real click. Scrolls the element into view first, and refuses a target that matches nothing or has zero size rather than silently doing nothing. Returns what the click CHANGED — what appeared, disappeared or updated once the page settled — so you do not need a follow-up qa_snapshot to find out whether it did anything. `changed: null` means the page did not move, which is itself the answer. On a recent extension build the reply also carries `announced` — what the page's live regions (aria-live / role=status / role=alert) actually spoke during the settle window, or null if they stayed silent: a toast that appeared in `changed` but not in `announced` is invisible to a screen reader (WCAG 4.1.3). Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", + "description": "Click an element on the page under test — by a qa_snapshot ref, by a CSS selector, or by the control's accessible `name` (the one to use right after a fill, since filling re-renders the step and staleness is what makes a ref refuse). Sends the full pointer sequence (pointerdown → mouseup → click), so component libraries that listen for pointerdown — Radix, shadcn, MUI — react to it like a real click. Scrolls the element into view first, and refuses a target that matches nothing or has zero size rather than silently doing nothing. Returns what the click CHANGED — what appeared, disappeared or updated once the page settled — so you do not need a follow-up qa_snapshot to find out whether it did anything. `changed: null` means the page did not move, which is itself the answer. On a recent extension build the reply also carries `announced` — what the page's live regions (aria-live / role=status / role=alert) actually spoke during the settle window, or null if they stayed silent: a toast that appeared in `changed` but not in `announced` is invisible to a screen reader (WCAG 4.1.3). AND WHEN THE PRESS NAVIGATED there is no diff to give: the reply carries `navigated: <the new url>` instead. That IS what the press did — do not press again, a second one would land on the NEXT page rather than repeat this one. Requires Qiksy Pro and the one-time “Agent control” confirmation in the panel; without both the extension answers pro-required / consent-required and does nothing.", "execution": { "taskSupport": "forbidden" },
+ tool added: qa_screenshot
--- pinned/qa_upload +++ observed/qa_upload { - "description": "Put a file into an <input type=file> — including the hidden ones behind a styled dropzone, which a click can never reach. Three ways to choose what goes in: `fixture` for a hostile edge-case file (the upload counterpart of naughty strings — empty, oversize, wrong-type, corrupt, unicode name, unsanitised SVG…), `path` for a file on this machine, or `content` as base64. Pass `content` as base64 to attach a file YOU generated (a PDF fixture, a malformed CSV, an oversized image); omit it and Qiksy attaches a neutral test file matched to the field's `accept`. A content script has no filesystem access and the OS file picker is a modal an agent must never open, so bytes over this bridge are the only way a file from outside the browser gets in. Delivery fires both paths uploaders use — assigning `input.files` + change, and a synthetic drop on the nearest dropzone — because react-dropzone-style components listen only for the second. SEVERAL FIELDS IN ONE CALL: pass `files` — an array of the same arguments — and they are attached in order, one round trip for the whole step instead of one per file.", + "description": "Put a file into an <input type=file> — including the hidden ones behind a styled dropzone, which a click can never reach. Three ways to choose what goes in: `fixture` for a hostile edge-case file (the upload counterpart of naughty strings — empty, oversize, wrong-type, corrupt, unicode name, unsanitised SVG…), `path` for a file on this machine, or `content` as base64. Pass `content` as base64 to attach a file YOU generated (a PDF fixture, a malformed CSV, an oversized image); omit it and Qiksy attaches a neutral test file matched to the field's `accept`. A content script has no filesystem access and the OS file picker is a modal an agent must never open, so bytes over this bridge are the only way a file from outside the browser gets in. AND WHERE THERE IS NO FILE INPUT AT ALL — a button that calls the native picker straight away, as YouTube Studio's audio-language dialog does — name that button with `field` and the file is handed to the picker it opens, exactly as a person choosing it would. The reply says `via` when that route was used, and it says so only when the page actually took the file. Delivery fires both paths uploaders use — assigning `input.files` + change, and a synthetic drop on the nearest dropzone — because react-dropzone-style components listen only for the second. SEVERAL FIELDS IN ONE CALL: pass `files` — an array of the same arguments — and they are attached in order, one round trip for the whole step instead of one per file.", "execution": { "taskSupport": "forbidden" },
+ tool added: qa_wait_for
Current tools
Show all 80 tool fingerprints
Watch this server yourself
If you run this server, put the proxy in front of it. It pins these exact fingerprints on first connect and stops the session if they move.
npx -y mcp-pin@0.2.3 -- <your qiksy-mcp command>
Or subscribe to this page's RSS feed to be told when it changes.
Badge
The badge states one fact about time and nothing else. It never claims a server is safe.
[](https://mcp-pin.gautamkhosla.com/servers/ec41970852a8356d.html)