testdriverai 4.2.19 → 5.0.0-beta-8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/sandbox.js ADDED
@@ -0,0 +1,64 @@
1
+ const EventEmitter = require('events');
2
+ const WebSocket = require('ws');
3
+ const config = require('./config');
4
+
5
+ class Sandbox {
6
+ constructor() {
7
+ this.socket = null;
8
+ this.ps = {};
9
+ this.heartbeat = null;
10
+ }
11
+
12
+ send(message) {
13
+ let resolvePromise;
14
+ if (this.socket) {
15
+ message.requestId = Math.random().toString(36).substring(7) + new Date().getTime();
16
+ let p = new Promise((resolve, reject) => {
17
+ this.socket.send(JSON.stringify(message));
18
+ resolvePromise = resolve;
19
+ });
20
+ this.ps[message.requestId] = {promise: p, resolve: resolvePromise};
21
+ return p;
22
+ }
23
+ }
24
+
25
+ async boot() {
26
+ return new Promise((resolve, reject) => {
27
+
28
+ this.socket = new WebSocket(`${config.TD_API_ROOT.replace('https://', 'wss://')}`);
29
+
30
+ // handle errors
31
+ this.socket.on('error', (err) => {
32
+ console.log('Socket Closed');
33
+ console.log('If this happens during setup, check your API Key (`TD_API_KEY` in `.env`).');
34
+ err && console.log(err);
35
+ clearInterval(this.heartbeat);
36
+ reject();
37
+ });
38
+
39
+ this.socket.on('open', async () => {
40
+
41
+ this.heartbeat = setInterval(() => {
42
+ this.send({type: 'ping'});
43
+ }, 5000);
44
+
45
+ resolve(this);
46
+ });
47
+
48
+ this.socket.on('message', (raw) => {
49
+ let message = JSON.parse(raw);
50
+
51
+ if (this.ps[message.requestId]) {
52
+ this.ps[message.requestId].resolve(message);
53
+ delete this.ps[message.requestId];
54
+ } else {
55
+ console.log('unhandled message', message);
56
+ }
57
+ });
58
+
59
+ });
60
+ }
61
+ }
62
+
63
+ const sandboxInstance = new Sandbox();
64
+ module.exports = sandboxInstance;
package/lib/speak.js CHANGED
@@ -1,9 +1,12 @@
1
1
  const say = require("say");
2
2
  const config = require("./config");
3
+ const websockets = require("./websockets");
4
+
3
5
 
