Skip to content

Commit 2f1b734

Browse files
authored
Merge pull request #50 from nut-tree/feature/49/waitFor
Closes #49
2 parents 770dd46 + 07396c9 commit 2f1b734

File tree

5 files changed

+201
-1
lines changed

5 files changed

+201
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ It's work in progress and will undergo constant modification.
8888
## Screen
8989

9090
- [x] findOnScreen
91-
- [ ] waitFor
91+
- [x] waitFor
9292
- [ ] Hooks to trigger actions based on images
9393

9494
## Integration

lib/screen.class.e2e.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,26 @@ describe("Screen.", () => {
8787
});
8888
});
8989
});
90+
91+
it("should reject after timeout", async () => {
92+
// GIVEN
93+
jest.setTimeout(10000);
94+
const timeout = 5000;
95+
const visionAdapter = new VisionAdapter();
96+
const SUT = new Screen(visionAdapter);
97+
SUT.config.resourceDirectory = "./e2e/assets";
98+
99+
// WHEN
100+
const start = Date.now();
101+
try {
102+
await SUT.waitFor("calculator.png", timeout);
103+
} catch (e) {
104+
// THEN
105+
expect(e).toBe(`Action timed out after ${timeout} ms`);
106+
}
107+
const end = Date.now();
108+
109+
// THEN
110+
expect(end - start).toBeGreaterThanOrEqual(timeout);
111+
});
90112
});

lib/screen.class.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { generateOutputPath } from "./generate-output-path.function";
66
import { LocationParameters } from "./locationparameters.class";
77
import { MatchRequest } from "./match-request.class";
88
import { Region } from "./region.class";
9+
import { timeout } from "./util/poll-action.function";
910

1011
export class Screen {
1112
public config = {
@@ -64,6 +65,14 @@ export class Screen {
6465
});
6566
}
6667

