mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
32b9db2d7a
* test: add Android full emulator coverage * ci: package Android helpers before nightly coverage * fix: harden Android nightly runtime evidence * fix: expose trace artifacts in MCP schema * style: format Android coverage manifest * fix: address Android coverage review findings * refactor: share live device coverage helpers * fix: restore fixture landmarks in device smokes * refactor: centralize live artifact assertions * fix: normalize fixture canary visibility
232 lines
8.9 KiB
TypeScript
232 lines
8.9 KiB
TypeScript
import { PUBLIC_COMMANDS } from '../../../src/command-catalog.ts';
|
|
import { ANDROID_AUDIO_CONTRACT_EVIDENCE } from '../../../src/daemon/handlers/__tests__/session-audio.coverage.ts';
|
|
import { ANDROID_INSTALL_SOURCE_CONTRACT_EVIDENCE } from '../../../src/platforms/__tests__/install-source.coverage.ts';
|
|
import { ANDROID_LIFECYCLE_CONTRACT_EVIDENCE } from '../provider-scenarios/android-lifecycle.coverage.ts';
|
|
import type { AndroidContractEvidence } from './contract-evidence.ts';
|
|
|
|
type PublicCommand = (typeof PUBLIC_COMMANDS)[keyof typeof PUBLIC_COMMANDS];
|
|
|
|
export type AndroidEmulatorCoverageEntry =
|
|
| { assertion: string; level: 'live'; scenario: string }
|
|
| {
|
|
assertion: string;
|
|
evidence: AndroidContractEvidence;
|
|
level: 'command-contract';
|
|
}
|
|
| { assertion: string; level: 'capability-denial' };
|
|
|
|
export type AndroidEmulatorCoverageClassificationSummary = {
|
|
capabilityDenial: number;
|
|
contract: number;
|
|
gap: number;
|
|
live: number;
|
|
total: number;
|
|
};
|
|
|
|
const C = PUBLIC_COMMANDS;
|
|
const live = (scenario: string, assertion: string): AndroidEmulatorCoverageEntry => ({
|
|
assertion,
|
|
level: 'live',
|
|
scenario,
|
|
});
|
|
const contract = (
|
|
evidence: AndroidContractEvidence,
|
|
assertion: string,
|
|
): AndroidEmulatorCoverageEntry => ({
|
|
assertion,
|
|
evidence,
|
|
level: 'command-contract',
|
|
});
|
|
|
|
/** One primary, observable owner for every public command on an Android emulator. */
|
|
export const ANDROID_EMULATOR_E2E_COVERAGE = {
|
|
[C.alert]: live('smoke:automation-system', 'native alert actions update fixture-visible results'),
|
|
[C.appSwitcher]: live(
|
|
'smoke:automation-system',
|
|
'Recents pixels differ from Home and the fixture restores through Android app state',
|
|
),
|
|
[C.apps]: live('smoke:inventory', 'installed fixture package appears in app inventory'),
|
|
[C.appState]: live(
|
|
'smoke:automation-system',
|
|
'Android foreground package changes on Home and returns to the fixture after restoration',
|
|
),
|
|
[C.artifacts]: live(
|
|
'full:observability-artifacts',
|
|
'inventory exposes generated recording and trace artifacts and one download consumes its entry',
|
|
),
|
|
[C.audio]: contract(
|
|
ANDROID_AUDIO_CONTRACT_EVIDENCE,
|
|
'host audio probing has an Android-emulator session contract',
|
|
),
|
|
[C.back]: live('smoke:automation-system', 'back returns from automation to the Settings tab'),
|
|
[C.batch]: live(
|
|
'full:observability-artifacts',
|
|
'nested get and is results retain their Android fixture evidence',
|
|
),
|
|
[C.boot]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario asserts typed Android boot result',
|
|
),
|
|
[C.capabilities]: live(
|
|
'smoke:inventory',
|
|
'typed capability response includes fixture-driving Android commands',
|
|
),
|
|
[C.click]: live('smoke:automation-system', 'resource-id selector opens fixture controls'),
|
|
[C.clipboard]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario round-trips Android clipboard text',
|
|
),
|
|
[C.close]: live('smoke:capture-close', 'session inventory proves fixture lease removal'),
|
|
[C.devices]: live('smoke:inventory', 'selected emulator serial appears in inventory'),
|
|
[C.diff]: live(
|
|
'smoke:automation-system',
|
|
'snapshot diff observes the Automation-to-Settings transition',
|
|
),
|
|
[C.doctor]: live('smoke:inventory', 'doctor discovers the installed fixture package'),
|
|
[C.events]: live(
|
|
'full:observability-artifacts',
|
|
'paged timeline includes commands from the active Android fixture session',
|
|
),
|
|
[C.fill]: live('smoke:form-input', 'replacement form text is read back from Android UI'),
|
|
[C.find]: live('smoke:automation-system', 'find observes the automation landmark'),
|
|
[C.focus]: live('smoke:form-input', 'snapshot-derived Android field point receives typed text'),
|
|
[C.gesture]: live(
|
|
'full:fixture-replays',
|
|
'helper-backed one- and two-pointer gestures produce every fixture transform effect',
|
|
),
|
|
[C.get]: live('smoke:automation-system', 'get returns fixture automation canary text'),
|
|
[C.home]: live(
|
|
'smoke:automation-system',
|
|
'Home changes Android foreground evidence before the fixture is restored',
|
|
),
|
|
[C.install]: live('smoke:fixture-bootstrap', 'public CLI installs cached/repacked fixture APK'),
|
|
[C.installFromSource]: contract(
|
|
ANDROID_INSTALL_SOURCE_CONTRACT_EVIDENCE,
|
|
'Android install-source resolves an installable artifact with typed identity',
|
|
),
|
|
[C.is]: live('smoke:automation-system', 'visible predicate passes for Android fixture node'),
|
|
[C.keyboard]: live('smoke:keyboard-ime', 'safe dismissal hides keyboard without navigating Back'),
|
|
[C.logs]: live(
|
|
'full:observability-artifacts',
|
|
'Android logcat starts, exposes a concrete path, and stops cleanly',
|
|
),
|
|
[C.longPress]: live('smoke:automation-system', '800ms hold increments durable fixture counter'),
|
|
[C.network]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario returns typed Android network entries',
|
|
),
|
|
[C.open]: live(
|
|
'smoke:automation-system',
|
|
'cold deep link and normal fixture launch render landmarks',
|
|
),
|
|
[C.orientation]: live(
|
|
'smoke:automation-system',
|
|
'fixture window state observes landscape then portrait Android rotation',
|
|
),
|
|
[C.perf]: live(
|
|
'full:observability-artifacts',
|
|
'startup, process memory, and CPU metrics are typed and numeric on the emulator',
|
|
),
|
|
[C.prepare]: {
|
|
assertion: 'Android emulator capability model rejects Apple runner preparation',
|
|
level: 'capability-denial',
|
|
},
|
|
[C.press]: live('smoke:automation-system', 'semantic press updates durable fixture input state'),
|
|
[C.push]: live(
|
|
'full:lifecycle-system',
|
|
'typed broadcast extras are persisted by the fixture receiver and rendered after refresh',
|
|
),
|
|
[C.reactNative]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario returns Android overlay dismissal state',
|
|
),
|
|
[C.record]: live(
|
|
'full:observability-artifacts',
|
|
'short visible fixture mutation produces a non-empty playable Android MP4',
|
|
),
|
|
[C.reinstall]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario validates APK and bundle reinstall identities',
|
|
),
|
|
[C.replay]: live(
|
|
'full:fixture-replays',
|
|
'the catalog traversal fixture runs through replay without retrying its deterministic flow',
|
|
),
|
|
[C.screenshot]: live('smoke:capture-close', 'captured fixture file has a valid PNG signature'),
|
|
[C.scroll]: live(
|
|
'full:fixture-replays',
|
|
'edge-aware catalog traversal reaches its footer and safely rediscovers the top',
|
|
),
|
|
[C.settings]: live(
|
|
'full:lifecycle-system',
|
|
'Android grant and deny permission transitions are observed by the fixture',
|
|
),
|
|
[C.shutdown]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario asserts typed Android shutdown result',
|
|
),
|
|
[C.snapshot]: live(
|
|
'smoke:automation-system',
|
|
'interactive tree exposes Android resource-id fixture nodes',
|
|
),
|
|
[C.swipe]: live(
|
|
'full:fixture-replays',
|
|
'direct fixture swipe moves the catalog before edge-aware recovery',
|
|
),
|
|
[C.test]: live('full:fixture-replays', 'deterministic fixture suite emits JUnit without retries'),
|
|
[C.trace]: live(
|
|
'full:observability-artifacts',
|
|
'a visible fixture mutation creates non-empty trace diagnostics at the requested path',
|
|
),
|
|
[C.triggerAppEvent]: contract(
|
|
ANDROID_LIFECYCLE_CONTRACT_EVIDENCE,
|
|
'provider scenario validates Android deep-link event delivery',
|
|
),
|
|
[C.tvRemote]: {
|
|
assertion: 'Android mobile emulator capability model rejects TV remote input',
|
|
level: 'capability-denial',
|
|
},
|
|
[C.type]: live('smoke:form-input', 'typed suffix is read back from focused Android field'),
|
|
[C.viewport]: {
|
|
assertion: 'Android emulator capability model rejects standalone viewport control',
|
|
level: 'capability-denial',
|
|
},
|
|
[C.wait]: live('smoke:automation-system', 'wait observes durable fixture landmarks'),
|
|
} satisfies Record<PublicCommand, AndroidEmulatorCoverageEntry>;
|
|
|
|
export const ANDROID_EMULATOR_COVERAGE_CLASSIFICATION_SUMMARY = buildCoverageClassificationSummary(
|
|
Object.values(ANDROID_EMULATOR_E2E_COVERAGE),
|
|
);
|
|
|
|
export function liveCommandsForScenario(scenarioId: string): PublicCommand[] {
|
|
return Object.entries(ANDROID_EMULATOR_E2E_COVERAGE)
|
|
.filter(([, entry]) => entry.level === 'live' && entry.scenario === scenarioId)
|
|
.map(([command]) => command as PublicCommand);
|
|
}
|
|
|
|
function buildCoverageClassificationSummary(
|
|
entries: readonly AndroidEmulatorCoverageEntry[],
|
|
): AndroidEmulatorCoverageClassificationSummary {
|
|
const summary: AndroidEmulatorCoverageClassificationSummary = {
|
|
capabilityDenial: 0,
|
|
contract: 0,
|
|
gap: 0,
|
|
live: 0,
|
|
total: entries.length,
|
|
};
|
|
for (const entry of entries) {
|
|
switch (entry.level) {
|
|
case 'live':
|
|
summary.live += 1;
|
|
break;
|
|
case 'command-contract':
|
|
summary.contract += 1;
|
|
break;
|
|
case 'capability-denial':
|
|
summary.capabilityDenial += 1;
|
|
break;
|
|
}
|
|
}
|
|
return summary;
|
|
}
|