diff --git a/index.ts b/index.ts index cfdccd60..031d6c74 100644 --- a/index.ts +++ b/index.ts @@ -1,37 +1,39 @@ -import { NativeAdapter } from "./lib/adapter/native.adapter.class"; -import { VisionAdapter } from "./lib/adapter/vision.adapter.class"; -import { AssertClass } from "./lib/assert.class"; -import { ClipboardClass } from "./lib/clipboard.class"; -import { KeyboardClass } from "./lib/keyboard.class"; -import { MouseClass } from "./lib/mouse.class"; -import { createMovementApi } from "./lib/movement.function"; -import { ScreenClass } from "./lib/screen.class"; -import { LineHelper } from "./lib/util/linehelper.class"; -import { createWindowApi } from "./lib/window.function"; +import {NativeAdapter} from "./lib/adapter/native.adapter.class"; +import {VisionAdapter} from "./lib/adapter/vision.adapter.class"; +import {AssertClass} from "./lib/assert.class"; +import {ClipboardClass} from "./lib/clipboard.class"; +import {KeyboardClass} from "./lib/keyboard.class"; +import {MouseClass} from "./lib/mouse.class"; +import {createMovementApi} from "./lib/movement.function"; +import {ScreenClass} from "./lib/screen.class"; +import {LineHelper} from "./lib/util/linehelper.class"; +import {createWindowApi} from "./lib/window.function"; +import providerRegistry from "./lib/provider/provider-registry.class"; export { - AssertClass, - ClipboardClass, - KeyboardClass, - MouseClass, - ScreenClass, + AssertClass, + ClipboardClass, + KeyboardClass, + MouseClass, + ScreenClass, + providerRegistry } -export { jestMatchers } from "./lib/expect/jest.matcher.function"; -export { sleep } from "./lib/sleep.function"; -export { Image } from "./lib/image.class"; -export { Key } from "./lib/key.enum"; -export { Button } from "./lib/button.enum"; -export { centerOf, randomPointIn } from "./lib/location.function"; -export { LocationParameters } from "./lib/locationparameters.class"; -export { OptionalSearchParameters } from "./lib/optionalsearchparameters.class"; -export { linear } from "./lib/movementtype.function"; -export { Point } from "./lib/point.class"; -export { Region } from "./lib/region.class"; -export { Window } from "./lib/window.class"; +export {jestMatchers} from "./lib/expect/jest.matcher.function"; +export {sleep} from "./lib/sleep.function"; +export {Image} from "./lib/image.class"; +export {Key} from "./lib/key.enum"; +export {Button} from "./lib/button.enum"; +export {centerOf, randomPointIn} from "./lib/location.function"; +export {LocationParameters} from "./lib/locationparameters.class"; +export {OptionalSearchParameters} from "./lib/optionalsearchparameters.class"; +export {linear} from "./lib/movementtype.function"; +export {Point} from "./lib/point.class"; +export {Region} from "./lib/region.class"; +export {Window} from "./lib/window.class"; -const screenActions = new VisionAdapter(); -const nativeActions = new NativeAdapter(); +const screenActions = new VisionAdapter(providerRegistry); +const nativeActions = new NativeAdapter(providerRegistry); const lineHelper = new LineHelper(); const clipboard = new ClipboardClass(nativeActions); @@ -41,19 +43,19 @@ const screen = new ScreenClass(screenActions); const assert = new AssertClass(screen); const {straightTo, up, down, left, right} = createMovementApi(nativeActions, lineHelper); -const {getWindows, getActiveWindow } = createWindowApi(nativeActions); +const {getWindows, getActiveWindow} = createWindowApi(nativeActions); export { - clipboard, - keyboard, - mouse, - screen, - assert, - straightTo, - up, - down, - left, - right, - getWindows, - getActiveWindow, + clipboard, + keyboard, + mouse, + screen, + assert, + straightTo, + up, + down, + left, + right, + getWindows, + getActiveWindow, }; diff --git a/lib/adapter/native.adapter.class.spec.ts b/lib/adapter/native.adapter.class.spec.ts index ab0da5a4..247069d1 100644 --- a/lib/adapter/native.adapter.class.spec.ts +++ b/lib/adapter/native.adapter.class.spec.ts @@ -2,25 +2,42 @@ import { Button } from "../button.enum"; import { Key } from "../key.enum"; import { Point } from "../point.class"; import { NativeAdapter } from "./native.adapter.class"; -import ClipboardAction from "../provider/native/clipboardy-clipboard-action.class"; -import KeyboardAction from "../provider/native/libnut-keyboard-action.class"; -import MouseAction from "../provider/native/libnut-mouse-action.class"; -import WindowAction from "../provider/native/libnut-window-action.class"; +import ClipboardAction from "../provider/native/clipboardy-clipboard.class"; +import KeyboardAction from "../provider/native/libnut-keyboard.class"; +import MouseAction from "../provider/native/libnut-mouse.class"; +import WindowAction from "../provider/native/libnut-window.class"; +import providerRegistry from "../provider/provider-registry.class"; + +jest.mock("../provider/native/clipboardy-clipboard.class"); +jest.mock("../provider/native/libnut-mouse.class"); +jest.mock("../provider/native/libnut-keyboard.class"); +jest.mock("../provider/native/libnut-window.class"); + +let clipboardMock: ClipboardAction; +let keyboardMock: KeyboardAction; +let mouseMock: MouseAction; +let windowMock: WindowAction; + +beforeAll(() => { + clipboardMock = new ClipboardAction(); + keyboardMock = new KeyboardAction(); + mouseMock = new MouseAction(); + windowMock = new WindowAction(); + providerRegistry.registerClipboardProvider(clipboardMock); + providerRegistry.registerKeyboardProvider(keyboardMock); + providerRegistry.registerMouseProvider(mouseMock); + providerRegistry.registerWindowProvider(windowMock); +}); -jest.mock("../provider/native/clipboardy-clipboard-action.class"); -jest.mock("../provider/native/libnut-mouse-action.class"); -jest.mock("../provider/native/libnut-keyboard-action.class"); -jest.mock("../provider/native/libnut-window-action.class"); +beforeEach(() => { + jest.resetAllMocks(); +}); describe("NativeAdapter class", () => { describe("MouseAction", () => { it("should delegate calls to setMouseDelay", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const delay = 5; // WHEN @@ -33,11 +50,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to setMousePosition", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const newPosition = new Point(10, 10); // WHEN @@ -50,11 +63,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to currentMousePosition", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.currentMousePosition(); @@ -65,11 +74,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to leftClick", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.leftClick(); @@ -80,11 +85,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to rightClick", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.rightClick(); @@ -95,11 +96,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to middleClick", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.middleClick(); @@ -110,11 +107,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to pressButton", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const buttonToPress = Button.LEFT; // WHEN @@ -127,11 +120,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to releaseButton", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const buttonToRelease = Button.LEFT; // WHEN @@ -146,11 +135,7 @@ describe("NativeAdapter class", () => { describe("KeyboardAction", () => { it("should delegate calls to pressKey", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const keyToPress = Key.A; // WHEN @@ -163,11 +148,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to releaseButton", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const keyToRelease = Key.A; // WHEN @@ -180,11 +161,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to click", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const keyToClick = Key.A; // WHEN @@ -197,11 +174,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to type", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const stringToType = "testString"; // WHEN @@ -216,11 +189,7 @@ describe("NativeAdapter class", () => { describe("ClipboardAction", () => { it("should delegate calls to copy", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const stringToCopy = "testString"; // WHEN @@ -233,11 +202,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to paste", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.paste(); @@ -250,11 +215,7 @@ describe("NativeAdapter class", () => { describe("WindowAction", () => { it("should delegate calls to getActiveWindow", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.getActiveWindow(); @@ -265,11 +226,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to getWindows", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); // WHEN await SUT.getWindows(); @@ -280,11 +237,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to getWindowTitle", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const windowHandle = 123; // WHEN @@ -296,11 +249,7 @@ describe("NativeAdapter class", () => { it("should delegate calls to getWindowRegion", async () => { // GIVEN - const clipboardMock = new ClipboardAction(); - const keyboardMock = new KeyboardAction(); - const mouseMock = new MouseAction(); - const windowMock = new WindowAction(); - const SUT = new NativeAdapter(clipboardMock, keyboardMock, mouseMock, windowMock); + const SUT = new NativeAdapter(providerRegistry); const windowHandle = 123; // WHEN diff --git a/lib/adapter/native.adapter.class.ts b/lib/adapter/native.adapter.class.ts index 67122d97..264b39e4 100644 --- a/lib/adapter/native.adapter.class.ts +++ b/lib/adapter/native.adapter.class.ts @@ -1,15 +1,8 @@ -import { Button } from "../button.enum"; -import { Key } from "../key.enum"; -import { Point } from "../point.class"; -import { ClipboardActionProvider } from "../provider/native/clipboard-action-provider.interface"; -import { KeyboardActionProvider } from "../provider/native/keyboard-action-provider.interface"; -import { MouseActionProvider } from "../provider/native/mouse-action-provider.interface"; -import { Region } from "../region.class"; -import { WindowActionProvider } from "../provider/native/window-action-provider.interface"; -import ClipboardAction from "../provider/native/clipboardy-clipboard-action.class"; -import KeyboardAction from "../provider/native/libnut-keyboard-action.class"; -import MouseAction from "../provider/native/libnut-mouse-action.class"; -import WindowAction from "../provider/native/libnut-window-action.class"; +import {Button} from "../button.enum"; +import {Key} from "../key.enum"; +import {Point} from "../point.class"; +import {Region} from "../region.class"; +import {ProviderRegistry} from "../provider/provider-registry.class"; /** * {@link NativeAdapter} serves as an abstraction layer for all OS level interactions. @@ -19,213 +12,208 @@ import WindowAction from "../provider/native/libnut-window-action.class"; * All actions which involve the OS are bundled in this adapter. */ export class NativeAdapter { - /** - * {@link NativeAdapter} class constructor - * @param clipboard {@link ClipboardActionProvider} instance used to interact with a systems clipboard (Default: {@link ClipboardAction}) - * @param keyboard {@link KeyboardActionProvider} instance used to interact with a systems keybaord (Default: {@link KeyboardAction}) - * @param mouse {@link MouseActionProvider} instance used to interact with a systems mouse (Default: {@link MouseAction}) - * @param window {@link WindowActionProvider} instance used to interact with a systems windows (Default: {@link WindowAction}) - */ - constructor( - private clipboard: ClipboardActionProvider = new ClipboardAction(), - private keyboard: KeyboardActionProvider = new KeyboardAction(), - private mouse: MouseActionProvider = new MouseAction(), - private window: WindowActionProvider = new WindowAction(), - ) {} - - /** - * {@link setMouseDelay} configures mouse speed for movement - * - * @param delay Mouse delay in milliseconds - */ - public setMouseDelay(delay: number): void { - this.mouse.setMouseDelay(delay); - } - - /** - * {@link setKeyboardDelay} configures keyboard delay between key presses - * - * @param delay The keyboard delay in milliseconds - */ - public setKeyboardDelay(delay: number): void { - this.keyboard.setKeyboardDelay(delay); - } - - /** - * {@link setMousePosition} changes the current mouse cursor position to a given {@link Point} - * - * @param p The new cursor position at {@link Point} p - */ - public setMousePosition(p: Point): Promise { - return this.mouse.setMousePosition(p); - } - - /** - * {@link currentMousePosition} returns the current mouse position - * - * @returns Current cursor position at a certain {@link Point} - */ - public currentMousePosition(): Promise { - return this.mouse.currentMousePosition(); - } - - /** - * {@link leftClick} triggers a native left-click event via OS API - */ - public leftClick(): Promise { - return this.mouse.leftClick(); - } - - /** - * {@link rightClick} triggers a native right-click event via OS API - */ - public rightClick(): Promise { - return this.mouse.rightClick(); - } - - /** - * {@link middleClick} triggers a native middle-click event via OS API - */ - public middleClick(): Promise { - return this.mouse.middleClick(); - } - - /** - * {@link pressButton} presses and holds a mouse {@link Button} - * - * @param btn The mouse {@link Button} to press - */ - public pressButton(btn: Button): Promise { - return this.mouse.pressButton(btn); - } - - /** - * {@link releaseButton} releases a mouse {@link Button} previously clicked via {@link pressButton} - * - * @param btn The mouse {@link Button} to release - */ - public releaseButton(btn: Button): Promise { - return this.mouse.releaseButton(btn); - } - - /** - * {@link type} types a given string via native keyboard events - * - * @param input The text to type - */ - public type(input: string): Promise { - return this.keyboard.type(input); - } - - /** - * {@link click} clicks a {@link Key} via native keyboard event - * - * @param keys Array of {@link Key}s to click - */ - public click(...keys: Key[]): Promise { - return this.keyboard.click(...keys); - } - - /** - * {@link pressKey} presses and holds a given {@link Key} - * - * @param keys Array of {@link Key}s to press and hold - */ - public pressKey(...keys: Key[]): Promise { - return this.keyboard.pressKey(...keys); - } - - /** - * {@link releaseKey} releases a {@link Key} previously presses via {@link pressKey} - * - * @param keys Array of {@link Key}s to release - */ - public releaseKey(...keys: Key[]): Promise { - return this.keyboard.releaseKey(...keys); - } - - /** - * {@link scrollUp} triggers an upwards mouse wheel scroll - * - * @param amount The amount of 'ticks' to scroll - */ - public scrollUp(amount: number): Promise { - return this.mouse.scrollUp(amount); - } - - /** - * {@link scrollDown} triggers a downward mouse wheel scroll - * - * @param amount The amount of 'ticks' to scroll - */ - public scrollDown(amount: number): Promise { - return this.mouse.scrollDown(amount); - } - - /** - * {@link scrollLeft} triggers a left mouse scroll - * - * @param amount The amount of 'ticks' to scroll - */ - public scrollLeft(amount: number): Promise { - return this.mouse.scrollLeft(amount); - } - - /** - * {@link scrollRight} triggers a right mouse scroll - * - * @param amount The amount of 'ticks' to scroll - */ - public scrollRight(amount: number): Promise { - return this.mouse.scrollRight(amount); - } - - /** - * {@link copy} copies a given text to the system clipboard - * - * @param text The text to copy - */ - public copy(text: string): Promise { - return this.clipboard.copy(text); - } - - /** - * {@link paste} pastes the current text on the system clipboard - * - * @returns The clipboard text - */ - public paste(): Promise { - return this.clipboard.paste(); - } - - public getWindows(): Promise { - return this.window.getWindows(); - } - - /** - * {@link getActiveWindow} returns the window handle of the currently active foreground window - * - * @returns The handle to the currently active foreground window - */ - public getActiveWindow(): Promise { - return this.window.getActiveWindow(); - } - - /** - * {@link getWindowTitle} returns the title of a window addressed via its window handle - * - * @returns A string representing the title of a window addressed via its window handle - */ - public getWindowTitle(windowHandle: number): Promise { - return this.window.getWindowTitle(windowHandle); - } - - /** - * {@link getWindowRegion} returns a {@link Region} object representing the size and position of the window addressed via its window handle - * - * @returns The {@link Region} occupied by the window addressed via its window handle - */ - public getWindowRegion(windowHandle: number): Promise { - return this.window.getWindowRegion(windowHandle); - } + /** + * {@link NativeAdapter} class constructor + * @param registry: {@link ProviderRegistry} to access providers + */ + constructor( + private registry: ProviderRegistry + ) { + } + + /** + * {@link setMouseDelay} configures mouse speed for movement + * + * @param delay Mouse delay in milliseconds + */ + public setMouseDelay(delay: number): void { + this.registry.getMouse().setMouseDelay(delay); + } + + /** + * {@link setKeyboardDelay} configures keyboard delay between key presses + * + * @param delay The keyboard delay in milliseconds + */ + public setKeyboardDelay(delay: number): void { + this.registry.getKeyboard().setKeyboardDelay(delay); + } + + /** + * {@link setMousePosition} changes the current mouse cursor position to a given {@link Point} + * + * @param p The new cursor position at {@link Point} p + */ + public setMousePosition(p: Point): Promise { + return this.registry.getMouse().setMousePosition(p); + } + + /** + * {@link currentMousePosition} returns the current mouse position + * + * @returns Current cursor position at a certain {@link Point} + */ + public currentMousePosition(): Promise { + return this.registry.getMouse().currentMousePosition(); + } + + /** + * {@link leftClick} triggers a native left-click event via OS API + */ + public leftClick(): Promise { + return this.registry.getMouse().leftClick(); + } + + /** + * {@link rightClick} triggers a native right-click event via OS API + */ + public rightClick(): Promise { + return this.registry.getMouse().rightClick(); + } + + /** + * {@link middleClick} triggers a native middle-click event via OS API + */ + public middleClick(): Promise { + return this.registry.getMouse().middleClick(); + } + + /** + * {@link pressButton} presses and holds a mouse {@link Button} + * + * @param btn The mouse {@link Button} to press + */ + public pressButton(btn: Button): Promise { + return this.registry.getMouse().pressButton(btn); + } + + /** + * {@link releaseButton} releases a mouse {@link Button} previously clicked via {@link pressButton} + * + * @param btn The mouse {@link Button} to release + */ + public releaseButton(btn: Button): Promise { + return this.registry.getMouse().releaseButton(btn); + } + + /** + * {@link type} types a given string via native keyboard events + * + * @param input The text to type + */ + public type(input: string): Promise { + return this.registry.getKeyboard().type(input); + } + + /** + * {@link click} clicks a {@link Key} via native keyboard event + * + * @param keys Array of {@link Key}s to click + */ + public click(...keys: Key[]): Promise { + return this.registry.getKeyboard().click(...keys); + } + + /** + * {@link pressKey} presses and holds a given {@link Key} + * + * @param keys Array of {@link Key}s to press and hold + */ + public pressKey(...keys: Key[]): Promise { + return this.registry.getKeyboard().pressKey(...keys); + } + + /** + * {@link releaseKey} releases a {@link Key} previously presses via {@link pressKey} + * + * @param keys Array of {@link Key}s to release + */ + public releaseKey(...keys: Key[]): Promise { + return this.registry.getKeyboard().releaseKey(...keys); + } + + /** + * {@link scrollUp} triggers an upwards mouse wheel scroll + * + * @param amount The amount of 'ticks' to scroll + */ + public scrollUp(amount: number): Promise { + return this.registry.getMouse().scrollUp(amount); + } + + /** + * {@link scrollDown} triggers a downward mouse wheel scroll + * + * @param amount The amount of 'ticks' to scroll + */ + public scrollDown(amount: number): Promise { + return this.registry.getMouse().scrollDown(amount); + } + + /** + * {@link scrollLeft} triggers a left mouse scroll + * + * @param amount The amount of 'ticks' to scroll + */ + public scrollLeft(amount: number): Promise { + return this.registry.getMouse().scrollLeft(amount); + } + + /** + * {@link scrollRight} triggers a right mouse scroll + * + * @param amount The amount of 'ticks' to scroll + */ + public scrollRight(amount: number): Promise { + return this.registry.getMouse().scrollRight(amount); + } + + /** + * {@link copy} copies a given text to the system clipboard + * + * @param text The text to copy + */ + public copy(text: string): Promise { + return this.registry.getClipboard().copy(text); + } + + /** + * {@link paste} pastes the current text on the system clipboard + * + * @returns The clipboard text + */ + public paste(): Promise { + return this.registry.getClipboard().paste(); + } + + public getWindows(): Promise { + return this.registry.getWindow().getWindows(); + } + + /** + * {@link getActiveWindow} returns the window handle of the currently active foreground window + * + * @returns The handle to the currently active foreground window + */ + public getActiveWindow(): Promise { + return this.registry.getWindow().getActiveWindow(); + } + + /** + * {@link getWindowTitle} returns the title of a window addressed via its window handle + * + * @returns A string representing the title of a window addressed via its window handle + */ + public getWindowTitle(windowHandle: number): Promise { + return this.registry.getWindow().getWindowTitle(windowHandle); + } + + /** + * {@link getWindowRegion} returns a {@link Region} object representing the size and position of the window addressed via its window handle + * + * @returns The {@link Region} occupied by the window addressed via its window handle + */ + public getWindowRegion(windowHandle: number): Promise { + return this.registry.getWindow().getWindowRegion(windowHandle); + } } diff --git a/lib/adapter/vision.adapter.class.spec.ts b/lib/adapter/vision.adapter.class.spec.ts index df79ce7e..4149e4cc 100644 --- a/lib/adapter/vision.adapter.class.spec.ts +++ b/lib/adapter/vision.adapter.class.spec.ts @@ -1,19 +1,22 @@ import { Image } from "../image.class"; import { MatchRequest } from "../match-request.class"; -import ScreenAction from "../provider/native/libnut-screen-action.class"; +import ScreenAction from "../provider/native/libnut-screen.class"; import TemplateMatchingFinder from "../provider/opencv/template-matching-finder.class"; import { Region } from "../region.class"; import { VisionAdapter } from "./vision.adapter.class"; +import providerRegistry from "../provider/provider-registry.class"; jest.mock("../provider/opencv/template-matching-finder.class"); -jest.mock("../provider/native/libnut-screen-action.class"); +jest.mock("../provider/native/libnut-screen.class"); describe("VisionAdapter class", () => { it("should delegate calls to grabScreen", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); // WHEN SUT.grabScreen(); @@ -26,7 +29,9 @@ describe("VisionAdapter class", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); const screenRegion = new Region(0, 0, 100, 100); // WHEN @@ -41,7 +46,9 @@ describe("VisionAdapter class", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); const screenRegion = new Region(0, 0, 100, 100); const opacity = 0.25; const duration = 1; @@ -58,7 +65,9 @@ describe("VisionAdapter class", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); // WHEN await SUT.screenWidth(); @@ -71,7 +80,9 @@ describe("VisionAdapter class", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); // WHEN await SUT.screenHeight(); @@ -84,7 +95,9 @@ describe("VisionAdapter class", () => { // GIVEN const finderMock = new TemplateMatchingFinder(); const screenMock = new ScreenAction(); - const SUT = new VisionAdapter(finderMock, screenMock); + providerRegistry.registerImageFinder(finderMock); + providerRegistry.registerScreenProvider(screenMock); + const SUT = new VisionAdapter(providerRegistry); // WHEN await SUT.screenSize(); @@ -96,7 +109,8 @@ describe("VisionAdapter class", () => { it("should delegate calls to findImage", async () => { // GIVEN const finderMock = new TemplateMatchingFinder(); - const SUT = new VisionAdapter(finderMock); + providerRegistry.registerImageFinder(finderMock); + const SUT = new VisionAdapter(providerRegistry); const request = new MatchRequest( new Image(100, 100, new ArrayBuffer(0), 3), "foo", diff --git a/lib/adapter/vision.adapter.class.ts b/lib/adapter/vision.adapter.class.ts index ad5f51ae..ac8445ca 100644 --- a/lib/adapter/vision.adapter.class.ts +++ b/lib/adapter/vision.adapter.class.ts @@ -1,13 +1,8 @@ -import { Image } from "../image.class"; -import { MatchRequest } from "../match-request.class"; -import { MatchResult } from "../match-result.class"; -import ScreenAction from "../provider/native/libnut-screen-action.class"; -import { ScreenActionProvider } from "../provider/native/screen-action-provider.interface"; -import { DataSink } from "../provider/opencv/data-sink.interface"; -import { FinderInterface } from "../provider/opencv/finder.interface"; -import { ImageWriter } from "../provider/opencv/image-writer.class"; -import TemplateMatchingFinder from "../provider/opencv/template-matching-finder.class"; -import { Region } from "../region.class"; +import {Image} from "../image.class"; +import {MatchRequest} from "../match-request.class"; +import {MatchResult} from "../match-result.class"; +import {Region} from "../region.class"; +import {ProviderRegistry} from "../provider/provider-registry.class"; /** * {@link VisionAdapter} serves as an abstraction layer for all image based interactions. @@ -17,110 +12,106 @@ import { Region } from "../region.class"; * All actions which involve screenshots / images are bundled in this adapter. */ export class VisionAdapter { - /** - * {@link VisionAdapter} class constructor - * @param finder A {@link FinderInterface} instance used for on-screen image detection (Default: {@link TemplateMatchingFinder}) - * @param screen A {@link ScreenActionProvider} instance used to retrieve screen data (Default: {@link ScreenAction}) - * @param dataSink A {@link DataSink} instance used to write output data to disk (Default: {@link ImageWriter}) - */ - constructor( - private finder: FinderInterface = new TemplateMatchingFinder(), - private screen: ScreenActionProvider = new ScreenAction(), - private dataSink: DataSink = new ImageWriter() - ) { - } + /** + * {@link VisionAdapter} class constructor + * @param registry: {@link ProviderRegistry} to access providers + */ + constructor( + private registry: ProviderRegistry + ) { + } - /** - * {@link grabScreen} will return an {@link Image} containing the current screen image - * - * @returns An {@link Image} which will contain screenshot data as well as dimensions - */ - public grabScreen(): Promise { - return this.screen.grabScreen(); - } + /** + * {@link grabScreen} will return an {@link Image} containing the current screen image + * + * @returns An {@link Image} which will contain screenshot data as well as dimensions + */ + public grabScreen(): Promise { + return this.registry.getScreen().grabScreen(); + } - /** - * {@link grabScreenRegion} essentially does the same as grabScreen, but only returns a specified {@link Region} - * - * @param region The screen {@link Region} we want to grab - * @returns An {@link Image} which will contain screenshot data of the specified {@link Region} as well as dimensions - */ - public grabScreenRegion(region: Region): Promise { - return this.screen.grabScreenRegion(region); - } + /** + * {@link grabScreenRegion} essentially does the same as grabScreen, but only returns a specified {@link Region} + * + * @param region The screen {@link Region} we want to grab + * @returns An {@link Image} which will contain screenshot data of the specified {@link Region} as well as dimensions + */ + public grabScreenRegion(region: Region): Promise { + return this.registry.getScreen().grabScreenRegion(region); + } - /** - * {@link highlightScreenRegion} highlights a screen {@link Region} for a given duration by overlaying it with an opaque window - * - * @param region The {@link Region} to highlight - * @param duration The highlight duration - * @param opacity Overlay opacity - */ - public highlightScreenRegion(region: Region, duration: number, opacity: number): Promise { - return this.screen.highlightScreenRegion(region, duration, opacity); - } + /** + * {@link highlightScreenRegion} highlights a screen {@link Region} for a given duration by overlaying it with an opaque window + * + * @param region The {@link Region} to highlight + * @param duration The highlight duration + * @param opacity Overlay opacity + */ + public highlightScreenRegion(region: Region, duration: number, opacity: number): Promise { + return this.registry.getScreen().highlightScreenRegion(region, duration, opacity); + } - /** - * {@link findOnScreenRegion} will search for a given pattern inside a {@link Region} of the main screen - * If multiple possible occurrences are found, the one with the highest probability is returned. - * For matchProbability < 0.99 the search will be performed on grayscale images. - * - * @param matchRequest A {@link MatchRequest} which holds all required matching data - * @returns {@link MatchResult} containing location and probability of a possible match - */ - public async findOnScreenRegion( - matchRequest: MatchRequest, - ): Promise { - return new Promise(async (resolve, reject) => { - try { - const matchResult = await this.finder.findMatch(matchRequest); - resolve(matchResult); - } catch (e) { - reject(e); - } - }); - } + /** + * {@link findOnScreenRegion} will search for a given pattern inside a {@link Region} of the main screen + * If multiple possible occurrences are found, the one with the highest probability is returned. + * For matchProbability < 0.99 the search will be performed on grayscale images. + * + * @param matchRequest A {@link MatchRequest} which holds all required matching data + * @returns {@link MatchResult} containing location and probability of a possible match + */ + public async findOnScreenRegion( + matchRequest: MatchRequest, + ): Promise { + return new Promise(async (resolve, reject) => { + try { + const matchResult = await this.registry.getImageFinder().findMatch(matchRequest); + resolve(matchResult); + } catch (e) { + reject(e); + } + }); + } - /** - * {@link screenWidth} returns the main screens width as reported by the OS. - * Please notice that on e.g. Apples Retina display the reported width - * and the actual pixel size may differ - * - * @returns The main screens width as reported by the OS - */ - public screenWidth(): Promise { - return this.screen.screenWidth(); - } + /** + * {@link screenWidth} returns the main screens width as reported by the OS. + * Please notice that on e.g. Apples Retina display the reported width + * and the actual pixel size may differ + * + * @returns The main screens width as reported by the OS + */ + public screenWidth(): Promise { + return this.registry.getScreen().screenWidth(); + } - /** - * {@link screenHeight} returns the main screens height as reported by the OS. - * Please notice that on e.g. Apples Retina display the reported width - * and the actual pixel size may differ - * - * @returns The main screens height as reported by the OS - */ - public screenHeight(): Promise { - return this.screen.screenHeight(); - } + /** + * {@link screenHeight} returns the main screens height as reported by the OS. + * Please notice that on e.g. Apples Retina display the reported width + * and the actual pixel size may differ + * + * @returns The main screens height as reported by the OS + */ + public screenHeight(): Promise { + return this.registry.getScreen().screenHeight(); + } - /** - * {@link screenSize} returns a {@link Region} object with the main screens size. - * Please note that on e.g. Apples Retina display the reported width - * and the actual pixel size may differ - * - * @returns A {@link Region} object representing the size of a systems main screen - */ - public screenSize(): Promise { - return this.screen.screenSize(); - } + /** + * {@link screenSize} returns a {@link Region} object with the main screens size. + * Please note that on e.g. Apples Retina display the reported width + * and the actual pixel size may differ + * + * @returns A {@link Region} object representing the size of a systems main screen + */ + public screenSize(): Promise { + return this.registry.getScreen().screenSize(); + } - /** - * {@link saveImage} saves an {@link Image} to a given path on disk. - * - * @param image The {@link Image} to store - * @param path The path where to store the image - */ - public saveImage(image: Image, path: string): Promise { - return (this.dataSink as ImageWriter).store(image, path); - } + /** + * {@link saveImage} saves an {@link Image} to a given path on disk. + * + * @param image The {@link Image} to store + * @param path The path where to store the image + */ + public saveImage(image: Image, path: string): Promise { + return this.registry.getImageWriter().store({data: image, path}); + } } diff --git a/lib/assert.class.spec.ts b/lib/assert.class.spec.ts index 87b61a9e..0b50a872 100644 --- a/lib/assert.class.spec.ts +++ b/lib/assert.class.spec.ts @@ -2,6 +2,7 @@ import { VisionAdapter } from "./adapter/vision.adapter.class"; import { AssertClass } from "./assert.class"; import { Region } from "./region.class"; import { ScreenClass } from "./screen.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); jest.mock("./adapter/vision.adapter.class"); @@ -11,7 +12,7 @@ describe("Assert", () => { it("isVisible should not throw if a match is found.", async () => { // GIVEN ScreenClass.prototype.find = jest.fn(() => Promise.resolve(new Region(0, 0, 100, 100))); - const screenMock = new ScreenClass(new VisionAdapter()); + const screenMock = new ScreenClass(new VisionAdapter(providerRegistry)); const SUT = new AssertClass(screenMock); const needle = "foo"; @@ -24,7 +25,7 @@ describe("Assert", () => { it("isVisible should throw if a match is found.", async () => { // GIVEN ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo")); - const screenMock = new ScreenClass(new VisionAdapter()); + const screenMock = new ScreenClass(new VisionAdapter(providerRegistry)); const SUT = new AssertClass(screenMock); const needle = "foo"; @@ -37,7 +38,7 @@ describe("Assert", () => { it("isVisible should throw if a match is found.", async () => { // GIVEN ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo")); - const screenMock = new ScreenClass(new VisionAdapter()); + const screenMock = new ScreenClass(new VisionAdapter(providerRegistry)); const SUT = new AssertClass(screenMock); const searchRegion = new Region(10, 10, 10, 10); const needle = "foo"; @@ -54,7 +55,7 @@ describe("Assert", () => { it("isNotVisible should throw if a match is found.", async () => { // GIVEN ScreenClass.prototype.find = jest.fn(() => Promise.resolve(new Region(0, 0, 100, 100))); - const screenMock = new ScreenClass(new VisionAdapter()); + const screenMock = new ScreenClass(new VisionAdapter(providerRegistry)); const SUT = new AssertClass(screenMock); const needle = "foo"; @@ -67,7 +68,7 @@ describe("Assert", () => { it("isVisible should throw if a match is found.", async () => { // GIVEN ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo")); - const screenMock = new ScreenClass(new VisionAdapter()); + const screenMock = new ScreenClass(new VisionAdapter(providerRegistry)); const SUT = new AssertClass(screenMock); const needle = "foo"; diff --git a/lib/clipboard.class.e2e.spec.ts b/lib/clipboard.class.e2e.spec.ts index fe303f95..c7c36f9c 100644 --- a/lib/clipboard.class.e2e.spec.ts +++ b/lib/clipboard.class.e2e.spec.ts @@ -1,9 +1,10 @@ import {NativeAdapter} from "./adapter/native.adapter.class"; import {ClipboardClass} from "./clipboard.class"; +import providerRegistry from "./provider/provider-registry.class"; describe("Clipboard class", () => { it("should paste copied input from system clipboard.", async () => { - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new ClipboardClass(adapterMock); const textToCopy = "bar"; diff --git a/lib/clipboard.class.spec.ts b/lib/clipboard.class.spec.ts index 4f894b40..3dc38a68 100644 --- a/lib/clipboard.class.spec.ts +++ b/lib/clipboard.class.spec.ts @@ -1,5 +1,6 @@ import {NativeAdapter} from "./adapter/native.adapter.class"; import {ClipboardClass} from "./clipboard.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); @@ -9,7 +10,7 @@ beforeEach(() => { describe("Clipboard class", () => { it("should call the native adapters copy method.", () => { - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new ClipboardClass(adapterMock); const textToCopy = "bar"; @@ -20,7 +21,7 @@ describe("Clipboard class", () => { }); it("should call the native adapters paste method.", () => { - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new ClipboardClass(adapterMock); SUT.paste(); diff --git a/lib/keyboard.class.spec.ts b/lib/keyboard.class.spec.ts index 86d4058c..ad6edcbc 100644 --- a/lib/keyboard.class.spec.ts +++ b/lib/keyboard.class.spec.ts @@ -1,6 +1,7 @@ import { NativeAdapter } from "./adapter/native.adapter.class"; import { Key } from "./key.enum"; import { KeyboardClass } from "./keyboard.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); jest.setTimeout(10000); @@ -12,7 +13,7 @@ beforeEach(() => { describe("Keyboard", () => { it("should have a default delay of 300 ms", () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); // WHEN @@ -23,7 +24,7 @@ describe("Keyboard", () => { it("should pass input strings down to the type call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = "Test input!"; @@ -39,7 +40,7 @@ describe("Keyboard", () => { it("should pass multiple input strings down to the type call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = ["Test input!", "Array test2"]; @@ -55,7 +56,7 @@ describe("Keyboard", () => { it("should pass input keys down to the click call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = [Key.A, Key.S, Key.D, Key.F]; @@ -69,7 +70,7 @@ describe("Keyboard", () => { it("should pass a list of input keys down to the click call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = [Key.A, Key.S, Key.D, Key.F]; @@ -84,7 +85,7 @@ describe("Keyboard", () => { it("should pass a list of input keys down to the pressKey call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = [Key.A, Key.S, Key.D, Key.F]; @@ -99,7 +100,7 @@ describe("Keyboard", () => { it("should pass a list of input keys down to the releaseKey call.", async () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new KeyboardClass(adapterMock); const payload = [Key.A, Key.S, Key.D, Key.F]; diff --git a/lib/mouse.class.spec.ts b/lib/mouse.class.spec.ts index 258d18f5..949889ac 100644 --- a/lib/mouse.class.spec.ts +++ b/lib/mouse.class.spec.ts @@ -3,6 +3,7 @@ import { Button } from "./button.enum"; import { MouseClass } from "./mouse.class"; import { Point } from "./point.class"; import { LineHelper } from "./util/linehelper.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); @@ -15,7 +16,7 @@ const linehelper = new LineHelper(); describe("Mouse class", () => { it("should have a default delay of 500 ms", () => { // GIVEN - const adapterMock = new NativeAdapter(); + const adapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(adapterMock); // WHEN @@ -26,7 +27,7 @@ describe("Mouse class", () => { it("should forward scrollLeft to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const scrollAmount = 5; @@ -40,7 +41,7 @@ describe("Mouse class", () => { it("should forward scrollRight to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const scrollAmount = 5; @@ -54,7 +55,7 @@ describe("Mouse class", () => { it("should forward scrollDown to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const scrollAmount = 5; @@ -68,7 +69,7 @@ describe("Mouse class", () => { it("should forward scrollUp to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const scrollAmount = 5; @@ -82,7 +83,7 @@ describe("Mouse class", () => { it("should forward leftClick to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); // WHEN @@ -95,7 +96,7 @@ describe("Mouse class", () => { it("should forward rightClick to the native adapter class", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); // WHEN @@ -108,7 +109,7 @@ describe("Mouse class", () => { it("update mouse position along path on move", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10)); @@ -122,7 +123,7 @@ describe("Mouse class", () => { it("should press and hold left mouse button, move and release left mouse button on drag", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10)); @@ -143,7 +144,7 @@ describe("Mousebuttons", () => { [Button.MIDDLE, Button.MIDDLE], [Button.RIGHT, Button.RIGHT], ] as Array<[Button, Button]>)("should be pressed and released", async (input: Button, expected: Button) => { - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const SUT = new MouseClass(nativeAdapterMock); const pressed = await SUT.pressButton(input); const released = await SUT.releaseButton(input); diff --git a/lib/provider/native/clipboard-action-provider.interface.ts b/lib/provider/clipboard-provider.interface.ts similarity index 94% rename from lib/provider/native/clipboard-action-provider.interface.ts rename to lib/provider/clipboard-provider.interface.ts index f451b669..8f7d75a9 100644 --- a/lib/provider/native/clipboard-action-provider.interface.ts +++ b/lib/provider/clipboard-provider.interface.ts @@ -1,7 +1,7 @@ /** * A ClipboardActionProvider should allow access to the system clipboard */ -export interface ClipboardActionProvider { +export interface ClipboardProvider { /** * hasText should return whether the system clipboard currently holds text or not * diff --git a/lib/provider/data-sink.interface.ts b/lib/provider/data-sink.interface.ts new file mode 100644 index 00000000..71080b4c --- /dev/null +++ b/lib/provider/data-sink.interface.ts @@ -0,0 +1,12 @@ +/** + * A DataSink should provide methods to store data + * + * @interface DataSink + */ +export interface DataSink { + /** + * store will store data to disk + * @param parameters Required parameters + */ + store(parameters: PARAMETER_TYPE): Promise; +} diff --git a/lib/provider/data-source.interface.ts b/lib/provider/data-source.interface.ts new file mode 100644 index 00000000..24499d4a --- /dev/null +++ b/lib/provider/data-source.interface.ts @@ -0,0 +1,12 @@ +/** + * A DataSource should provide methods to load data + * + * @interface DataSource + */ +export interface DataSource { + /** + * load will load data from disk + * @param parameters Required parameters + */ + load(parameters: PARAMETER_TYPE): Promise; +} diff --git a/lib/provider/opencv/finder.interface.ts b/lib/provider/image-finder.interface.ts similarity index 77% rename from lib/provider/opencv/finder.interface.ts rename to lib/provider/image-finder.interface.ts index 83da0d00..6ddaca28 100644 --- a/lib/provider/opencv/finder.interface.ts +++ b/lib/provider/image-finder.interface.ts @@ -1,20 +1,20 @@ -import { MatchRequest } from "../../match-request.class"; -import { MatchResult } from "../../match-result.class"; +import { MatchRequest } from "../match-request.class"; +import { MatchResult } from "../match-result.class"; /** * A Finder should provide an abstraction layer to perform * image processing and matching via a 3rd part library * - * @interface FinderInterface + * @interface ImageFinderInterface */ -export interface FinderInterface { +export interface ImageFinderInterface { /** * findMatch should provide an abstraction to search for an image needle * in another image haystack * * @param {MatchRequest} matchRequest A matchrequest containing needed matching data * @returns {Promise} A matchresult containing the match probability and location - * @memberof FinderInterface + * @memberof ImageFinderInterface */ findMatch(matchRequest: MatchRequest): Promise; @@ -24,7 +24,7 @@ export interface FinderInterface { * * @param {MatchRequest} matchRequest A matchrequest containing needed matching data * @returns {Promise} A list of matchresults containing the match probability and location - * @memberof FinderInterface + * @memberof ImageFinderInterface */ findMatches(matchRequest: MatchRequest): Promise; } diff --git a/lib/provider/image-reader.type.ts b/lib/provider/image-reader.type.ts new file mode 100644 index 00000000..202c9b13 --- /dev/null +++ b/lib/provider/image-reader.type.ts @@ -0,0 +1,4 @@ +import {DataSource} from "./data-source.interface"; +import {Image} from "../image.class"; + +export type ImageReader = DataSource; diff --git a/lib/provider/image-writer.type.ts b/lib/provider/image-writer.type.ts new file mode 100644 index 00000000..4c4f5662 --- /dev/null +++ b/lib/provider/image-writer.type.ts @@ -0,0 +1,9 @@ +import {Image} from "../image.class"; +import {DataSink} from "./data-sink.interface"; + +export interface ImageWriterParameters { + data: Image, + path: string +} + +export type ImageWriter = DataSink; diff --git a/lib/provider/native/keyboard-action-provider.interface.ts b/lib/provider/keyboard-provider.interface.ts similarity index 92% rename from lib/provider/native/keyboard-action-provider.interface.ts rename to lib/provider/keyboard-provider.interface.ts index 6cedae6f..9e9c5b44 100644 --- a/lib/provider/native/keyboard-action-provider.interface.ts +++ b/lib/provider/keyboard-provider.interface.ts @@ -1,9 +1,9 @@ -import { Key } from "../../key.enum"; +import { Key } from "../key.enum"; /** * A KeyboardActionProvider should provide access to a systems keyboard */ -export interface KeyboardActionProvider { +export interface KeyboardProvider { /** * setKeyboardDelay should allow to configure a delay between key presses * diff --git a/lib/provider/native/mouse-action-provider.interface.ts b/lib/provider/mouse-provider.interface.ts similarity index 93% rename from lib/provider/native/mouse-action-provider.interface.ts rename to lib/provider/mouse-provider.interface.ts index 7240dbce..622b24b1 100644 --- a/lib/provider/native/mouse-action-provider.interface.ts +++ b/lib/provider/mouse-provider.interface.ts @@ -1,10 +1,10 @@ -import { Button } from "../../button.enum"; -import { Point } from "../../point.class"; +import { Button } from "../button.enum"; +import { Point } from "../point.class"; /** * A MouseActionProvider should provide access to a systems mouse input */ -export interface MouseActionProvider { +export interface MouseProvider { /** * setMouseDelay should allow to configure mouse movement speed * diff --git a/lib/provider/native/clipboardy-clipboard-action.class.spec.ts b/lib/provider/native/clipboardy-clipboard.class.spec.ts similarity index 90% rename from lib/provider/native/clipboardy-clipboard-action.class.spec.ts rename to lib/provider/native/clipboardy-clipboard.class.spec.ts index 415c9b1e..e25ebb36 100644 --- a/lib/provider/native/clipboardy-clipboard-action.class.spec.ts +++ b/lib/provider/native/clipboardy-clipboard.class.spec.ts @@ -1,4 +1,4 @@ -import ClipboardAction from "./clipboardy-clipboard-action.class"; +import ClipboardAction from "./clipboardy-clipboard.class"; beforeEach(() => { jest.resetAllMocks(); diff --git a/lib/provider/native/clipboardy-clipboard-action.class.ts b/lib/provider/native/clipboardy-clipboard.class.ts similarity index 83% rename from lib/provider/native/clipboardy-clipboard-action.class.ts rename to lib/provider/native/clipboardy-clipboard.class.ts index 03ee2f18..06a65c3a 100644 --- a/lib/provider/native/clipboardy-clipboard-action.class.ts +++ b/lib/provider/native/clipboardy-clipboard.class.ts @@ -1,7 +1,7 @@ import clippy from "clipboardy"; -import { ClipboardActionProvider } from "./clipboard-action-provider.interface"; +import { ClipboardProvider } from "../clipboard-provider.interface"; -export default class implements ClipboardActionProvider { +export default class implements ClipboardProvider { constructor() { } diff --git a/lib/provider/native/libnut-keyboard.action.class.spec.ts b/lib/provider/native/libnut-keyboard.class.spec.ts similarity index 98% rename from lib/provider/native/libnut-keyboard.action.class.spec.ts rename to lib/provider/native/libnut-keyboard.class.spec.ts index 57058ac8..d1cedfcf 100644 --- a/lib/provider/native/libnut-keyboard.action.class.spec.ts +++ b/lib/provider/native/libnut-keyboard.class.spec.ts @@ -1,6 +1,6 @@ import libnut = require("@nut-tree/libnut"); import { Key } from "../../key.enum"; -import KeyboardAction from "./libnut-keyboard-action.class"; +import KeyboardAction from "./libnut-keyboard.class"; jest.mock("@nut-tree/libnut"); diff --git a/lib/provider/native/libnut-keyboard-action.class.ts b/lib/provider/native/libnut-keyboard.class.ts similarity index 96% rename from lib/provider/native/libnut-keyboard-action.class.ts rename to lib/provider/native/libnut-keyboard.class.ts index b13c1fd2..1ce053f3 100644 --- a/lib/provider/native/libnut-keyboard-action.class.ts +++ b/lib/provider/native/libnut-keyboard.class.ts @@ -1,8 +1,8 @@ import libnut = require("@nut-tree/libnut"); import { Key } from "../../key.enum"; -import { KeyboardActionProvider } from "./keyboard-action-provider.interface"; +import { KeyboardProvider } from "../keyboard-provider.interface"; -export default class KeyboardAction implements KeyboardActionProvider { +export default class KeyboardAction implements KeyboardProvider { public static KeyLookupMap = new Map([ [Key.A, "a"], diff --git a/lib/provider/native/libnut-mouse-action.class.spec.ts b/lib/provider/native/libnut-mouse.class.spec.ts similarity index 99% rename from lib/provider/native/libnut-mouse-action.class.spec.ts rename to lib/provider/native/libnut-mouse.class.spec.ts index 4ceaa0e3..cd74c31e 100644 --- a/lib/provider/native/libnut-mouse-action.class.spec.ts +++ b/lib/provider/native/libnut-mouse.class.spec.ts @@ -1,7 +1,7 @@ import libnut = require("@nut-tree/libnut"); import { Button } from "../../button.enum"; import { Point } from "../../point.class"; -import MouseAction from "./libnut-mouse-action.class"; +import MouseAction from "./libnut-mouse.class"; jest.mock("@nut-tree/libnut"); diff --git a/lib/provider/native/libnut-mouse-action.class.ts b/lib/provider/native/libnut-mouse.class.ts similarity index 95% rename from lib/provider/native/libnut-mouse-action.class.ts rename to lib/provider/native/libnut-mouse.class.ts index 81d05cb9..35166cad 100644 --- a/lib/provider/native/libnut-mouse-action.class.ts +++ b/lib/provider/native/libnut-mouse.class.ts @@ -1,9 +1,9 @@ import libnut = require("@nut-tree/libnut"); import { Button } from "../../button.enum"; import { Point } from "../../point.class"; -import { MouseActionProvider } from "./mouse-action-provider.interface"; +import { MouseProvider } from "../mouse-provider.interface"; -export default class MouseAction implements MouseActionProvider { +export default class MouseAction implements MouseProvider { public static buttonLookup(btn: Button): any { return this.ButtonLookupMap.get(btn); } diff --git a/lib/provider/native/libnut-screen-action.class.spec.ts b/lib/provider/native/libnut-screen.class.spec.ts similarity index 99% rename from lib/provider/native/libnut-screen-action.class.spec.ts rename to lib/provider/native/libnut-screen.class.spec.ts index a29df6a8..0e2ca231 100644 --- a/lib/provider/native/libnut-screen-action.class.spec.ts +++ b/lib/provider/native/libnut-screen.class.spec.ts @@ -1,6 +1,6 @@ import libnut = require("@nut-tree/libnut"); import { Region } from "../../region.class"; -import ScreenAction from "./libnut-screen-action.class"; +import ScreenAction from "./libnut-screen.class"; jest.mock("@nut-tree/libnut"); diff --git a/lib/provider/native/libnut-screen-action.class.ts b/lib/provider/native/libnut-screen.class.ts similarity index 95% rename from lib/provider/native/libnut-screen-action.class.ts rename to lib/provider/native/libnut-screen.class.ts index a21b5359..9b2ab341 100644 --- a/lib/provider/native/libnut-screen-action.class.ts +++ b/lib/provider/native/libnut-screen.class.ts @@ -1,9 +1,9 @@ import libnut = require("@nut-tree/libnut"); import { Image } from "../../image.class"; import { Region } from "../../region.class"; -import { ScreenActionProvider } from "./screen-action-provider.interface"; +import { ScreenProvider } from "../screen-provider.interface"; -export default class ScreenAction implements ScreenActionProvider { +export default class ScreenAction implements ScreenProvider { private static determinePixelDensity( screen: Region, diff --git a/lib/provider/native/libnut-window-action.class.spec.ts b/lib/provider/native/libnut-window.class.spec.ts similarity index 98% rename from lib/provider/native/libnut-window-action.class.spec.ts rename to lib/provider/native/libnut-window.class.spec.ts index 93d4115b..a89a9deb 100644 --- a/lib/provider/native/libnut-window-action.class.spec.ts +++ b/lib/provider/native/libnut-window.class.spec.ts @@ -1,5 +1,5 @@ import libnut = require("@nut-tree/libnut"); -import WindowAction from "./libnut-window-action.class"; +import WindowAction from "./libnut-window.class"; import {Region} from "../../region.class"; jest.mock("@nut-tree/libnut"); diff --git a/lib/provider/native/libnut-window-action.class.ts b/lib/provider/native/libnut-window.class.ts similarity index 88% rename from lib/provider/native/libnut-window-action.class.ts rename to lib/provider/native/libnut-window.class.ts index 1352f682..1c69d497 100644 --- a/lib/provider/native/libnut-window-action.class.ts +++ b/lib/provider/native/libnut-window.class.ts @@ -1,8 +1,8 @@ import libnut = require("@nut-tree/libnut"); import { Region } from "../../region.class"; -import { WindowActionProvider } from "./window-action-provider.interface"; +import { WindowProvider } from "../window-provider.interface"; -export default class WindowAction implements WindowActionProvider { +export default class WindowAction implements WindowProvider { public getWindows(): Promise { return new Promise((resolve, reject) => { try { diff --git a/lib/provider/opencv/data-sink.interface.ts b/lib/provider/opencv/data-sink.interface.ts deleted file mode 100644 index 79f15b1c..00000000 --- a/lib/provider/opencv/data-sink.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * A DataSink should provide methods to store data - * - * @interface DataSink - */ -export interface DataSink { - /** - * store will write data to disk - * @param data Data to write - * @param path Absolute output file path - */ - store(data: any, path: string): Promise; -} diff --git a/lib/provider/opencv/data-source.interface.ts b/lib/provider/opencv/data-source.interface.ts deleted file mode 100644 index f59d06b7..00000000 --- a/lib/provider/opencv/data-source.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * A DataSource should provide methods to load data - * - * @interface DataSource - */ -export interface DataSource { - /** - * load will load image data from disk - * @param path Absolute path to output file - */ - load(path: string): Promise; -} diff --git a/lib/provider/opencv/image-processor.class.spec.ts b/lib/provider/opencv/image-processor.class.spec.ts index 19651f7d..c1a9da48 100644 --- a/lib/provider/opencv/image-processor.class.spec.ts +++ b/lib/provider/opencv/image-processor.class.spec.ts @@ -1,6 +1,6 @@ import {resolve} from "path"; import {Region} from "../../region.class"; -import {ImageReader} from "./image-reader.class"; +import ImageReader from "./image-reader.class"; import {fromImageWithAlphaChannel, fromImageWithoutAlphaChannel} from "./image-processor.class"; describe("ImageProcessor", () => { diff --git a/lib/provider/opencv/image-reader.class.spec.ts b/lib/provider/opencv/image-reader.class.spec.ts index f51d93bb..7034f618 100644 --- a/lib/provider/opencv/image-reader.class.spec.ts +++ b/lib/provider/opencv/image-reader.class.spec.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { ImageReader } from "./image-reader.class"; +import ImageReader from "./image-reader.class"; describe("Image loader", () => { it("should resolve to a non-empty Mat on successful load", async () => { diff --git a/lib/provider/opencv/image-reader.class.ts b/lib/provider/opencv/image-reader.class.ts index 73d90c34..ad333422 100644 --- a/lib/provider/opencv/image-reader.class.ts +++ b/lib/provider/opencv/image-reader.class.ts @@ -1,16 +1,16 @@ import * as cv from "opencv4nodejs-prebuilt"; -import { Image } from "../../image.class"; -import { DataSource } from "./data-source.interface"; +import {Image} from "../../image.class"; +import {ImageReader} from "../image-reader.type"; -export class ImageReader implements DataSource { - public async load(path: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const image = await cv.imreadAsync(path, cv.IMREAD_UNCHANGED); - resolve(new Image(image.cols, image.rows, image.getData(), image.channels)); - } catch (e) { - reject(`Failed to load image from '${path}'`); - } - }); - } +export default class implements ImageReader { + public async load(path: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const image = await cv.imreadAsync(path, cv.IMREAD_UNCHANGED); + resolve(new Image(image.cols, image.rows, image.getData(), image.channels)); + } catch (e) { + reject(`Failed to load image from '${path}'`); + } + }); + } } diff --git a/lib/provider/opencv/image-writer.class.spec.ts b/lib/provider/opencv/image-writer.class.spec.ts index d672ed9a..7170451b 100644 --- a/lib/provider/opencv/image-writer.class.spec.ts +++ b/lib/provider/opencv/image-writer.class.spec.ts @@ -1,32 +1,32 @@ -import { existsSync, unlinkSync } from "fs"; -import { resolve } from "path"; -import { ImageReader } from "./image-reader.class"; -import { ImageWriter } from "./image-writer.class"; +import {existsSync, unlinkSync} from "fs"; +import {resolve} from "path"; +import ImageReader from "./image-reader.class"; +import ImageWriter from "./image-writer.class"; const INPUT_PATH = resolve(__dirname, "./__mocks__/mouse.png"); const OUTPUT_PATH_PNG = resolve(__dirname, "./__mocks__/output.png"); const OUTPUT_PATH_JPG = resolve(__dirname, "./__mocks__/output.jpg"); beforeEach(() => { - for (const file of [OUTPUT_PATH_JPG, OUTPUT_PATH_PNG]) { - if (existsSync(file)) { - unlinkSync(file); + for (const file of [OUTPUT_PATH_JPG, OUTPUT_PATH_PNG]) { + if (existsSync(file)) { + unlinkSync(file); + } } - } }); describe.each([[OUTPUT_PATH_PNG], [OUTPUT_PATH_JPG]])( - "Image writer", (outputPath) => { - test("should allow to store image data to disk", async () => { - // GIVEN - const imageReader = new ImageReader(); - const image = await imageReader.load(INPUT_PATH); - const imageWriter = new ImageWriter(); + "Image writer", (outputPath) => { + test("should allow to store image data to disk", async () => { + // GIVEN + const imageReader = new ImageReader(); + const image = await imageReader.load(INPUT_PATH); + const imageWriter = new ImageWriter(); - // WHEN - await imageWriter.store(image, outputPath); + // WHEN + await imageWriter.store({data: image, path: outputPath}); - // THEN - expect(existsSync(outputPath)).toBeTruthy(); + // THEN + expect(existsSync(outputPath)).toBeTruthy(); + }); }); - }); diff --git a/lib/provider/opencv/image-writer.class.ts b/lib/provider/opencv/image-writer.class.ts index 6ae6b90c..cefb4b0f 100644 --- a/lib/provider/opencv/image-writer.class.ts +++ b/lib/provider/opencv/image-writer.class.ts @@ -1,10 +1,10 @@ import * as cv from "opencv4nodejs-prebuilt"; -import {Image} from "../../image.class"; -import {DataSink} from "./data-sink.interface"; import {fromImageWithAlphaChannel, fromImageWithoutAlphaChannel} from "./image-processor.class"; +import {ImageWriter, ImageWriterParameters} from "../image-writer.type"; -export class ImageWriter implements DataSink { - public async store(data: Image, path: string): Promise { + +export default class implements ImageWriter { + public async store({data, path}: ImageWriterParameters): Promise { let outputMat: cv.Mat; if (data.hasAlphaChannel) { outputMat = await fromImageWithAlphaChannel(data); diff --git a/lib/provider/opencv/scale-image.function.spec.ts b/lib/provider/opencv/scale-image.function.spec.ts index c25f4dee..d4df407d 100644 --- a/lib/provider/opencv/scale-image.function.spec.ts +++ b/lib/provider/opencv/scale-image.function.spec.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import {ImageReader} from "./image-reader.class"; +import ImageReader from "./image-reader.class"; import {scaleImage} from "./scale-image.function"; import {fromImageWithoutAlphaChannel} from "./image-processor.class"; diff --git a/lib/provider/opencv/template-matching-finder.class.spec.ts b/lib/provider/opencv/template-matching-finder.class.spec.ts index 974dc897..b8222c8e 100644 --- a/lib/provider/opencv/template-matching-finder.class.spec.ts +++ b/lib/provider/opencv/template-matching-finder.class.spec.ts @@ -2,7 +2,7 @@ import * as path from "path"; import {Image} from "../../image.class"; import {MatchRequest} from "../../match-request.class"; import {Region} from "../../region.class"; -import {ImageReader} from "./image-reader.class"; +import ImageReader from "./image-reader.class"; import TemplateMatchingFinder from "./template-matching-finder.class"; describe("Template-matching finder", () => { diff --git a/lib/provider/opencv/template-matching-finder.class.ts b/lib/provider/opencv/template-matching-finder.class.ts index 4ea34976..1ba6f883 100755 --- a/lib/provider/opencv/template-matching-finder.class.ts +++ b/lib/provider/opencv/template-matching-finder.class.ts @@ -5,14 +5,14 @@ import {MatchRequest} from "../../match-request.class"; import {MatchResult} from "../../match-result.class"; import {Region} from "../../region.class"; import {ScaledMatchResult} from "../../scaled-match-result.class"; -import {DataSource} from "./data-source.interface"; import {determineScaledSearchRegion} from "./determine-searchregion.function"; -import {FinderInterface} from "./finder.interface"; -import {ImageReader} from "./image-reader.class"; +import {ImageFinderInterface} from "../image-finder.interface"; +import ImageReaderImpl from "./image-reader.class"; import {matchImages} from "./match-image.function"; import {scaleImage} from "./scale-image.function"; import {scaleLocation} from "./scale-location.function"; import {fromImageWithAlphaChannel, fromImageWithoutAlphaChannel} from "./image-processor.class"; +import {ImageReader} from "../image-reader.type"; async function loadNeedle(image: Image): Promise { if (image.hasAlphaChannel) { @@ -73,12 +73,12 @@ function createResultForInvalidSearch (currentScale: number) { ) } -export default class TemplateMatchingFinder implements FinderInterface { +export default class TemplateMatchingFinder implements ImageFinderInterface { private initialScale = [1.0]; private scaleSteps = [0.9, 0.8, 0.7, 0.6, 0.5]; constructor( - private source: DataSource = new ImageReader(), + private source: ImageReader = new ImageReaderImpl(), ) { } diff --git a/lib/provider/provider-registry.class.ts b/lib/provider/provider-registry.class.ts new file mode 100644 index 00000000..0128c601 --- /dev/null +++ b/lib/provider/provider-registry.class.ts @@ -0,0 +1,153 @@ +import {ClipboardProvider} from "./clipboard-provider.interface"; +import {ImageFinderInterface} from "./image-finder.interface"; +import {KeyboardProvider} from "./keyboard-provider.interface"; +import {MouseProvider} from "./mouse-provider.interface"; +import {ScreenProvider} from "./screen-provider.interface"; +import {WindowProvider} from "./window-provider.interface"; + +import Clipboard from "./native/clipboardy-clipboard.class"; +import Finder from "./opencv/template-matching-finder.class"; +import Mouse from "./native/libnut-mouse.class"; +import Keyboard from "./native/libnut-keyboard.class"; +import Screen from "./native/libnut-screen.class"; +import Window from "./native/libnut-window.class"; +import {ImageReader} from "./image-reader.type"; +import {ImageWriter} from "./image-writer.type"; +import ImageReaderImpl from "./opencv/image-reader.class"; +import ImageWriterImpl from "./opencv/image-writer.class"; + +export interface ProviderRegistry { + getClipboard(): ClipboardProvider; + registerClipboardProvider(value: ClipboardProvider): void; + + getImageFinder(): ImageFinderInterface; + registerImageFinder(value: ImageFinderInterface): void; + + getKeyboard(): KeyboardProvider; + registerKeyboardProvider(value: KeyboardProvider): void; + + getMouse(): MouseProvider; + registerMouseProvider(value: MouseProvider): void; + + getScreen(): ScreenProvider; + registerScreenProvider(value: ScreenProvider): void; + + getWindow(): WindowProvider; + registerWindowProvider(value: WindowProvider): void; + + getImageReader(): ImageReader; + registerImageReader(value: ImageReader): void; + + getImageWriter(): ImageWriter; + registerImageWriter(value: ImageWriter): void; +} + +class DefaultProviderRegistry implements ProviderRegistry { + private _clipboard?: ClipboardProvider; + private _finder?: ImageFinderInterface; + private _keyboard?: KeyboardProvider; + private _mouse?: MouseProvider; + private _screen?: ScreenProvider; + private _window?: WindowProvider; + private _imageReader?: ImageReader; + private _imageWriter?: ImageWriter; + + getClipboard(): ClipboardProvider { + if (this._clipboard) { + return this._clipboard; + } + throw new Error(`No ClipboardProvider registered`); + } + + registerClipboardProvider(value: ClipboardProvider) { + this._clipboard = value; + } + + getImageFinder(): ImageFinderInterface { + if (this._finder) { + return this._finder; + } + throw new Error(`No ImageFinder registered`); + } + + registerImageFinder(value: ImageFinderInterface) { + this._finder = value; + } + + getKeyboard(): KeyboardProvider { + if (this._keyboard) { + return this._keyboard; + } + throw new Error(`No KeyboardProvider registered`); + } + + registerKeyboardProvider(value: KeyboardProvider) { + this._keyboard = value; + } + + getMouse(): MouseProvider { + if (this._mouse) { + return this._mouse; + } + throw new Error(`No MouseProvider registered`); + } + + registerMouseProvider(value: MouseProvider) { + this._mouse = value; + } + + getScreen(): ScreenProvider { + if (this._screen) { + return this._screen; + } + throw new Error(`No ScreenProvider registered`); + } + + registerScreenProvider(value: ScreenProvider) { + this._screen = value; + } + + getWindow(): WindowProvider { + if (this._window) { + return this._window; + } + throw new Error(`No WindowProvider registered`); + } + + registerWindowProvider(value: WindowProvider) { + this._window = value; + } + + getImageReader(): ImageReader { + if (this._imageReader) { + return this._imageReader; + } + throw new Error(`No ImageReader registered`); + } + registerImageReader(value: ImageReader) { + this._imageReader = value; + } + + getImageWriter(): ImageWriter { + if (this._imageWriter) { + return this._imageWriter; + } + throw new Error(`No ImageWriter registered`); + } + registerImageWriter(value: ImageWriter) { + this._imageWriter = value; + } +} + +const providerRegistry = new DefaultProviderRegistry(); + +providerRegistry.registerClipboardProvider(new Clipboard()); +providerRegistry.registerImageFinder(new Finder()); +providerRegistry.registerKeyboardProvider(new Keyboard()); +providerRegistry.registerMouseProvider(new Mouse()); +providerRegistry.registerScreenProvider(new Screen()); +providerRegistry.registerWindowProvider(new Window()); +providerRegistry.registerImageReader(new ImageReaderImpl()); +providerRegistry.registerImageWriter(new ImageWriterImpl()); + +export default providerRegistry; \ No newline at end of file diff --git a/lib/provider/native/screen-action-provider.interface.ts b/lib/provider/screen-provider.interface.ts similarity index 89% rename from lib/provider/native/screen-action-provider.interface.ts rename to lib/provider/screen-provider.interface.ts index 2eb390df..0c09a881 100644 --- a/lib/provider/native/screen-action-provider.interface.ts +++ b/lib/provider/screen-provider.interface.ts @@ -1,12 +1,12 @@ -import { Image } from "../../image.class"; -import { Region } from "../../region.class"; +import { Image } from "../image.class"; +import { Region } from "../region.class"; /** * A ScreenActionProvider should provide access to a system's main screen * - * @interface ScreenActionProvider + * @interface ScreenProvider */ -export interface ScreenActionProvider { +export interface ScreenProvider { /** * grabScreen should return an {@link Image} object containing a screenshot data of a systems * main screen as well as its dimensions diff --git a/lib/provider/native/window-action-provider.interface.ts b/lib/provider/window-provider.interface.ts similarity index 90% rename from lib/provider/native/window-action-provider.interface.ts rename to lib/provider/window-provider.interface.ts index 0d5e3e42..594face6 100644 --- a/lib/provider/native/window-action-provider.interface.ts +++ b/lib/provider/window-provider.interface.ts @@ -1,11 +1,11 @@ -import { Region } from "../../region.class"; +import { Region } from "../region.class"; /** * A WindowActionProvider should provide access to a system's window system * - * @interface WindowActionProvider + * @interface WindowProvider */ -export interface WindowActionProvider { +export interface WindowProvider { /** * {@link getWindows} returns a list of window handles for further processing. * These window handles may serve as input to e.g. {@link getWindowTitle} diff --git a/lib/screen.class.e2e.spec.ts b/lib/screen.class.e2e.spec.ts index 7f3f1405..73616ac8 100644 --- a/lib/screen.class.e2e.spec.ts +++ b/lib/screen.class.e2e.spec.ts @@ -5,13 +5,14 @@ import {ScreenClass} from "./screen.class"; import {sleep} from "./sleep.function"; import AbortController from "node-abort-controller"; import {Region} from "./region.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.setTimeout(10000); describe("Screen.", () => { it("should capture the screen", () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); // WHEN @@ -26,7 +27,7 @@ describe("Screen.", () => { it("should capture the screen and save to JPG", () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); // WHEN @@ -41,7 +42,7 @@ describe("Screen.", () => { it("should capture the screen and save file with prefix", () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); const prefix = "foo_"; @@ -58,7 +59,7 @@ describe("Screen.", () => { it("should capture the screen and save file with postfix", () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); const postfix = "_bar"; @@ -75,7 +76,7 @@ describe("Screen.", () => { it("should capture the screen and save file with pre- and postfix", () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); const filename = "asdf"; const prefix = "foo_"; @@ -95,7 +96,7 @@ describe("Screen.", () => { it("should reject after timeout", async () => { // GIVEN const timeout = 5000; - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); SUT.config.resourceDirectory = "./e2e/assets"; @@ -119,7 +120,7 @@ describe("Screen.", () => { const abortAfterMs = 1000; const controller = new AbortController(); const signal = controller.signal; - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); SUT.config.resourceDirectory = "./e2e/assets"; @@ -139,7 +140,7 @@ describe("Screen.", () => { it("should grab the whole screen content and return an Image", async () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); const screenWidth = await SUT.width(); const screenHeight = await SUT.height(); @@ -155,7 +156,7 @@ describe("Screen.", () => { it("should grab a screen region and return an Image", async () => { // GIVEN - const visionAdapter = new VisionAdapter(); + const visionAdapter = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapter); const regionToGrab = new Region(0, 0, 100, 100); diff --git a/lib/screen.class.spec.ts b/lib/screen.class.spec.ts index e84cca22..ee1407b2 100644 --- a/lib/screen.class.spec.ts +++ b/lib/screen.class.spec.ts @@ -9,6 +9,7 @@ import { Region } from "./region.class"; import { ScreenClass } from "./screen.class"; import { mockPartial } from "sneer"; import { FileType } from "./file-type.enum"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); jest.mock("./adapter/vision.adapter.class"); @@ -35,7 +36,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; @@ -61,7 +62,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const testCallback = jest.fn(() => Promise.resolve()); @@ -83,7 +84,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const testCallback = jest.fn(() => Promise.resolve()); @@ -111,7 +112,7 @@ describe("Screen.", () => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; @@ -133,7 +134,7 @@ describe("Screen.", () => { return Promise.reject(rejectionReason); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; @@ -159,7 +160,7 @@ describe("Screen.", () => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); @@ -187,7 +188,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; const parameters = new LocationParameters(customSearchRegion); @@ -211,7 +212,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; const parameters = new LocationParameters(searchRegion, undefined, false); @@ -237,7 +238,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imagePath = "test/path/to/image.png"; const parameters = new LocationParameters(customSearchRegion, minMatch); @@ -271,7 +272,7 @@ describe("Screen.", () => { VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { return Promise.resolve(matchResult); }); - const SUT = new ScreenClass(new VisionAdapter()); + const SUT = new ScreenClass(new VisionAdapter(providerRegistry)); // WHEN const matchRegion = await SUT.find( @@ -305,7 +306,7 @@ describe("Screen.", () => { // GIVEN const imagePath = "test/path/to/image.png" - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); @@ -331,7 +332,7 @@ describe("Screen.", () => { // GIVEN const highlightRegion = new Region(10, 20, 30, 40); VisionAdapter.prototype.highlightScreenRegion = jest.fn(); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); // WHEN @@ -346,7 +347,7 @@ describe("Screen.", () => { const highlightRegion = new Region(10, 20, 30, 40); const highlightRegionPromise = new Promise(res => res(highlightRegion)); VisionAdapter.prototype.highlightScreenRegion = jest.fn(); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); // WHEN @@ -363,7 +364,7 @@ describe("Screen.", () => { const screenshot = mockPartial({data: "pretty pretty image"}); VisionAdapter.prototype.grabScreen = jest.fn(() => Promise.resolve(screenshot)); VisionAdapter.prototype.saveImage = jest.fn(); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imageName = "foobar.png" const expectedImagePath = join(cwd(), imageName) @@ -380,7 +381,7 @@ describe("Screen.", () => { it("should consider output configuration", async () => { // GIVEN - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imageName = "foobar" const filePath = "/path/to/file" @@ -404,7 +405,7 @@ describe("Screen.", () => { const regionToCapture = mockPartial({top:42, left:9, height: 10, width: 3.14159265359}) VisionAdapter.prototype.grabScreenRegion = jest.fn(() => Promise.resolve(screenshot)); VisionAdapter.prototype.saveImage = jest.fn(); - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imageName = "foobar.png" const expectedImagePath = join(cwd(), imageName) @@ -422,7 +423,7 @@ describe("Screen.", () => { // GIVEN const regionToCapture = mockPartial({top:42, left:9, height: 10, width: 3.14159265359}) - const visionAdapterMock = new VisionAdapter(); + const visionAdapterMock = new VisionAdapter(providerRegistry); const SUT = new ScreenClass(visionAdapterMock); const imageName = "foobar" const filePath = "/path/to/file" diff --git a/lib/window.class.spec.ts b/lib/window.class.spec.ts index 75d60ebf..23479248 100644 --- a/lib/window.class.spec.ts +++ b/lib/window.class.spec.ts @@ -1,12 +1,13 @@ import {Window} from "./window.class"; import {NativeAdapter} from "./adapter/native.adapter.class"; +import providerRegistry from "./provider/provider-registry.class"; jest.mock("./adapter/native.adapter.class"); describe("Window class", () => { it("should retrieve the window region via its native adapter", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const mockWindowHandle = 123; const SUT = new Window(nativeAdapterMock, mockWindowHandle); @@ -20,7 +21,7 @@ describe("Window class", () => { it("should retrieve the window title via its native adapter", async () => { // GIVEN - const nativeAdapterMock = new NativeAdapter(); + const nativeAdapterMock = new NativeAdapter(providerRegistry); const mockWindowHandle = 123; const SUT = new Window(nativeAdapterMock, mockWindowHandle); diff --git a/lib/window.function.spec.ts b/lib/window.function.spec.ts index 15c9ebf4..c8968011 100644 --- a/lib/window.function.spec.ts +++ b/lib/window.function.spec.ts @@ -1,12 +1,13 @@ import {createWindowApi} from "./window.function"; import {NativeAdapter} from "./adapter/native.adapter.class"; import {Window} from "./window.class"; +import providerRegistry from "./provider/provider-registry.class"; describe("WindowApi", () => { describe("getWindows", () => { it("should return a list of open Windows", async () => { // GIVEN - const SUT = createWindowApi(new NativeAdapter()); + const SUT = createWindowApi(new NativeAdapter(providerRegistry)); // WHEN const windows = await SUT.getWindows() @@ -21,7 +22,7 @@ describe("WindowApi", () => { describe("getActiveWindow", () => { it("should return the a single Window which is currently active", async () => { // GIVEN - const SUT = createWindowApi(new NativeAdapter()); + const SUT = createWindowApi(new NativeAdapter(providerRegistry)); // WHEN const window = await SUT.getActiveWindow();