@kraken-e2e/driver-android 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ import type { Logger } from '@kraken-e2e/contracts';
2
+ /**
3
+ * Idempotent: refreshes symlinks every start (pnpm store paths move); version
4
+ * bumps change the generated package.json, which changes its md5 and triggers
5
+ * Appium's manifest re-sync.
6
+ */
7
+ export declare function prepareAppiumHome(homeDir: string, driverPackages: readonly string[]): void;
8
+ /** Allocates an OS-assigned free port (hazard 1: appium dies on EADDRINUSE). */
9
+ export declare function allocatePort(): Promise<number>;
10
+ export interface AppiumServerHandle {
11
+ readonly port: number;
12
+ close(): Promise<void>;
13
+ }
14
+ export interface StartServerOptions {
15
+ readonly homeDir: string;
16
+ readonly driverPackages: readonly string[];
17
+ readonly logFile: string;
18
+ readonly logger: Logger;
19
+ /** Appium 3 scoped syntax, e.g. 'uiautomator2:adb_shell'. */
20
+ readonly allowInsecure?: readonly string[];
21
+ }
22
+ export declare function startAppiumServer(options: StartServerOptions): Promise<AppiumServerHandle>;
23
+ //# sourceMappingURL=appium-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appium-server.d.ts","sourceRoot":"","sources":["../src/appium-server.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAWpD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAkC1F;AAED,gFAAgF;AAChF,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAapD;AAeD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+DhG"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Embedded Appium server lifecycle (ADR-0007, mechanism live-verified against
3
+ * appium@3.5.2):
4
+ *
5
+ * SYNTHESIZED PROJECT-MODE HOME — we generate an APPIUM_HOME whose
6
+ * package.json declares the EXACT appium+driver versions resolved from THIS
7
+ * package's own dependency tree (lockfile-governed, ADR-0001 §5.10) and
8
+ * symlink the resolved packages into its node_modules. Appium's project-mode
9
+ * manifest auto-discovers them; no `appium driver install` ever runs
10
+ * (`--source=local` is verified broken: it shells to `npm link`, which fires
11
+ * the drivers' `prepare` scripts and fails on published tarballs).
12
+ *
13
+ * Live-verified hazards handled here:
14
+ * - EADDRINUSE calls process.exit(1) even with throwInsteadOfExit → we
15
+ * allocate the port ourselves immediately before boot.
16
+ * - main() installs its own SIGINT/SIGTERM handlers that process.exit(0) →
17
+ * we strip them so the Kraken CLI owns signals (ADR-0001 §5.11).
18
+ */
19
+ import { mkdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
20
+ import { createRequire } from 'node:module';
21
+ import { createServer } from 'node:net';
22
+ import { dirname, join } from 'node:path';
23
+ import { KrakenError } from '@kraken-e2e/contracts';
24
+ const require_ = createRequire(import.meta.url);
25
+ function resolvedPackage(name) {
26
+ const packageJsonPath = require_.resolve(`${name}/package.json`);
27
+ const { version } = require_(packageJsonPath);
28
+ return { dir: dirname(packageJsonPath), version };
29
+ }
30
+ /**
31
+ * Idempotent: refreshes symlinks every start (pnpm store paths move); version
32
+ * bumps change the generated package.json, which changes its md5 and triggers
33
+ * Appium's manifest re-sync.
34
+ */
35
+ export function prepareAppiumHome(homeDir, driverPackages) {
36
+ const nodeModules = join(homeDir, 'node_modules');
37
+ mkdirSync(nodeModules, { recursive: true });
38
+ const appium = resolvedPackage('appium');
39
+ const dependencies = { appium: appium.version };
40
+ for (const name of [...driverPackages, 'appium']) {
41
+ const resolved = name === 'appium' ? appium : resolvedPackage(name);
42
+ if (name !== 'appium')
43
+ dependencies[name] = resolved.version;
44
+ const linkPath = join(nodeModules, name);
45
+ mkdirSync(dirname(linkPath), { recursive: true });
46
+ // Concurrent-run-safe replace: symlink to a temp name, rename over the
47
+ // target (atomic on POSIX) — a plain rm+symlink pair races a sibling run.
48
+ const tempLink = `${linkPath}.tmp-${process.pid}`;
49
+ rmSync(tempLink, { force: true });
50
+ symlinkSync(resolved.dir, tempLink, 'dir');
51
+ rmSync(linkPath, { recursive: true, force: true });
52
+ renameSync(tempLink, linkPath);
53
+ }
54
+ writeFileSync(join(homeDir, 'package.json'), `${JSON.stringify({
55
+ name: 'kraken-appium-home',
56
+ private: true,
57
+ description: 'Generated by Kraken — appium project-mode home (ADR-0007). Do not edit.',
58
+ dependencies,
59
+ }, null, 2)}\n`);
60
+ }
61
+ /** Allocates an OS-assigned free port (hazard 1: appium dies on EADDRINUSE). */
62
+ export async function allocatePort() {
63
+ return new Promise((resolve, reject) => {
64
+ const probe = createServer();
65
+ probe.once('error', reject);
66
+ probe.listen(0, '127.0.0.1', () => {
67
+ const address = probe.address();
68
+ const port = typeof address === 'object' && address !== null ? address.port : undefined;
69
+ probe.close(() => {
70
+ if (port === undefined)
71
+ reject(new Error('could not allocate a port'));
72
+ else
73
+ resolve(port);
74
+ });
75
+ });
76
+ });
77
+ }
78
+ function stripAddedSignalHandlers(before) {
79
+ for (const signal of ['SIGINT', 'SIGTERM']) {
80
+ for (const listener of process.listeners(signal)) {
81
+ if (!before[signal].includes(listener)) {
82
+ process.removeListener(signal, listener);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ export async function startAppiumServer(options) {
88
+ prepareAppiumHome(options.homeDir, options.driverPackages);
89
+ mkdirSync(dirname(options.logFile), { recursive: true });
90
+ // Import-safety rule (ADR-0001 §5.5): appium loads HERE, never at top level.
91
+ // Non-literal specifier: appium ships raw .ts type sources that explode under
92
+ // strict verbatimModuleSyntax; we type the surface we use by hand instead.
93
+ const appiumSpecifier = 'appium';
94
+ // The package is CJS — named ESM imports fail; use the default namespace.
95
+ const appiumModule = (await import(appiumSpecifier));
96
+ const main = appiumModule.default?.main ?? appiumModule.main;
97
+ if (!main) {
98
+ throw new KrakenError('KRK-DRIVER-START-FAILED', 'appium package has no main() export.');
99
+ }
100
+ // Allocate AFTER the (slow, seconds-cold) appium import so the port-reuse
101
+ // race window stays microseconds — appium process.exit(1)s on EADDRINUSE.
102
+ const port = await allocatePort();
103
+ // Hazard 2: strip the SIGINT/SIGTERM handlers main() installs.
104
+ const before = {
105
+ SIGINT: process.listeners('SIGINT'),
106
+ SIGTERM: process.listeners('SIGTERM'),
107
+ };
108
+ options.logger.info('starting embedded Appium server', {
109
+ port,
110
+ home: options.homeDir,
111
+ logFile: options.logFile,
112
+ });
113
+ let server;
114
+ try {
115
+ server = (await main({
116
+ subcommand: 'server',
117
+ port,
118
+ address: '127.0.0.1',
119
+ appiumHome: options.homeDir,
120
+ throwInsteadOfExit: true,
121
+ // Quiet console (the TUI owns stdout), full debug into the log file.
122
+ loglevel: 'error:debug',
123
+ logFile: options.logFile,
124
+ ...(options.allowInsecure !== undefined ? { allowInsecure: [...options.allowInsecure] } : {}),
125
+ }));
126
+ }
127
+ catch (cause) {
128
+ stripAddedSignalHandlers(before);
129
+ throw KrakenError.wrap(cause, 'KRK-DRIVER-START-FAILED', 'embedded Appium failed to start');
130
+ }
131
+ // Appium registers its handlers in a continuation AFTER main() resolves
132
+ // (live-verified) — wait one macrotask before stripping.
133
+ await new Promise((resolve) => setImmediate(resolve));
134
+ stripAddedSignalHandlers(before);
135
+ options.logger.info('Appium server ready', { port });
136
+ return {
137
+ port,
138
+ close: async () => {
139
+ await server.close();
140
+ options.logger.info('Appium server stopped', { port });
141
+ },
142
+ };
143
+ }
144
+ //# sourceMappingURL=appium-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appium-server.js","sourceRoot":"","sources":["../src/appium-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { DoctorCheck } from '@kraken-e2e/contracts';
2
+ /** Injectable so doctor checks are unit-testable without an Android SDK. */
3
+ export type CommandRunner = (command: string, args: readonly string[]) => {
4
+ status: number | null;
5
+ stdout: string;
6
+ };
7
+ export declare const spawnRunner: CommandRunner;
8
+ export interface FsProbe {
9
+ exists(path: string): boolean;
10
+ }
11
+ /**
12
+ * Kraken-specific Android checks (ADR-0001 §5.13): the failures Appium's own
13
+ * doctor does not diagnose for students — missing AVDs, JDK too old for
14
+ * sdkmanager, deprecated env vars. Wrapping `appium driver doctor uiautomator2`
15
+ * arrives with the server-lifecycle work (ADR-0007).
16
+ */
17
+ export declare function androidDoctorChecks(run?: CommandRunner, fs?: FsProbe): readonly DoctorCheck[];
18
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,KACpB;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,eAAO,MAAM,WAAW,EAAE,aAGzB,CAAC;AAEF,MAAM,WAAW,OAAO;IACtB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAID;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,GAAE,aAA2B,EAChC,EAAE,GAAE,OAAgB,GACnB,SAAS,WAAW,EAAE,CAsHxB"}
package/dist/doctor.js ADDED
@@ -0,0 +1,130 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ export const spawnRunner = (command, args) => {
5
+ const result = spawnSync(command, [...args], { encoding: 'utf8', timeout: 20_000 });
6
+ return { status: result.status, stdout: `${result.stdout ?? ''}${result.stderr ?? ''}` };
7
+ };
8
+ const realFs = { exists: (path) => existsSync(path) };
9
+ /**
10
+ * Kraken-specific Android checks (ADR-0001 §5.13): the failures Appium's own
11
+ * doctor does not diagnose for students — missing AVDs, JDK too old for
12
+ * sdkmanager, deprecated env vars. Wrapping `appium driver doctor uiautomator2`
13
+ * arrives with the server-lifecycle work (ADR-0007).
14
+ */
15
+ export function androidDoctorChecks(run = spawnRunner, fs = realFs) {
16
+ return [
17
+ {
18
+ id: 'android.sdk-home',
19
+ title: 'ANDROID_HOME points at an Android SDK',
20
+ run: async (host) => {
21
+ const home = host.env['ANDROID_HOME'];
22
+ const legacy = host.env['ANDROID_SDK_ROOT'];
23
+ if (!home && legacy) {
24
+ return {
25
+ status: 'warn',
26
+ detail: `only ANDROID_SDK_ROOT is set (${legacy}) — deprecated by Google`,
27
+ fix: 'Set ANDROID_HOME to the same directory (Google deprecated ANDROID_SDK_ROOT).',
28
+ };
29
+ }
30
+ if (!home) {
31
+ return {
32
+ status: 'fail',
33
+ detail: 'ANDROID_HOME is not set',
34
+ fix: 'Install the Android SDK and export ANDROID_HOME (usually ~/Library/Android/sdk on macOS).',
35
+ };
36
+ }
37
+ if (!fs.exists(home)) {
38
+ return {
39
+ status: 'fail',
40
+ detail: `ANDROID_HOME=${home} does not exist`,
41
+ fix: 'Point ANDROID_HOME at the actual SDK directory.',
42
+ };
43
+ }
44
+ return { status: 'ok', detail: home };
45
+ },
46
+ },
47
+ {
48
+ id: 'android.jdk',
49
+ title: 'JDK 17+ (sdkmanager requires it; uiautomator2 needs a JDK)',
50
+ run: async (host) => {
51
+ if (!host.env['JAVA_HOME']) {
52
+ return {
53
+ status: 'fail',
54
+ detail: 'JAVA_HOME is not set',
55
+ fix: 'Install a JDK (17+; 21 LTS recommended) and export JAVA_HOME.',
56
+ };
57
+ }
58
+ const result = run('java', ['--version']);
59
+ const major = Number.parseInt(/(?:openjdk|java)\s+(\d+)/i.exec(result.stdout)?.[1] ?? '0', 10);
60
+ if (result.status !== 0 || major === 0) {
61
+ return {
62
+ status: 'fail',
63
+ detail: 'java is not runnable',
64
+ fix: 'Ensure $JAVA_HOME/bin is on PATH.',
65
+ };
66
+ }
67
+ if (major < 17) {
68
+ return {
69
+ status: 'fail',
70
+ detail: `JDK ${major} found`,
71
+ fix: 'Android SDK tooling is compiled for JDK 17+ (class file 61) — upgrade (21 LTS recommended).',
72
+ };
73
+ }
74
+ return { status: 'ok', detail: `JDK ${major}` };
75
+ },
76
+ },
77
+ {
78
+ id: 'android.adb',
79
+ title: 'adb (platform-tools) is runnable',
80
+ run: async (host) => {
81
+ const home = host.env['ANDROID_HOME'];
82
+ const adb = home ? join(home, 'platform-tools', 'adb') : 'adb';
83
+ const result = run(adb, ['--version']);
84
+ if (result.status !== 0) {
85
+ return {
86
+ status: 'fail',
87
+ detail: 'adb did not run',
88
+ fix: 'Install platform-tools: sdkmanager "platform-tools" (or via Android Studio).',
89
+ };
90
+ }
91
+ return { status: 'ok', detail: result.stdout.split('\n')[0] ?? 'adb ok' };
92
+ },
93
+ },
94
+ {
95
+ id: 'android.target',
96
+ title: 'A device is connected or an AVD exists',
97
+ run: async (host) => {
98
+ const home = host.env['ANDROID_HOME'];
99
+ const adb = home ? join(home, 'platform-tools', 'adb') : 'adb';
100
+ const devices = run(adb, ['devices'])
101
+ .stdout.split('\n')
102
+ .slice(1)
103
+ .filter((line) => line.trim().endsWith('device'));
104
+ if (devices.length > 0) {
105
+ return { status: 'ok', detail: `${devices.length} device(s)/emulator(s) connected` };
106
+ }
107
+ const emulator = home ? join(home, 'emulator', 'emulator') : 'emulator';
108
+ const avds = run(emulator, ['-list-avds'])
109
+ .stdout.split('\n')
110
+ .map((line) => line.trim())
111
+ .filter((line) => line.length > 0 && !line.startsWith('INFO'));
112
+ if (avds.length > 0) {
113
+ return {
114
+ status: 'ok',
115
+ detail: `no device connected; ${avds.length} AVD(s) available for auto-boot: ${avds.join(', ')}`,
116
+ };
117
+ }
118
+ const armNote = host.platform === 'darwin' && host.arch === 'arm64'
119
+ ? ' On Apple Silicon only arm64-v8a system images boot (API 26+ required by uiautomator2).'
120
+ : '';
121
+ return {
122
+ status: 'fail',
123
+ detail: 'no connected device and no AVD',
124
+ fix: `Create an AVD (Android Studio → Device Manager) or connect a device with USB debugging.${armNote}`,
125
+ };
126
+ },
127
+ },
128
+ ];
129
+ }
130
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface AndroidDriverOptions {
2
+ /** Default AVD to boot for actors that don't specify a device (appium:avd). */
3
+ readonly avd?: string;
4
+ /** Appium 3 scoped insecure features, e.g. ['uiautomator2:adb_shell']. */
5
+ readonly allowInsecure?: readonly string[];
6
+ /** Extra capabilities merged into every session (appium:* keys included). */
7
+ readonly capabilities?: Readonly<Record<string, unknown>>;
8
+ }
9
+ export declare const android: (opts?: AndroidDriverOptions | undefined) => import("@kraken-e2e/contracts").KrakenDriver<AndroidDriverOptions>;
10
+ export default android;
11
+ export { allocatePort, prepareAppiumHome, startAppiumServer } from './appium-server.js';
12
+ export { androidDoctorChecks } from './doctor.js';
13
+ export { keyCodeFor, toAndroidSelector } from './locators.js';
14
+ export { manifest } from './manifest.js';
15
+ export { AndroidUserSession } from './wdio-session.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC3D;AAED,eAAO,MAAM,OAAO,iHAwFlB,CAAC;AAEH,eAAe,OAAO,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @kraken-e2e/driver-android — Android driver (Appium 3 + UiAutomator2, ADR-0007).
3
+ *
4
+ * IMPORT-SAFETY RULE (ADR-0001 §5.5): this entry must be importable on every
5
+ * host — appium and webdriverio load DYNAMICALLY inside start()/createSession().
6
+ */
7
+ import { join } from 'node:path';
8
+ import { defineDriver } from '@kraken-e2e/contracts';
9
+ import { allocatePort, startAppiumServer } from './appium-server.js';
10
+ import { androidDoctorChecks } from './doctor.js';
11
+ import { manifest } from './manifest.js';
12
+ import { AndroidUserSession } from './wdio-session.js';
13
+ export const android = defineDriver((opts = {}) => {
14
+ let server;
15
+ return {
16
+ manifest,
17
+ doctor: androidDoctorChecks(),
18
+ async start(host, services) {
19
+ if (server) {
20
+ // Double start would silently leak the first embedded server.
21
+ throw new Error('driver-android: already started — call stop() first.');
22
+ }
23
+ const projectRoot = host.projectRoot ?? process.cwd();
24
+ server = await startAppiumServer({
25
+ homeDir: join(projectRoot, '.kraken', 'appium', 'android-home'),
26
+ driverPackages: ['appium-uiautomator2-driver'],
27
+ logFile: join(services.artifactsDir, 'appium-android.log'),
28
+ logger: services.logger,
29
+ ...(opts.allowInsecure !== undefined ? { allowInsecure: opts.allowInsecure } : {}),
30
+ });
31
+ },
32
+ async createSession(actor, services) {
33
+ if (!server) {
34
+ throw new Error('driver-android: start() must run before createSession().');
35
+ }
36
+ const config = actor.config;
37
+ // OS-assigned free port: process-local counters over a fixed base collide
38
+ // across CONCURRENT kraken runs on one machine (uia2 accepts any port).
39
+ const systemPort = await allocatePort();
40
+ const capabilities = {
41
+ platformName: 'Android',
42
+ 'appium:automationName': 'UiAutomator2',
43
+ 'appium:systemPort': systemPort,
44
+ 'appium:newCommandTimeout': 300,
45
+ // Cold emulator boots on a laptop take a while — be generous.
46
+ 'appium:avdLaunchTimeout': 180_000,
47
+ 'appium:avdReadyTimeout': 180_000,
48
+ 'appium:adbExecTimeout': 60_000,
49
+ // Laptop-class default: the 20s upstream default flakes on loaded
50
+ // machines (observed live: server-APK install timeout mid-demo).
51
+ 'appium:uiautomator2ServerInstallTimeout': 120_000,
52
+ ...(typeof config['udid'] === 'string' ? { 'appium:udid': config['udid'] } : {}),
53
+ ...(typeof config['avd'] === 'string' || opts.avd !== undefined
54
+ ? { 'appium:avd': config['avd'] ?? opts.avd }
55
+ : {}),
56
+ ...(typeof config['app'] === 'string' ? { 'appium:app': config['app'] } : {}),
57
+ ...(typeof config['appPackage'] === 'string'
58
+ ? { 'appium:appPackage': config['appPackage'] }
59
+ : {}),
60
+ ...(typeof config['appActivity'] === 'string'
61
+ ? { 'appium:appActivity': config['appActivity'] }
62
+ : {}),
63
+ ...opts.capabilities,
64
+ ...(typeof config['capabilities'] === 'object' && config['capabilities'] !== null
65
+ ? config['capabilities']
66
+ : {}),
67
+ };
68
+ services.logger.info('creating Android session', { actor: actor.id, systemPort });
69
+ // Import-safety rule: webdriverio loads here, not at the top level.
70
+ const { remote } = await import('webdriverio');
71
+ const browser = await remote({
72
+ hostname: '127.0.0.1',
73
+ port: server.port,
74
+ capabilities: capabilities,
75
+ logLevel: 'error',
76
+ connectionRetryTimeout: 300_000,
77
+ connectionRetryCount: 1,
78
+ });
79
+ const appPackage = typeof capabilities['appium:appPackage'] === 'string'
80
+ ? capabilities['appium:appPackage']
81
+ : undefined;
82
+ return new AndroidUserSession(browser, actor, services, appPackage);
83
+ },
84
+ async stop() {
85
+ await server?.close();
86
+ server = undefined;
87
+ },
88
+ };
89
+ });
90
+ export default android;
91
+ export { allocatePort, prepareAppiumHome, startAppiumServer } from './appium-server.js';
92
+ export { androidDoctorChecks } from './doctor.js';
93
+ export { keyCodeFor, toAndroidSelector } from './locators.js';
94
+ export { manifest } from './manifest.js';
95
+ export { AndroidUserSession } from './wdio-session.js';
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type SemanticKey, type TargetLocator } from '@kraken-e2e/contracts';
2
+ /**
3
+ * Portable locator strategies → UiAutomator2 selectors (ADR-0002 D1 mapping:
4
+ * testId → resource-id, a11y → content-desc, text → visible text).
5
+ *
6
+ * Unqualified resource ids ("composer") are matched with a regex against the
7
+ * `<package>:id/<value>` convention, so feature files stay package-agnostic.
8
+ */
9
+ export declare function toAndroidSelector(target: TargetLocator): string;
10
+ /**
11
+ * Android key codes for the semantic keys (contract 2.0). KEYCODE_BACK (4)
12
+ * is intentionally absent — 'back' left SemanticKey (Android-only concept);
13
+ * it remains reachable via { by: 'native' } flows or native() (ADR-0007).
14
+ */
15
+ export declare const ANDROID_KEY_CODES: Readonly<Record<SemanticKey, number>>;
16
+ export declare function keyCodeFor(key: SemanticKey): number;
17
+ //# sourceMappingURL=locators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locators.d.ts","sourceRoot":"","sources":["../src/locators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE1F;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAgB/D;AAMD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAInE,CAAC;AAEF,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CASnD"}
@@ -0,0 +1,46 @@
1
+ import { KrakenError } from '@kraken-e2e/contracts';
2
+ /**
3
+ * Portable locator strategies → UiAutomator2 selectors (ADR-0002 D1 mapping:
4
+ * testId → resource-id, a11y → content-desc, text → visible text).
5
+ *
6
+ * Unqualified resource ids ("composer") are matched with a regex against the
7
+ * `<package>:id/<value>` convention, so feature files stay package-agnostic.
8
+ */
9
+ export function toAndroidSelector(target) {
10
+ switch (target.by) {
11
+ case 'testId':
12
+ return target.value.includes(':id/')
13
+ ? `android=new UiSelector().resourceId("${target.value}")`
14
+ : `android=new UiSelector().resourceIdMatches(".*:id/${escapeForUiSelector(target.value)}")`;
15
+ case 'text':
16
+ return target.exact
17
+ ? `android=new UiSelector().text("${escapeForUiSelector(target.value)}")`
18
+ : `android=new UiSelector().textContains("${escapeForUiSelector(target.value)}")`;
19
+ case 'a11y':
20
+ return `~${target.value}`;
21
+ case 'native':
22
+ // Explicitly non-portable: any raw WebdriverIO/Appium selector string.
23
+ return target.value;
24
+ }
25
+ }
26
+ function escapeForUiSelector(value) {
27
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
28
+ }
29
+ /**
30
+ * Android key codes for the semantic keys (contract 2.0). KEYCODE_BACK (4)
31
+ * is intentionally absent — 'back' left SemanticKey (Android-only concept);
32
+ * it remains reachable via { by: 'native' } flows or native() (ADR-0007).
33
+ */
34
+ export const ANDROID_KEY_CODES = {
35
+ enter: 66, // KEYCODE_ENTER
36
+ escape: 111, // KEYCODE_ESCAPE
37
+ tab: 61, // KEYCODE_TAB
38
+ };
39
+ export function keyCodeFor(key) {
40
+ const code = ANDROID_KEY_CODES[key];
41
+ if (code === undefined) {
42
+ throw new KrakenError('KRK-SESSION-OP-UNSUPPORTED', `Unknown semantic key "${key}" for Android.`);
43
+ }
44
+ return code;
45
+ }
46
+ //# sourceMappingURL=locators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locators.js","sourceRoot":"","sources":["../src/locators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwC,MAAM,uBAAuB,CAAC;AAE1F;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,QAAQ,MAAM,CAAC,EAAE,EAAE,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAClC,CAAC,CAAC,wCAAwC,MAAM,CAAC,KAAK,IAAI;gBAC1D,CAAC,CAAC,qDAAqD,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QACjG,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,KAAK;gBACjB,CAAC,CAAC,kCAAkC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI;gBACzE,CAAC,CAAC,0CAA0C,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QACtF,KAAK,MAAM;YACT,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,KAAK,QAAQ;YACX,uEAAuE;YACvE,OAAO,MAAM,CAAC,KAAK,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA0C;IACtE,KAAK,EAAE,EAAE,EAAE,gBAAgB;IAC3B,MAAM,EAAE,GAAG,EAAE,iBAAiB;IAC9B,GAAG,EAAE,EAAE,EAAE,cAAc;CACxB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,GAAgB;IACzC,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,WAAW,CACnB,4BAA4B,EAC5B,yBAAyB,GAAG,gBAAgB,CAC7C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The /manifest subpath (ADR-0001 §5.5): ZERO heavy imports — the registry
3
+ * host-gates string-form registrations by importing THIS module before the
4
+ * main entry ever loads. Keep it dependency-light forever.
5
+ */
6
+ import { type DriverManifest } from '@kraken-e2e/contracts';
7
+ export declare const manifest: DriverManifest;
8
+ export default manifest;
9
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE9E,eAAO,MAAM,QAAQ,EAAE,cActB,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The /manifest subpath (ADR-0001 §5.5): ZERO heavy imports — the registry
3
+ * host-gates string-form registrations by importing THIS module before the
4
+ * main entry ever loads. Keep it dependency-light forever.
5
+ */
6
+ import { CONTRACT_VERSION } from '@kraken-e2e/contracts';
7
+ export const manifest = {
8
+ kind: 'kraken-driver',
9
+ id: 'android',
10
+ platforms: ['android'],
11
+ version: '0.0.0',
12
+ contract: CONTRACT_VERSION,
13
+ platformLabel: 'Android (UiAutomator2 via Appium 3)',
14
+ // No hostRequirements: Android automation runs on macOS, Linux and Windows.
15
+ setupHints: [
16
+ 'Install the Android SDK and set ANDROID_HOME',
17
+ 'Install a JDK (17+) and set JAVA_HOME',
18
+ 'Create an arm64-v8a AVD at API 26+ (on Apple Silicon) or connect a device',
19
+ 'Run: kraken doctor',
20
+ ],
21
+ };
22
+ export default manifest;
23
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AAE9E,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,IAAI,EAAE,eAAe;IACrB,EAAE,EAAE,SAAS;IACb,SAAS,EAAE,CAAC,SAAS,CAAC;IACtB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,gBAAgB;IAC1B,aAAa,EAAE,qCAAqC;IACpD,4EAA4E;IAC5E,UAAU,EAAE;QACV,8CAA8C;QAC9C,uCAAuC;QACvC,2EAA2E;QAC3E,oBAAoB;KACrB;CACF,CAAC;AAEF,eAAe,QAAQ,CAAC"}