From fe6bf38f72854f9a72b28b3263399028fd26e093 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Sat, 6 Jun 2026 02:16:55 +0300 Subject: [PATCH 1/3] Prevent checkbox double-toggle in Tester interactions Re-clicking an already-selected checkbox toggled it back off, dropping a "Select N tests" selection to zero and saving an empty result. Teach the shared action rules that toggle controls (checkboxes, switches, selectable rows) are not idempotent: after a click, read the resulting ariaDiff and stop re-clicking once the control already shows the desired state. Document idempotent I.checkOption / I.uncheckOption and route them through the form() tool (the click tool only accepts I.click). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 5 +++++ src/ai/rules.ts | 15 +++++++++++++++ src/ai/tools.ts | 2 ++ 3 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3a1a8..350c990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026-06-06 + +### Changes +- [Tester] Checkboxes and other toggle controls (switches, selectable rows/items) are no longer flipped back off by accident. The Tester now sets a checkbox with the idempotent `I.checkOption` / `I.uncheckOption` commands instead of `I.click`, so selecting an already-selected checkbox keeps it selected. It also reads the page state after each click and stops re-clicking a control once it already shows the wanted result (checked, selected, or a count/label confirming success). Previously a second click on a selected checkbox could toggle it off — for example dropping a "Select 32 tests" selection back to "Select 0 tests" and saving an empty result. + ## 2026-06-04 ### Changes diff --git a/src/ai/rules.ts b/src/ai/rules.ts index fe40137..9e989c4 100644 --- a/src/ai/rules.ts +++ b/src/ai/rules.ts @@ -270,6 +270,8 @@ export const actionRule = dedent` If locator doesn't work, try CSS or XPath locators. If nothing works, use I.clickXY(x, y) as last resort. + For checkboxes, prefer I.checkOption/I.uncheckOption over I.click. + ### I.fillField @@ -356,6 +358,19 @@ export const actionRule = dedent` I.selectOption('form select[name=account]', 'Premium'); + ### I.checkOption / I.uncheckOption + + Set a checkbox/radio to a definite state — idempotent, never toggles. Use for checkboxes instead of I.click. Run via form(), not click(). + + I.checkOption(, ) + I.uncheckOption(, ) + + + I.checkOption('Subscribe'); + I.checkOption({ role: 'checkbox', text: 'Agree' }); + I.uncheckOption('Subscribe', '.preferences'); + + ### I.attachFile Attaches a file to a file input element. diff --git a/src/ai/tools.ts b/src/ai/tools.ts index e31100a..961cd49 100644 --- a/src/ai/tools.ts +++ b/src/ai/tools.ts @@ -307,6 +307,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) { Use cases: - Typing into input fields (I.fillField, I.type) + - Setting checkboxes/radios to a definite state (I.checkOption, I.uncheckOption) - Working with iframes (switch context with I.switchTo) - Performing multiple form actions in a single batch - Complex interactions requiring sequential commands @@ -314,6 +315,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) { Example - filling a form with context (PREFERRED): I.fillField('Username', 'John', '.login-form') I.selectOption('Country', 'USA', '.address-section') + I.checkOption('Agree', '.terms-section') I.attachFile('input[type="file"]', 'path/to/file', '.upload-section') Example - filling a form with ARIA locators: From 06338adafe15c55fb3600d2c6a0afdc66ffa4927 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Sat, 6 Jun 2026 02:33:09 +0300 Subject: [PATCH 2/3] Read form field requirements before filling data-changing forms Add formRequirementsRule and inject it into the Tester system message so the Tester reads each control's requirements (required, type/format, length, placeholder/aria hints) before filling create/update forms; view-only search/filter/sort forms are exempt. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + src/ai/rules.ts | 6 +++++ src/ai/tester.ts | 4 +++- tests/unit/tester-form-requirements.test.ts | 25 +++++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/unit/tester-form-requirements.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 350c990..5ef697d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changes - [Tester] Checkboxes and other toggle controls (switches, selectable rows/items) are no longer flipped back off by accident. The Tester now sets a checkbox with the idempotent `I.checkOption` / `I.uncheckOption` commands instead of `I.click`, so selecting an already-selected checkbox keeps it selected. It also reads the page state after each click and stops re-clicking a control once it already shows the wanted result (checked, selected, or a count/label confirming success). Previously a second click on a selected checkbox could toggle it off — for example dropping a "Select 32 tests" selection back to "Select 0 tests" and saving an empty result. +- [Tester] Before filling a form that saves data (create/update), the Tester now reads each field's requirements — required, type/format, length, and placeholder/hint text — and enters values that satisfy them, instead of submitting and discovering validation errors. Search, filter, and sort forms that only change the view are skipped. ## 2026-06-04 diff --git a/src/ai/rules.ts b/src/ai/rules.ts index 9e989c4..bc4d41e 100644 --- a/src/ai/rules.ts +++ b/src/ai/rules.ts @@ -131,6 +131,12 @@ export const fileUploadRule = dedent` `; +export const formRequirementsRule = dedent` + + Before filling a form that persists data (create/update), read each control's requirements (required, type/format, length, placeholder/aria-describedby hints) from and page HTML — call context() if not visible — and enter values that satisfy them. Search/filter/sort forms that only change the view do not need this. + +`; + // in rage mode we do not protect from irreversible actions export const protectionRule = dedent` diff --git a/src/ai/tester.ts b/src/ai/tester.ts index bb634b7..4b63990 100644 --- a/src/ai/tester.ts +++ b/src/ai/tester.ts @@ -25,7 +25,7 @@ import { Navigator } from './navigator.ts'; import type { Pilot } from './pilot.ts'; import { Provider } from './provider.ts'; import { Researcher } from './researcher.ts'; -import { actionRule, focusedElementRule, locatorRule, multipleTabsRule, protectionRule, sectionContextRule } from './rules.ts'; +import { actionRule, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, protectionRule, sectionContextRule } from './rules.ts'; import { TaskAgent } from './task-agent.ts'; import { createCodeceptJSTools, createSpecialContextTools } from './tools.ts'; @@ -773,6 +773,8 @@ export class Tester extends TaskAgent implements Agent { ${sectionContextRule} + ${formRequirementsRule} + ${this.provider.getSystemPromptForAgent('tester', this.explorer.getStateManager().getCurrentState()?.url) || ''} `; } diff --git a/tests/unit/tester-form-requirements.test.ts b/tests/unit/tester-form-requirements.test.ts new file mode 100644 index 0000000..5622bc5 --- /dev/null +++ b/tests/unit/tester-form-requirements.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'bun:test'; +import { Tester } from '../../src/ai/tester.ts'; + +function buildTester(): Tester { + const explorer: any = { + getConfig: () => ({}), + getStateManager: () => ({ getCurrentState: () => null }), + }; + const provider: any = { + getSystemPromptForAgent: () => '', + }; + const researcher: any = {}; + const navigator: any = {}; + return new Tester(explorer, provider, researcher, navigator); +} + +describe('Tester getSystemMessage — form requirements', () => { + it('instructs reading form requirements before filling data-changing forms', () => { + const message = buildTester().getSystemMessage(); + + expect(message).toContain(''); + expect(message).toContain('persists data'); + expect(message).toContain('Search/filter/sort'); + }); +}); From 65e1d958cb2e077aeb139391cd95f6eea8d751e8 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Sat, 6 Jun 2026 02:36:31 +0300 Subject: [PATCH 3/3] Drop tester-form-requirements string-containment test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It only asserted the rule text appears in the system message — a tautological wiring check coupled to exact prompt wording, no behavior. Co-Authored-By: Claude Opus 4.8 --- tests/unit/tester-form-requirements.test.ts | 25 --------------------- 1 file changed, 25 deletions(-) delete mode 100644 tests/unit/tester-form-requirements.test.ts diff --git a/tests/unit/tester-form-requirements.test.ts b/tests/unit/tester-form-requirements.test.ts deleted file mode 100644 index 5622bc5..0000000 --- a/tests/unit/tester-form-requirements.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, it } from 'bun:test'; -import { Tester } from '../../src/ai/tester.ts'; - -function buildTester(): Tester { - const explorer: any = { - getConfig: () => ({}), - getStateManager: () => ({ getCurrentState: () => null }), - }; - const provider: any = { - getSystemPromptForAgent: () => '', - }; - const researcher: any = {}; - const navigator: any = {}; - return new Tester(explorer, provider, researcher, navigator); -} - -describe('Tester getSystemMessage — form requirements', () => { - it('instructs reading form requirements before filling data-changing forms', () => { - const message = buildTester().getSystemMessage(); - - expect(message).toContain(''); - expect(message).toContain('persists data'); - expect(message).toContain('Search/filter/sort'); - }); -});