From b48407115e22963178dc4b8ad103afa632ddc72c Mon Sep 17 00:00:00 2001 From: CRT Date: Mon, 12 Oct 2026 07:19:50 +0800 Subject: [PATCH] console fill: checkbox / radio ticks are gated on agreements only (help text like 'saved place in a game' no longer blocks Product Interaction); buttons keep the full word gate --- cli/console/fill.ts | 8 ++++++-- cli/lib/console-forms.ts | 8 ++++++++ tests/console-fill.test.ts | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cli/console/fill.ts b/cli/console/fill.ts index 208a0df..d30d6f8 100644 --- a/cli/console/fill.ts +++ b/cli/console/fill.ts @@ -29,7 +29,7 @@ import { join } from 'node:path'; import type { Ctx } from '../lib/ctx'; import { type Answer, type ConsoleResult, consoleResult, DISCLAIMER, FORM_IDS, type FormId, fillValue, formMeta, isForm, valueText } from '../lib/console-answers'; import type { ShipConfig } from '../lib/config'; -import { ALL_APPS, APP_PRIVACY_HEADING, consoleReached, CREATE_FORM_FIELD, createAppAllowed, DATA_TYPES_HEADING, type FieldDef, FLOW_NAMES, flowAllowed, flowClass, type FlowWhere, type FormDef, formDef, FORM_OPENERS, forbiddenControl, hasToken, isSignInText, isSignInUrl, type Name, navAllowed, type NavBases, type NavStep, navPlan } from '../lib/console-forms'; +import { ALL_APPS, APP_PRIVACY_HEADING, consoleReached, CREATE_FORM_FIELD, createAppAllowed, DATA_TYPES_HEADING, type FieldDef, FLOW_NAMES, flowAllowed, flowClass, type FlowWhere, type FormDef, formDef, FORM_OPENERS, forbiddenControl, forbiddenTick, hasToken, isSignInText, isSignInUrl, type Name, navAllowed, type NavBases, type NavStep, navPlan } from '../lib/console-forms'; import { log, printJson, warn } from '../lib/log'; import { openAcross, withAccountIds } from './accounts'; import { appleAppId, withAppleAppId } from './app-id'; @@ -112,7 +112,11 @@ export async function accName(loc: Locator): Promise { * Turkish). Every click storebound makes, checkboxes and radio buttons included, goes through here. */ export async function safeClick(loc: Locator, name: string, action: 'click' | 'check' | 'uncheck' = 'click'): Promise { - if (forbiddenControl(name)) { warn(TAG, `not clicking "${name}": storebound never presses save, submit, publish, next or similar`); return false; } + // buttons and links: the full word gate; ticks (checkbox / radio): only agreements are refused, help text is not a button label + if (action === 'click' ? forbiddenControl(name) : forbiddenTick(name)) { + warn(TAG, action === 'click' ? `not clicking "${name}": storebound never presses save, submit, publish, next or similar` : `not ticking "${name}": agreements are yours to accept`); + return false; + } if (action === 'click') await loc.click({ timeout: ACTION_MS }); else await loc.setChecked(action === 'check', { timeout: ACTION_MS }); return true; diff --git a/cli/lib/console-forms.ts b/cli/lib/console-forms.ts index 745e637..e34796d 100644 --- a/cli/lib/console-forms.ts +++ b/cli/lib/console-forms.ts @@ -144,6 +144,14 @@ const FORBIDDEN = new RegExp(`(? FORBIDDEN.test(name.toLocaleLowerCase('tr').normalize('NFC')) || FORBIDDEN.test(name.toLowerCase()); +/** + * Ticking a checkbox / radio never saves or sends anything, so the button word list above does not apply to them (their + * names carry help text: "Product InteractionSuch as ... saved place in a game"). What a tick must never do is accept an + * agreement: those are refused here. + */ +const AGREEMENT = /\b(?:i agree|agree to|accept(?:s|ed)?\b|terms of service|developer program polic|export laws?|declaration|consent)|kabul|onaylıyorum|beyan/iu; +export const forbiddenTick = (name: string): boolean => AGREEMENT.test(name.toLocaleLowerCase('tr').normalize('NFC')); + const SIGN_IN_HOSTS = ['appleid.apple.com', 'idmsa.apple.com', 'accounts.google.com']; /** diff --git a/tests/console-fill.test.ts b/tests/console-fill.test.ts index 99ca5d0..bae654b 100644 --- a/tests/console-fill.test.ts +++ b/tests/console-fill.test.ts @@ -964,3 +964,15 @@ describe.skipIf(!browser)('play-app-record (headless, modeled pages)', () => { expect((await done).filled).toBe(4); }, SLOW_TEST_MS); }); + +test('ticks are gated on agreements only: help text with "saved" is fine, Developer Program Policies is not; buttons keep the full gate', async () => { + const { forbiddenTick, forbiddenControl } = await import('../cli/lib/console-forms'); + const productInteraction = 'Product InteractionSuch as app launches, taps, clicks, scrolling information, music listening data, video views, saved place in a game, video, or song, or other information about how the user interacts with the app'; + expect(forbiddenTick(productInteraction)).toBe(false); + expect(forbiddenTick('NameIncluding first or last name')).toBe(false); + expect(forbiddenTick('Developer Program Policies')).toBe(true); + expect(forbiddenTick('US export laws')).toBe(true); + expect(forbiddenTick('Kabul ediyorum')).toBe(true); + expect(forbiddenControl('Save')).toBe(true); + expect(forbiddenControl('Publish')).toBe(true); +});