console fill apple-app-privacy: fit the Data Collection dialog to the real console
A real run (sanitized debug dump) showed why the walk stopped after Next:
the data types step is the same "Data Collection" dialog (URL unchanged),
and each checkbox's accessible name is its label run together with its
help text ("NameIncluding first or last name"). Some labels also differ
from Apple's help page ("Purchases", "Other Data").
- labelNames: the exact name, then the label as a prefix that ends where
the help text starts (no lowercase letter follows), so "Name" never
matches "Phone Number...", "Head" never "Health...", "Contact" never
"Contacts...". Used for the data types and the purposes.
TYPE_ALIASES maps Purchase History / Other Data Types to the console's
labels.
- Dialog per step: a section's dialog is also found as the one visible
dialog with a heading of that name.
- Every click and check is bounded (ACTION_MS, 10 s; the scan test now
also rejects clicks without a timeout).
- The flow fixture mirrors the real dialog's structure (dialog/h2 title,
group h3s, input name = Apple's enum, <label> with label + help <p>s,
Save disabled until a type is ticked); no data from the dump. A test
checks every Apple type matches exactly one real-style checkbox.
- Pitfall 15.
Publish is still never clicked; unknown steps still get the watchdog dump.
This commit is contained in:
parent
8b669f5c86
commit
59150c1186
7 changed files with 135 additions and 41 deletions
|
|
@ -11,7 +11,8 @@ import { fillSession } from '../cli/console/fill';
|
|||
import type { ConsoleBrowser } from '../cli/console/session';
|
||||
import { APPLE_DATA_TYPE_IDS, type AppleDataTypeId, type AppleTypeAnswer } from '../cli/lib/app-privacy';
|
||||
import type { ShipConfig } from '../cli/lib/config';
|
||||
import { type FlowWhere, flowAllowed, flowClass, FORM_BUTTONS } from '../cli/lib/console-forms';
|
||||
import { type FlowWhere, flowAllowed, flowClass, FORM_BUTTONS, labelNames, TYPE_ALIASES } from '../cli/lib/console-forms';
|
||||
import { APPLE_PRIVACY_TYPES } from '../cli/lib/app-privacy';
|
||||
import { AFTER_ALL_MS, AFTER_EACH_MS, launchTestBrowser, logLines, testScope } from './helpers/browser';
|
||||
|
||||
const FIX = join(import.meta.dir, 'fixtures', 'console');
|
||||
|
|
@ -66,6 +67,26 @@ const NAV_YOURSELF = /\b(Next|Continue|Get Started|Edit|Back|Set Up|Start|Manage
|
|||
const navAsked = (lines: string[]) => lines.filter(l => NAV_YOURSELF.test(l));
|
||||
const PUBLISH = 'review the summary, then press Publish yourself';
|
||||
|
||||
describe('App Privacy data types as the real Data Collection dialog names them (label + help text, no separator)', () => {
|
||||
// the labels in the order of a real dialog (October 2026); each name is the label run together with its help text
|
||||
const REAL = ['Name', 'Email Address', 'Phone Number', 'Physical Address', 'Other User Contact Info', 'Health', 'Fitness', 'Payment Info', 'Credit Info',
|
||||
'Other Financial Info', 'Precise Location', 'Coarse Location', 'Sensitive Info', 'Contacts', 'Emails or Text Messages', 'Photos or Videos', 'Audio Data',
|
||||
'Gameplay Content', 'Customer Support', 'Other User Content', 'Browsing History', 'Search History', 'User ID', 'Device ID', 'Purchases',
|
||||
'Product Interaction', 'Advertising Data', 'Other Usage Data', 'Crash Data', 'Performance Data', 'Other Diagnostic Data', 'Environment Scanning', 'Hands',
|
||||
'Head', 'Other Data'];
|
||||
const names = REAL.map(l => `${l}Such as a placeholder help text`);
|
||||
test('every Apple data type matches exactly one checkbox; "Name" never matches "Phone Number...", "Head" never "Health..."', () => {
|
||||
for (const [, label] of Object.values(APPLE_PRIVACY_TYPES)) {
|
||||
const cands = labelNames(label, TYPE_ALIASES[label]);
|
||||
// what find() does: the first candidate name that matches anything decides, and it must match one checkbox
|
||||
const hits = cands.map(c => names.filter(n => (typeof c === 'string' ? n === c : c.test(n)))).find(h => h.length) ?? [];
|
||||
expect({ label, hits: hits.length }).toEqual({ label, hits: 1 });
|
||||
expect(hits[0]!.startsWith(TYPE_ALIASES[label]?.[0] ?? label)).toBe(true);
|
||||
}
|
||||
expect(labelNames('Contact').some(c => typeof c !== 'string' && c.test('ContactsSuch as a list'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('button classes (FORM_BUTTONS, flowClass, flowAllowed)', () => {
|
||||
const at = (w: Partial<FlowWhere> = {}): FlowWhere => ({ privacyPage: true, inDialog: false, inDataTypes: false, dataTypes: ['Device ID'], ...w });
|
||||
test('App Privacy: openers, Next and the in-dialog Save are clicked; Publish never', () => {
|
||||
|
|
@ -299,12 +320,13 @@ describe('every wait of the console engine is bounded (code scan)', () => {
|
|||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
test('no Playwright wait without a timeout, and the window\'s close is awaited only after a bounded wait or a close()', () => {
|
||||
test('no Playwright wait or click without a timeout, and the window\'s close is awaited only after a bounded wait or a close()', () => {
|
||||
const bad: string[] = [];
|
||||
for (const f of files) {
|
||||
const lines = readFileSync(join(SRC, f), 'utf8').split('\n');
|
||||
lines.forEach((l, i) => {
|
||||
if (/\.waitFor(Selector|URL|Event|LoadState|Function)?\(/.test(l) && !/timeout/.test(l)) bad.push(`${f}:${i + 1} ${l.trim()}`);
|
||||
if (/\.(click|setChecked|check|uncheck)\(/.test(l) && !/timeout|ACTION_MS/.test(l) && !/^\s*(\/\/|\*)/.test(l)) bad.push(`${f}:${i + 1} ${l.trim()}`);
|
||||
if (/await [\w.]+\.closed\s*;/.test(l) && !lines.slice(Math.max(0, i - 3), i).some(p => /close\(\)/.test(p))) bad.push(`${f}:${i + 1} ${l.trim()}`);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue