@win-auto/core 0.1.2
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/README.md +80 -0
- package/dist/api/app.d.ts +20 -0
- package/dist/api/app.js +50 -0
- package/dist/api/automation.d.ts +9 -0
- package/dist/api/automation.js +39 -0
- package/dist/api/element.d.ts +10 -0
- package/dist/api/element.js +30 -0
- package/dist/api/testAutomation.d.ts +8 -0
- package/dist/api/testAutomation.js +30 -0
- package/dist/api/types.d.ts +36 -0
- package/dist/api/types.js +2 -0
- package/dist/api/window.d.ts +11 -0
- package/dist/api/window.js +33 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +15 -0
- package/dist/mock/mockRuntime.d.ts +31 -0
- package/dist/mock/mockRuntime.js +74 -0
- package/dist/mock/mockRuntime.test.d.ts +1 -0
- package/dist/mock/mockRuntime.test.js +34 -0
- package/dist/native/classNames.d.ts +3 -0
- package/dist/native/classNames.js +12 -0
- package/dist/native/loadNative.d.ts +2 -0
- package/dist/native/loadNative.js +36 -0
- package/dist/testing/context.d.ts +1 -0
- package/dist/testing/context.js +6 -0
- package/dist/testing/globals.d.ts +8 -0
- package/dist/testing/globals.js +2 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +21 -0
- package/dist/testing/installGlobals.d.ts +3 -0
- package/dist/testing/installGlobals.js +8 -0
- package/dist/testing/setup.d.ts +1 -0
- package/dist/testing/setup.js +12 -0
- package/dist/testing/testAutomation.d.ts +6 -0
- package/dist/testing/testAutomation.js +12 -0
- package/dist/testing/vitest.d.ts +2 -0
- package/dist/testing/vitest.js +6 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# win-auto-core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/win-auto-core)
|
|
4
|
+
|
|
5
|
+
Core TypeScript API for Windows desktop automation.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install win-auto-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Automation } from 'win-auto-core';
|
|
17
|
+
|
|
18
|
+
const automation = new Automation();
|
|
19
|
+
const app = await automation.launch('C:\\Windows\\System32\\notepad.exe');
|
|
20
|
+
const textbox = await app.find({ role: 'textbox' });
|
|
21
|
+
await textbox?.type('Hello from win-auto!');
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
### Automation
|
|
27
|
+
|
|
28
|
+
Main entry point.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const automation = new Automation();
|
|
32
|
+
const app = await automation.launch(executablePath);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### App
|
|
36
|
+
|
|
37
|
+
Launched application instance.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const mainWindow = await app.getMainWindow();
|
|
41
|
+
const element = await app.find({ role: 'button', name: 'OK' });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Window
|
|
45
|
+
|
|
46
|
+
Window in the application.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const element = await window.findElement({ role: 'textbox' });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Element
|
|
53
|
+
|
|
54
|
+
UI element for interaction.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
await element.typeText('Hello');
|
|
58
|
+
await element.click();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Testing Support
|
|
62
|
+
|
|
63
|
+
Export testing utilities:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { setupAutomation } from 'win-auto-core/testing';
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- Windows 10/11
|
|
72
|
+
- Node.js 18+
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
See the [main README](../../README.md) for complete documentation, examples, and troubleshooting.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Window } from "./window";
|
|
2
|
+
import { Element } from "./element";
|
|
3
|
+
export declare class App {
|
|
4
|
+
readonly processId: number;
|
|
5
|
+
readonly executablePath: string;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
constructor(processId: number, executablePath: string, title: string);
|
|
8
|
+
listWindows(): Promise<Window[]>;
|
|
9
|
+
getMainWindow(): Promise<Window | null>;
|
|
10
|
+
waitForMainWindow(options?: {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
intervalMs?: number;
|
|
13
|
+
}): Promise<Window>;
|
|
14
|
+
find(selector: {
|
|
15
|
+
automationId?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
role?: string;
|
|
18
|
+
}): Promise<Element | null>;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
package/dist/api/app.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.App = void 0;
|
|
4
|
+
const window_1 = require("./window");
|
|
5
|
+
const loadNative_1 = require("../native/loadNative");
|
|
6
|
+
class App {
|
|
7
|
+
processId;
|
|
8
|
+
executablePath;
|
|
9
|
+
title;
|
|
10
|
+
constructor(processId, executablePath, title) {
|
|
11
|
+
this.processId = processId;
|
|
12
|
+
this.executablePath = executablePath;
|
|
13
|
+
this.title = title;
|
|
14
|
+
}
|
|
15
|
+
async listWindows() {
|
|
16
|
+
const handles = await (0, loadNative_1.loadNativeBindings)().enumerateWindows(this.processId);
|
|
17
|
+
return handles.map((handle) => new window_1.Window(handle, this.processId));
|
|
18
|
+
}
|
|
19
|
+
async getMainWindow() {
|
|
20
|
+
const windows = await this.listWindows();
|
|
21
|
+
if (windows.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return windows[0];
|
|
25
|
+
}
|
|
26
|
+
async waitForMainWindow(options) {
|
|
27
|
+
const timeoutMs = options?.timeoutMs ?? 10_000;
|
|
28
|
+
const intervalMs = options?.intervalMs ?? 100;
|
|
29
|
+
const maxAttempts = Math.max(1, Math.ceil(timeoutMs / intervalMs));
|
|
30
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
31
|
+
const window = await this.getMainWindow();
|
|
32
|
+
if (window) {
|
|
33
|
+
return window;
|
|
34
|
+
}
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`No top-level window found for process ${this.processId} within ${timeoutMs}ms.`);
|
|
38
|
+
}
|
|
39
|
+
async find(selector) {
|
|
40
|
+
const mainWindow = await this.getMainWindow();
|
|
41
|
+
if (!mainWindow) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return mainWindow.findElement(selector);
|
|
45
|
+
}
|
|
46
|
+
async close() {
|
|
47
|
+
await (0, loadNative_1.loadNativeBindings)().closeApp(this.processId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.App = App;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { App } from "./app";
|
|
2
|
+
import type { AppSelector, LaunchOptions } from "./types";
|
|
3
|
+
export declare class Automation {
|
|
4
|
+
launch(executablePath: string): Promise<App>;
|
|
5
|
+
launchApp(options: LaunchOptions): Promise<App>;
|
|
6
|
+
connectApp(selector: AppSelector): Promise<App>;
|
|
7
|
+
pingNative(): string;
|
|
8
|
+
debugDiscovery(processId: number): import("./types").WindowDebugInfo[];
|
|
9
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Automation = void 0;
|
|
4
|
+
const app_1 = require("./app");
|
|
5
|
+
const classNames_1 = require("../native/classNames");
|
|
6
|
+
const loadNative_1 = require("../native/loadNative");
|
|
7
|
+
class Automation {
|
|
8
|
+
async launch(executablePath) {
|
|
9
|
+
return this.launchApp({ executablePath });
|
|
10
|
+
}
|
|
11
|
+
async launchApp(options) {
|
|
12
|
+
const native = (0, loadNative_1.loadNativeBindings)();
|
|
13
|
+
if (native.setAppConfig) {
|
|
14
|
+
const classNames = options.executablePath.toLowerCase().includes("notepad.exe")
|
|
15
|
+
? classNames_1.DEFAULT_NOTEPAD_CLASS_NAMES
|
|
16
|
+
: [];
|
|
17
|
+
native.setAppConfig(options.executablePath, classNames);
|
|
18
|
+
}
|
|
19
|
+
const processId = await native.launch(options.executablePath);
|
|
20
|
+
return new app_1.App(processId, options.executablePath, options.title ?? "Launched App");
|
|
21
|
+
}
|
|
22
|
+
async connectApp(selector) {
|
|
23
|
+
if (!selector.processId) {
|
|
24
|
+
throw new Error("connectApp currently requires processId for the native backend.");
|
|
25
|
+
}
|
|
26
|
+
return new app_1.App(selector.processId, selector.executablePath ?? "unknown", selector.title ?? "Connected App");
|
|
27
|
+
}
|
|
28
|
+
pingNative() {
|
|
29
|
+
return (0, loadNative_1.loadNativeBindings)().ping();
|
|
30
|
+
}
|
|
31
|
+
debugDiscovery(processId) {
|
|
32
|
+
const native = (0, loadNative_1.loadNativeBindings)();
|
|
33
|
+
if (!native.debugDiscovery) {
|
|
34
|
+
throw new Error("debugDiscovery is not available in the loaded native module.");
|
|
35
|
+
}
|
|
36
|
+
return native.debugDiscovery(processId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.Automation = Automation;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Element {
|
|
2
|
+
readonly handle: string;
|
|
3
|
+
private readonly windowHandle;
|
|
4
|
+
constructor(handle: string, windowHandle: string);
|
|
5
|
+
click(): Promise<void>;
|
|
6
|
+
typeText(text: string): Promise<void>;
|
|
7
|
+
type(text: string): Promise<void>;
|
|
8
|
+
getText(): Promise<string>;
|
|
9
|
+
exists(): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Element = void 0;
|
|
4
|
+
const loadNative_1 = require("../native/loadNative");
|
|
5
|
+
class Element {
|
|
6
|
+
handle;
|
|
7
|
+
windowHandle;
|
|
8
|
+
constructor(handle, windowHandle) {
|
|
9
|
+
this.handle = handle;
|
|
10
|
+
this.windowHandle = windowHandle;
|
|
11
|
+
}
|
|
12
|
+
async click() {
|
|
13
|
+
await Promise.resolve();
|
|
14
|
+
}
|
|
15
|
+
async typeText(text) {
|
|
16
|
+
await (0, loadNative_1.loadNativeBindings)().typeText(this.handle, text);
|
|
17
|
+
}
|
|
18
|
+
async type(text) {
|
|
19
|
+
return this.typeText(text);
|
|
20
|
+
}
|
|
21
|
+
async getText() {
|
|
22
|
+
// Minimal backend currently supports writing text only.
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
async exists() {
|
|
26
|
+
const candidate = await (0, loadNative_1.loadNativeBindings)().findElement(this.windowHandle);
|
|
27
|
+
return candidate === this.handle;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.Element = Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Automation } from "./automation";
|
|
2
|
+
import type { LaunchOptions } from "./types";
|
|
3
|
+
import type { App } from "./app";
|
|
4
|
+
export declare function trackApp(app: App): App;
|
|
5
|
+
export declare function closeTrackedApps(): Promise<void>;
|
|
6
|
+
export declare class TestAutomation extends Automation {
|
|
7
|
+
launchApp(options: LaunchOptions): Promise<App>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestAutomation = void 0;
|
|
4
|
+
exports.trackApp = trackApp;
|
|
5
|
+
exports.closeTrackedApps = closeTrackedApps;
|
|
6
|
+
const automation_1 = require("./automation");
|
|
7
|
+
const launchedApps = new Set();
|
|
8
|
+
function trackApp(app) {
|
|
9
|
+
launchedApps.add(app);
|
|
10
|
+
return app;
|
|
11
|
+
}
|
|
12
|
+
async function closeTrackedApps() {
|
|
13
|
+
const apps = [...launchedApps];
|
|
14
|
+
launchedApps.clear();
|
|
15
|
+
await Promise.all(apps.map(async (app) => {
|
|
16
|
+
try {
|
|
17
|
+
await app.close();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Best-effort cleanup between tests.
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
class TestAutomation extends automation_1.Automation {
|
|
25
|
+
async launchApp(options) {
|
|
26
|
+
const app = await super.launchApp(options);
|
|
27
|
+
return trackApp(app);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.TestAutomation = TestAutomation;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type AppSelector = {
|
|
2
|
+
executablePath?: string;
|
|
3
|
+
processId?: number;
|
|
4
|
+
title?: string;
|
|
5
|
+
};
|
|
6
|
+
export type LaunchOptions = {
|
|
7
|
+
executablePath: string;
|
|
8
|
+
args?: string[];
|
|
9
|
+
title?: string;
|
|
10
|
+
};
|
|
11
|
+
export type ElementSelector = {
|
|
12
|
+
automationId?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
role?: string;
|
|
15
|
+
};
|
|
16
|
+
export type WindowDebugInfo = {
|
|
17
|
+
hwnd: string;
|
|
18
|
+
pid: number;
|
|
19
|
+
className: string;
|
|
20
|
+
title: string;
|
|
21
|
+
visible: boolean;
|
|
22
|
+
ownerInvalid: boolean;
|
|
23
|
+
matchesTargetPid: boolean;
|
|
24
|
+
passesTopLevelVisible: boolean;
|
|
25
|
+
processImage: string;
|
|
26
|
+
};
|
|
27
|
+
export type NativeBindings = {
|
|
28
|
+
ping: () => string;
|
|
29
|
+
setAppConfig?: (executable: string, classNames: string[]) => void;
|
|
30
|
+
launch: (executablePath?: string | null) => Promise<number>;
|
|
31
|
+
enumerateWindows: (processId: number) => Promise<string[]>;
|
|
32
|
+
debugDiscovery?: (processId: number) => WindowDebugInfo[];
|
|
33
|
+
findElement: (windowHandle: string, classNames?: string[] | null, automationId?: string | null, name?: string | null, role?: string | null) => Promise<string | null>;
|
|
34
|
+
typeText: (elementHandle: string, text: string) => Promise<void>;
|
|
35
|
+
closeApp: (processId: number) => Promise<void>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ElementSelector } from "./types";
|
|
2
|
+
import { Element } from "./element";
|
|
3
|
+
export declare class Window {
|
|
4
|
+
readonly handle: string;
|
|
5
|
+
readonly processId: number;
|
|
6
|
+
constructor(handle: string, processId: number);
|
|
7
|
+
findElement(selector: ElementSelector): Promise<Element | null>;
|
|
8
|
+
find(selector: ElementSelector): Promise<Element | null>;
|
|
9
|
+
findElements(selector: ElementSelector): Promise<Element[]>;
|
|
10
|
+
focus(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Window = void 0;
|
|
4
|
+
const element_1 = require("./element");
|
|
5
|
+
const classNames_1 = require("../native/classNames");
|
|
6
|
+
const loadNative_1 = require("../native/loadNative");
|
|
7
|
+
class Window {
|
|
8
|
+
handle;
|
|
9
|
+
processId;
|
|
10
|
+
constructor(handle, processId) {
|
|
11
|
+
this.handle = handle;
|
|
12
|
+
this.processId = processId;
|
|
13
|
+
}
|
|
14
|
+
async findElement(selector) {
|
|
15
|
+
const elementHandle = await (0, loadNative_1.loadNativeBindings)().findElement(this.handle, (0, classNames_1.classNamesForSelector)(selector), selector.automationId, selector.name, selector.role);
|
|
16
|
+
if (!elementHandle) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return new element_1.Element(elementHandle, this.handle);
|
|
20
|
+
}
|
|
21
|
+
async find(selector) {
|
|
22
|
+
return this.findElement(selector);
|
|
23
|
+
}
|
|
24
|
+
async findElements(selector) {
|
|
25
|
+
const element = await this.findElement(selector);
|
|
26
|
+
return element ? [element] : [];
|
|
27
|
+
}
|
|
28
|
+
async focus() {
|
|
29
|
+
// Focus support is intentionally minimal for the initial native backend.
|
|
30
|
+
await Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.Window = Window;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Automation } from "./api/automation";
|
|
2
|
+
export { App } from "./api/app";
|
|
3
|
+
export { Window } from "./api/window";
|
|
4
|
+
export { Element } from "./api/element";
|
|
5
|
+
export { TestAutomation, trackApp, closeTrackedApps } from "./api/testAutomation";
|
|
6
|
+
export type { AppSelector, ElementSelector, LaunchOptions } from "./api/types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.closeTrackedApps = exports.trackApp = exports.TestAutomation = exports.Element = exports.Window = exports.App = exports.Automation = void 0;
|
|
4
|
+
var automation_1 = require("./api/automation");
|
|
5
|
+
Object.defineProperty(exports, "Automation", { enumerable: true, get: function () { return automation_1.Automation; } });
|
|
6
|
+
var app_1 = require("./api/app");
|
|
7
|
+
Object.defineProperty(exports, "App", { enumerable: true, get: function () { return app_1.App; } });
|
|
8
|
+
var window_1 = require("./api/window");
|
|
9
|
+
Object.defineProperty(exports, "Window", { enumerable: true, get: function () { return window_1.Window; } });
|
|
10
|
+
var element_1 = require("./api/element");
|
|
11
|
+
Object.defineProperty(exports, "Element", { enumerable: true, get: function () { return element_1.Element; } });
|
|
12
|
+
var testAutomation_1 = require("./api/testAutomation");
|
|
13
|
+
Object.defineProperty(exports, "TestAutomation", { enumerable: true, get: function () { return testAutomation_1.TestAutomation; } });
|
|
14
|
+
Object.defineProperty(exports, "trackApp", { enumerable: true, get: function () { return testAutomation_1.trackApp; } });
|
|
15
|
+
Object.defineProperty(exports, "closeTrackedApps", { enumerable: true, get: function () { return testAutomation_1.closeTrackedApps; } });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ElementSelector, LaunchOptions } from "../api/types";
|
|
2
|
+
export type MockElementRecord = {
|
|
3
|
+
id: string;
|
|
4
|
+
selector: ElementSelector;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type MockWindowRecord = {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
elements: MockElementRecord[];
|
|
11
|
+
};
|
|
12
|
+
export type MockAppRecord = {
|
|
13
|
+
id: string;
|
|
14
|
+
executablePath: string;
|
|
15
|
+
title: string;
|
|
16
|
+
windows: MockWindowRecord[];
|
|
17
|
+
};
|
|
18
|
+
export declare class MockRuntime {
|
|
19
|
+
private appCounter;
|
|
20
|
+
private windowCounter;
|
|
21
|
+
private elementCounter;
|
|
22
|
+
private apps;
|
|
23
|
+
launchApp(options: LaunchOptions): Promise<MockAppRecord>;
|
|
24
|
+
listApps(): Promise<MockAppRecord[]>;
|
|
25
|
+
getAppById(appId: string): Promise<MockAppRecord | null>;
|
|
26
|
+
closeApp(appId: string): Promise<void>;
|
|
27
|
+
findElement(windowRecord: MockWindowRecord, selector: ElementSelector): Promise<MockElementRecord | null>;
|
|
28
|
+
listElements(windowRecord: MockWindowRecord, selector: ElementSelector): Promise<MockElementRecord[]>;
|
|
29
|
+
setElementText(windowRecord: MockWindowRecord, elementId: string, text: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare const runtime: MockRuntime;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runtime = exports.MockRuntime = void 0;
|
|
4
|
+
const DELAY_MS = 15;
|
|
5
|
+
function wait() {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, DELAY_MS));
|
|
7
|
+
}
|
|
8
|
+
function selectorMatches(recordSelector, query) {
|
|
9
|
+
if (query.automationId && recordSelector.automationId !== query.automationId) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (query.name && recordSelector.name !== query.name) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (query.role && recordSelector.role !== query.role) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
class MockRuntime {
|
|
21
|
+
appCounter = 1;
|
|
22
|
+
windowCounter = 1;
|
|
23
|
+
elementCounter = 1;
|
|
24
|
+
apps = new Map();
|
|
25
|
+
async launchApp(options) {
|
|
26
|
+
await wait();
|
|
27
|
+
const appId = `app-${this.appCounter++}`;
|
|
28
|
+
const windowId = `win-${this.windowCounter++}`;
|
|
29
|
+
const defaultElement = {
|
|
30
|
+
id: `el-${this.elementCounter++}`,
|
|
31
|
+
selector: { automationId: "main-input", name: "Main Input", role: "textbox" },
|
|
32
|
+
text: ""
|
|
33
|
+
};
|
|
34
|
+
const app = {
|
|
35
|
+
id: appId,
|
|
36
|
+
executablePath: options.executablePath,
|
|
37
|
+
title: options.title ?? "Mock App",
|
|
38
|
+
windows: [{ id: windowId, title: options.title ?? "Main Window", elements: [defaultElement] }]
|
|
39
|
+
};
|
|
40
|
+
this.apps.set(appId, app);
|
|
41
|
+
return app;
|
|
42
|
+
}
|
|
43
|
+
async listApps() {
|
|
44
|
+
await wait();
|
|
45
|
+
return [...this.apps.values()];
|
|
46
|
+
}
|
|
47
|
+
async getAppById(appId) {
|
|
48
|
+
await wait();
|
|
49
|
+
return this.apps.get(appId) ?? null;
|
|
50
|
+
}
|
|
51
|
+
async closeApp(appId) {
|
|
52
|
+
await wait();
|
|
53
|
+
this.apps.delete(appId);
|
|
54
|
+
}
|
|
55
|
+
async findElement(windowRecord, selector) {
|
|
56
|
+
await wait();
|
|
57
|
+
const match = windowRecord.elements.find((record) => selectorMatches(record.selector, selector));
|
|
58
|
+
return match ?? null;
|
|
59
|
+
}
|
|
60
|
+
async listElements(windowRecord, selector) {
|
|
61
|
+
await wait();
|
|
62
|
+
return windowRecord.elements.filter((record) => selectorMatches(record.selector, selector));
|
|
63
|
+
}
|
|
64
|
+
async setElementText(windowRecord, elementId, text) {
|
|
65
|
+
await wait();
|
|
66
|
+
const element = windowRecord.elements.find((record) => record.id === elementId);
|
|
67
|
+
if (!element) {
|
|
68
|
+
throw new Error(`Element not found: ${elementId}`);
|
|
69
|
+
}
|
|
70
|
+
element.text = text;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.MockRuntime = MockRuntime;
|
|
74
|
+
exports.runtime = new MockRuntime();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const automation_1 = require("../api/automation");
|
|
5
|
+
const nativeMock = {
|
|
6
|
+
ping: vitest_1.vi.fn(() => "ok"),
|
|
7
|
+
launch: vitest_1.vi.fn(async () => 4242),
|
|
8
|
+
enumerateWindows: vitest_1.vi.fn(async () => ["100"]),
|
|
9
|
+
findElement: vitest_1.vi.fn(async () => "200"),
|
|
10
|
+
typeText: vitest_1.vi.fn(async () => undefined)
|
|
11
|
+
};
|
|
12
|
+
vitest_1.vi.mock("../native/loadNative", () => ({
|
|
13
|
+
loadNativeBindings: () => nativeMock
|
|
14
|
+
}));
|
|
15
|
+
(0, vitest_1.describe)("native API wrapper", () => {
|
|
16
|
+
(0, vitest_1.beforeEach)(() => {
|
|
17
|
+
vitest_1.vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)("keeps async object flow through native bindings", async () => {
|
|
20
|
+
const automation = new automation_1.Automation();
|
|
21
|
+
const app = await automation.launchApp({
|
|
22
|
+
executablePath: "C:\\Windows\\System32\\notepad.exe",
|
|
23
|
+
title: "Notepad"
|
|
24
|
+
});
|
|
25
|
+
const window = await app.getMainWindow();
|
|
26
|
+
const element = await window?.findElement({ role: "textbox" });
|
|
27
|
+
await element?.typeText("hello");
|
|
28
|
+
(0, vitest_1.expect)(app.processId).toBe(4242);
|
|
29
|
+
(0, vitest_1.expect)(nativeMock.launch).toHaveBeenCalledTimes(1);
|
|
30
|
+
(0, vitest_1.expect)(nativeMock.enumerateWindows).toHaveBeenCalledWith(4242);
|
|
31
|
+
(0, vitest_1.expect)(nativeMock.findElement).toHaveBeenCalled();
|
|
32
|
+
(0, vitest_1.expect)(nativeMock.typeText).toHaveBeenCalledWith("200", "hello");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_NOTEPAD_CLASS_NAMES = void 0;
|
|
4
|
+
exports.classNamesForSelector = classNamesForSelector;
|
|
5
|
+
const TEXTBOX_CLASS_NAMES = ["Edit", "RichEditD2DPT", "Scintilla"];
|
|
6
|
+
function classNamesForSelector(selector) {
|
|
7
|
+
if (selector.role === "textbox") {
|
|
8
|
+
return TEXTBOX_CLASS_NAMES;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
exports.DEFAULT_NOTEPAD_CLASS_NAMES = TEXTBOX_CLASS_NAMES;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadNativeBindings = loadNativeBindings;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const nativeSearchPaths = [
|
|
9
|
+
"../../../../native/win-auto-native/win-auto-native.win32-x64-msvc.node",
|
|
10
|
+
"../../../../native/win-auto-native/win-auto-native.win32-arm64-msvc.node",
|
|
11
|
+
"../../../../native/win-auto-native/win_auto_native.node",
|
|
12
|
+
"../../../../native/win-auto-native/index.node",
|
|
13
|
+
"../../../../native/win-auto-native/index.js",
|
|
14
|
+
"../../../../native/win-auto-native"
|
|
15
|
+
];
|
|
16
|
+
function loadNativeBindings() {
|
|
17
|
+
for (const relativePath of nativeSearchPaths) {
|
|
18
|
+
const resolvedPath = node_path_1.default.resolve(__dirname, relativePath);
|
|
19
|
+
try {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
|
+
const mod = require(resolvedPath);
|
|
22
|
+
if (typeof mod.ping === "function" &&
|
|
23
|
+
typeof mod.launch === "function" &&
|
|
24
|
+
typeof mod.enumerateWindows === "function" &&
|
|
25
|
+
typeof mod.findElement === "function" &&
|
|
26
|
+
typeof mod.typeText === "function" &&
|
|
27
|
+
typeof mod.closeApp === "function") {
|
|
28
|
+
return mod;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error("Native module not found. Run `npm run build:native` at workspace root before calling native functions.");
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { trackApp, closeTrackedApps } from "../api/testAutomation";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.closeTrackedApps = exports.trackApp = void 0;
|
|
4
|
+
var testAutomation_1 = require("../api/testAutomation");
|
|
5
|
+
Object.defineProperty(exports, "trackApp", { enumerable: true, get: function () { return testAutomation_1.trackApp; } });
|
|
6
|
+
Object.defineProperty(exports, "closeTrackedApps", { enumerable: true, get: function () { return testAutomation_1.closeTrackedApps; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { it as winAutoIt } from "./vitest";
|
|
2
|
+
import type { describe as vitestDescribe, expect as vitestExpect } from "vitest";
|
|
3
|
+
declare global {
|
|
4
|
+
const describe: typeof vitestDescribe;
|
|
5
|
+
const it: typeof winAutoIt;
|
|
6
|
+
const expect: typeof vitestExpect;
|
|
7
|
+
}
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./globals";
|
|
2
|
+
export { describe } from "vitest";
|
|
3
|
+
export { it } from "./vitest";
|
|
4
|
+
export { expect, vi, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
|
5
|
+
export { TestAutomation, trackApp, closeTrackedApps } from "../api/testAutomation";
|
|
6
|
+
export { installTestGlobals } from "./installGlobals";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installTestGlobals = exports.closeTrackedApps = exports.trackApp = exports.TestAutomation = exports.afterAll = exports.beforeAll = exports.afterEach = exports.beforeEach = exports.vi = exports.expect = exports.it = exports.describe = void 0;
|
|
4
|
+
require("./globals");
|
|
5
|
+
var vitest_1 = require("vitest");
|
|
6
|
+
Object.defineProperty(exports, "describe", { enumerable: true, get: function () { return vitest_1.describe; } });
|
|
7
|
+
var vitest_2 = require("./vitest");
|
|
8
|
+
Object.defineProperty(exports, "it", { enumerable: true, get: function () { return vitest_2.it; } });
|
|
9
|
+
var vitest_3 = require("vitest");
|
|
10
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return vitest_3.expect; } });
|
|
11
|
+
Object.defineProperty(exports, "vi", { enumerable: true, get: function () { return vitest_3.vi; } });
|
|
12
|
+
Object.defineProperty(exports, "beforeEach", { enumerable: true, get: function () { return vitest_3.beforeEach; } });
|
|
13
|
+
Object.defineProperty(exports, "afterEach", { enumerable: true, get: function () { return vitest_3.afterEach; } });
|
|
14
|
+
Object.defineProperty(exports, "beforeAll", { enumerable: true, get: function () { return vitest_3.beforeAll; } });
|
|
15
|
+
Object.defineProperty(exports, "afterAll", { enumerable: true, get: function () { return vitest_3.afterAll; } });
|
|
16
|
+
var testAutomation_1 = require("../api/testAutomation");
|
|
17
|
+
Object.defineProperty(exports, "TestAutomation", { enumerable: true, get: function () { return testAutomation_1.TestAutomation; } });
|
|
18
|
+
Object.defineProperty(exports, "trackApp", { enumerable: true, get: function () { return testAutomation_1.trackApp; } });
|
|
19
|
+
Object.defineProperty(exports, "closeTrackedApps", { enumerable: true, get: function () { return testAutomation_1.closeTrackedApps; } });
|
|
20
|
+
var installGlobals_1 = require("./installGlobals");
|
|
21
|
+
Object.defineProperty(exports, "installTestGlobals", { enumerable: true, get: function () { return installGlobals_1.installTestGlobals; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installTestGlobals = installTestGlobals;
|
|
4
|
+
require("./setup");
|
|
5
|
+
/** Installs win-auto describe/it/expect on globalThis (also runs automatically via testing/setup). */
|
|
6
|
+
function installTestGlobals() {
|
|
7
|
+
// setup.ts assigns globals on import.
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const context_1 = require("./context");
|
|
5
|
+
const vitest_2 = require("./vitest");
|
|
6
|
+
(0, vitest_1.afterEach)(async () => {
|
|
7
|
+
await (0, context_1.closeTrackedApps)();
|
|
8
|
+
});
|
|
9
|
+
const globals = globalThis;
|
|
10
|
+
globals.describe = vitest_1.describe;
|
|
11
|
+
globals.it = vitest_2.it;
|
|
12
|
+
globals.expect = vitest_1.expect;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestAutomation = void 0;
|
|
4
|
+
const automation_1 = require("../api/automation");
|
|
5
|
+
const context_1 = require("./context");
|
|
6
|
+
class TestAutomation extends automation_1.Automation {
|
|
7
|
+
async launchApp(options) {
|
|
8
|
+
const app = await super.launchApp(options);
|
|
9
|
+
return (0, context_1.trackApp)(app);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.TestAutomation = TestAutomation;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.it = void 0;
|
|
4
|
+
const vitest_1 = require("vitest");
|
|
5
|
+
const DEFAULT_TEST_TIMEOUT_MS = 30_000;
|
|
6
|
+
exports.it = Object.assign((name, fn, timeout) => (0, vitest_1.it)(name, fn, timeout ?? DEFAULT_TEST_TIMEOUT_MS), vitest_1.it);
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@win-auto/core",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Core TypeScript API for Windows desktop automation.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/mihailDamchevski/win-auto.git",
|
|
8
|
+
"directory": "packages/core"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/mihailDamchevski/win-auto/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/mihailDamchevski/win-auto/tree/main/packages/core#readme",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Your Name",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./testing": {
|
|
24
|
+
"types": "./dist/testing/index.d.ts",
|
|
25
|
+
"default": "./dist/testing/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./testing/setup": {
|
|
28
|
+
"types": "./dist/testing/setup.d.ts",
|
|
29
|
+
"default": "./dist/testing/setup.js"
|
|
30
|
+
},
|
|
31
|
+
"./testing/globals": {
|
|
32
|
+
"types": "./dist/testing/globals.d.ts",
|
|
33
|
+
"default": "./dist/testing/globals.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"win-auto-native": "0.1.3"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"vitest": ">=3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"vitest": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"vitest": "^3.1.1"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.json",
|
|
57
|
+
"test": "vitest run src/mock/mockRuntime.test.ts",
|
|
58
|
+
"prepublishOnly": "npm run build",
|
|
59
|
+
"prepack": "npm run build"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
}
|
|
64
|
+
}
|