From 5ae993afeb770c8e120c0a9cd623f3c20cf248c7 Mon Sep 17 00:00:00 2001 From: dancesWithBugs <75453205+dancesWithBugs@users.noreply.github.com> Date: Thu, 10 Dec 2020 21:50:13 +0100 Subject: [PATCH 1/6] Reject promise if searchRegion outside screenSize Prevents a comparatively cryptic OpenCV runtime error. --- lib/screen.class.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/screen.class.ts b/lib/screen.class.ts index 79506b70..5396ac13 100644 --- a/lib/screen.class.ts +++ b/lib/screen.class.ts @@ -83,8 +83,8 @@ export class Screen { params?: LocationParameters, ): Promise { const minMatch = (params && params.confidence) || this.config.confidence; - const searchRegion = - (params && params.searchRegion) || await this.vision.screenSize(); + const screenSize = await this.vision.screenSize(); + const searchRegion = (params && params.searchRegion) || screenSize; const searchMultipleScales = (params && params.searchMultipleScales) const fullPathToNeedle = normalize(join(this.config.resourceDirectory, templateImageFilename)); @@ -101,6 +101,9 @@ export class Screen { return new Promise(async (resolve, reject) => { try { + if ( searchRegion.left > await screenSize.width || searchRegion.top > screenSize.height ) { + throw new Error(`Requested search region (${searchRegion.left}, ${searchRegion.top}, ${searchRegion.width}, ${searchRegion.height}) lies beyond screen boundaries (${screenSize.width}, ${screenSize.height})`); + } const matchResult = await this.vision.findOnScreenRegion(matchRequest); if (matchResult.confidence >= minMatch) { const possibleHooks = this.findHooks.get(templateImageFilename) || []; From f426085edd88dbffe425ac56045a6dc2a7b3149f Mon Sep 17 00:00:00 2001 From: dancesWithBugs <75453205+dancesWithBugs@users.noreply.github.com> Date: Mon, 4 Jan 2021 11:13:49 +0100 Subject: [PATCH 2/6] catching more search region to screen size mismatches --- lib/screen.class.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/screen.class.ts b/lib/screen.class.ts index 5396ac13..60319eb7 100644 --- a/lib/screen.class.ts +++ b/lib/screen.class.ts @@ -101,8 +101,17 @@ export class Screen { return new Promise(async (resolve, reject) => { try { - if ( searchRegion.left > await screenSize.width || searchRegion.top > screenSize.height ) { - throw new Error(`Requested search region (${searchRegion.left}, ${searchRegion.top}, ${searchRegion.width}, ${searchRegion.height}) lies beyond screen boundaries (${screenSize.width}, ${screenSize.height})`); + if ( region.left < 0 || region.top < 0 || region.width < 0 || region.height < 0 ) { + throw new Error(`Negative values in search region ${region}`) + } + if ( isNaN(region.left) || isNaN(region.top) || isNaN(region.width) || isNaN(region.height) ) { + throw new Error(`NaN values in search region ${region}`) + } + if ( region.width < 2 || region.height < 2 ) { + throw new Error(`Search region ${region} is not large enough. Must be at least two pixels in both width and height.`) + } + if ( region.left + region.width > screenSize.width || region.top + region.height > screenSize.height ) { + throw new Error(`Search region ${region} extends beyond screen boundaries (${screenSize.width}x${screenSize.height})`) } const matchResult = await this.vision.findOnScreenRegion(matchRequest); if (matchResult.confidence >= minMatch) { From d8fb90e46777fcac145d2e5d96003632f112a3cc Mon Sep 17 00:00:00 2001 From: dancesWithBugs <75453205+dancesWithBugs@users.noreply.github.com> Date: Mon, 4 Jan 2021 12:03:20 +0100 Subject: [PATCH 3/6] fixed name of search region variable --- lib/screen.class.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/screen.class.ts b/lib/screen.class.ts index 60319eb7..346ddb7f 100644 --- a/lib/screen.class.ts +++ b/lib/screen.class.ts @@ -101,17 +101,17 @@ export class Screen { return new Promise(async (resolve, reject) => { try { - if ( region.left < 0 || region.top < 0 || region.width < 0 || region.height < 0 ) { - throw new Error(`Negative values in search region ${region}`) + if ( searchRegion.left < 0 || searchRegion.top < 0 || searchRegion.width < 0 || searchRegion.height < 0 ) { + throw new Error(`Negative values in search region ${searchRegion}`) } - if ( isNaN(region.left) || isNaN(region.top) || isNaN(region.width) || isNaN(region.height) ) { - throw new Error(`NaN values in search region ${region}`) + if ( isNaN(searchRegion.left) || isNaN(searchRegion.top) || isNaN(searchRegion.width) || isNaN(searchRegion.height) ) { + throw new Error(`NaN values in search region ${searchRegion}`) } - if ( region.width < 2 || region.height < 2 ) { - throw new Error(`Search region ${region} is not large enough. Must be at least two pixels in both width and height.`) + if ( searchRegion.width < 2 || searchRegion.height < 2 ) { + throw new Error(`Search region ${searchRegion} is not large enough. Must be at least two pixels in both width and height.`) } - if ( region.left + region.width > screenSize.width || region.top + region.height > screenSize.height ) { - throw new Error(`Search region ${region} extends beyond screen boundaries (${screenSize.width}x${screenSize.height})`) + if ( searchRegion.left + searchRegion.width > screenSize.width || searchRegion.top + searchRegion.height > screenSize.height ) { + throw new Error(`Search region ${searchRegion} extends beyond screen boundaries (${screenSize.width}x${screenSize.height})`) } const matchResult = await this.vision.findOnScreenRegion(matchRequest); if (matchResult.confidence >= minMatch) { From 19f28db6c3cd33bd4954647bf28e26d7bb93cfa6 Mon Sep 17 00:00:00 2001 From: Sven Hettwer Date: Tue, 16 Feb 2021 16:58:10 +0100 Subject: [PATCH 4/6] grouped test for `find()` --- lib/screen.class.spec.ts | 400 ++++++++++++++++++++++----------------- 1 file changed, 222 insertions(+), 178 deletions(-) diff --git a/lib/screen.class.spec.ts b/lib/screen.class.spec.ts index 2c55bded..27206ee9 100644 --- a/lib/screen.class.spec.ts +++ b/lib/screen.class.spec.ts @@ -26,193 +26,266 @@ beforeAll(() => { }); describe("Screen.", () => { - it("should resolve with sufficient confidence.", async () => { - const matchResult = new MatchResult(0.99, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); - }); + describe("find", () => { + it("should resolve with sufficient confidence.", async () => { - const visionAdapterMock = new VisionAdapter(); + // GIVEN + const matchResult = new MatchResult(0.99, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - await expect(SUT.find(imagePath)).resolves.toEqual(matchResult.location); - const matchRequest = new MatchRequest( - expect.any(Image), - join(cwd(), imagePath), - searchRegion, - SUT.config.confidence, - true); - expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(matchRequest); - }); - it("should call registered hook before resolve", async () => { - const matchResult = new MatchResult(0.99, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + // WHEN + const resultRegion = SUT.find(imagePath); + + // THEN + await expect(resultRegion).resolves.toEqual(matchResult.location); + const matchRequest = new MatchRequest( + expect.any(Image), + join(cwd(), imagePath), + searchRegion, + SUT.config.confidence, + true); + expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(matchRequest); }); - const visionAdapterMock = new VisionAdapter(); - const SUT = new Screen(visionAdapterMock); - const testCallback = jest.fn(() => Promise.resolve()); - const imagePath = "test/path/to/image.png"; - SUT.on(imagePath, testCallback); - await SUT.find(imagePath); - expect(testCallback).toBeCalledTimes(1); - expect(testCallback).toBeCalledWith(matchResult); - }); + it("should call registered hook before resolve", async () => { - it("should call multiple registered hooks before resolve", async () => { - const matchResult = new MatchResult(0.99, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + // GIVEN + const matchResult = new MatchResult(0.99, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + const testCallback = jest.fn(() => Promise.resolve()); + const imagePath = "test/path/to/image.png"; + SUT.on(imagePath, testCallback); + + // WHEN + await SUT.find(imagePath); + + // THEN + expect(testCallback).toBeCalledTimes(1); + expect(testCallback).toBeCalledWith(matchResult); }); - const visionAdapterMock = new VisionAdapter(); - const SUT = new Screen(visionAdapterMock); - const testCallback = jest.fn(() => Promise.resolve()); - const secondCallback = jest.fn(() => Promise.resolve()); - const imagePath = "test/path/to/image.png"; - SUT.on(imagePath, testCallback); - SUT.on(imagePath, secondCallback); - await SUT.find(imagePath); - for (const callback of [testCallback, secondCallback]) { - expect(callback).toBeCalledTimes(1); - expect(callback).toBeCalledWith(matchResult); - } - }); + it("should call multiple registered hooks before resolve", async () => { + + // GIVEN + const matchResult = new MatchResult(0.99, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + const testCallback = jest.fn(() => Promise.resolve()); + const secondCallback = jest.fn(() => Promise.resolve()); + const imagePath = "test/path/to/image.png"; + SUT.on(imagePath, testCallback); + SUT.on(imagePath, secondCallback); - it("should reject with insufficient confidence.", async () => { - const matchResult = new MatchResult(0.8, searchRegion); + // WHEN + await SUT.find(imagePath); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + // THEN + for (const callback of [testCallback, secondCallback]) { + expect(callback).toBeCalledTimes(1); + expect(callback).toBeCalledWith(matchResult); + } }); - const visionAdapterMock = new VisionAdapter(); + it("should reject with insufficient confidence.", async () => { - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - await expect(SUT.find(imagePath)) - .rejects - .toEqual(`No match for ${imagePath}. Required: ${SUT.config.confidence}, given: ${matchResult.confidence}`); - }); + // GIVEN + const matchResult = new MatchResult(0.8, searchRegion); + + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; + + // WHEN + const resultRegion = SUT.find(imagePath); - it("should reject when search fails.", async () => { - const rejectionReason = "Search failed."; - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.reject(rejectionReason); + // THEN + await expect(resultRegion) + .rejects + .toEqual(`No match for ${imagePath}. Required: ${SUT.config.confidence}, given: ${matchResult.confidence}`); }); - const visionAdapterMock = new VisionAdapter(); + it("should reject when search fails.", async () => { - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - await expect(SUT.find(imagePath)) - .rejects - .toEqual(`Searching for ${imagePath} failed. Reason: '${rejectionReason}'`); - }); + // GIVEN + const rejectionReason = "Search failed."; + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.reject(rejectionReason); + }); + + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; + + // WHEN + const resultRegion = SUT.find(imagePath); + + // THEN + await expect(resultRegion) + .rejects + .toEqual(`Searching for ${imagePath} failed. Reason: '${rejectionReason}'`); - it("should override default confidence value with parameter.", async () => { - const minMatch = 0.8; - const matchResult = new MatchResult(minMatch, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); }); - const visionAdapterMock = new VisionAdapter(); + it("should override default confidence value with parameter.", async () => { - const SUT = new Screen(visionAdapterMock); + // GIVEN + const minMatch = 0.8; + const matchResult = new MatchResult(minMatch, searchRegion); - const imagePath = "test/path/to/image.png"; - const parameters = new LocationParameters(undefined, minMatch); - await expect(SUT.find(imagePath, parameters)).resolves.toEqual(matchResult.location); - const matchRequest = new MatchRequest( - expect.any(Image), - join(cwd(), imagePath), - searchRegion, - minMatch, - true); - expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(matchRequest); - }); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); - it("should override default search region with parameter.", async () => { - // GIVEN - const customSearchRegion = new Region(10, 10, 90, 90); - const matchResult = new MatchResult(0.99, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + + const imagePath = "test/path/to/image.png"; + const parameters = new LocationParameters(undefined, minMatch); + + // WHEN + const resultRegion = SUT.find(imagePath, parameters); + + // THEN + await expect(resultRegion).resolves.toEqual(matchResult.location); + const matchRequest = new MatchRequest( + expect.any(Image), + join(cwd(), imagePath), + searchRegion, + minMatch, + true); + expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(matchRequest); }); - const visionAdapterMock = new VisionAdapter(); - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - const parameters = new LocationParameters(customSearchRegion); - const expectedMatchRequest = new MatchRequest( - expect.any(Image), - join(cwd(), imagePath), - customSearchRegion, - SUT.config.confidence, - true); - // WHEN - await SUT.find(imagePath, parameters); + it("should override default search region with parameter.", async () => { + // GIVEN + const customSearchRegion = new Region(10, 10, 90, 90); + const matchResult = new MatchResult(0.99, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; + const parameters = new LocationParameters(customSearchRegion); + const expectedMatchRequest = new MatchRequest( + expect.any(Image), + join(cwd(), imagePath), + customSearchRegion, + SUT.config.confidence, + true); - // THEN - expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); - }); + // WHEN + await SUT.find(imagePath, parameters); - it("should override searchMultipleScales with parameter.", async () => { - // GIVEN - const matchResult = new MatchResult(0.99, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + // THEN + expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); }); - const visionAdapterMock = new VisionAdapter(); - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - const parameters = new LocationParameters(searchRegion, undefined, false); - const expectedMatchRequest = new MatchRequest( - expect.any(Image), - join(cwd(), imagePath), - searchRegion, - SUT.config.confidence, - false); - // WHEN - await SUT.find(imagePath, parameters); + it("should override searchMultipleScales with parameter.", async () => { + // GIVEN + const matchResult = new MatchResult(0.99, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; + const parameters = new LocationParameters(searchRegion, undefined, false); + const expectedMatchRequest = new MatchRequest( + expect.any(Image), + join(cwd(), imagePath), + searchRegion, + SUT.config.confidence, + false); - // THEN - expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); - }); + // WHEN + await SUT.find(imagePath, parameters); - it("should override both confidence and search region with parameter.", async () => { - // GIVEN - const minMatch = 0.8; - const customSearchRegion = new Region(10, 10, 90, 90); - const matchResult = new MatchResult(minMatch, searchRegion); - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); + // THEN + expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); }); - const visionAdapterMock = new VisionAdapter(); - const SUT = new Screen(visionAdapterMock); - const imagePath = "test/path/to/image.png"; - const parameters = new LocationParameters(customSearchRegion, minMatch); - const expectedMatchRequest = new MatchRequest( - expect.any(Image), - join(cwd(), imagePath), - customSearchRegion, - minMatch, - true); - // WHEN - await SUT.find(imagePath, parameters); + it("should override both confidence and search region with parameter.", async () => { + // GIVEN + const minMatch = 0.8; + const customSearchRegion = new Region(10, 10, 90, 90); + const matchResult = new MatchResult(minMatch, searchRegion); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imagePath = "test/path/to/image.png"; + const parameters = new LocationParameters(customSearchRegion, minMatch); + const expectedMatchRequest = new MatchRequest( + expect.any(Image), + join(cwd(), imagePath), + customSearchRegion, + minMatch, + true); - // THEN - expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); + // WHEN + await SUT.find(imagePath, parameters); + + // THEN + expect(visionAdapterMock.findOnScreenRegion).toHaveBeenCalledWith(expectedMatchRequest); + }); + + it("should add search region offset to result image location", async () => { + + // GIVEN + const limitedSearchRegion = new Region(100, 200, 300, 400); + const resultRegion = new Region(50, 100, 150, 200); + const matchResult = new MatchResult(0.99, resultRegion); + + const expectedMatchRegion = new Region( + limitedSearchRegion.left + resultRegion.left, + limitedSearchRegion.top + resultRegion.top, + resultRegion.width, + resultRegion.height); + + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + const SUT = new Screen(new VisionAdapter()); + + // WHEN + const matchRegion = await SUT.find( + "test/path/to/image.png", + { + searchRegion: limitedSearchRegion + }); + + // THEN + expect(matchRegion).toEqual(expectedMatchRegion); + }) }); + it("should return region to highlight for chaining", async () => { // GIVEN const highlightRegion = new Region(10, 20, 30, 40); @@ -242,35 +315,6 @@ describe("Screen.", () => { expect(result).toEqual(highlightRegion); }); - it("should add search region offset to result image location", async () => { - - // GIVEN - const limitedSearchRegion = new Region(100, 200, 300, 400); - const resultRegion = new Region(50, 100, 150, 200); - const matchResult = new MatchResult(0.99, resultRegion); - - const expectedMatchRegion = new Region( - limitedSearchRegion.left + resultRegion.left, - limitedSearchRegion.top + resultRegion.top, - resultRegion.width, - resultRegion.height); - - VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { - return Promise.resolve(matchResult); - }); - const SUT = new Screen(new VisionAdapter()); - - // WHEN - const matchRegion = await SUT.find( - "test/path/to/image.png", - { - searchRegion: limitedSearchRegion - }); - - // THEN - expect(matchRegion).toEqual(expectedMatchRegion); - }) - describe("capture",() => { it("should capture the whole screen and save image", async() => { From 9ae3fb020a561c3cc3c3d61cfe08e2b4c7cd22d4 Mon Sep 17 00:00:00 2001 From: Sven Hettwer Date: Tue, 16 Feb 2021 16:58:29 +0100 Subject: [PATCH 5/6] increased display size --- lib/screen.class.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screen.class.spec.ts b/lib/screen.class.spec.ts index 27206ee9..c08984da 100644 --- a/lib/screen.class.spec.ts +++ b/lib/screen.class.spec.ts @@ -13,7 +13,7 @@ import { FileType } from "./file-type.enum"; jest.mock("./adapter/native.adapter.class"); jest.mock("./adapter/vision.adapter.class"); -const searchRegion = new Region(0, 0, 100, 100); +const searchRegion = new Region(0, 0, 1000, 1000); beforeAll(() => { VisionAdapter.prototype.grabScreen = jest.fn(() => { From b8a386ae280d5df98bf903372c72d0531e2a5dd5 Mon Sep 17 00:00:00 2001 From: Sven Hettwer Date: Wed, 17 Feb 2021 15:39:22 +0100 Subject: [PATCH 6/6] added tests verifying search region validation --- lib/screen.class.spec.ts | 41 ++++++++++++++++++++++++++++++++++++++++ lib/screen.class.ts | 28 +++++++++++++++------------ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/lib/screen.class.spec.ts b/lib/screen.class.spec.ts index c08984da..29e735f4 100644 --- a/lib/screen.class.spec.ts +++ b/lib/screen.class.spec.ts @@ -283,6 +283,47 @@ describe("Screen.", () => { // THEN expect(matchRegion).toEqual(expectedMatchRegion); }) + + it.each([ + ["with negative x coordinate", new Region(-1, 0, 100, 100)], + ["with negative y coordinate", new Region(0, -1, 100, 100)], + ["with negative width", new Region(0, 0, -100, 100)], + ["with negative height", new Region(0, 0, 100, -100)], + ["with region outside screen on x axis", new Region(1100, 0, 100, 100)], + ["with region outside screen on y axis", new Region(0, 1100, 100, 100)], + ["with region bigger than screen on x axis", new Region(0, 0, 1100, 100)], + ["with region bigger than screen on y axis", new Region(0, 0, 1000, 1100)], + ["with region of 1 px width", new Region(0, 0, 1, 1100)], + ["with region of 1 px height", new Region(0, 0, 100, 1)], + ["with region leaving screen on x axis", new Region(600, 0, 500, 100)], + ["with region leaving screen on y axis", new Region(0, 500, 100, 600)], + ["with NaN x coordinate", new Region("a" as unknown as number, 0, 100, 100)], + ["with NaN y coordinate", new Region(0, "a" as unknown as number, 100, 600)], + ["with NaN on width", new Region(0, 0, "a" as unknown as number, 100)], + ["with NaN on height", new Region(0, 0, 100, "a" as unknown as number)], + ])("should reject search regions %s", async (_, region) =>{ + + // GIVEN + const imagePath = "test/path/to/image.png" + const visionAdapterMock = new VisionAdapter(); + + const SUT = new Screen(visionAdapterMock); + + const matchResult = new MatchResult(0.99, region); + VisionAdapter.prototype.findOnScreenRegion = jest.fn(() => { + return Promise.resolve(matchResult); + }); + + // WHEN + const findPromise = SUT.find( + imagePath, + { + searchRegion: region + }); + + // THEN + await expect(findPromise).rejects.toContain(`Searching for ${imagePath} failed. Reason:`); + }) }); diff --git a/lib/screen.class.ts b/lib/screen.class.ts index 4c263fcc..cfbf516b 100644 --- a/lib/screen.class.ts +++ b/lib/screen.class.ts @@ -100,20 +100,24 @@ export class Screen { searchMultipleScales ); + function validateSearchRegion(search: Region, screen: Region) { + if ( search.left < 0 || search.top < 0 || search.width < 0 || search.height < 0 ) { + throw new Error(`Negative values in search region ${search}`) + } + if ( isNaN(search.left) || isNaN(search.top) || isNaN(search.width) || isNaN(search.height) ) { + throw new Error(`NaN values in search region ${search}`) + } + if ( search.width < 2 || search.height < 2 ) { + throw new Error(`Search region ${search} is not large enough. Must be at least two pixels in both width and height.`) + } + if ( search.left + search.width > screen.width || search.top + search.height > screen.height ) { + throw new Error(`Search region ${search} extends beyond screen boundaries (${screen.width}x${screen.height})`) + } + } + return new Promise(async (resolve, reject) => { try { - if ( searchRegion.left < 0 || searchRegion.top < 0 || searchRegion.width < 0 || searchRegion.height < 0 ) { - throw new Error(`Negative values in search region ${searchRegion}`) - } - if ( isNaN(searchRegion.left) || isNaN(searchRegion.top) || isNaN(searchRegion.width) || isNaN(searchRegion.height) ) { - throw new Error(`NaN values in search region ${searchRegion}`) - } - if ( searchRegion.width < 2 || searchRegion.height < 2 ) { - throw new Error(`Search region ${searchRegion} is not large enough. Must be at least two pixels in both width and height.`) - } - if ( searchRegion.left + searchRegion.width > screenSize.width || searchRegion.top + searchRegion.height > screenSize.height ) { - throw new Error(`Search region ${searchRegion} extends beyond screen boundaries (${screenSize.width}x${screenSize.height})`) - } + validateSearchRegion(searchRegion, screenSize); const matchResult = await this.vision.findOnScreenRegion(matchRequest); if (matchResult.confidence >= minMatch) { const possibleHooks = this.findHooks.get(templateImageFilename) || [];