@e2edev/mobile 0.8.0-canary-20260921180210

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.
Files changed (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +5 -0
  3. package/README.md +149 -0
  4. package/dist/.build.json +1 -0
  5. package/dist/actions.d.ts +29 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +62 -0
  8. package/dist/actions.js.map +1 -0
  9. package/dist/bindings.d.ts +57 -0
  10. package/dist/bindings.d.ts.map +1 -0
  11. package/dist/bindings.js +79 -0
  12. package/dist/bindings.js.map +1 -0
  13. package/dist/device.d.ts +77 -0
  14. package/dist/device.d.ts.map +1 -0
  15. package/dist/device.js +101 -0
  16. package/dist/device.js.map +1 -0
  17. package/dist/engine.d.ts +17 -0
  18. package/dist/engine.d.ts.map +1 -0
  19. package/dist/engine.js +92 -0
  20. package/dist/engine.js.map +1 -0
  21. package/dist/errors.d.ts +30 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +95 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index.d.ts +21 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +14 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/locate.d.ts +11 -0
  30. package/dist/locate.d.ts.map +1 -0
  31. package/dist/locate.js +115 -0
  32. package/dist/locate.js.map +1 -0
  33. package/dist/nodes.d.ts +86 -0
  34. package/dist/nodes.d.ts.map +1 -0
  35. package/dist/nodes.js +315 -0
  36. package/dist/nodes.js.map +1 -0
  37. package/dist/options.d.ts +93 -0
  38. package/dist/options.d.ts.map +1 -0
  39. package/dist/options.js +7 -0
  40. package/dist/options.js.map +1 -0
  41. package/dist/png.d.ts +4 -0
  42. package/dist/png.d.ts.map +1 -0
  43. package/dist/png.js +25 -0
  44. package/dist/png.js.map +1 -0
  45. package/dist/pool.d.ts +72 -0
  46. package/dist/pool.d.ts.map +1 -0
  47. package/dist/pool.js +185 -0
  48. package/dist/pool.js.map +1 -0
  49. package/dist/provider.d.ts +105 -0
  50. package/dist/provider.d.ts.map +1 -0
  51. package/dist/provider.js +98 -0
  52. package/dist/provider.js.map +1 -0
  53. package/dist/selector.d.ts +17 -0
  54. package/dist/selector.d.ts.map +1 -0
  55. package/dist/selector.js +84 -0
  56. package/dist/selector.js.map +1 -0
  57. package/dist/support.d.ts +58 -0
  58. package/dist/support.d.ts.map +1 -0
  59. package/dist/support.js +82 -0
  60. package/dist/support.js.map +1 -0
  61. package/dist/surface.d.ts +243 -0
  62. package/dist/surface.d.ts.map +1 -0
  63. package/dist/surface.js +837 -0
  64. package/dist/surface.js.map +1 -0
  65. package/dist/tools.d.ts +24 -0
  66. package/dist/tools.d.ts.map +1 -0
  67. package/dist/tools.js +90 -0
  68. package/dist/tools.js.map +1 -0
  69. package/package.json +83 -0
package/dist/pool.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The devices one target drives: one simulator or emulator per worker slot,
3
+ * each under its own agent-device session. A named `device` is a pool of one
4
+ * and a list a pool of many; with no `device` the pool is every booted device
5
+ * of the platform, discovered in `prepare`; a `DeviceProvider` leases one
6
+ * hosted device per slot instead. Each is a `DeviceSource`; `prepare` binds
7
+ * one `SlotBinding` per slot from it, warms each once per run before any
8
+ * worker exists, and hands the bindings to the workers through the run's
9
+ * environment. The worker that owns a slot then resumes its session in
10
+ * `init`, and `finish` releases what the source acquired.
11
+ */
12
+ import { type EngineFinishInfo, type EnginePrepareInfo, type EnginePrepareResult } from 'e2e/engine';
13
+ import { type SlotBinding } from './bindings.ts';
14
+ import type { MobileOptions, MobilePlatform, ClientFactory } from './options.ts';
15
+ /**
16
+ * The device selection agent-device commands take: the platform, and the
17
+ * device when one is named, as `udid` for a simulator UDID and `device` for a
18
+ * name, the two fields agent-device resolves them by.
19
+ */
20
+ export declare function deviceSelection(platform: MobilePlatform, device: string | undefined): {
21
+ platform: MobilePlatform;
22
+ device?: string;
23
+ udid?: string;
24
+ };
25
+ export declare class DevicePool {
26
+ private readonly options;
27
+ private readonly createClient;
28
+ private readonly source;
29
+ /** What this process's `prepare` bound, per target: a handle may serve several targets and runs. */
30
+ private readonly bound;
31
+ constructor(options: MobileOptions, createClient: ClientFactory);
32
+ /**
33
+ * Workers the engine serves per target, one per device: known up front for
34
+ * a configured pool, and `undefined` for a discovered or leased one until
35
+ * `prepare` reports it, which lets the run plan with its own cap meanwhile.
36
+ */
37
+ get size(): number | undefined;
38
+ /** The agent-device session a worker slot drives its device under: the `session` option or `e2e-<target>`, then `-<slot>`. */
39
+ session(targetName: string, slot: number): string;
40
+ /**
41
+ * What a worker slot drives, for a worker's `init`: what this process's
42
+ * `prepare` bound for the target (an in-process run), else what `prepare`
43
+ * left in the environment (a child worker), else the source's fallback
44
+ * for a run without `prepare`. With none, the choice stays with the local
45
+ * daemon, which picks a booted device. A slot beyond the pool is a broken
46
+ * invariant: the runner caps the target at the workers this engine
47
+ * declared or reported.
48
+ */
49
+ binding(targetName: string, slot: number, env: Readonly<Record<string, string | undefined>>): SlotBinding | undefined;
50
+ /**
51
+ * Binds a device to every worker slot the run will use and warms each,
52
+ * once per run and outside every launch budget, so `init` finds a booted
53
+ * device. The bindings are reported back as the target's worker cap and
54
+ * left in the environment for the workers. A target nothing runs on binds
55
+ * nothing: a hosted session is billed from the moment it starts.
56
+ */
57
+ prepare(info: EnginePrepareInfo): Promise<EnginePrepareResult>;
58
+ /** Releases what the source acquired for the target; a local pool has nothing to release. */
59
+ finish(info: EngineFinishInfo): Promise<void>;
60
+ /**
61
+ * Boots every bound device and opens the pinned app on it once, so its
62
+ * automation runner is up. One slot after another, on purpose: workers
63
+ * booting at once in `init` contend for the host and the daemon, and one
64
+ * cold boot pushes the others past `launchTimeout`. Each slot warms under
65
+ * the session its worker resumes. A device that cannot boot ends the run
66
+ * here; an app that does not open is logged and left to the first attempt.
67
+ * A build `appPath` installs in `init` is not on the device yet, so that
68
+ * slot boots only, unless a lease says the build is already there.
69
+ */
70
+ private warm;
71
+ }
72
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAwC,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC3I,OAAO,EAAkF,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjI,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAMjF;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAG9D;AA8ED,qBAAa,UAAU;IAMnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAN/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6C;IAEnE,YACmB,OAAO,EAAE,aAAa,EACtB,YAAY,EAAE,aAAa,EAG7C;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,8HAA8H;IAC9H,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,WAAW,GAAG,SAAS,CAYpH;IAED;;;;;;OAMG;IACG,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAMnE;IAED,6FAA6F;IACvF,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAElD;IAED;;;;;;;;;OASG;YACW,IAAI;CAiBnB"}
package/dist/pool.js ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * The devices one target drives: one simulator or emulator per worker slot,
3
+ * each under its own agent-device session. A named `device` is a pool of one
4
+ * and a list a pool of many; with no `device` the pool is every booted device
5
+ * of the platform, discovered in `prepare`; a `DeviceProvider` leases one
6
+ * hosted device per slot instead. Each is a `DeviceSource`; `prepare` binds
7
+ * one `SlotBinding` per slot from it, warms each once per run before any
8
+ * worker exists, and hands the bindings to the workers through the run's
9
+ * environment. The worker that owns a slot then resumes its session in
10
+ * `init`, and `finish` releases what the source acquired.
11
+ */
12
+ import { ConfigurationError, EngineError, obj } from 'e2e/engine';
13
+ import { bindingsVariable, decodeBindings, encodeBindings, pinnedApp } from './bindings.js';
14
+ import { message, runCommand } from './errors.js';
15
+ import { asDeviceProvider, LeasedDevices } from './provider.js';
16
+ /** An Apple simulator UDID; anything else names a device. */
17
+ const UDID = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
18
+ /**
19
+ * The device selection agent-device commands take: the platform, and the
20
+ * device when one is named, as `udid` for a simulator UDID and `device` for a
21
+ * name, the two fields agent-device resolves them by.
22
+ */
23
+ export function deviceSelection(platform, device) {
24
+ if (device !== undefined && UDID.test(device))
25
+ return { platform, udid: device };
26
+ return obj({ platform, device });
27
+ }
28
+ function isDeviceList(device) {
29
+ return Array.isArray(device);
30
+ }
31
+ /** The source the `device` option names: a configured pool, the booted devices, or a provider's leases. */
32
+ function sourceFor(options, createClient, session) {
33
+ const { device } = options;
34
+ if (device === undefined)
35
+ return new BootedDevices(options.platform, createClient, session);
36
+ if (typeof device === 'string')
37
+ return new ConfiguredDevices([device]);
38
+ if (isDeviceList(device)) {
39
+ if (device.length === 0) {
40
+ throw new ConfigurationError('INVALID_CONFIG', 'mobile: `device` is an empty pool; name at least one simulator or emulator, or omit it to use every booted one');
41
+ }
42
+ return new ConfiguredDevices(device);
43
+ }
44
+ return new LeasedDevices(asDeviceProvider(device), options);
45
+ }
46
+ /** The devices named in the config, bound in order, as many as the run has slots. */
47
+ class ConfiguredDevices {
48
+ size;
49
+ fallback;
50
+ constructor(devices) {
51
+ this.size = devices.length;
52
+ this.fallback = devices.map((device) => ({ device }));
53
+ }
54
+ async bind(info) {
55
+ return this.fallback.slice(0, Math.min(info.slots, this.size));
56
+ }
57
+ }
58
+ /** Every booted device of the platform, as many as the run has slots; with none booted, one slot the daemon fills. */
59
+ class BootedDevices {
60
+ platform;
61
+ createClient;
62
+ session;
63
+ constructor(platform, createClient, session) {
64
+ this.platform = platform;
65
+ this.createClient = createClient;
66
+ this.session = session;
67
+ }
68
+ async bind(info) {
69
+ const booted = await this.inventory(info.targetName, info.signal);
70
+ const chosen = booted.slice(0, info.slots);
71
+ if (chosen.length === 0) {
72
+ info.log(`no booted ${this.platform} device; agent-device boots one`);
73
+ return [{}];
74
+ }
75
+ info.log(`${booted.length} booted ${this.platform} device(s); driving ${chosen.length}: ${chosen.map((device) => device.name).join(', ')}`);
76
+ return chosen.map((device) => ({ device: device.id }));
77
+ }
78
+ /** Every booted device of the platform, by stable id and name, in agent-device's inventory order. */
79
+ async inventory(targetName, signal) {
80
+ const client = this.createClient(this.session(targetName, 0));
81
+ const inventory = await runCommand('devices', () => client.devices.list({ platform: this.platform }), signal);
82
+ if (!Array.isArray(inventory))
83
+ return [];
84
+ return inventory
85
+ .filter((device) => typeof device === 'object' &&
86
+ device !== null &&
87
+ device.booted === true &&
88
+ device.platform === this.platform &&
89
+ typeof device.id === 'string')
90
+ .map((device) => ({ id: device.id, name: typeof device.name === 'string' ? device.name : device.id }));
91
+ }
92
+ }
93
+ export class DevicePool {
94
+ options;
95
+ createClient;
96
+ source;
97
+ /** What this process's `prepare` bound, per target: a handle may serve several targets and runs. */
98
+ bound = new Map();
99
+ constructor(options, createClient) {
100
+ this.options = options;
101
+ this.createClient = createClient;
102
+ this.source = sourceFor(options, createClient, (targetName, slot) => this.session(targetName, slot));
103
+ }
104
+ /**
105
+ * Workers the engine serves per target, one per device: known up front for
106
+ * a configured pool, and `undefined` for a discovered or leased one until
107
+ * `prepare` reports it, which lets the run plan with its own cap meanwhile.
108
+ */
109
+ get size() {
110
+ return this.source.size;
111
+ }
112
+ /** The agent-device session a worker slot drives its device under: the `session` option or `e2e-<target>`, then `-<slot>`. */
113
+ session(targetName, slot) {
114
+ return `${this.options.session ?? `e2e-${targetName}`}-${slot}`;
115
+ }
116
+ /**
117
+ * What a worker slot drives, for a worker's `init`: what this process's
118
+ * `prepare` bound for the target (an in-process run), else what `prepare`
119
+ * left in the environment (a child worker), else the source's fallback
120
+ * for a run without `prepare`. With none, the choice stays with the local
121
+ * daemon, which picks a booted device. A slot beyond the pool is a broken
122
+ * invariant: the runner caps the target at the workers this engine
123
+ * declared or reported.
124
+ */
125
+ binding(targetName, slot, env) {
126
+ const bindings = this.bound.get(targetName) ?? decodeBindings(env[bindingsVariable(targetName)]) ?? this.source.fallback;
127
+ if (bindings === undefined)
128
+ return undefined;
129
+ const binding = bindings[slot];
130
+ if (binding === undefined) {
131
+ throw new EngineError('ENGINE_FAILURE', `worker slot ${slot} is outside a device pool of ${bindings.length}; the runner must cap the target at the engine's declared workers`, { retryable: false });
132
+ }
133
+ return binding;
134
+ }
135
+ /**
136
+ * Binds a device to every worker slot the run will use and warms each,
137
+ * once per run and outside every launch budget, so `init` finds a booted
138
+ * device. The bindings are reported back as the target's worker cap and
139
+ * left in the environment for the workers. A target nothing runs on binds
140
+ * nothing: a hosted session is billed from the moment it starts.
141
+ */
142
+ async prepare(info) {
143
+ if (info.slots === 0)
144
+ return {};
145
+ const bindings = await this.source.bind(info);
146
+ this.bound.set(info.targetName, bindings);
147
+ await this.warm(bindings, info);
148
+ return { workers: bindings.length, env: { [bindingsVariable(info.targetName)]: encodeBindings(bindings) } };
149
+ }
150
+ /** Releases what the source acquired for the target; a local pool has nothing to release. */
151
+ async finish(info) {
152
+ await this.source.finish?.(info);
153
+ }
154
+ /**
155
+ * Boots every bound device and opens the pinned app on it once, so its
156
+ * automation runner is up. One slot after another, on purpose: workers
157
+ * booting at once in `init` contend for the host and the daemon, and one
158
+ * cold boot pushes the others past `launchTimeout`. Each slot warms under
159
+ * the session its worker resumes. A device that cannot boot ends the run
160
+ * here; an app that does not open is logged and left to the first attempt.
161
+ * A build `appPath` installs in `init` is not on the device yet, so that
162
+ * slot boots only, unless a lease says the build is already there.
163
+ */
164
+ async warm(bindings, info) {
165
+ for (const [slot, binding] of bindings.entries()) {
166
+ const label = binding.device ?? `a booted ${this.options.platform} device`;
167
+ const where = deviceSelection(this.options.platform, binding.device);
168
+ const client = this.createClient(this.session(info.targetName, slot), binding.daemon);
169
+ info.log(`booting ${label} (${slot + 1} of ${bindings.length})`);
170
+ await runCommand('boot', () => client.devices.boot(where), info.signal);
171
+ const app = pinnedApp(this.options, binding.installedApp);
172
+ if (app === undefined)
173
+ continue;
174
+ try {
175
+ await runCommand(`open ${app}`, () => client.apps.open({ app, ...where }), info.signal);
176
+ }
177
+ catch (cause) {
178
+ if (info.signal.aborted)
179
+ throw cause;
180
+ info.log(`${label}: automation runner not warmed up (${message(cause)}); the first attempt starts it`);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,GAAG,EAA2E,MAAM,YAAY,CAAC;AAC3I,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAuC,MAAM,eAAe,CAAC;AACjI,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEhE,6DAA6D;AAC7D,MAAM,IAAI,GAAG,iEAAiE,CAAC;AAE/E;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAwB,EACxB,MAA0B;IAE1B,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACjF,OAAO,GAAG,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACnC,CAAC;AAKD,SAAS,YAAY,CAAC,MAAe;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED,2GAA2G;AAC3G,SAAS,SAAS,CAAC,OAAsB,EAAE,YAA2B,EAAE,OAAqB;IAC3F,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAC5F,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,kBAAkB,CAC1B,gBAAgB,EAChB,gHAAgH,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,aAAa,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,qFAAqF;AACrF,MAAM,iBAAiB;IACZ,IAAI,CAAS;IACb,QAAQ,CAAyB;IAE1C,YAAY,OAA0B;QACpC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAuB;QAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC;CACF;AAED,sHAAsH;AACtH,MAAM,aAAa;IAEE,QAAQ;IACR,YAAY;IACZ,OAAO;IAH1B,YACmB,QAAwB,EACxB,YAA2B,EAC3B,OAAqB;wBAFrB,QAAQ;4BACR,YAAY;uBACZ,OAAO;IACvB,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,IAAuB;QAChC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,QAAQ,iCAAiC,CAAC,CAAC;YACtE,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,WAAW,IAAI,CAAC,QAAQ,uBAAuB,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5I,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,qGAAqG;IAC7F,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,MAAmB;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAY,MAAM,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QACvH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,CAAC;QACzC,OAAO,SAAS;aACb,MAAM,CACL,CAAC,MAAM,EAA2C,EAAE,CAClD,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACd,MAA+B,CAAC,MAAM,KAAK,IAAI;YAC/C,MAAiC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAC7D,OAAQ,MAA2B,CAAC,EAAE,KAAK,QAAQ,CACtD;aACA,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3G,CAAC;CACF;AAED,MAAM,OAAO,UAAU;IAMF,OAAO;IACP,YAAY;IANd,MAAM,CAAe;IACtC,oGAAoG;IACnF,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAC;IAEnE,YACmB,OAAsB,EACtB,YAA2B;uBAD3B,OAAO;4BACP,YAAY;QAE7B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;IACvG,CAAC;IAED;;;;OAIG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,8HAA8H;IAC9H,OAAO,CAAC,UAAkB,EAAE,IAAY;QACtC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC;IAClE,CAAC;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAkB,EAAE,IAAY,EAAE,GAAiD;QACzF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QACzH,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,WAAW,CACnB,gBAAgB,EAChB,eAAe,IAAI,gCAAgC,QAAQ,CAAC,MAAM,mEAAmE,EACrI,EAAE,SAAS,EAAE,KAAK,EAAE,CACrB,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,IAAuB;QACnC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAChC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IAC9G,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,MAAM,CAAC,IAAsB;QACjC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;OASG;IACK,KAAK,CAAC,IAAI,CAAC,QAAgC,EAAE,IAAuB;QAC1E,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,IAAI,CAAC,OAAO,CAAC,QAAQ,SAAS,CAAC;YAC3E,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACrE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACtF,IAAI,CAAC,GAAG,CAAC,WAAW,KAAK,KAAK,IAAI,GAAG,CAAC,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YACjE,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACxE,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YAC1D,IAAI,GAAG,KAAK,SAAS;gBAAE,SAAS;YAChC,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,QAAQ,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1F,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO;oBAAE,MAAM,KAAK,CAAC;gBACrC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,sCAAsC,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACzG,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The seam a hosted device service plugs into: a `DeviceProvider` leases one
3
+ * device per worker slot for the run and hands back the agent-device daemon
4
+ * that drives it. The engine acquires in `prepare`, drives each lease from
5
+ * its worker, and releases in `finish`, on every exit path. Nothing here
6
+ * knows any vendor: a hosted simulator service, a device farm, or a daemon on
7
+ * a machine down the hall are each one small provider in user code.
8
+ */
9
+ import { type EngineFinishInfo, type EnginePrepareInfo } from 'e2e/engine';
10
+ import { type DeviceDaemon, type DeviceSource, type SlotBinding } from './bindings.ts';
11
+ import type { MobileOptions, MobilePlatform } from './options.ts';
12
+ /** What the engine asks a provider for: one device for one worker slot of a run. */
13
+ export interface DeviceRequest {
14
+ /** Platform the target's device must run. */
15
+ readonly platform: MobilePlatform;
16
+ /** The run id, for naming the session at the provider. */
17
+ readonly runId: string;
18
+ /** The target the device serves. */
19
+ readonly targetName: string;
20
+ /** Worker slot the device serves, `0` to `slots - 1`; `slots` leases are acquired at once. */
21
+ readonly slot: number;
22
+ /** Devices the run acquires for this target, in all. */
23
+ readonly slots: number;
24
+ /** The `app` option: the bundle id or package the engine opens fresh per attempt, when the config names one. */
25
+ readonly app?: string | undefined;
26
+ /**
27
+ * The build the engine would install, resolved to an absolute path, when
28
+ * the config names one. A provider that installs it itself (uploading it,
29
+ * or naming a build it already hosts) reports `installedApp` on the lease
30
+ * and the engine skips its own install.
31
+ */
32
+ readonly appPath?: string | undefined;
33
+ /** The run's environment: where a provider reads its token from, never `process.env`. */
34
+ readonly env: Readonly<Record<string, string | undefined>>;
35
+ /** Aborts on interrupt only; acquiring has no budget of its own. */
36
+ readonly signal: AbortSignal;
37
+ /** Reports one line of progress to the run's reporter (a session URL to watch). */
38
+ readonly log: (line: string) => void;
39
+ }
40
+ /**
41
+ * One leased device, as `acquire` returns it and `release` gets it back.
42
+ * JSON data only: a lease travels from the runner process to the worker that
43
+ * drives it through the environment.
44
+ */
45
+ export interface DeviceLease extends SlotBinding {
46
+ /**
47
+ * The provider's handle on the lease (a session id); named in progress
48
+ * lines and handed back to `release`. A provider may keep further fields on
49
+ * the object it returns: `release` gets that same object, while only the
50
+ * fields declared here travel to the worker.
51
+ */
52
+ readonly id: string;
53
+ /** The agent-device daemon the worker connects to instead of its local one. */
54
+ readonly daemon: DeviceDaemon;
55
+ /** Device to select inside that daemon, by name or UDID, when it hosts more than one. */
56
+ readonly device?: string | undefined;
57
+ /**
58
+ * Bundle id or package of the app the provider installed from the
59
+ * request's `appPath`, and only then: with it the engine installs nothing
60
+ * and, without an `app` option, opens this app fresh per attempt. A lease
61
+ * reporting one for a request without `appPath` fails the run.
62
+ */
63
+ readonly installedApp?: string | undefined;
64
+ }
65
+ /** Handed to `release`, once per lease at the end of the run. */
66
+ export interface DeviceReleaseContext {
67
+ readonly runId: string;
68
+ readonly targetName: string;
69
+ /** The run's environment, the same `acquire` saw. */
70
+ readonly env: Readonly<Record<string, string | undefined>>;
71
+ /** Aborts when the cleanup budget is spent. */
72
+ readonly signal: AbortSignal;
73
+ readonly log: (line: string) => void;
74
+ }
75
+ /**
76
+ * A source of hosted devices. `acquire` is called once per worker slot, for
77
+ * every slot at once; `release` once per lease acquired, at the end of the
78
+ * run, also after an `acquire` of another slot failed. Both run in the
79
+ * runner process, so a provider may keep state between them.
80
+ */
81
+ export interface DeviceProvider {
82
+ /** Label in progress lines and error messages. */
83
+ readonly name: string;
84
+ acquire(request: DeviceRequest): Promise<DeviceLease>;
85
+ release(lease: DeviceLease, context: DeviceReleaseContext): Promise<void>;
86
+ }
87
+ /** Narrows an intended provider, or names what it is missing. */
88
+ export declare function asDeviceProvider(device: object): DeviceProvider;
89
+ /**
90
+ * Devices a provider leases, one per slot, all at once: hosted sessions start
91
+ * in parallel and are billed from the moment they do, so nothing waits on a
92
+ * sibling. Every lease granted is held for `finish` before the outcome is
93
+ * known, so a slot that fails to lease fails the run and the others are
94
+ * still released, each one whatever happened to the rest.
95
+ */
96
+ export declare class LeasedDevices implements DeviceSource {
97
+ private readonly provider;
98
+ private readonly options;
99
+ /** Leases granted so far, per target, filled as each `acquire` settles. */
100
+ private readonly held;
101
+ constructor(provider: DeviceProvider, options: Pick<MobileOptions, 'platform' | 'app' | 'appPath'>);
102
+ bind(info: EnginePrepareInfo): Promise<readonly SlotBinding[]>;
103
+ finish(info: EngineFinishInfo): Promise<void>;
104
+ }
105
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The seam a hosted device service plugs into: a `DeviceProvider` leases one
3
+ * device per worker slot for the run and hands back the agent-device daemon
4
+ * that drives it. The engine acquires in `prepare`, drives each lease from
5
+ * its worker, and releases in `finish`, on every exit path. Nothing here
6
+ * knows any vendor: a hosted simulator service, a device farm, or a daemon on
7
+ * a machine down the hall are each one small provider in user code.
8
+ */
9
+ import path from 'node:path';
10
+ import { ConfigurationError, EngineError, obj } from 'e2e/engine';
11
+ import { isSlotBinding } from './bindings.js';
12
+ import { message } from './errors.js';
13
+ /** Narrows an intended provider, or names what it is missing. */
14
+ export function asDeviceProvider(device) {
15
+ const candidate = device;
16
+ if (typeof candidate.name !== 'string' || candidate.name.trim() === '') {
17
+ throw new ConfigurationError('INVALID_CONFIG', 'mobile: a `device` provider needs a non-empty `name`');
18
+ }
19
+ for (const member of ['acquire', 'release']) {
20
+ if (typeof candidate[member] !== 'function') {
21
+ throw new ConfigurationError('INVALID_CONFIG', `mobile: device provider "${candidate.name}" must implement ${member}()`);
22
+ }
23
+ }
24
+ return device;
25
+ }
26
+ /** A lease as a provider returned it, checked field by field: the engine trusts nothing it did not write. */
27
+ function isDeviceLease(value) {
28
+ return isSlotBinding(value) && typeof value.id === 'string' && value.daemon !== undefined;
29
+ }
30
+ /** Runs every task, then reports the first failure: nothing is skipped because a sibling failed. */
31
+ async function allOrFirstFailure(tasks, describe) {
32
+ // `async` so a task that throws before its first await is a rejection like any other, not an escape.
33
+ const settled = await Promise.allSettled(tasks.map(async (task) => task()));
34
+ const failed = settled.find((result) => result.status === 'rejected');
35
+ if (failed !== undefined) {
36
+ throw new EngineError('ENGINE_FAILURE', `${describe}: ${message(failed.reason)}`, { retryable: false, cause: failed.reason });
37
+ }
38
+ return settled.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []));
39
+ }
40
+ /**
41
+ * Devices a provider leases, one per slot, all at once: hosted sessions start
42
+ * in parallel and are billed from the moment they do, so nothing waits on a
43
+ * sibling. Every lease granted is held for `finish` before the outcome is
44
+ * known, so a slot that fails to lease fails the run and the others are
45
+ * still released, each one whatever happened to the rest.
46
+ */
47
+ export class LeasedDevices {
48
+ provider;
49
+ options;
50
+ /** Leases granted so far, per target, filled as each `acquire` settles. */
51
+ held = new Map();
52
+ constructor(provider, options) {
53
+ this.provider = provider;
54
+ this.options = options;
55
+ }
56
+ async bind(info) {
57
+ const { provider } = this;
58
+ const { platform, app } = this.options;
59
+ const appPath = this.options.appPath === undefined ? undefined : path.resolve(info.projectRoot, this.options.appPath);
60
+ const held = [];
61
+ this.held.set(info.targetName, held);
62
+ info.log(`leasing ${info.slots} ${platform} device(s) from ${provider.name}`);
63
+ const leases = await allOrFirstFailure(Array.from({ length: info.slots }, (_, slot) => async () => {
64
+ const lease = await provider.acquire({
65
+ platform,
66
+ runId: info.runId,
67
+ targetName: info.targetName,
68
+ slot,
69
+ slots: info.slots,
70
+ app,
71
+ appPath,
72
+ env: info.env,
73
+ signal: info.signal,
74
+ log: (line) => info.log(`${provider.name} (${slot + 1} of ${info.slots}): ${line}`),
75
+ });
76
+ if (!isDeviceLease(lease))
77
+ throw new Error('returned a lease without an id and a daemon baseUrl');
78
+ held.push(lease);
79
+ if (lease.installedApp !== undefined && appPath === undefined) {
80
+ throw new Error('reported an installed app for a request without `appPath`');
81
+ }
82
+ info.log(`${provider.name}: leased ${lease.id}${lease.device === undefined ? '' : ` (${lease.device})`}`);
83
+ return lease;
84
+ }), `device provider "${provider.name}" could not lease a device`);
85
+ return leases.map((lease) => obj({ device: lease.device, daemon: lease.daemon, installedApp: lease.installedApp }));
86
+ }
87
+ async finish(info) {
88
+ const { provider } = this;
89
+ const leases = this.held.get(info.targetName);
90
+ this.held.delete(info.targetName);
91
+ if (leases === undefined || leases.length === 0)
92
+ return;
93
+ const context = { runId: info.runId, targetName: info.targetName, env: info.env, signal: info.signal, log: (line) => info.log(`${provider.name}: ${line}`) };
94
+ await allOrFirstFailure(leases.map((lease) => () => provider.release(lease, context)), `device provider "${provider.name}" could not release a device`);
95
+ info.log(`${provider.name}: released ${leases.length} device(s)`);
96
+ }
97
+ }
98
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * agent-device selector support for the `selector` locator expression:
3
+ * `screen.locator('id=save role=button')` and the structural hint on every
4
+ * observed node. The grammar is agent-device's own (`parseSelectorChain`), so a
5
+ * selector that works on the agent-device CLI works here unchanged; matching
6
+ * runs against the projected snapshot, one immediate pass, every match returned.
7
+ */
8
+ import { type ProjectedNode } from './nodes.ts';
9
+ export type CompiledSelector = (entries: readonly ProjectedNode[]) => ProjectedNode[];
10
+ /**
11
+ * Compiles one selector string. Alternatives (agent-device's fallback chain)
12
+ * are tried in order and the first that matches anything wins, mirroring how
13
+ * agent-device itself resolves a chain; a term's text compares exactly after
14
+ * whitespace collapsing, like every other text rule in the runner.
15
+ */
16
+ export declare function compileSelector(raw: string): CompiledSelector;
17
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAQ/D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,KAAK,aAAa,EAAE,CAAC;AAEtF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAiB7D"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * agent-device selector support for the `selector` locator expression:
3
+ * `screen.locator('id=save role=button')` and the structural hint on every
4
+ * observed node. The grammar is agent-device's own (`parseSelectorChain`), so a
5
+ * selector that works on the agent-device CLI works here unchanged; matching
6
+ * runs against the projected snapshot, one immediate pass, every match returned.
7
+ */
8
+ import { parseSelectorChain } from 'agent-device/selectors';
9
+ import { EngineError } from 'e2e/engine';
10
+ import { normalizeKind } from './nodes.js';
11
+ import { message } from './errors.js';
12
+ /**
13
+ * Compiles one selector string. Alternatives (agent-device's fallback chain)
14
+ * are tried in order and the first that matches anything wins, mirroring how
15
+ * agent-device itself resolves a chain; a term's text compares exactly after
16
+ * whitespace collapsing, like every other text rule in the runner.
17
+ */
18
+ export function compileSelector(raw) {
19
+ let chain;
20
+ try {
21
+ chain = parseSelectorChain(raw);
22
+ }
23
+ catch (cause) {
24
+ throw new EngineError('ENGINE_FAILURE', `invalid agent-device selector ${JSON.stringify(raw)}: ${message(cause)}`, {
25
+ retryable: false,
26
+ cause,
27
+ });
28
+ }
29
+ return (entries) => {
30
+ for (const alternative of chain.selectors) {
31
+ const matches = entries.filter((entry) => alternative.terms.every((term) => matchesTerm(entry, term)));
32
+ if (matches.length > 0)
33
+ return matches;
34
+ }
35
+ return [];
36
+ };
37
+ }
38
+ function normalize(text) {
39
+ return text.replaceAll(/\s+/g, ' ').trim();
40
+ }
41
+ function textEquals(actual, expected) {
42
+ return actual !== undefined && normalize(actual) === normalize(String(expected));
43
+ }
44
+ function flag(value) {
45
+ return value === true || String(value).toLowerCase() === 'true';
46
+ }
47
+ function matchesTerm(entry, term) {
48
+ const states = entry.node.states ?? {};
49
+ switch (term.key) {
50
+ case 'id':
51
+ return textEquals(entry.raw.identifier, term.value);
52
+ case 'role': {
53
+ const wanted = normalizeKind(String(term.value));
54
+ return entry.kind === wanted || entry.node.role === wanted;
55
+ }
56
+ case 'text':
57
+ return textEquals(entry.raw.label, term.value) || textEquals(entry.raw.value, term.value);
58
+ case 'label':
59
+ return textEquals(entry.raw.label, term.value);
60
+ case 'value':
61
+ return textEquals(entry.raw.value, term.value);
62
+ case 'appname':
63
+ return textEquals(entry.raw.appName, term.value);
64
+ case 'windowtitle':
65
+ return textEquals(entry.raw.windowTitle, term.value);
66
+ case 'visible':
67
+ return (states.hidden !== true) === flag(term.value);
68
+ case 'hidden':
69
+ return (states.hidden === true) === flag(term.value);
70
+ case 'editable':
71
+ return (entry.node.role === 'textbox') === flag(term.value);
72
+ case 'selected':
73
+ return (states.selected === true) === flag(term.value);
74
+ case 'focused':
75
+ return (states.focused === true) === flag(term.value);
76
+ case 'enabled':
77
+ return (states.disabled !== true) === flag(term.value);
78
+ case 'hittable':
79
+ return (entry.raw.hittable !== false) === flag(term.value);
80
+ default:
81
+ return false;
82
+ }
83
+ }
84
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"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"}