App Privacy stopped at "now click Get Started yourself" and every page asked the owner to click Next. The owner should not click what storebound can safely do. - Every button of every console form is classified (navigation, draft-save, final-submit) in FORM_BUTTONS (cli/lib/console-forms.ts), citing Apple's and Google's help pages. A scoped flow gate (flowAllowed) lets navigation through; the generic click gate stays absolute for field clicks. - App Privacy: Get Started, Edit next to Data Types (never the Privacy Policy Edit), each data type / "Set Up <type>", Next, and the Save inside the questionnaire dialogs (a draft: Apple publishes only after Publish, per "Manage app privacy"). Each type's dialog may be one page or purposes > Next > linked > Next > tracking > Save. It ends with "review the summary, then press Publish yourself". Publish is never clicked. - Play content rating and target audience: Next by itself. Save draft, Submit, the final Save, Create and Create app stay the owner's. - An uncertain answer stops the walk on its page (outlined, "answer needed on ...") until the owner has set it; then it goes on. Fixture apple-app-privacy-flow.html (first time, ?existing=1, decoys); tests/console-flow.test.ts with the guardrail that no log line asks the owner to click a navigation control.
22 lines
1.5 KiB
JavaScript
22 lines
1.5 KiB
JavaScript
// Shared by the console fixtures (modeled pages, not recorded): records every click on a button, link, menu item or row
|
|
// (the engine must never click a save / submit / publish / create / next control) and lets a test act as the owner
|
|
// (window.__next() shows the next step, like the owner pressing Next). Navigation pages keep the log across pages in
|
|
// sessionStorage (window.__navClicks).
|
|
window.__clicks = [];
|
|
const navLog = () => { try { return JSON.parse(sessionStorage.getItem('navClicks') || '[]'); } catch { return []; } };
|
|
window.__navClicks = navLog;
|
|
document.addEventListener('click', e => {
|
|
const b = e.target.closest('button, a, [role="button"], [role="menuitem"], [role="menuitemradio"], [role="option"], [role="link"], [role="row"], tr');
|
|
if (!b) return;
|
|
const name = (b.getAttribute('aria-label') || b.textContent).trim().replace(/\s+/g, ' ');
|
|
if (b.matches('button')) window.__clicks.push(b.textContent.trim());
|
|
try { sessionStorage.setItem('navClicks', JSON.stringify([...navLog(), name])); } catch { /* file: pages */ }
|
|
}, true);
|
|
// a control with data-next acts like the console's Next: shows the next step (what storebound's flow clicks)
|
|
document.addEventListener('click', e => { if (e.target.closest('[data-next]')) window.__next(); });
|
|
window.__step = 0;
|
|
window.__next = () => {
|
|
const steps = [...document.querySelectorAll('[data-step]')];
|
|
window.__step = Math.min(window.__step + 1, steps.length - 1);
|
|
steps.forEach((s, i) => { s.hidden = i !== window.__step; });
|
|
};
|