Skip to content

Commit 699e0de

Browse files
committed
Merge branch 'develop'
# Conflicts: # README.md # lib/image.class.spec.ts # lib/keyboard.class.ts # lib/mouse.class.ts # lib/movement.function.ts # lib/region.class.spec.ts # lib/region.class.ts # lib/screen.class.spec.ts # lib/screen.class.ts # lib/screen.colorAt.spec.ts # package-lock.json # package.json
2 parents c5e34b1 + 4ecf0c0 commit 699e0de

18 files changed

+509
-118
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,34 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 2.0.0
6+
- Feature: Apple Silicon [(libnut#49)](https://github.com/nut-tree/libnut/issues/49)
7+
- Enhancement: Enable warning message for missing accessibility permissions on macOS [(#354)](https://github.com/nut-tree/nut.js/issues/354)
8+
- Enhancement: Add runtime typechecks for `screen.find` etc. [(#351)](https://github.com/nut-tree/nut.js/issues/351)
9+
- Bugfix: Fix Windows scaling issue [(#349)](https://github.com/nut-tree/nut.js/issues/349)
10+
- Maintenance: Refine types [(#340)](https://github.com/nut-tree/nut.js/issues/340)
11+
- Maintenance: Cleanup deprecated code [(#341)](https://github.com/nut-tree/nut.js/issues/341)
12+
- Enhancement: Support for mouse capturing games [(#168)](https://github.com/nut-tree/nut.js/issues/168)
13+
- Feature: Provide functions to convert images between BGR and RGB color mode [(#336)](https://github.com/nut-tree/nut.js/issues/336)
14+
- Feature: Audio keys support [(#233)](https://github.com/nut-tree/nut.js/issues/233)
15+
- Enhancement: Configurable interval for `waitFor` [(#312)](https://github.com/nut-tree/nut.js/issues/312)
16+
- Bugfix: Apply pixel density scaling on `colorAt` [(#327)](https://github.com/nut-tree/nut.js/issues/327)
17+
- Enhancement: Change `find` signature to only work on `Image` instances [(#329)](https://github.com/nut-tree/nut.js/issues/329)
18+
- Enhancement: Adjust `assert` class to new `Screen#find` parameter types [(#324)](https://github.com/nut-tree/nut.js/issues/324)
19+
- Feature: Get screen pixel color [(#259)](https://github.com/nut-tree/nut.js/issues/259)
20+
- Feature: Add `Screen#findAll` to enable matching multiple template occurrences [(#320)](https://github.com/nut-tree/nut.js/issues/321)
21+
- Enhancement: Make Screen#find accept `Promise<Image>` [(#320)](https://github.com/nut-tree/nut.js/issues/320)
22+
- Enhancement: Accepting a Buffer with image data for `Screen#find` [(#204)](https://github.com/nut-tree/nut.js/issues/204)
23+
- Enhancement: Get rid of adapter layer in favour of providerRegistry [(#310)](https://github.com/nut-tree/nut.js/issues/310)
24+
- Feature: Provide a default implementation for `ImageReader` and `ImageWriter` [(#307)](https://github.com/nut-tree/nut.js/issues/307)
25+
- Feature: Define interface for mouse movement type [(#130)](https://github.com/nut-tree/nut.js/issues/130)
26+
- Feature: Separate image matching code [(#279)](https://github.com/nut-tree/nut.js/issues/279)
27+
- Enhancement: Export `FileType` [(#301)](https://github.com/nut-tree/nut.js/issues/301)
28+
- Enhancement: Export `ImageWriterParameters` [(#296)](https://github.com/nut-tree/nut.js/issues/296)
29+
- Enhancement: Export provider interfaces [(#294)](https://github.com/nut-tree/nut.js/issues/294)
30+
- Feature: Introduce a registry for providers [(#292)](https://github.com/nut-tree/nut.js/issues/292)
31+
- Feature: Add methods to grab the current screen content as Buffer [(#278)](https://github.com/nut-tree/nut.js/issues/278)
32+
533
## 1.7.0
634
- Enhancement: Trigger snapshot releases [(#234)](https://github.com/nut-tree/nut.js/issues/234)
735
- Feature: Cancel screen.waitFor if needed [(#241)](https://github.com/nut-tree/nut.js/issues/241)

README.md

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
[![Downloads per month](https://img.shields.io/npm/dm/@nut-tree/nut-js)](https://www.npmjs.com/package/@nut-tree/nut-js)
1212

13-
<p style="text-align: center">
14-
Native UI testing / automation with node.js
13+
<h1 align="center"><a href="https://nutjs.dev">nutjs.dev</a></h1>
14+
15+
<p align="center">
16+
Native UI testing / automation with Node.js
1517
</p>
1618
<br/>
1719

@@ -42,6 +44,10 @@ Check out this demo video to get a first impression of what nut.js is capable of
4244

4345
[![nut.js demo video](https://img.youtube.com/vi/MpIyUJnU_Bk/1.jpg)](https://www.youtube.com/watch?v=MpIyUJnU_Bk)
4446

47+
# Tutorials
48+
49+
Please consult the project website at [nutjs.dev](https://nutjs.dev/docs/tutorial-first_steps/prerequisits) for in-depth tutorials
50+
4551
# Examples
4652

4753
[nut-tree/trailmix](https://github.com/nut-tree/trailmix) contains a set of ready to use examples which demo the usage ot nut.js.
@@ -50,10 +56,6 @@ Check out this demo video to get a first impression of what nut.js is capable of
5056

5157
nut.js provides [public API documentation](https://nut-tree.github.io/apidoc/) auto-generated by [TypeDoc](https://typedoc.org).
5258

53-
# Discussion
54-
55-
In [nut-tree/rfc](https://github.com/nut-tree/rfc) documents regarding larger design / implementation changes in nut.js are up for discussion.
56-
5759
# Community
5860

5961
Feel free to join our [Discord community](https://discord.gg/sJkN7789XR)
@@ -89,11 +91,11 @@ It's work in progress and will undergo constant modification.
8991

9092
## Screen
9193

92-
- [x] Find an image on screen
94+
- [x] Find an image on screen (requires an additional provider package like e.g. [nut-tree/template-matcher](https://www.npmjs.com/package/@nut-tree/template-matcher))
9395
- [x] Find all image occurrences on screen
94-
- [x] Wait for an image to appear on screen
96+
- [x] Wait for an image to appear on screen (requires an additional provider package like e.g. [nut-tree/template-matcher](https://www.npmjs.com/package/@nut-tree/template-matcher))
9597
- [x] Retrieve RGBA color information on screen
96-
- [x] Hooks to trigger actions based on images
98+
- [x] Hooks to trigger actions based on images (requires an additional provider package like e.g. [nut-tree/template-matcher](https://www.npmjs.com/package/@nut-tree/template-matcher))
9799
- [x] Highlighting screen regions
98100

99101
## Integration
@@ -103,12 +105,12 @@ It's work in progress and will undergo constant modification.
103105

104106
# Sample
105107

106-
The following snippet shows a valid `nut.js` example (on macOS)
108+
The following snippet shows a valid `nut.js` example:
107109

108110
```js
109111
"use strict";
110112

111-
const { keyboard, Key, mouse, left, right, up, down, screen } = require("@nut-tree/nut-js");
113+
const { mouse, left, right, up, down, straightTo, centerOf, Region} = require("@nut-tree/nut-js");
112114

113115
const square = async () => {
114116
await mouse.move(right(500));
@@ -117,30 +119,23 @@ const square = async () => {
117119
await mouse.move(up(500));
118120
};
119121

120-
const openSpotlight = async () => {
121-
await keyboard.pressKey(Key.LeftSuper);
122-
await keyboard.pressKey(Key.Space);
123-
await keyboard.releaseKey(Key.Space);
124-
await keyboard.releaseKey(Key.LeftSuper);
125-
};
126-
127122
(async () => {
128123
await square();
129-
await openSpotlight();
130-
await keyboard.type("calculator");
131-
await keyboard.type(Key.Return);
124+
await mouse.move(
125+
straightTo(
126+
centerOf(
127+
new Region(100, 100, 200, 300)
128+
)
129+
)
130+
);
132131
})();
133132
```
134133

135134
# Installation
136135

137-
`nut.js` comes with a pre-built version of OpenCV for your respective target platform.
138-
In order to use these pre-compiled bindings, certain runtime conditions have to be met.
139-
140136
## Prerequisites
141137

142138
This section lists runtime requirements for `nut.js` on the respective target platform.
143-
`nut.js` is built and tested against node 10 and later as well as Electron 4 and later, so in order to use `nut.js` please make sure to use one of these versions.
144139

145140
#### Windows
146141

lib/image.class.spec.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Image} from "./image.class";
1+
import {Image, isImage} from "./image.class";
22
import {imageToJimp} from "./provider/io/imageToJimp.function";
33
import {ColorMode} from "./colormode.enum";
44

@@ -65,4 +65,65 @@ describe("Image class", () => {
6565
expect(imageToJimp).not.toBeCalledTimes(1)
6666
});
6767
});
68+
69+
describe('isImage typeguard', () => {
70+
it('should identify an Image', () => {
71+
// GIVEN
72+
const img = new Image(100, 100, Buffer.from([]), 4, 'foo');
73+
74+
// WHEN
75+
const result = isImage(img);
76+
77+
// THEN
78+
expect(result).toBeTruthy();
79+
});
80+
81+
it('should rule out non-objects', () => {
82+
// GIVEN
83+
const i = "foo";
84+
85+
// WHEN
86+
const result = isImage(i);
87+
88+
// THEN
89+
expect(result).toBeFalsy();
90+
});
91+
92+
it('should rule out possible object with missing properties', () => {
93+
// GIVEN
94+
const img = {
95+
width: 100,
96+
height: 100,
97+
data: Buffer.from([]),
98+
channels: 'foo',
99+
id: 'foo',
100+
colorMode: ColorMode.BGR
101+
};
102+
103+
// WHEN
104+
const result = isImage(img);
105+
106+
// THEN
107+
expect(result).toBeFalsy();
108+
});
109+
110+
it('should rule out possible object with wrong property type', () => {
111+
// GIVEN
112+
const img = {
113+
width: 100,
114+
height: 100,
115+
data: Buffer.from([]),
116+
channels: 'foo',
117+
id: 'foo',
118+
colorMode: ColorMode.BGR,
119+
pixelDensity: 25
120+
};
121+
122+
// WHEN
123+
const result = isImage(img);
124+
125+
// THEN
126+
expect(result).toBeFalsy();
127+
});
128+
})
68129
});

lib/image.class.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,23 @@ export class Image {
7070
return new Image(width, height, jimpImage.bitmap.data, channels, id);
7171
}
7272
}
73+
74+
const testImage = new Image(100, 100, Buffer.from([]), 4, "typeCheck");
75+
const imageKeys = Object.keys(testImage);
76+
77+
export function isImage(possibleImage: any): possibleImage is Image {
78+
if (typeof possibleImage !== 'object') {
79+
return false;
80+
}
81+
for (const key of imageKeys) {
82+
if (!(key in possibleImage)) {
83+
return false;
84+
}
85+
const possibleImageKeyType = typeof possibleImage[key];
86+
const imageKeyType = typeof testImage[key as keyof typeof testImage];
87+
if (possibleImageKeyType !== imageKeyType) {
88+
return false
89+
}
90+
}
91+
return true;
92+
}

lib/keyboard.class.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class KeyboardClass {
6565
* @example
6666
* ```typescript
6767
* // Will press and hold key combination STRG + V
68-
* await keyboard.pressKey(Key.STRG, Key.A);
68+
* await keyboard.pressKey(Key.STRG, Key.V);
6969
* ```
7070
*
7171
* @param keys Array of {@link Key}s to press and hold
@@ -87,7 +87,7 @@ export class KeyboardClass {
8787
* @example
8888
* ```typescript
8989
* // Will release key combination STRG + V
90-
* await keyboard.releaseKey(Key.STRG, Key.A);
90+
* await keyboard.releaseKey(Key.STRG, Key.V);
9191
* ```
9292
*
9393
* @param keys Array of {@link Key}s to release

lib/location.function.spec.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,45 @@
1-
import { centerOf, randomPointIn } from "./location.function";
2-
import { Point } from "./point.class";
3-
import { Region } from "./region.class";
1+
import {centerOf, randomPointIn} from "./location.function";
2+
import {Point} from "./point.class";
3+
import {Region} from "./region.class";
44

55
describe("Location", () => {
6-
it("should return the center point of an area.", () => {
7-
const expected = new Point(2, 2);
8-
const testRegion = new Region(0, 0, 4, 4);
6+
describe('centerOf', () => {
7+
it("should return the center point of an area.", () => {
8+
const expected = new Point(2, 2);
9+
const testRegion = new Region(0, 0, 4, 4);
910

10-
expect(centerOf(testRegion)).resolves.toEqual(expected);
11-
});
11+
expect(centerOf(testRegion)).resolves.toEqual(expected);
12+
});
1213

13-
it("should return a random point inside of an area.", async () => {
14-
const testRegion = new Region(100, 20, 50, 35);
15-
const result = await randomPointIn(testRegion);
16-
expect(result.x).toBeGreaterThanOrEqual(testRegion.left);
17-
expect(result.x).toBeLessThanOrEqual(testRegion.left + testRegion.width);
18-
expect(result.y).toBeGreaterThanOrEqual(testRegion.top);
19-
expect(result.y).toBeLessThanOrEqual(testRegion.top + testRegion.height);
20-
});
14+
it("should throw on non Region input", async () => {
15+
const testRegion = {
16+
left: 0,
17+
top: 0,
18+
width: 4
19+
};
20+
21+
await expect(centerOf(testRegion as Region)).rejects.toThrowError(/^centerOf requires a Region, but received/);
22+
});
23+
});
24+
25+
describe('randomPointIn', () => {
26+
it("should return a random point inside of an area.", async () => {
27+
const testRegion = new Region(100, 20, 50, 35);
28+
const result = await randomPointIn(testRegion);
29+
expect(result.x).toBeGreaterThanOrEqual(testRegion.left);
30+
expect(result.x).toBeLessThanOrEqual(testRegion.left + testRegion.width);
31+
expect(result.y).toBeGreaterThanOrEqual(testRegion.top);
32+
expect(result.y).toBeLessThanOrEqual(testRegion.top + testRegion.height);
33+
});
34+
35+
it("should throw on non Region input", async () => {
36+
const testRegion = {
37+
left: 0,
38+
top: 0,
39+
width: 4
40+
};
41+
42+
await expect(randomPointIn(testRegion as Region)).rejects.toThrowError(/^randomPointIn requires a Region, but received/);
43+
});
44+
});
2145
});

lib/location.function.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1-
import { Point } from "./point.class";
2-
import { Region } from "./region.class";
1+
import {Point} from "./point.class";
2+
import {isRegion, Region} from "./region.class";
33

44
/**
55
* {@link centerOf} returns the center {@link Point} for a given {@link Region}
66
* @param target {@link Region} to determine the center {@link Point} for
77
*/
88
export const centerOf = async (target: Region | Promise<Region>): Promise<Point> => {
9-
const targetRegion = await target;
10-
const x = Math.floor(targetRegion.left + targetRegion.width / 2);
11-
const y = Math.floor(targetRegion.top + targetRegion.height / 2);
9+
const targetRegion = await target;
10+
if (!isRegion(targetRegion)) {
11+
throw Error(`centerOf requires a Region, but received ${JSON.stringify(targetRegion)}`)
12+
}
13+
const x = Math.floor(targetRegion.left + targetRegion.width / 2);
14+
const y = Math.floor(targetRegion.top + targetRegion.height / 2);
1215

13-
return new Point(x, y);
16+
return new Point(x, y);
1417
};
1518

1619
/**
1720
* {@link randomPointIn} returns a random {@link Point} within a given {@link Region}
1821
* @param target {@link Region} the random {@link Point} has to be within
1922
*/
2023
export const randomPointIn = async (target: Region | Promise<Region>): Promise<Point> => {
21-
const targetRegion = await target;
22-
const x = Math.floor(targetRegion.left + Math.random() * targetRegion.width);
23-
const y = Math.floor(targetRegion.top + Math.random() * targetRegion.height);
24+
const targetRegion = await target;
25+
if (!isRegion(targetRegion)) {
26+
throw Error(`randomPointIn requires a Region, but received ${JSON.stringify(targetRegion)}`)
27+
}
28+
const x = Math.floor(targetRegion.left + Math.random() * targetRegion.width);
29+
const y = Math.floor(targetRegion.top + Math.random() * targetRegion.height);
2430

25-
return new Point(x, y);
31+
return new Point(x, y);
2632
};

lib/mouse.class.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Button} from "./button.enum";
2-
import {Point} from "./point.class";
2+
import {isPoint, Point} from "./point.class";
33
import {busyWaitForNanoSeconds, sleep} from "./sleep.function";
44
import {calculateMovementTimesteps, EasingFunction, linear} from "./mouse-movement.function";
55
import {ProviderRegistry} from "./provider/provider-registry.class";
@@ -36,6 +36,9 @@ export class MouseClass {
3636
* @param target {@link Point} to move the cursor to
3737
*/
3838
public async setPosition(target: Point): Promise<MouseClass> {
39+
if (!isPoint(target)) {
40+
throw Error(`setPosition requires a Point, but received ${JSON.stringify(target)}`)
41+
}
3942
return new Promise<MouseClass>(async (resolve, reject) => {
4043
try {
4144
await this.providerRegistry.getMouse().setMousePosition(target);
@@ -67,7 +70,7 @@ export class MouseClass {
6770
const node = pathSteps[idx];
6871
const minTime = timeSteps[idx];
6972
await busyWaitForNanoSeconds(minTime);
70-
await this.providerRegistry.getMouse().setMousePosition(node);
73+
await this.setPosition(node);
7174
}
7275
resolve(this);
7376
} catch (e) {

0 commit comments

Comments
 (0)