@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,74 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createDesktopDriverStoryHarness } from '../testing/protocolHarness.js';
|
|
6
|
+
describe('sanctioned WebdriverIO API', () => {
|
|
7
|
+
test('lists and runs an authored plan through registered browser commands', async () => {
|
|
8
|
+
const manifest = {
|
|
9
|
+
endpoint: 'windows',
|
|
10
|
+
entries: [
|
|
11
|
+
{
|
|
12
|
+
id: 'components-button--default',
|
|
13
|
+
name: 'Default',
|
|
14
|
+
packageName: '@fluentui-react-native/components',
|
|
15
|
+
sourcePath: 'src/components/button/button.stories.tsx',
|
|
16
|
+
tags: ['e2e', 'story'],
|
|
17
|
+
tests: {
|
|
18
|
+
version: 1,
|
|
19
|
+
tests: [
|
|
20
|
+
{
|
|
21
|
+
id: 'click',
|
|
22
|
+
steps: [
|
|
23
|
+
{ action: 'click', target: { testId: 'button-primary' } },
|
|
24
|
+
{ expect: { state: 'focused', target: { testId: 'button-primary' }, value: true } },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
title: 'Components/Button',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
platformManifestDigest: 'platform-digest',
|
|
33
|
+
portablePlanDigest: 'portable-digest',
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
};
|
|
36
|
+
const harness = await createDesktopDriverStoryHarness(manifest);
|
|
37
|
+
const artifactsRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-wdio-'));
|
|
38
|
+
try {
|
|
39
|
+
const response = await runContract(harness.server.url, harness.target.id, artifactsRoot);
|
|
40
|
+
expect(response).toMatchObject({
|
|
41
|
+
manifestEntries: 1,
|
|
42
|
+
result: {
|
|
43
|
+
status: 'passed',
|
|
44
|
+
tests: [{ status: 'passed', storyId: 'components-button--default', testId: 'click' }],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(fs.existsSync(path.join(artifactsRoot, 'run.json'))).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await harness.close();
|
|
51
|
+
fs.rmSync(artifactsRoot, { force: true, recursive: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
function runContract(url, targetId, artifactsRoot) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'wdioRunner.contract.cjs'), url, targetId, artifactsRoot], {
|
|
58
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
59
|
+
});
|
|
60
|
+
const stdout = [];
|
|
61
|
+
const stderr = [];
|
|
62
|
+
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
63
|
+
child.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
64
|
+
child.once('error', reject);
|
|
65
|
+
child.once('exit', (code) => {
|
|
66
|
+
if (code !== 0) {
|
|
67
|
+
reject(new Error(Buffer.concat(stderr).toString('utf8') || `WebdriverIO runner exited with code ${code}.`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve(JSON.parse(Buffer.concat(stdout).toString('utf8')));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=DesktopWebdriver.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DesktopWebdriver.test.js","sourceRoot":"","sources":["../../src/wdio/DesktopWebdriver.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wdio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACzF,YAAY,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/wdio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
const { pathToFileURL } = require('node:url');
|
|
37
|
+
async function main() {
|
|
38
|
+
const moduleUrl = pathToFileURL(path.resolve(__dirname, '..', '..', 'lib', 'wdio', 'index.js')).href;
|
|
39
|
+
const { connectDesktopWebdriver } = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
|
|
40
|
+
const [url, targetId, artifactsRoot] = process.argv.slice(2);
|
|
41
|
+
const desktop = await connectDesktopWebdriver({
|
|
42
|
+
platformName: 'windows',
|
|
43
|
+
targetId,
|
|
44
|
+
url,
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
const manifest = await desktop.browser.desktopListStories();
|
|
48
|
+
await desktop.browser.desktopExpect({
|
|
49
|
+
state: 'enabled',
|
|
50
|
+
target: { testId: 'button-primary' },
|
|
51
|
+
value: true,
|
|
52
|
+
});
|
|
53
|
+
const result = await desktop.browser.desktopRunStoryTests({ artifactsRoot });
|
|
54
|
+
process.stdout.write(JSON.stringify({ manifestEntries: manifest.entries.length, result }));
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await desktop.delete();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
main().catch((error) => {
|
|
61
|
+
process.stderr.write(error.stack ?? error.message);
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=wdioRunner.contract.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wdioRunner.contract.cjs","sourceRoot":"","sources":["../../src/wdio/wdioRunner.contract.cjs"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAClC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAE9C,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACrG,MAAM,EAAE,uBAAuB,EAAE,GAAG,yBAAa,SAAS,uCAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC;QAC5C,YAAY,EAAE,SAAS;QACvB,QAAQ;QACR,GAAG;KACJ,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAC5D,MAAM,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAClC,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE;YACpC,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;QAC7E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7F,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IACnD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wdioRunner.contract.d.cts","sourceRoot":"","sources":["../../src/wdio/wdioRunner.contract.cjs"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluentui-react-native/desktop-driver",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "W3C WebDriver-compatible automation for React Native desktop applications",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/microsoft/fluentui-react-native.git",
|
|
10
|
+
"directory": "packages/agentic/desktop-driver"
|
|
11
|
+
},
|
|
12
|
+
"bin": "./config/cli.cjs",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"module": "lib/index.js",
|
|
16
|
+
"types": "lib/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./lib/index.d.ts",
|
|
20
|
+
"import": "./lib/index.js",
|
|
21
|
+
"default": "./src/index.ts"
|
|
22
|
+
},
|
|
23
|
+
"./client": {
|
|
24
|
+
"types": "./lib/client/index.d.ts",
|
|
25
|
+
"import": "./lib/client/index.js",
|
|
26
|
+
"default": "./src/client/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"./cli": {
|
|
29
|
+
"types": "./lib/cli/index.d.ts",
|
|
30
|
+
"import": "./lib/cli/index.js",
|
|
31
|
+
"default": "./src/cli/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"./authoring": {
|
|
34
|
+
"types": "./lib/authoring/index.d.ts",
|
|
35
|
+
"import": "./lib/authoring/index.js",
|
|
36
|
+
"default": "./src/authoring/index.ts"
|
|
37
|
+
},
|
|
38
|
+
"./agent": {
|
|
39
|
+
"types": "./lib/agent/index.d.ts",
|
|
40
|
+
"import": "./lib/agent/index.js",
|
|
41
|
+
"default": "./src/agent/index.ts"
|
|
42
|
+
},
|
|
43
|
+
"./artifacts": {
|
|
44
|
+
"types": "./lib/artifacts/index.d.ts",
|
|
45
|
+
"import": "./lib/artifacts/index.js",
|
|
46
|
+
"default": "./src/artifacts/index.ts"
|
|
47
|
+
},
|
|
48
|
+
"./server": {
|
|
49
|
+
"types": "./lib/server/index.d.ts",
|
|
50
|
+
"import": "./lib/server/index.js",
|
|
51
|
+
"default": "./src/server/index.ts"
|
|
52
|
+
},
|
|
53
|
+
"./storybook": {
|
|
54
|
+
"types": "./lib/storybook.d.ts",
|
|
55
|
+
"import": "./lib/storybook.js",
|
|
56
|
+
"default": "./src/storybook.ts"
|
|
57
|
+
},
|
|
58
|
+
"./testing": {
|
|
59
|
+
"types": "./lib/testing/index.d.ts",
|
|
60
|
+
"import": "./lib/testing/index.js",
|
|
61
|
+
"default": "./src/testing/index.ts"
|
|
62
|
+
},
|
|
63
|
+
"./wdio": {
|
|
64
|
+
"types": "./lib/wdio/index.d.ts",
|
|
65
|
+
"import": "./lib/wdio/index.js",
|
|
66
|
+
"default": "./src/wdio/index.ts"
|
|
67
|
+
},
|
|
68
|
+
"./runner": {
|
|
69
|
+
"types": "./lib/runner/index.d.ts",
|
|
70
|
+
"import": "./lib/runner/index.js",
|
|
71
|
+
"default": "./src/runner/index.ts"
|
|
72
|
+
},
|
|
73
|
+
"./package.json": "./package.json"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsc -b",
|
|
77
|
+
"clean": "fluentui-scripts clean",
|
|
78
|
+
"format": "fluentui-scripts format",
|
|
79
|
+
"lint": "fluentui-scripts lint",
|
|
80
|
+
"test": "fluentui-scripts jest"
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"commander": "^14.0.2",
|
|
84
|
+
"webdriverio": "^9.24.0"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@fluentui-react-native/scripts": "0.1.0"
|
|
88
|
+
},
|
|
89
|
+
"furn": {
|
|
90
|
+
"jestPlatform": "react"
|
|
91
|
+
},
|
|
92
|
+
"rnx-kit": {
|
|
93
|
+
"kitType": "library",
|
|
94
|
+
"extends": "@fluentui-react-native/scripts/kit-config"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { createDesktopDriverClient } from '../client/DesktopDriverClient.js';
|
|
7
|
+
import type { DesktopStoryManifest } from '../storybook.js';
|
|
8
|
+
import { createDesktopDriverTestHarness } from '../testing/protocolHarness.js';
|
|
9
|
+
import { createDesktopDriverStoryHarness } from '../testing/protocolHarness.js';
|
|
10
|
+
import { connectDesktopAgent } from './DesktopAgent.js';
|
|
11
|
+
|
|
12
|
+
describe('DesktopAgent', () => {
|
|
13
|
+
test('lists, explains, inspects, acts, checks, captures, and runs the same authored plan', async () => {
|
|
14
|
+
const manifest: DesktopStoryManifest = {
|
|
15
|
+
endpoint: 'windows',
|
|
16
|
+
entries: [
|
|
17
|
+
{
|
|
18
|
+
id: 'components-button--default',
|
|
19
|
+
name: 'Default',
|
|
20
|
+
packageName: '@fluentui-react-native/components',
|
|
21
|
+
sourcePath: 'src/components/button/button.stories.tsx',
|
|
22
|
+
tags: ['e2e', 'story'],
|
|
23
|
+
tests: {
|
|
24
|
+
version: 1,
|
|
25
|
+
tests: [
|
|
26
|
+
{
|
|
27
|
+
id: 'agent-plan',
|
|
28
|
+
steps: [{ expect: { state: 'enabled', target: { testId: 'button-primary' }, value: true } }],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
title: 'Components/Button',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
platformManifestDigest: 'platform-digest',
|
|
36
|
+
portablePlanDigest: 'portable-digest',
|
|
37
|
+
schemaVersion: 1,
|
|
38
|
+
};
|
|
39
|
+
const harness = await createDesktopDriverStoryHarness(manifest);
|
|
40
|
+
const artifactsRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-agent-'));
|
|
41
|
+
try {
|
|
42
|
+
const response = await runContract(harness.server.url, harness.target.id, artifactsRoot);
|
|
43
|
+
expect(response).toMatchObject({
|
|
44
|
+
check: { passed: true },
|
|
45
|
+
run: { status: 'passed', tests: [{ testId: 'agent-plan' }] },
|
|
46
|
+
screenshot: { kind: 'screenshot', name: 'agent-button' },
|
|
47
|
+
stories: 1,
|
|
48
|
+
tree: 1,
|
|
49
|
+
});
|
|
50
|
+
expect(fs.existsSync(path.join(artifactsRoot, 'run.json'))).toBe(true);
|
|
51
|
+
} finally {
|
|
52
|
+
await harness.close();
|
|
53
|
+
fs.rmSync(artifactsRoot, { force: true, recursive: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('validates the artifact root before reserving a target session', async () => {
|
|
58
|
+
const harness = await createDesktopDriverTestHarness();
|
|
59
|
+
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-agent-invalid-'));
|
|
60
|
+
const invalidRoot = path.join(temporaryDirectory, 'file');
|
|
61
|
+
fs.writeFileSync(invalidRoot, 'not a directory');
|
|
62
|
+
try {
|
|
63
|
+
await expect(
|
|
64
|
+
connectDesktopAgent({
|
|
65
|
+
artifactsRoot: invalidRoot,
|
|
66
|
+
platformName: 'windows',
|
|
67
|
+
targetId: harness.target.id,
|
|
68
|
+
url: harness.server.url,
|
|
69
|
+
}),
|
|
70
|
+
).rejects.toThrow();
|
|
71
|
+
|
|
72
|
+
const client = createDesktopDriverClient({ url: harness.server.url });
|
|
73
|
+
const session = await client.newSession({
|
|
74
|
+
alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
|
|
75
|
+
});
|
|
76
|
+
await session.delete();
|
|
77
|
+
} finally {
|
|
78
|
+
await harness.close();
|
|
79
|
+
fs.rmSync(temporaryDirectory, { force: true, recursive: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function runContract(url: string, targetId: string, artifactsRoot: string): Promise<Record<string, unknown>> {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'agent.contract.cjs'), url, targetId, artifactsRoot], {
|
|
87
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
88
|
+
});
|
|
89
|
+
const stdout: Buffer[] = [];
|
|
90
|
+
const stderr: Buffer[] = [];
|
|
91
|
+
child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));
|
|
92
|
+
child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));
|
|
93
|
+
child.once('error', reject);
|
|
94
|
+
child.once('exit', (code) => {
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
reject(new Error(Buffer.concat(stderr).toString('utf8') || `Desktop agent exited with code ${code}.`));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
resolve(JSON.parse(Buffer.concat(stdout).toString('utf8')) as Record<string, unknown>);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { DesktopArtifact, DesktopStoryRunResult } from '../authoring/results.js';
|
|
2
|
+
import type { DesktopStoryExpectation, DesktopStorySelector } from '../authoring/storyTests.js';
|
|
3
|
+
import { ArtifactManager } from '../artifacts/ArtifactManager.js';
|
|
4
|
+
import type { DesktopTreeNode } from '../host/types.js';
|
|
5
|
+
import { DesktopAssertionError, assertDesktopExpectation, findDesktopElement } from '../runner/StoryTestRunner.js';
|
|
6
|
+
import { WebDriverError } from '../protocol/errors.js';
|
|
7
|
+
import type { DesktopStoryManifestEntry } from '../storybook.js';
|
|
8
|
+
import { connectDesktopWebdriver } from '../wdio/DesktopWebdriver.js';
|
|
9
|
+
import type { DesktopWebdriverOptions, DesktopWebdriverSession } from '../wdio/DesktopWebdriver.js';
|
|
10
|
+
|
|
11
|
+
export type DesktopAgentOptions = DesktopWebdriverOptions & {
|
|
12
|
+
artifactsRoot: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type DesktopAgentStory = Pick<DesktopStoryManifestEntry, 'id' | 'name' | 'tags' | 'title'> & {
|
|
16
|
+
tests: readonly { id: string; title: string }[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type DesktopAgentElement = {
|
|
20
|
+
accessibleName: unknown;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
id: string;
|
|
23
|
+
role: string;
|
|
24
|
+
selected: boolean;
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type DesktopAgentCheckResult = {
|
|
29
|
+
message?: string;
|
|
30
|
+
passed: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type DesktopAgentDescribeOptions = {
|
|
34
|
+
depth?: number;
|
|
35
|
+
maxNodes?: number;
|
|
36
|
+
scope?: 'application' | 'story';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class DesktopAgent {
|
|
40
|
+
private readonly artifacts: ArtifactManager;
|
|
41
|
+
private readonly desktop: DesktopWebdriverSession;
|
|
42
|
+
private readonly captured: DesktopArtifact[] = [];
|
|
43
|
+
|
|
44
|
+
constructor(desktop: DesktopWebdriverSession, artifacts: ArtifactManager) {
|
|
45
|
+
this.desktop = desktop;
|
|
46
|
+
this.artifacts = artifacts;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async listStories(): Promise<DesktopAgentStory[]> {
|
|
50
|
+
const manifest = await this.desktop.listStories();
|
|
51
|
+
return manifest.entries.map((entry) => ({
|
|
52
|
+
id: entry.id,
|
|
53
|
+
name: entry.name,
|
|
54
|
+
tags: entry.tags,
|
|
55
|
+
tests: (entry.tests?.tests ?? []).map((test) => ({ id: test.id, title: test.title ?? test.id })),
|
|
56
|
+
title: entry.title,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async explainStory(storyId: string): Promise<DesktopAgentStory> {
|
|
61
|
+
const story = (await this.listStories()).find(({ id }) => id === storyId);
|
|
62
|
+
if (!story) {
|
|
63
|
+
throw new Error(`Story "${storyId}" is not present in the active platform manifest.`);
|
|
64
|
+
}
|
|
65
|
+
return story;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
openStory(storyId: string): Promise<{ previewGeneration: number; runId: string; storyId: string }> {
|
|
69
|
+
return this.desktop.openStory(storyId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async describe(options: DesktopAgentDescribeOptions = {}): Promise<DesktopTreeNode[]> {
|
|
73
|
+
const depth = options.depth ?? 3;
|
|
74
|
+
const maxNodes = options.maxNodes ?? 100;
|
|
75
|
+
if (!Number.isInteger(depth) || depth < 0 || !Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
76
|
+
throw new TypeError('Agent tree depth must be non-negative and maxNodes must be positive.');
|
|
77
|
+
}
|
|
78
|
+
const roots = await this.desktop.session.getTree();
|
|
79
|
+
const scoped =
|
|
80
|
+
options.scope === 'story'
|
|
81
|
+
? roots.flatMap((root) =>
|
|
82
|
+
findTreeNodes(root, (node) => node.testId === 'story-root' || (node.testId?.endsWith('-story-root') ?? false)),
|
|
83
|
+
)
|
|
84
|
+
: roots;
|
|
85
|
+
let remaining = maxNodes;
|
|
86
|
+
return scoped.flatMap((root) => {
|
|
87
|
+
const projected = projectTree(root, depth, () => remaining-- > 0);
|
|
88
|
+
return projected ? [projected] : [];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async find(selector: DesktopStorySelector): Promise<DesktopAgentElement> {
|
|
93
|
+
const element = await findDesktopElement(this.desktop.session, selector);
|
|
94
|
+
return {
|
|
95
|
+
accessibleName: await element.getAttribute('name'),
|
|
96
|
+
enabled: await element.isEnabled(),
|
|
97
|
+
id: element.id,
|
|
98
|
+
role: await element.getTagName(),
|
|
99
|
+
selected: await element.isSelected(),
|
|
100
|
+
text: await element.getText(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async click(selector: DesktopStorySelector): Promise<void> {
|
|
105
|
+
await (await findDesktopElement(this.desktop.session, selector)).click();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async type(selector: DesktopStorySelector, text: string): Promise<void> {
|
|
109
|
+
await (await findDesktopElement(this.desktop.session, selector)).sendKeys(text);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async check(expectation: DesktopStoryExpectation): Promise<DesktopAgentCheckResult> {
|
|
113
|
+
try {
|
|
114
|
+
await assertDesktopExpectation(this.desktop.session, expectation);
|
|
115
|
+
return { passed: true };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof DesktopAssertionError || (error instanceof WebDriverError && error.code === 'no such element')) {
|
|
118
|
+
return { message: error.message, passed: false };
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async screenshot(name: string): Promise<DesktopArtifact> {
|
|
125
|
+
const artifact = this.artifacts.writeScreenshot('agent', name, await this.desktop.session.takeScreenshot());
|
|
126
|
+
this.captured.push(artifact);
|
|
127
|
+
return artifact;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
runStoryTest(storyId: string, testId: string): Promise<DesktopStoryRunResult> {
|
|
131
|
+
return this.desktop.runStoryTests({
|
|
132
|
+
artifactsRoot: this.artifacts.root,
|
|
133
|
+
selection: { story: storyId, test: testId },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getArtifacts(): readonly DesktopArtifact[] {
|
|
138
|
+
return [...this.captured];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
delete(): Promise<void> {
|
|
142
|
+
return this.desktop.delete();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function connectDesktopAgent(options: DesktopAgentOptions): Promise<DesktopAgent> {
|
|
147
|
+
const artifacts = new ArtifactManager(options.artifactsRoot);
|
|
148
|
+
const desktop = await connectDesktopWebdriver(options);
|
|
149
|
+
return new DesktopAgent(desktop, artifacts);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function findTreeNodes(node: DesktopTreeNode, predicate: (node: DesktopTreeNode) => boolean): DesktopTreeNode[] {
|
|
153
|
+
return [...(predicate(node) ? [node] : []), ...node.children.flatMap((child) => findTreeNodes(child, predicate))];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function projectTree(node: DesktopTreeNode, depth: number, take: () => boolean): DesktopTreeNode | undefined {
|
|
157
|
+
if (!take()) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...node,
|
|
162
|
+
children:
|
|
163
|
+
depth === 0
|
|
164
|
+
? []
|
|
165
|
+
: node.children.flatMap((child) => {
|
|
166
|
+
const projected = projectTree(child, depth - 1, take);
|
|
167
|
+
return projected ? [projected] : [];
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const { pathToFileURL } = require('node:url');
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const moduleUrl = pathToFileURL(path.resolve(__dirname, '..', '..', 'lib', 'agent', 'index.js')).href;
|
|
6
|
+
const { connectDesktopAgent } = await import(moduleUrl);
|
|
7
|
+
const [url, targetId, artifactsRoot] = process.argv.slice(2);
|
|
8
|
+
const agent = await connectDesktopAgent({
|
|
9
|
+
artifactsRoot,
|
|
10
|
+
platformName: 'windows',
|
|
11
|
+
targetId,
|
|
12
|
+
url,
|
|
13
|
+
});
|
|
14
|
+
try {
|
|
15
|
+
const stories = await agent.listStories();
|
|
16
|
+
const story = await agent.explainStory('components-button--default');
|
|
17
|
+
await agent.openStory(story.id);
|
|
18
|
+
const tree = await agent.describe({ depth: 2, maxNodes: 10, scope: 'story' });
|
|
19
|
+
await agent.click({ testId: 'button-primary' });
|
|
20
|
+
const check = await agent.check({ state: 'focused', target: { testId: 'button-primary' }, value: true });
|
|
21
|
+
const screenshot = await agent.screenshot('agent-button');
|
|
22
|
+
const run = await agent.runStoryTest(story.id, 'agent-plan');
|
|
23
|
+
process.stdout.write(JSON.stringify({ check, run, screenshot, stories: stories.length, tree: tree.length }));
|
|
24
|
+
} finally {
|
|
25
|
+
await agent.delete();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main().catch((error) => {
|
|
30
|
+
process.stderr.write(error.stack ?? error.message);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { ArtifactManager } from './ArtifactManager.js';
|
|
6
|
+
|
|
7
|
+
describe('ArtifactManager', () => {
|
|
8
|
+
test('confines and atomically writes artifacts beneath the run root', () => {
|
|
9
|
+
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-artifacts-'));
|
|
10
|
+
try {
|
|
11
|
+
const manager = new ArtifactManager(temporaryDirectory);
|
|
12
|
+
const artifact = manager.writeSource('../story:id', '../source', '<application />');
|
|
13
|
+
|
|
14
|
+
expect(artifact.path).toBe('tests/-story-id/-source.xml');
|
|
15
|
+
expect(fs.readFileSync(path.join(temporaryDirectory, ...artifact.path.split('/')), 'utf8')).toBe('<application />');
|
|
16
|
+
} finally {
|
|
17
|
+
fs.rmSync(temporaryDirectory, { force: true, recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|