@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,421 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
|
|
3
|
+
import { createDesktopDriverClient } from '../client/DesktopDriverClient.js';
|
|
4
|
+
import type { FakeDesktopHost } from '../hosts/fake/FakeDesktopHost.js';
|
|
5
|
+
import { webElementIdentifier } from '../protocol/constants.js';
|
|
6
|
+
import { runDesktopStoryTests } from '../runner/StoryTestRunner.js';
|
|
7
|
+
import { createDesktopDriverTestHarness } from '../testing/protocolHarness.js';
|
|
8
|
+
import type { DesktopStoryManifest, StoryOrchestrator, StoryReadyResult } from '../storybook.js';
|
|
9
|
+
|
|
10
|
+
describe('Desktop Driver W3C remote end', () => {
|
|
11
|
+
test('serves status and a complete fake-host session through raw HTTP', async () => {
|
|
12
|
+
const harness = await createDesktopDriverTestHarness();
|
|
13
|
+
try {
|
|
14
|
+
const status = await getJson(`${harness.server.url}/status`);
|
|
15
|
+
expect(status.value).toMatchObject({ ready: true });
|
|
16
|
+
|
|
17
|
+
const created = await getJson(`${harness.server.url}/session`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
capabilities: {
|
|
21
|
+
alwaysMatch: {
|
|
22
|
+
browserName: 'furn-native-desktop',
|
|
23
|
+
platformName: 'windows',
|
|
24
|
+
'furn:target': harness.target.id,
|
|
25
|
+
'furn:clickMode': 'physical',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
const sessionId = created.value.sessionId as string;
|
|
31
|
+
expect(created.value.capabilities).toMatchObject({
|
|
32
|
+
browserName: 'furn-native-desktop',
|
|
33
|
+
platformName: 'windows',
|
|
34
|
+
'furn:clickMode': 'physical',
|
|
35
|
+
'furn:endpoint': 'windows',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const found = await getJson(`${harness.server.url}/session/${sessionId}/element`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: JSON.stringify({ using: 'accessibility id', value: 'button-primary' }),
|
|
41
|
+
});
|
|
42
|
+
const elementId = found.value[webElementIdentifier] as string;
|
|
43
|
+
expect(elementId).toEqual(expect.any(String));
|
|
44
|
+
|
|
45
|
+
await getJson(`${harness.server.url}/session/${sessionId}/element/${elementId}/click`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: '{}',
|
|
48
|
+
});
|
|
49
|
+
expect(harness.host.actions).toContainEqual({ type: 'click', elementId: 'button', mode: 'physical' });
|
|
50
|
+
|
|
51
|
+
const screenshot = await getJson(`${harness.server.url}/session/${sessionId}/screenshot`);
|
|
52
|
+
expect(
|
|
53
|
+
Buffer.from(screenshot.value as string, 'base64')
|
|
54
|
+
.subarray(1, 4)
|
|
55
|
+
.toString(),
|
|
56
|
+
).toBe('PNG');
|
|
57
|
+
|
|
58
|
+
const source = await getJson(`${harness.server.url}/session/${sessionId}/source`);
|
|
59
|
+
expect(source.value).toContain('id="button-primary"');
|
|
60
|
+
|
|
61
|
+
const malformedActions = await getJson(`${harness.server.url}/session/${sessionId}/actions`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
actions: [{ id: 'invalid', type: 'not-a-source', actions: [{ type: 'not-an-action' }] }],
|
|
65
|
+
}),
|
|
66
|
+
expectStatus: 400,
|
|
67
|
+
});
|
|
68
|
+
expect(malformedActions.value).toMatchObject({ error: 'invalid argument' });
|
|
69
|
+
|
|
70
|
+
const shadow = await getJson(`${harness.server.url}/session/${sessionId}/element/${elementId}/shadow`, {
|
|
71
|
+
expectStatus: 500,
|
|
72
|
+
});
|
|
73
|
+
expect(shadow.value).toMatchObject({ error: 'unsupported operation' });
|
|
74
|
+
|
|
75
|
+
const unsupported = await getJson(`${harness.server.url}/session/${sessionId}/url`, { expectStatus: 500 });
|
|
76
|
+
expect(unsupported.value).toMatchObject({ error: 'unsupported operation' });
|
|
77
|
+
|
|
78
|
+
await getJson(`${harness.server.url}/session/${sessionId}`, { method: 'DELETE' });
|
|
79
|
+
expect(harness.host.actions).toContainEqual(expect.objectContaining({ type: 'release-actions' }));
|
|
80
|
+
} finally {
|
|
81
|
+
await harness.close();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('uses stable WebDriver element IDs and reports stale references', async () => {
|
|
86
|
+
const harness = await createDesktopDriverTestHarness();
|
|
87
|
+
try {
|
|
88
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
89
|
+
const session = await client.newSession({
|
|
90
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
91
|
+
});
|
|
92
|
+
const first = await session.findElement('accessibility id', 'button-primary');
|
|
93
|
+
const second = await session.findElement('accessibility id', 'button-primary');
|
|
94
|
+
expect(second.id).toBe(first.id);
|
|
95
|
+
|
|
96
|
+
harness.host.removeElement('button');
|
|
97
|
+
await expect(first.getText()).rejects.toMatchObject({ code: 'stale element reference' });
|
|
98
|
+
await session.delete();
|
|
99
|
+
} finally {
|
|
100
|
+
await harness.close();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('rejects duplicate capabilities and concurrent sessions per target', async () => {
|
|
105
|
+
const harness = await createDesktopDriverTestHarness({ launchDelayMs: 50 });
|
|
106
|
+
try {
|
|
107
|
+
const duplicate = await getJson(`${harness.server.url}/session`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
capabilities: {
|
|
111
|
+
alwaysMatch: { platformName: 'windows' },
|
|
112
|
+
firstMatch: [{ platformName: 'windows' }],
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
expectStatus: 400,
|
|
116
|
+
});
|
|
117
|
+
expect(duplicate.value).toMatchObject({ error: 'invalid argument' });
|
|
118
|
+
|
|
119
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
120
|
+
const capabilities = { alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id } };
|
|
121
|
+
const [first, second] = await Promise.allSettled([client.newSession(capabilities), client.newSession(capabilities)]);
|
|
122
|
+
expect([first.status, second.status].sort()).toEqual(['fulfilled', 'rejected']);
|
|
123
|
+
const session = first.status === 'fulfilled' ? first.value : second.status === 'fulfilled' ? second.value : undefined;
|
|
124
|
+
expect(session).toBeDefined();
|
|
125
|
+
if (!session) {
|
|
126
|
+
throw new Error('Expected one concurrent session request to succeed.');
|
|
127
|
+
}
|
|
128
|
+
expect(harness.host.actions.filter(({ type }) => type === 'launch')).toHaveLength(1);
|
|
129
|
+
await expect(client.newSession(capabilities)).rejects.toMatchObject({ code: 'session not created' });
|
|
130
|
+
await session.delete();
|
|
131
|
+
} finally {
|
|
132
|
+
await harness.close();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('waits for in-flight session creation before server cleanup', async () => {
|
|
137
|
+
const harness = await createDesktopDriverTestHarness({ launchDelayMs: 80 });
|
|
138
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
139
|
+
const creating = client.newSession({
|
|
140
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await waitUntil(() => harness.host.actions.some(({ type }) => type === 'launch'));
|
|
144
|
+
|
|
145
|
+
const closing = harness.server.close();
|
|
146
|
+
await expect(creating).rejects.toMatchObject({ code: 'session not created' });
|
|
147
|
+
await closing;
|
|
148
|
+
expect(harness.host.actions).toContainEqual(expect.objectContaining({ type: 'release-actions' }));
|
|
149
|
+
expect(harness.host.actions).toContainEqual(expect.objectContaining({ type: 'close-application' }));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('serializes release behind a timed-out native action and drains the host operation', async () => {
|
|
153
|
+
const harness = await createDesktopDriverTestHarness({ actionDelayMs: 50 });
|
|
154
|
+
try {
|
|
155
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
156
|
+
const session = await client.newSession({
|
|
157
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
harness.server.sessions.get(session.id).desktopTimeouts.nativeCommand = 10;
|
|
161
|
+
const performing = session.performActions([{ id: 'key', type: 'key', actions: [{ type: 'keyDown', value: 'A' }] }]);
|
|
162
|
+
await waitUntil(() => harness.host.actions.some(({ type }) => type === 'actions-start'));
|
|
163
|
+
const releasing = session.releaseActions();
|
|
164
|
+
|
|
165
|
+
await expect(performing).rejects.toMatchObject({ code: 'timeout' });
|
|
166
|
+
await expect(releasing).resolves.toBeNull();
|
|
167
|
+
const started = harness.host.actions.findIndex(({ type }) => type === 'actions-start');
|
|
168
|
+
const released = harness.host.actions.findIndex(({ type }) => type === 'release-actions');
|
|
169
|
+
expect(harness.host.actions.some(({ type }) => type === 'actions')).toBe(false);
|
|
170
|
+
expect(released).toBeGreaterThan(started);
|
|
171
|
+
await session.delete();
|
|
172
|
+
} finally {
|
|
173
|
+
await harness.close();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('keeps a target reserved until application teardown completes', async () => {
|
|
178
|
+
const harness = await createDesktopDriverTestHarness({ closeDelayMs: 50 });
|
|
179
|
+
try {
|
|
180
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
181
|
+
const capabilities = { alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id } };
|
|
182
|
+
const session = await client.newSession(capabilities);
|
|
183
|
+
const deleting = session.delete();
|
|
184
|
+
await waitUntil(() => harness.host.actions.some(({ type }) => type === 'close-application-start'));
|
|
185
|
+
|
|
186
|
+
await expect(client.newSession(capabilities)).rejects.toMatchObject({ code: 'session not created' });
|
|
187
|
+
await deleting;
|
|
188
|
+
const replacement = await client.newSession(capabilities);
|
|
189
|
+
await replacement.delete();
|
|
190
|
+
} finally {
|
|
191
|
+
await harness.close();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('supports the typed client for elements, text entry, actions, and screenshots', async () => {
|
|
196
|
+
const harness = await createDesktopDriverTestHarness({ endpoint: 'macos', platformName: 'macos' });
|
|
197
|
+
try {
|
|
198
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
199
|
+
const session = await client.newSession({
|
|
200
|
+
alwaysMatch: {
|
|
201
|
+
platformName: 'macos',
|
|
202
|
+
'furn:target': harness.target.id,
|
|
203
|
+
'furn:clickMode': 'accessibility',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
const input = await session.findElement('accessibility id', 'input-name');
|
|
207
|
+
await input.sendKeys('Ada');
|
|
208
|
+
expect(await input.getText()).toBe('Ada');
|
|
209
|
+
await input.clear();
|
|
210
|
+
expect(await input.getText()).toBe('');
|
|
211
|
+
await session.performActions([{ id: 'keyboard', type: 'key', actions: [{ type: 'keyDown', value: '\uE004' }] }]);
|
|
212
|
+
await session.releaseActions();
|
|
213
|
+
expect(await session.takeScreenshot()).toEqual(expect.any(String));
|
|
214
|
+
await session.delete();
|
|
215
|
+
} finally {
|
|
216
|
+
await harness.close();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('routes Storybook commands and invalidates only preview element references', async () => {
|
|
221
|
+
const manifest: DesktopStoryManifest = {
|
|
222
|
+
endpoint: 'windows',
|
|
223
|
+
entries: [
|
|
224
|
+
{
|
|
225
|
+
id: 'components-button--default',
|
|
226
|
+
name: 'Default',
|
|
227
|
+
packageName: '@fluentui-react-native/components',
|
|
228
|
+
sourcePath: 'src/components/button/button.stories.tsx',
|
|
229
|
+
tags: ['story'],
|
|
230
|
+
title: 'Components/Button',
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
platformManifestDigest: 'platform',
|
|
234
|
+
portablePlanDigest: 'portable',
|
|
235
|
+
schemaVersion: 1,
|
|
236
|
+
};
|
|
237
|
+
let currentStory: StoryReadyResult | null = null;
|
|
238
|
+
const hostRef: { current?: FakeDesktopHost } = {};
|
|
239
|
+
const orchestrator: StoryOrchestrator = {
|
|
240
|
+
async getManifest() {
|
|
241
|
+
return manifest;
|
|
242
|
+
},
|
|
243
|
+
async getCurrentStory() {
|
|
244
|
+
return currentStory;
|
|
245
|
+
},
|
|
246
|
+
async selectStory(request) {
|
|
247
|
+
currentStory = { previewGeneration: 1, runId: request.runId, storyId: request.storyId };
|
|
248
|
+
hostRef.current?.setElementName('story-root', JSON.stringify(currentStory));
|
|
249
|
+
return currentStory;
|
|
250
|
+
},
|
|
251
|
+
async resetStory(request) {
|
|
252
|
+
currentStory = { previewGeneration: 2, runId: request.runId, storyId: request.storyId };
|
|
253
|
+
hostRef.current?.setElementName('story-root', JSON.stringify(currentStory));
|
|
254
|
+
return currentStory;
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
const harness = await createDesktopDriverTestHarness({}, orchestrator);
|
|
258
|
+
hostRef.current = harness.host;
|
|
259
|
+
try {
|
|
260
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
261
|
+
const session = await client.newSession({
|
|
262
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
263
|
+
});
|
|
264
|
+
const previewBefore = await session.findElement('accessibility id', 'button-primary');
|
|
265
|
+
const chromeBefore = await session.findElement('accessibility id', 'app-root');
|
|
266
|
+
const elementOriginActions = [
|
|
267
|
+
{
|
|
268
|
+
id: 'wheel',
|
|
269
|
+
type: 'wheel' as const,
|
|
270
|
+
actions: [
|
|
271
|
+
{
|
|
272
|
+
type: 'scroll',
|
|
273
|
+
deltaX: 0,
|
|
274
|
+
deltaY: 100,
|
|
275
|
+
origin: { [webElementIdentifier]: previewBefore.id },
|
|
276
|
+
x: 0,
|
|
277
|
+
y: 0,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
await session.performActions(elementOriginActions);
|
|
283
|
+
expect(harness.host.actions).toContainEqual({
|
|
284
|
+
type: 'actions',
|
|
285
|
+
actions: [
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
actions: [expect.objectContaining({ origin: { elementId: 'button' } })],
|
|
288
|
+
}),
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await expect(session.getStoryManifest()).resolves.toEqual(manifest);
|
|
293
|
+
await expect(session.selectStory('components-button--default', 'run-1')).resolves.toMatchObject({
|
|
294
|
+
previewGeneration: 1,
|
|
295
|
+
runId: 'run-1',
|
|
296
|
+
});
|
|
297
|
+
await expect(previewBefore.getText()).rejects.toMatchObject({ code: 'stale element reference' });
|
|
298
|
+
await expect(session.performActions(elementOriginActions)).rejects.toMatchObject({ code: 'stale element reference' });
|
|
299
|
+
await expect(chromeBefore.getTagName()).resolves.toBe('application');
|
|
300
|
+
|
|
301
|
+
const previewAfter = await session.findElement('accessibility id', 'button-primary');
|
|
302
|
+
expect(previewAfter.id).not.toBe(previewBefore.id);
|
|
303
|
+
const runnerManifest: DesktopStoryManifest = {
|
|
304
|
+
...manifest,
|
|
305
|
+
entries: [
|
|
306
|
+
{
|
|
307
|
+
...manifest.entries[0],
|
|
308
|
+
tests: {
|
|
309
|
+
version: 1,
|
|
310
|
+
tests: [
|
|
311
|
+
{
|
|
312
|
+
id: 'clicks-button',
|
|
313
|
+
steps: [
|
|
314
|
+
{ action: 'wait', target: { testId: 'button-primary' }, timeoutMs: 100 },
|
|
315
|
+
{ action: 'click', target: { testId: 'button-primary' } },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
await expect(
|
|
324
|
+
runDesktopStoryTests({
|
|
325
|
+
endpoint: 'windows',
|
|
326
|
+
manifest: runnerManifest,
|
|
327
|
+
platformName: 'windows',
|
|
328
|
+
selection: { test: 'clicks-button' },
|
|
329
|
+
session,
|
|
330
|
+
targetId: harness.target.id,
|
|
331
|
+
}),
|
|
332
|
+
).resolves.toMatchObject({ status: 'passed' });
|
|
333
|
+
expect(harness.host.actions).toContainEqual({ type: 'click', elementId: 'button', mode: 'physical' });
|
|
334
|
+
await expect(session.resetStory('components-button--default', 'run-2')).resolves.toMatchObject({
|
|
335
|
+
previewGeneration: 2,
|
|
336
|
+
runId: 'run-2',
|
|
337
|
+
});
|
|
338
|
+
await expect(previewAfter.getText()).rejects.toMatchObject({ code: 'stale element reference' });
|
|
339
|
+
await session.delete();
|
|
340
|
+
} finally {
|
|
341
|
+
await harness.close();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('invalidates preview references before a reset whose marker verification fails', async () => {
|
|
346
|
+
const manifest: DesktopStoryManifest = {
|
|
347
|
+
endpoint: 'windows',
|
|
348
|
+
entries: [
|
|
349
|
+
{
|
|
350
|
+
id: 'components-button--default',
|
|
351
|
+
name: 'Default',
|
|
352
|
+
packageName: '@fluentui-react-native/components',
|
|
353
|
+
sourcePath: 'button.stories.tsx',
|
|
354
|
+
tags: ['story'],
|
|
355
|
+
title: 'Components/Button',
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
platformManifestDigest: 'platform',
|
|
359
|
+
portablePlanDigest: 'portable',
|
|
360
|
+
schemaVersion: 1,
|
|
361
|
+
};
|
|
362
|
+
const hostRef: { current?: FakeDesktopHost } = {};
|
|
363
|
+
const orchestrator: StoryOrchestrator = {
|
|
364
|
+
async getCurrentStory() {
|
|
365
|
+
return null;
|
|
366
|
+
},
|
|
367
|
+
async getManifest() {
|
|
368
|
+
return manifest;
|
|
369
|
+
},
|
|
370
|
+
async resetStory(request) {
|
|
371
|
+
hostRef.current?.resetPreview();
|
|
372
|
+
return { previewGeneration: 1, runId: request.runId, storyId: request.storyId };
|
|
373
|
+
},
|
|
374
|
+
async selectStory(request) {
|
|
375
|
+
return { previewGeneration: 1, runId: request.runId, storyId: request.storyId };
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
const harness = await createDesktopDriverTestHarness({}, orchestrator);
|
|
379
|
+
hostRef.current = harness.host;
|
|
380
|
+
try {
|
|
381
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
382
|
+
const session = await client.newSession({
|
|
383
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
384
|
+
});
|
|
385
|
+
harness.server.sessions.get(session.id).desktopTimeouts.storyRender = 20;
|
|
386
|
+
const preview = await session.findElement('accessibility id', 'button-primary');
|
|
387
|
+
|
|
388
|
+
await expect(session.resetStory('components-button--default', 'failed-run')).rejects.toMatchObject({
|
|
389
|
+
code: 'timeout',
|
|
390
|
+
});
|
|
391
|
+
await expect(preview.getText()).rejects.toMatchObject({ code: 'stale element reference' });
|
|
392
|
+
await session.delete();
|
|
393
|
+
} finally {
|
|
394
|
+
await harness.close();
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
type GetJsonOptions = RequestInit & {
|
|
400
|
+
expectStatus?: number;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
async function getJson(url: string, options: GetJsonOptions = {}): Promise<Record<string, any>> {
|
|
404
|
+
const { expectStatus = 200, ...init } = options;
|
|
405
|
+
const response = await fetch(url, {
|
|
406
|
+
...init,
|
|
407
|
+
headers: init.body ? { 'Content-Type': 'application/json', ...init.headers } : init.headers,
|
|
408
|
+
});
|
|
409
|
+
expect(response.status).toBe(expectStatus);
|
|
410
|
+
return response.json() as Promise<Record<string, any>>;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function waitUntil(predicate: () => boolean): Promise<void> {
|
|
414
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
415
|
+
if (predicate()) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
419
|
+
}
|
|
420
|
+
throw new Error('Timed out waiting for the test condition.');
|
|
421
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createDesktopDriverServer } from './createDesktopDriverServer.js';
|
|
2
|
+
export type { DesktopDriverServer, DesktopDriverServerOptions } from './createDesktopDriverServer.js';
|
|
3
|
+
export { SessionManager } from './SessionManager.js';
|
|
4
|
+
export type { DesktopSession, ElementRecord } from './SessionManager.js';
|
|
5
|
+
export { TargetRegistry } from './TargetRegistry.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { remote } = require('webdriverio');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const url = new URL(process.argv[2]);
|
|
5
|
+
const target = process.argv[3];
|
|
6
|
+
const capabilities = Object.assign(
|
|
7
|
+
{
|
|
8
|
+
browserName: 'furn-native-desktop',
|
|
9
|
+
platformName: 'windows',
|
|
10
|
+
},
|
|
11
|
+
{ 'furn:target': target },
|
|
12
|
+
);
|
|
13
|
+
const browser = await remote({
|
|
14
|
+
logLevel: 'silent',
|
|
15
|
+
hostname: url.hostname,
|
|
16
|
+
port: Number(url.port),
|
|
17
|
+
path: '/',
|
|
18
|
+
capabilities,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const button = await browser.$('~button-primary');
|
|
23
|
+
const result = {
|
|
24
|
+
enabled: await button.isEnabled(),
|
|
25
|
+
screenshot: await browser.takeScreenshot(),
|
|
26
|
+
tagName: await button.getTagName(),
|
|
27
|
+
};
|
|
28
|
+
await button.click();
|
|
29
|
+
process.stdout.write(JSON.stringify(result));
|
|
30
|
+
} finally {
|
|
31
|
+
await browser.deleteSession();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
main().catch((error) => {
|
|
36
|
+
process.stderr.write(error.stack ?? error.message);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { createDesktopDriverTestHarness } from '../testing/protocolHarness.js';
|
|
5
|
+
|
|
6
|
+
describe('WebdriverIO compatibility', () => {
|
|
7
|
+
test('drives the fake host without an Appium service', async () => {
|
|
8
|
+
const harness = await createDesktopDriverTestHarness();
|
|
9
|
+
try {
|
|
10
|
+
const result = await runContract(harness.server.url, harness.target.id);
|
|
11
|
+
expect(result).toMatchObject({
|
|
12
|
+
enabled: true,
|
|
13
|
+
screenshot: expect.any(String),
|
|
14
|
+
tagName: 'button',
|
|
15
|
+
});
|
|
16
|
+
expect(harness.host.actions).toContainEqual({ type: 'click', elementId: 'button', mode: 'physical' });
|
|
17
|
+
} finally {
|
|
18
|
+
await harness.close();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function runContract(url: string, target: string): Promise<Record<string, unknown>> {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'webdriverio.contract.cjs'), url, target], {
|
|
26
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
27
|
+
});
|
|
28
|
+
const stdout: Buffer[] = [];
|
|
29
|
+
const stderr: Buffer[] = [];
|
|
30
|
+
child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));
|
|
31
|
+
child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));
|
|
32
|
+
child.once('error', reject);
|
|
33
|
+
child.once('exit', (code) => {
|
|
34
|
+
if (code !== 0) {
|
|
35
|
+
reject(new Error(Buffer.concat(stderr).toString('utf8') || `WebdriverIO contract process exited with code ${code}.`));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
resolve(JSON.parse(Buffer.concat(stdout).toString('utf8')) as Record<string, unknown>);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
package/src/storybook.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DesktopStoryTests } from './authoring/storyTests.js';
|
|
2
|
+
import type { DesktopEndpoint } from './protocol/types.js';
|
|
3
|
+
|
|
4
|
+
export type DesktopStoryManifestEntry = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
packageName: string;
|
|
8
|
+
sourcePath: string;
|
|
9
|
+
tags: readonly string[];
|
|
10
|
+
title: string;
|
|
11
|
+
tests?: DesktopStoryTests;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DesktopStoryManifest = {
|
|
15
|
+
endpoint: DesktopEndpoint;
|
|
16
|
+
entries: readonly DesktopStoryManifestEntry[];
|
|
17
|
+
platformManifestDigest: string;
|
|
18
|
+
portablePlanDigest: string;
|
|
19
|
+
schemaVersion: 1;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type StorySelectionRequest = {
|
|
23
|
+
requestId: string;
|
|
24
|
+
runId: string;
|
|
25
|
+
storyId: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type StoryReadyResult = {
|
|
29
|
+
previewGeneration: number;
|
|
30
|
+
runId: string;
|
|
31
|
+
storyId: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface StoryOrchestrator {
|
|
35
|
+
getManifest(): Promise<DesktopStoryManifest>;
|
|
36
|
+
getCurrentStory(): Promise<StoryReadyResult | null>;
|
|
37
|
+
selectStory(request: StorySelectionRequest): Promise<StoryReadyResult>;
|
|
38
|
+
resetStory(request: StorySelectionRequest): Promise<StoryReadyResult>;
|
|
39
|
+
updateArgs?(storyId: string, args: Readonly<Record<string, unknown>>): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { FakeDesktopHost } from '../hosts/fake/FakeDesktopHost.js';
|
|
2
|
+
import type { DesktopStoryManifest, StoryOrchestrator, StoryReadyResult, StorySelectionRequest } from '../storybook.js';
|
|
3
|
+
|
|
4
|
+
export class FakeStoryOrchestrator implements StoryOrchestrator {
|
|
5
|
+
readonly argUpdates: { args: Readonly<Record<string, unknown>>; storyId: string }[] = [];
|
|
6
|
+
|
|
7
|
+
private readonly host: FakeDesktopHost;
|
|
8
|
+
private readonly manifest: DesktopStoryManifest;
|
|
9
|
+
private current: StoryReadyResult | null = null;
|
|
10
|
+
private generation = 0;
|
|
11
|
+
|
|
12
|
+
constructor(manifest: DesktopStoryManifest, host: FakeDesktopHost) {
|
|
13
|
+
this.manifest = manifest;
|
|
14
|
+
this.host = host;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getCurrentStory(): Promise<StoryReadyResult | null> {
|
|
18
|
+
return this.current;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getManifest(): Promise<DesktopStoryManifest> {
|
|
22
|
+
return this.manifest;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
selectStory(request: StorySelectionRequest): Promise<StoryReadyResult> {
|
|
26
|
+
return this.select(request);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
resetStory(request: StorySelectionRequest): Promise<StoryReadyResult> {
|
|
30
|
+
return this.select(request);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async updateArgs(storyId: string, args: Readonly<Record<string, unknown>>): Promise<void> {
|
|
34
|
+
this.requireStory(storyId);
|
|
35
|
+
this.argUpdates.push({ args, storyId });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async select(request: StorySelectionRequest): Promise<StoryReadyResult> {
|
|
39
|
+
this.requireStory(request.storyId);
|
|
40
|
+
this.host.resetPreview();
|
|
41
|
+
this.current = {
|
|
42
|
+
previewGeneration: ++this.generation,
|
|
43
|
+
runId: request.runId,
|
|
44
|
+
storyId: request.storyId,
|
|
45
|
+
};
|
|
46
|
+
this.host.setElementName('story-root', JSON.stringify(this.current));
|
|
47
|
+
return this.current;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private requireStory(storyId: string): void {
|
|
51
|
+
if (!this.manifest.entries.some(({ id }) => id === storyId)) {
|
|
52
|
+
throw new Error(`Story "${storyId}" is not present in the fake target manifest.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DesktopStoryManifest } from '../storybook.js';
|
|
2
|
+
import { createFakeStoryWindows } from './fakeStoryElements.js';
|
|
3
|
+
|
|
4
|
+
describe('createFakeStoryWindows', () => {
|
|
5
|
+
test('derives initial semantic state without pre-applying post-action assertions', () => {
|
|
6
|
+
const manifest: DesktopStoryManifest = {
|
|
7
|
+
endpoint: 'windows',
|
|
8
|
+
entries: [
|
|
9
|
+
{
|
|
10
|
+
id: 'components-checkbox--default',
|
|
11
|
+
name: 'Default',
|
|
12
|
+
packageName: '@fluentui-react-native/components',
|
|
13
|
+
sourcePath: 'src/components/checkbox/checkbox.stories.tsx',
|
|
14
|
+
tags: ['desktop-e2e'],
|
|
15
|
+
tests: {
|
|
16
|
+
version: 1,
|
|
17
|
+
tests: [
|
|
18
|
+
{
|
|
19
|
+
id: 'toggle',
|
|
20
|
+
steps: [
|
|
21
|
+
{ expect: { state: 'role', target: { testId: 'checkbox' }, value: 'checkbox' } },
|
|
22
|
+
{ expect: { state: 'checked', target: { testId: 'checkbox' }, value: false } },
|
|
23
|
+
{ action: 'click', target: { testId: 'checkbox' } },
|
|
24
|
+
{ expect: { state: 'checked', target: { testId: 'checkbox' }, value: true } },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
title: 'Components/Checkbox',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
platformManifestDigest: 'platform',
|
|
33
|
+
portablePlanDigest: 'portable',
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const checkbox = createFakeStoryWindows(manifest)[0].elements.find(({ automationId }) => automationId === 'checkbox');
|
|
38
|
+
expect(checkbox).toMatchObject({ checked: false, role: 'checkbox' });
|
|
39
|
+
});
|
|
40
|
+
});
|