68+
public async waitFor(
69+
pathToNeedle: string,
70+
timeoutMs: number = 5000,
71+
params?: LocationParameters,
72+
): Promise<Region> {
73+
return timeout(500, timeoutMs, () => this.find(pathToNeedle, params));
74+
}
75+
6776
public async capture(
6877
fileName: string,
6978
fileFormat: FileType = FileType.PNG,

lib/util/poll-action.function.spec.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {timeout} from "./poll-action.function";
2+
3+
describe("poll-action", () => {
4+
it("should timeout after maxDuration if action rejects", async () => {
5+
// GIVEN
6+
const updateInterval = 200;
7+
const maxDuration = 1000;
8+
const action = jest.fn(() => {
9+
console.log(`Polling...`);
10+
return Promise.reject(false);
11+
});
12+
13+
// WHEN
14+
const start = Date.now();
15+
try {
16+
await timeout(updateInterval, maxDuration, action);
17+
} catch (e) {
18+
expect(e).toBe(`Action timed out after ${maxDuration} ms`);
19+
}
20+
const end = Date.now();
21+
22+
// THEN
23+
expect((end - start)).toBeGreaterThanOrEqual(maxDuration);
24+
expect(action).toBeCalledTimes((maxDuration / updateInterval));
25+
});
26+
27+
it("should timeout after maxDuration if action resolve != true", async () => {
28+
// GIVEN
29+
const updateInterval = 200;
30+
const maxDuration = 1000;
31+
const action = jest.fn(async () => {
32+
console.log(`Polling...`);
33+
return false;
34+
});
35+
36+
// WHEN
37+
const start = Date.now();
38+
try {
39+
await timeout(updateInterval, maxDuration, action);
40+
} catch (e) {
41+
expect(e).toEqual(`Action timed out after ${maxDuration} ms`);
42+
}
43+
const end = Date.now();
44+
45+
// THEN
46+
expect((end - start)).toBeGreaterThanOrEqual(maxDuration);
47+
expect(action).toBeCalledTimes((maxDuration / updateInterval));
48+
});
49+
50+
it("should resolve after updateInterval if action resolves", async () => {
51+
// GIVEN
52+
const updateInterval = 200;
53+
const maxDuration = 1000;
54+
const action = jest.fn(() => {
55+
console.log(`Polling...`);
56+
return Promise.resolve(true);
57+
});
58+
59+
// WHEN
60+
const start = Date.now();
61+
await timeout(updateInterval, maxDuration, action);
62+
const end = Date.now();
63+
64+
// THEN
65+
expect((end - start)).toBeLessThan(updateInterval);
66+
expect(action).toBeCalledTimes(1);
67+
});
68+
69+
it("should resolve after updateInterval if action resolves != true", async () => {
70+
// GIVEN
71+
const updateInterval = 200;
72+
const maxDuration = 1000;
73+
const action = jest.fn(async () => {
74+
console.log(`Polling...`);
75+
return true;
76+
});
77+
78+
// WHEN
79+
const start = Date.now();
80+
await timeout(updateInterval, maxDuration, action);
81+
const end = Date.now();
82+
83+
// THEN
84+
expect((end - start)).toBeLessThan(updateInterval);
85+
expect(action).toBeCalledTimes(1);
86+
});
87+
88+
it("should retry until action succeeds", async () => {
89+
// GIVEN
90+
const updateInterval = 200;
91+
const maxDuration = 1000;
92+
const delay = 2.2 * updateInterval;
93+
const action = jest.fn(() => {
94+
console.log(`Polling...`);
95+
const interval = (Date.now() - start);
96+
return new Promise<boolean>((resolve, reject) => (interval > delay) ? resolve(true) : reject());
97+
});
98+
99+
// WHEN
100+
const start = Date.now();
101+
await timeout(updateInterval, maxDuration, action);
102+
const end = Date.now();
103+
104+
// THEN
105+
expect((end - start)).toBeGreaterThanOrEqual(delay);
106+
expect(action).toBeCalledTimes(4);
107+
});
108+
109+
it("should fail after timeout if timeout < retry interval", async () => {
110+
// GIVEN
111+
const updateInterval = 1000;
112+
const maxDuration = 200;
113+
const action = jest.fn(() => Promise.resolve(false));
114+
115+
// WHEN
116+
const start = Date.now();
117+
try {
118+
await timeout(updateInterval, maxDuration, action);
119+
} catch (e) {
120+
expect(e).toEqual(`Action timed out after ${maxDuration} ms`);
121+
}
122+
const end = Date.now();
123+
124+
// THEN
125+
expect(action).toBeCalledTimes(1);
126+
expect((end - start)).toBeLessThan(updateInterval);
127+
});
128+
});

lib/util/poll-action.function.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export function timeout<R>(updateIntervalMs: number, maxDurationMs: number, action: (...params: any) => Promise<R>): Promise<R> {
2+
return new Promise<R>((resolve, reject) => {
3+
let interval: NodeJS.Timeout;
4+
const timeout = setTimeout(
5+
() => {
6+
clearTimeout(timeout);
7+
if (interval) {
8+
clearTimeout(interval);
9+
}
10+
reject(`Action timed out after ${maxDurationMs} ms`);
11+
},
12+
maxDurationMs
13+
);
14+
const startInterval = () => {
15+
interval = setTimeout(function intervalFunc() {
16+
action().then((result) => {
17+
if (!result) {
18+
interval = setTimeout(intervalFunc, updateIntervalMs);
19+
} else {
20+
clearTimeout(timeout);
21+
clearTimeout(interval);
22+
resolve(result);
23+
}
24+
}).catch(() => {
25+
interval = setTimeout(intervalFunc, updateIntervalMs);
26+
});
27+
}, updateIntervalMs);
28+
};
29+
30+
action().then((result) => {
31+
if (!result) {
32+
startInterval();
33+
} else {
34+
clearTimeout(timeout);
35+
resolve(result);
36+
}
37+
}).catch(() => {
38+
startInterval();
39+
});
40+
});
41+
}

0 commit comments

Comments
 (0)