4
6
  module.exports = (message) => {
5
7
  if (config["TD_SPEAK"]) {
6
8
  say.stop();
9
+ // websockets.sendToClients("output", message);
7
10
  if (process.platform === "darwin") {
8
11
  say.speak(message, "Fred", 1.2);
9
12
  } else {
package/lib/system.js CHANGED
@@ -2,13 +2,28 @@
2
2
  const fs = require("fs");
3
3
  const os = require("os");
4
4
  const path = require("path");
5
- const screenshot = require("screenshot-desktop");
6
5
  const si = require("systeminformation");
7
6
  const robot = require("robotjs");
8
7
  const sharp = require("sharp");
9
8
  const { emitter, events } = require("./events.js");
9
+ const sandbox = require("./sandbox.js");
10
+ const config = require("./config.js");
10
11
 
11
- const { logger } = require("./logger.js");
12
+ let scshotdesk;
13
+ if (!config.TD_VM) {
14
+ scshotdesk = require("screenshot-desktop");
15
+ }
16
+
17
+ const screenshot = async (options) => {
18
+
19
+ if (config.TD_VM) {
20
+ let {base64} = await sandbox.send({type: 'screenshot'});
21
+ let image = Buffer.from(base64, 'base64');
22
+ fs.writeFileSync(options.filename, image);
23
+ } else {
24
+ return await scshotdesk({ filename: options.filename, format: "png" });
25
+ }
26
+ }
12
27
 
13
28
  let primaryDisplay = null;
14
29
 
@@ -26,7 +41,13 @@ const getPrimaryDisplay = async () => {
26
41
  };
27
42
 
28
43
  const getSystemInformationOsInfo = async () => {
29
- return await si.osInfo();
44
+ if (config.TD_VM) {
45
+ return {
46
+ os: 'linux'
47
+ }
48
+ } else {
49
+ return await si.osInfo();
50
+ }
30
51
  };
31
52
 
32
53
  let countImages = 0;
@@ -37,7 +58,7 @@ const tmpFilename = () => {
37
58
 
38
59
  const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
39
60
  try {
40
- const primaryDisplay = await getPrimaryDisplay();
61
+
41
62
  if (!silent) {
42
63
  emitter.emit(events.screenCapture.start, {
43
64
  scale,
@@ -51,21 +72,35 @@ const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
51
72
 
52
73
  await screenshot({ filename: step1, format: "png" });
53
74
 
54
- // Fetch the mouse position
55
- const mousePos = robot.getMousePos();
75
+ let sharpInstance;
76
+ if (config.TD_VM) {
77
+
78
+ // resize to 1:1 px ratio
79
+ sharpInstance = sharp(step1).resize(
80
+ Math.floor(1024 * scale),
81
+ Math.floor(768 * scale),
82
+ );
83
+
84
+ } else {
56
85
 
57
- // Location of cursor image
58
- const cursorPath = path.join(__dirname, "resources", "cursor.png");
86
+ const primaryDisplay = await getPrimaryDisplay();
87
+
88
+ sharpInstance = sharp(step1).resize(
89
+ Math.floor(primaryDisplay.currentResX * scale),
90
+ Math.floor(primaryDisplay.currentResY * scale),
91
+ );
59
92
 
60
- // resize to 1:1 px ratio
61
- const sharpInstance = sharp(step1).resize(
62
- Math.floor(primaryDisplay.currentResX * scale),
63
- Math.floor(primaryDisplay.currentResY * scale),
64
- );
93
+ // Fetch the mouse position
94
+ const mousePos = robot.getMousePos();
95
+
96
+ // Location of cursor image
97
+ const cursorPath = path.join(__dirname, "resources", "cursor.png");
98
+
99
+ if (mouse) {
100
+ // composite the mouse image ontop
101
+ sharpInstance.composite([{ input: cursorPath, left: mousePos.x, top: mousePos.y }]);
102
+ }
65
103
 
66
- if (mouse) {
67
- // composite the mouse image ontop
68
- sharpInstance.composite([{ input: cursorPath, left: mousePos.x, top: mousePos.y }]);
69
104
  }
70
105
 
71
106
  await sharpInstance.toFile(step2);
@@ -115,7 +150,7 @@ const platform = () => {
115
150
  // Import get-windows using dynamic import for ES module compatibility
116
151
  let activeWindowFn = null;
117
152
  const initializeActiveWindow = async () => {
118
- if (!activeWindowFn) {
153
+ if (!activeWindowFn && !config.TD_VM) {
119
154
  const { activeWindow } = await import('get-windows');
120
155
  activeWindowFn = activeWindow;
121
156
  }
@@ -124,17 +159,27 @@ const initializeActiveWindow = async () => {
124
159
 
125
160
  // this is the focused window
126
161
  const activeWin = async () => {
127
- try {
128
- const activeWindow = await initializeActiveWindow();
129
- return await activeWindow();
130
- } catch (error) {
131
- logger.error('Error getting active window: %s', error);
132
- return null;
162
+
163
+ if (config.TD_VM) {
164
+
165
+ return "error getting active window, proceed normally";
166
+
167
+ } else {
168
+
169
+ try {
170
+ const activeWindow = await initializeActiveWindow();
171
+ return await activeWindow();
172
+ } catch (error) {
173
+ logger.error('Error getting active window: %s', error);
174
+ return null;
175
+ }
176
+
133
177
  }
134
178
  };
135
179
 
136
180
  const getMousePosition = async () => {
137
- return await robot.getMousePos();
181
+ // @todo vm does expose mouse position, add it back
182
+ return config.TD_VM ? await robot.getMousePos() : null;
138
183
  };
139
184
 
140
185
  module.exports = {
@@ -0,0 +1,78 @@
1
+ const WebSocket = require('ws');
2
+
3
+ class WebSocketServerSingleton {
4
+ constructor() {
5
+ if (!WebSocketServerSingleton.instance) {
6
+ this.wss = new WebSocket.Server({ port: 8080 }, () => {
7
+ });
8
+
9
+ this.clients = new Set();
10
+ this.eventListeners = new Map();
11
+
12
+ this.wss.on('error', (error) => {
13
+ console.error('WebSocket server error:', error);
14
+ });
15
+
16
+ this.wss.on('connection', (ws) => {
17
+
18
+ console.log('Client connected');
19
+ this.clients.add(ws);
20
+
21
+ ws.on('message', (message) => {
22
+ try {
23
+ const parsedMessage = JSON.parse(message);
24
+ if (parsedMessage.event) {
25
+ this.triggerEvent(parsedMessage.event, parsedMessage);
26
+ } else {
27
+ console.error('Parsed message does not contain an event property');
28
+ }
29
+ } catch (error) {
30
+ console.error('Error parsing message:', error);
31
+ }
32
+ });
33
+
34
+ ws.on('close', () => {
35
+ this.clients.delete(ws);
36
+ console.log('Client disconnected');
37
+ });
38
+
39
+ ws.on('error', (error) => {
40
+ console.error('WebSocket error:', error);
41
+ });
42
+ });
43
+
44
+ WebSocketServerSingleton.instance = this;
45
+ }
46
+
47
+ return WebSocketServerSingleton.instance;
48
+ }
49
+
50
+ addEventListener(event, listener) {
51
+ if (!this.eventListeners.has(event)) {
52
+ this.eventListeners.set(event, []);
53
+ }
54
+ this.eventListeners.get(event).push(listener);
55
+ }
56
+
57
+ triggerEvent(event, data) {
58
+ if (this.eventListeners.has(event)) {
59
+ for (const listener of this.eventListeners.get(event)) {
60
+ listener(data);
61
+ }
62
+ }
63
+ }
64
+
65
+ sendToClients(event, message) {
66
+ const data = JSON.stringify({ event, message });
67
+ for (const client of this.clients) {
68
+ if (client.readyState === WebSocket.OPEN) {
69
+ client.send(data);
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ const instance = new WebSocketServerSingleton();
76
+ Object.freeze(instance);
77
+
78
+ module.exports = instance;