@fluentui-react-native/desktop-driver 0.2.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.
- package/.cache/tsconfig.tsbuildinfo +1 -0
- package/AGENTS.md +68 -0
- package/CHANGELOG.md +16 -0
- package/PLAN.md +1394 -0
- package/README.md +223 -0
- package/SPEC.md +105 -0
- package/config/cli.cjs +8 -0
- package/jest.config.cjs +9 -0
- package/lib/agent/DesktopAgent.d.ts +56 -0
- package/lib/agent/DesktopAgent.d.ts.map +1 -0
- package/lib/agent/DesktopAgent.js +118 -0
- package/lib/agent/DesktopAgent.js.map +1 -0
- package/lib/agent/DesktopAgent.test.d.ts +2 -0
- package/lib/agent/DesktopAgent.test.d.ts.map +1 -0
- package/lib/agent/DesktopAgent.test.js +97 -0
- package/lib/agent/DesktopAgent.test.js.map +1 -0
- package/lib/agent/agent.contract.cjs +66 -0
- package/lib/agent/agent.contract.cjs.map +1 -0
- package/lib/agent/agent.contract.d.cts +2 -0
- package/lib/agent/agent.contract.d.cts.map +1 -0
- package/lib/agent/index.d.ts +3 -0
- package/lib/agent/index.d.ts.map +1 -0
- package/lib/agent/index.js +2 -0
- package/lib/agent/index.js.map +1 -0
- package/lib/artifacts/ArtifactManager.d.ts +13 -0
- package/lib/artifacts/ArtifactManager.d.ts.map +1 -0
- package/lib/artifacts/ArtifactManager.js +88 -0
- package/lib/artifacts/ArtifactManager.js.map +1 -0
- package/lib/artifacts/ArtifactManager.test.d.ts +2 -0
- package/lib/artifacts/ArtifactManager.test.d.ts.map +1 -0
- package/lib/artifacts/ArtifactManager.test.js +19 -0
- package/lib/artifacts/ArtifactManager.test.js.map +1 -0
- package/lib/artifacts/index.d.ts +2 -0
- package/lib/artifacts/index.d.ts.map +1 -0
- package/lib/artifacts/index.js +2 -0
- package/lib/artifacts/index.js.map +1 -0
- package/lib/authoring/index.d.ts +4 -0
- package/lib/authoring/index.d.ts.map +1 -0
- package/lib/authoring/index.js +2 -0
- package/lib/authoring/index.js.map +1 -0
- package/lib/authoring/results.d.ts +43 -0
- package/lib/authoring/results.d.ts.map +1 -0
- package/lib/authoring/results.js +2 -0
- package/lib/authoring/results.js.map +1 -0
- package/lib/authoring/storyTests.d.ts +81 -0
- package/lib/authoring/storyTests.d.ts.map +1 -0
- package/lib/authoring/storyTests.js +272 -0
- package/lib/authoring/storyTests.js.map +1 -0
- package/lib/authoring/storyTests.test.d.ts +2 -0
- package/lib/authoring/storyTests.test.d.ts.map +1 -0
- package/lib/authoring/storyTests.test.js +46 -0
- package/lib/authoring/storyTests.test.js.map +1 -0
- package/lib/cli/DesktopDriverCli.test.d.ts +2 -0
- package/lib/cli/DesktopDriverCli.test.d.ts.map +1 -0
- package/lib/cli/DesktopDriverCli.test.js +141 -0
- package/lib/cli/DesktopDriverCli.test.js.map +1 -0
- package/lib/cli/createDesktopDriverCommand.d.ts +8 -0
- package/lib/cli/createDesktopDriverCommand.d.ts.map +1 -0
- package/lib/cli/createDesktopDriverCommand.js +242 -0
- package/lib/cli/createDesktopDriverCommand.js.map +1 -0
- package/lib/cli/index.d.ts +3 -0
- package/lib/cli/index.d.ts.map +1 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/client/DesktopDriverClient.d.ts +60 -0
- package/lib/client/DesktopDriverClient.d.ts.map +1 -0
- package/lib/client/DesktopDriverClient.js +155 -0
- package/lib/client/DesktopDriverClient.js.map +1 -0
- package/lib/client/index.d.ts +3 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +2 -0
- package/lib/client/index.js.map +1 -0
- package/lib/host/types.d.ts +153 -0
- package/lib/host/types.d.ts.map +1 -0
- package/lib/host/types.js +2 -0
- package/lib/host/types.js.map +1 -0
- package/lib/hosts/fake/FakeDesktopHost.d.ts +72 -0
- package/lib/hosts/fake/FakeDesktopHost.d.ts.map +1 -0
- package/lib/hosts/fake/FakeDesktopHost.js +452 -0
- package/lib/hosts/fake/FakeDesktopHost.js.map +1 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -0
- package/lib/protocol/actions.d.ts +11 -0
- package/lib/protocol/actions.d.ts.map +1 -0
- package/lib/protocol/actions.js +137 -0
- package/lib/protocol/actions.js.map +1 -0
- package/lib/protocol/capabilities.d.ts +11 -0
- package/lib/protocol/capabilities.d.ts.map +1 -0
- package/lib/protocol/capabilities.js +103 -0
- package/lib/protocol/capabilities.js.map +1 -0
- package/lib/protocol/constants.d.ts +2 -0
- package/lib/protocol/constants.d.ts.map +1 -0
- package/lib/protocol/constants.js +2 -0
- package/lib/protocol/constants.js.map +1 -0
- package/lib/protocol/errors.d.ts +37 -0
- package/lib/protocol/errors.d.ts.map +1 -0
- package/lib/protocol/errors.js +66 -0
- package/lib/protocol/errors.js.map +1 -0
- package/lib/protocol/timeouts.d.ts +2 -0
- package/lib/protocol/timeouts.d.ts.map +1 -0
- package/lib/protocol/timeouts.js +23 -0
- package/lib/protocol/timeouts.js.map +1 -0
- package/lib/protocol/types.d.ts +46 -0
- package/lib/protocol/types.d.ts.map +1 -0
- package/lib/protocol/types.js +2 -0
- package/lib/protocol/types.js.map +1 -0
- package/lib/runner/StoryTestRunner.d.ts +38 -0
- package/lib/runner/StoryTestRunner.d.ts.map +1 -0
- package/lib/runner/StoryTestRunner.js +497 -0
- package/lib/runner/StoryTestRunner.js.map +1 -0
- package/lib/runner/StoryTestRunner.test.d.ts +2 -0
- package/lib/runner/StoryTestRunner.test.d.ts.map +1 -0
- package/lib/runner/StoryTestRunner.test.js +349 -0
- package/lib/runner/StoryTestRunner.test.js.map +1 -0
- package/lib/runner/index.d.ts +3 -0
- package/lib/runner/index.d.ts.map +1 -0
- package/lib/runner/index.js +2 -0
- package/lib/runner/index.js.map +1 -0
- package/lib/server/SessionManager.d.ts +52 -0
- package/lib/server/SessionManager.d.ts.map +1 -0
- package/lib/server/SessionManager.js +208 -0
- package/lib/server/SessionManager.js.map +1 -0
- package/lib/server/TargetRegistry.d.ts +9 -0
- package/lib/server/TargetRegistry.d.ts.map +1 -0
- package/lib/server/TargetRegistry.js +24 -0
- package/lib/server/TargetRegistry.js.map +1 -0
- package/lib/server/createDesktopDriverServer.d.ts +19 -0
- package/lib/server/createDesktopDriverServer.d.ts.map +1 -0
- package/lib/server/createDesktopDriverServer.js +609 -0
- package/lib/server/createDesktopDriverServer.js.map +1 -0
- package/lib/server/desktopDriver.test.d.ts +2 -0
- package/lib/server/desktopDriver.test.d.ts.map +1 -0
- package/lib/server/desktopDriver.test.js +389 -0
- package/lib/server/desktopDriver.test.js.map +1 -0
- package/lib/server/index.d.ts +6 -0
- package/lib/server/index.d.ts.map +1 -0
- package/lib/server/index.js +4 -0
- package/lib/server/index.js.map +1 -0
- package/lib/server/webdriverio.contract.cjs +35 -0
- package/lib/server/webdriverio.contract.cjs.map +1 -0
- package/lib/server/webdriverio.contract.d.cts +2 -0
- package/lib/server/webdriverio.contract.d.cts.map +1 -0
- package/lib/server/webdriverio.test.d.ts +2 -0
- package/lib/server/webdriverio.test.d.ts.map +1 -0
- package/lib/server/webdriverio.test.js +40 -0
- package/lib/server/webdriverio.test.js.map +1 -0
- package/lib/storybook.d.ts +36 -0
- package/lib/storybook.d.ts.map +1 -0
- package/lib/storybook.js +2 -0
- package/lib/storybook.js.map +1 -0
- package/lib/testing/FakeStoryOrchestrator.d.ts +21 -0
- package/lib/testing/FakeStoryOrchestrator.d.ts.map +1 -0
- package/lib/testing/FakeStoryOrchestrator.js +44 -0
- package/lib/testing/FakeStoryOrchestrator.js.map +1 -0
- package/lib/testing/fakeStoryElements.d.ts +4 -0
- package/lib/testing/fakeStoryElements.d.ts.map +1 -0
- package/lib/testing/fakeStoryElements.js +143 -0
- package/lib/testing/fakeStoryElements.js.map +1 -0
- package/lib/testing/fakeStoryElements.test.d.ts +2 -0
- package/lib/testing/fakeStoryElements.test.d.ts.map +1 -0
- package/lib/testing/fakeStoryElements.test.js +38 -0
- package/lib/testing/fakeStoryElements.test.js.map +1 -0
- package/lib/testing/index.d.ts +7 -0
- package/lib/testing/index.d.ts.map +1 -0
- package/lib/testing/index.js +5 -0
- package/lib/testing/index.js.map +1 -0
- package/lib/testing/protocolHarness.d.ts +17 -0
- package/lib/testing/protocolHarness.d.ts.map +1 -0
- package/lib/testing/protocolHarness.js +46 -0
- package/lib/testing/protocolHarness.js.map +1 -0
- package/lib/wdio/DesktopWebdriver.d.ts +47 -0
- package/lib/wdio/DesktopWebdriver.d.ts.map +1 -0
- package/lib/wdio/DesktopWebdriver.js +76 -0
- package/lib/wdio/DesktopWebdriver.js.map +1 -0
- package/lib/wdio/DesktopWebdriver.test.d.ts +2 -0
- package/lib/wdio/DesktopWebdriver.test.d.ts.map +1 -0
- package/lib/wdio/DesktopWebdriver.test.js +74 -0
- package/lib/wdio/DesktopWebdriver.test.js.map +1 -0
- package/lib/wdio/index.d.ts +3 -0
- package/lib/wdio/index.d.ts.map +1 -0
- package/lib/wdio/index.js +2 -0
- package/lib/wdio/index.js.map +1 -0
- package/lib/wdio/wdioRunner.contract.cjs +64 -0
- package/lib/wdio/wdioRunner.contract.cjs.map +1 -0
- package/lib/wdio/wdioRunner.contract.d.cts +2 -0
- package/lib/wdio/wdioRunner.contract.d.cts.map +1 -0
- package/package.json +96 -0
- package/src/agent/DesktopAgent.test.ts +102 -0
- package/src/agent/DesktopAgent.ts +170 -0
- package/src/agent/agent.contract.cjs +32 -0
- package/src/agent/index.ts +8 -0
- package/src/artifacts/ArtifactManager.test.ts +20 -0
- package/src/artifacts/ArtifactManager.ts +105 -0
- package/src/artifacts/index.ts +1 -0
- package/src/authoring/index.ts +26 -0
- package/src/authoring/results.ts +47 -0
- package/src/authoring/storyTests.test.ts +58 -0
- package/src/authoring/storyTests.ts +345 -0
- package/src/cli/DesktopDriverCli.test.ts +150 -0
- package/src/cli/createDesktopDriverCommand.ts +290 -0
- package/src/cli/index.ts +2 -0
- package/src/client/DesktopDriverClient.ts +218 -0
- package/src/client/index.ts +2 -0
- package/src/host/types.ts +158 -0
- package/src/hosts/fake/FakeDesktopHost.ts +558 -0
- package/src/index.ts +101 -0
- package/src/protocol/actions.ts +163 -0
- package/src/protocol/capabilities.ts +129 -0
- package/src/protocol/constants.ts +1 -0
- package/src/protocol/errors.ts +76 -0
- package/src/protocol/timeouts.ts +26 -0
- package/src/protocol/types.ts +55 -0
- package/src/runner/StoryTestRunner.test.ts +362 -0
- package/src/runner/StoryTestRunner.ts +624 -0
- package/src/runner/index.ts +9 -0
- package/src/server/SessionManager.ts +302 -0
- package/src/server/TargetRegistry.ts +29 -0
- package/src/server/createDesktopDriverServer.ts +775 -0
- package/src/server/desktopDriver.test.ts +421 -0
- package/src/server/index.ts +5 -0
- package/src/server/webdriverio.contract.cjs +38 -0
- package/src/server/webdriverio.test.ts +41 -0
- package/src/storybook.ts +40 -0
- package/src/testing/FakeStoryOrchestrator.ts +55 -0
- package/src/testing/fakeStoryElements.test.ts +40 -0
- package/src/testing/fakeStoryElements.ts +167 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/protocolHarness.ts +66 -0
- package/src/wdio/DesktopWebdriver.test.ts +76 -0
- package/src/wdio/DesktopWebdriver.ts +130 -0
- package/src/wdio/index.ts +2 -0
- package/src/wdio/wdioRunner.contract.cjs +30 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ApplicationLease,
|
|
5
|
+
DesktopHostInfo,
|
|
6
|
+
DesktopTarget,
|
|
7
|
+
DesktopWindow,
|
|
8
|
+
NativeElementScope,
|
|
9
|
+
NativeElementSnapshot,
|
|
10
|
+
} from '../host/types.js';
|
|
11
|
+
import { createInputState } from '../protocol/actions.js';
|
|
12
|
+
import type { WebDriverInputState } from '../protocol/actions.js';
|
|
13
|
+
import { WebDriverError } from '../protocol/errors.js';
|
|
14
|
+
import { withCommandTimeout } from '../protocol/timeouts.js';
|
|
15
|
+
import type { DesktopClickMode, DesktopTimeouts, WebDriverTimeouts } from '../protocol/types.js';
|
|
16
|
+
|
|
17
|
+
export type ElementRecord = {
|
|
18
|
+
id: string;
|
|
19
|
+
nativeId: string;
|
|
20
|
+
previewGeneration: number;
|
|
21
|
+
scope: NativeElementScope;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type DesktopSession = {
|
|
25
|
+
clickMode: DesktopClickMode;
|
|
26
|
+
currentWindowId: string;
|
|
27
|
+
desktopTimeouts: DesktopTimeouts;
|
|
28
|
+
elements: Map<string, ElementRecord>;
|
|
29
|
+
elementIdsByNativeId: Map<string, string>;
|
|
30
|
+
hostInfo: DesktopHostInfo;
|
|
31
|
+
id: string;
|
|
32
|
+
inputState: WebDriverInputState;
|
|
33
|
+
issuedElementIds: Set<string>;
|
|
34
|
+
lease: ApplicationLease;
|
|
35
|
+
previewGeneration: number;
|
|
36
|
+
story: { previewGeneration: number; runId: string; storyId: string } | null;
|
|
37
|
+
target: DesktopTarget;
|
|
38
|
+
timeouts: WebDriverTimeouts;
|
|
39
|
+
windows: DesktopWindow[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export class SessionManager {
|
|
43
|
+
private readonly sessions = new Map<string, DesktopSession>();
|
|
44
|
+
private readonly sessionsByTarget = new Map<string, string>();
|
|
45
|
+
private readonly reservedTargets = new Set<string>();
|
|
46
|
+
private readonly commandQueues = new Map<string, Promise<void>>();
|
|
47
|
+
private readonly inFlightCreates = new Set<Promise<void>>();
|
|
48
|
+
private inputQueue: Promise<void> = Promise.resolve();
|
|
49
|
+
private closing = false;
|
|
50
|
+
|
|
51
|
+
create(
|
|
52
|
+
target: DesktopTarget,
|
|
53
|
+
hostInfo: DesktopHostInfo,
|
|
54
|
+
clickMode: DesktopClickMode,
|
|
55
|
+
launchMode: 'attach' | 'launch',
|
|
56
|
+
): Promise<DesktopSession> {
|
|
57
|
+
if (this.closing) {
|
|
58
|
+
return Promise.reject(new WebDriverError('session not created', 'Desktop Driver is shutting down.'));
|
|
59
|
+
}
|
|
60
|
+
const result = this.createSession(target, hostInfo, clickMode, launchMode);
|
|
61
|
+
const settled = result.then(
|
|
62
|
+
() => undefined,
|
|
63
|
+
() => undefined,
|
|
64
|
+
);
|
|
65
|
+
this.inFlightCreates.add(settled);
|
|
66
|
+
void settled.finally(() => this.inFlightCreates.delete(settled));
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
beginClose(): void {
|
|
71
|
+
this.closing = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async waitForCreates(): Promise<void> {
|
|
75
|
+
await Promise.all(this.inFlightCreates);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async createSession(
|
|
79
|
+
target: DesktopTarget,
|
|
80
|
+
hostInfo: DesktopHostInfo,
|
|
81
|
+
clickMode: DesktopClickMode,
|
|
82
|
+
launchMode: 'attach' | 'launch',
|
|
83
|
+
): Promise<DesktopSession> {
|
|
84
|
+
if (this.sessionsByTarget.has(target.id) || this.reservedTargets.has(target.id)) {
|
|
85
|
+
throw new WebDriverError('session not created', `Target "${target.id}" already has an active session.`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.reservedTargets.add(target.id);
|
|
89
|
+
let lease: ApplicationLease | undefined;
|
|
90
|
+
let releaseReservation = true;
|
|
91
|
+
let leasePromise: Promise<ApplicationLease> | undefined;
|
|
92
|
+
try {
|
|
93
|
+
lease = await withCommandTimeout(
|
|
94
|
+
(signal) => {
|
|
95
|
+
leasePromise = launchMode === 'attach' ? target.host.attach(target, signal) : target.host.launch(target, signal);
|
|
96
|
+
return leasePromise;
|
|
97
|
+
},
|
|
98
|
+
120_000,
|
|
99
|
+
`${launchMode === 'attach' ? 'Attaching to' : 'Launching'} target "${target.id}"`,
|
|
100
|
+
);
|
|
101
|
+
const activeLease = lease;
|
|
102
|
+
const windows = await withCommandTimeout(
|
|
103
|
+
(signal) => target.host.windows(activeLease, signal),
|
|
104
|
+
10_000,
|
|
105
|
+
`Reading windows for target "${target.id}"`,
|
|
106
|
+
);
|
|
107
|
+
if (windows.length === 0) {
|
|
108
|
+
throw new WebDriverError('session not created', `Target "${target.id}" did not expose any windows.`);
|
|
109
|
+
}
|
|
110
|
+
if (this.closing) {
|
|
111
|
+
throw new WebDriverError('session not created', 'Desktop Driver shut down while the target was starting.');
|
|
112
|
+
}
|
|
113
|
+
const session: DesktopSession = {
|
|
114
|
+
clickMode,
|
|
115
|
+
currentWindowId: windows[0].id,
|
|
116
|
+
desktopTimeouts: {
|
|
117
|
+
appLaunch: 120_000,
|
|
118
|
+
nativeCommand: 10_000,
|
|
119
|
+
stableLayout: 1_000,
|
|
120
|
+
storyRender: 30_000,
|
|
121
|
+
},
|
|
122
|
+
elements: new Map(),
|
|
123
|
+
elementIdsByNativeId: new Map(),
|
|
124
|
+
hostInfo,
|
|
125
|
+
id: randomUUID(),
|
|
126
|
+
inputState: createInputState(),
|
|
127
|
+
issuedElementIds: new Set(),
|
|
128
|
+
lease,
|
|
129
|
+
previewGeneration: 0,
|
|
130
|
+
story: null,
|
|
131
|
+
target,
|
|
132
|
+
timeouts: {
|
|
133
|
+
implicit: 0,
|
|
134
|
+
pageLoad: 300_000,
|
|
135
|
+
script: 30_000,
|
|
136
|
+
},
|
|
137
|
+
windows,
|
|
138
|
+
};
|
|
139
|
+
this.sessions.set(session.id, session);
|
|
140
|
+
this.sessionsByTarget.set(target.id, session.id);
|
|
141
|
+
return session;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
await this.runInputCommand(() =>
|
|
144
|
+
withCommandTimeout((signal) => target.host.releaseActions(signal), 10_000, `Releasing input for target "${target.id}"`),
|
|
145
|
+
).catch(() => undefined);
|
|
146
|
+
if (lease) {
|
|
147
|
+
const failedLease = lease;
|
|
148
|
+
await withCommandTimeout(
|
|
149
|
+
(signal) => target.host.closeApplication(failedLease, signal),
|
|
150
|
+
10_000,
|
|
151
|
+
`Closing failed target "${target.id}"`,
|
|
152
|
+
).catch(() => undefined);
|
|
153
|
+
} else if (leasePromise) {
|
|
154
|
+
releaseReservation = false;
|
|
155
|
+
void leasePromise
|
|
156
|
+
.then(async (lateLease) => {
|
|
157
|
+
await this.runInputCommand(() =>
|
|
158
|
+
withCommandTimeout((signal) => target.host.releaseActions(signal), 10_000, `Releasing late input for target "${target.id}"`),
|
|
159
|
+
).catch(() => undefined);
|
|
160
|
+
await withCommandTimeout(
|
|
161
|
+
(signal) => target.host.closeApplication(lateLease, signal),
|
|
162
|
+
10_000,
|
|
163
|
+
`Closing late target "${target.id}"`,
|
|
164
|
+
).catch(() => undefined);
|
|
165
|
+
})
|
|
166
|
+
.catch(() => undefined)
|
|
167
|
+
.finally(() => this.reservedTargets.delete(target.id));
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
} finally {
|
|
171
|
+
if (releaseReservation) {
|
|
172
|
+
this.reservedTargets.delete(target.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get(id: string): DesktopSession {
|
|
178
|
+
const session = this.sessions.get(id);
|
|
179
|
+
if (!session) {
|
|
180
|
+
throw new WebDriverError('invalid session id', `Session "${id}" does not exist.`);
|
|
181
|
+
}
|
|
182
|
+
return session;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
runCommand<T>(sessionId: string, operation: () => Promise<T>): Promise<T> {
|
|
186
|
+
const previous = this.commandQueues.get(sessionId) ?? Promise.resolve();
|
|
187
|
+
const result = previous.catch(() => undefined).then(operation);
|
|
188
|
+
const tail = result.then(
|
|
189
|
+
() => undefined,
|
|
190
|
+
() => undefined,
|
|
191
|
+
);
|
|
192
|
+
this.commandQueues.set(sessionId, tail);
|
|
193
|
+
void tail.finally(() => {
|
|
194
|
+
if (this.commandQueues.get(sessionId) === tail) {
|
|
195
|
+
this.commandQueues.delete(sessionId);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
runInputCommand<T>(operation: () => Promise<T>): Promise<T> {
|
|
202
|
+
const result = this.inputQueue.catch(() => undefined).then(operation);
|
|
203
|
+
this.inputQueue = result.then(
|
|
204
|
+
() => undefined,
|
|
205
|
+
() => undefined,
|
|
206
|
+
);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async delete(id: string): Promise<void> {
|
|
211
|
+
const session = this.get(id);
|
|
212
|
+
this.reservedTargets.add(session.target.id);
|
|
213
|
+
const failures: unknown[] = [];
|
|
214
|
+
try {
|
|
215
|
+
try {
|
|
216
|
+
await this.runInputCommand(() =>
|
|
217
|
+
withCommandTimeout(
|
|
218
|
+
(signal) => session.target.host.releaseActions(signal),
|
|
219
|
+
session.desktopTimeouts.nativeCommand,
|
|
220
|
+
`Releasing input for session "${id}"`,
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
failures.push(error);
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
await withCommandTimeout(
|
|
228
|
+
(signal) => session.target.host.closeApplication(session.lease, signal),
|
|
229
|
+
session.desktopTimeouts.nativeCommand,
|
|
230
|
+
`Closing application for session "${id}"`,
|
|
231
|
+
);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
failures.push(error);
|
|
234
|
+
}
|
|
235
|
+
if (failures.length === 1) {
|
|
236
|
+
throw failures[0];
|
|
237
|
+
}
|
|
238
|
+
if (failures.length > 1) {
|
|
239
|
+
throw new AggregateError(failures, `Failed to close session "${id}".`);
|
|
240
|
+
}
|
|
241
|
+
this.sessions.delete(id);
|
|
242
|
+
this.sessionsByTarget.delete(session.target.id);
|
|
243
|
+
} finally {
|
|
244
|
+
this.reservedTargets.delete(session.target.id);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async deleteAll(): Promise<void> {
|
|
249
|
+
const failures: unknown[] = [];
|
|
250
|
+
for (const id of Array.from(this.sessions.keys())) {
|
|
251
|
+
try {
|
|
252
|
+
await this.delete(id);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
failures.push(error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (failures.length > 0) {
|
|
258
|
+
throw new AggregateError(failures, 'One or more desktop sessions failed to close.');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
registerElement(session: DesktopSession, snapshot: NativeElementSnapshot): ElementRecord {
|
|
263
|
+
const existingId = session.elementIdsByNativeId.get(snapshot.id);
|
|
264
|
+
if (existingId) {
|
|
265
|
+
return session.elements.get(existingId)!;
|
|
266
|
+
}
|
|
267
|
+
const record: ElementRecord = {
|
|
268
|
+
id: randomUUID(),
|
|
269
|
+
nativeId: snapshot.id,
|
|
270
|
+
previewGeneration: session.previewGeneration,
|
|
271
|
+
scope: snapshot.scope,
|
|
272
|
+
};
|
|
273
|
+
session.elements.set(record.id, record);
|
|
274
|
+
session.elementIdsByNativeId.set(record.nativeId, record.id);
|
|
275
|
+
session.issuedElementIds.add(record.id);
|
|
276
|
+
return record;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
resolveElement(session: DesktopSession, id: string): ElementRecord {
|
|
280
|
+
const element = session.elements.get(id);
|
|
281
|
+
if (!element) {
|
|
282
|
+
throw new WebDriverError(
|
|
283
|
+
session.issuedElementIds.has(id) ? 'stale element reference' : 'no such element',
|
|
284
|
+
`Element "${id}" is not available in this session.`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (element.scope === 'preview' && element.previewGeneration !== session.previewGeneration) {
|
|
288
|
+
throw new WebDriverError('stale element reference', `Element "${id}" is no longer attached to the current view.`);
|
|
289
|
+
}
|
|
290
|
+
return element;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
invalidatePreview(session: DesktopSession): void {
|
|
294
|
+
session.previewGeneration += 1;
|
|
295
|
+
for (const [id, element] of session.elements) {
|
|
296
|
+
if (element.scope === 'preview') {
|
|
297
|
+
session.elements.delete(id);
|
|
298
|
+
session.elementIdsByNativeId.delete(element.nativeId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DesktopTarget } from '../host/types.js';
|
|
2
|
+
|
|
3
|
+
export class TargetRegistry {
|
|
4
|
+
private readonly targets = new Map<string, DesktopTarget>();
|
|
5
|
+
|
|
6
|
+
constructor(targets: readonly DesktopTarget[] = []) {
|
|
7
|
+
for (const target of targets) {
|
|
8
|
+
this.register(target);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
register(target: DesktopTarget): void {
|
|
13
|
+
if (!target.id) {
|
|
14
|
+
throw new TypeError('Desktop targets require a non-empty id.');
|
|
15
|
+
}
|
|
16
|
+
if (this.targets.has(target.id)) {
|
|
17
|
+
throw new Error(`Desktop target "${target.id}" is already registered.`);
|
|
18
|
+
}
|
|
19
|
+
this.targets.set(target.id, target);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get(id: string): DesktopTarget | undefined {
|
|
23
|
+
return this.targets.get(id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
list(): readonly DesktopTarget[] {
|
|
27
|
+
return [...this.targets.values()];
|
|
28
|
+
}
|
|
29
|
+
}
|