frame.simulator 1.0.0
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/command-server.js +132 -0
- package/command-server.ts +143 -0
- package/dist/command-server.d.ts +3 -0
- package/dist/command-server.js +89 -0
- package/dist/simulator.d.ts +12 -0
- package/dist/simulator.js +93 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +2 -0
- package/package.json +20 -0
- package/simulator.ts +109 -0
- package/tsconfig.json +14 -0
- package/types.ts +15 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var express_1 = require("express");
|
|
40
|
+
var cors_1 = require("cors");
|
|
41
|
+
var app = (0, express_1.default)();
|
|
42
|
+
var PORT = 3010;
|
|
43
|
+
// Middleware
|
|
44
|
+
app.use((0, cors_1.default)());
|
|
45
|
+
app.use(express_1.default.json());
|
|
46
|
+
// Store for command queue and results
|
|
47
|
+
var commandQueue = [];
|
|
48
|
+
var commandResults = new Map();
|
|
49
|
+
// Endpoint to receive commands from batch processor
|
|
50
|
+
app.post('/send-command', function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
51
|
+
var _a, command, data, commandId, attempts, maxAttempts, result, error_1;
|
|
52
|
+
return __generator(this, function (_b) {
|
|
53
|
+
switch (_b.label) {
|
|
54
|
+
case 0:
|
|
55
|
+
_b.trys.push([0, 4, , 5]);
|
|
56
|
+
_a = req.body, command = _a.command, data = _a.data;
|
|
57
|
+
console.log('Received command:', command, data);
|
|
58
|
+
commandId = Date.now().toString();
|
|
59
|
+
commandQueue.push({
|
|
60
|
+
id: commandId,
|
|
61
|
+
command: command,
|
|
62
|
+
data: data,
|
|
63
|
+
timestamp: new Date(),
|
|
64
|
+
});
|
|
65
|
+
attempts = 0;
|
|
66
|
+
maxAttempts = 50;
|
|
67
|
+
_b.label = 1;
|
|
68
|
+
case 1:
|
|
69
|
+
if (!(attempts < maxAttempts)) return [3 /*break*/, 3];
|
|
70
|
+
if (commandResults.has(commandId)) {
|
|
71
|
+
result = commandResults.get(commandId);
|
|
72
|
+
commandResults.delete(commandId);
|
|
73
|
+
return [2 /*return*/, res.json(result)];
|
|
74
|
+
}
|
|
75
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
|
|
76
|
+
case 2:
|
|
77
|
+
_b.sent();
|
|
78
|
+
attempts++;
|
|
79
|
+
return [3 /*break*/, 1];
|
|
80
|
+
case 3:
|
|
81
|
+
// Timeout
|
|
82
|
+
res.status(408).json({
|
|
83
|
+
status: 'error',
|
|
84
|
+
message: 'Command timeout',
|
|
85
|
+
});
|
|
86
|
+
return [3 /*break*/, 5];
|
|
87
|
+
case 4:
|
|
88
|
+
error_1 = _b.sent();
|
|
89
|
+
console.error('Server error:', error_1);
|
|
90
|
+
res.status(500).json({
|
|
91
|
+
status: 'error',
|
|
92
|
+
message: error_1 instanceof Error ? error_1.message : 'Unknown error',
|
|
93
|
+
});
|
|
94
|
+
return [3 /*break*/, 5];
|
|
95
|
+
case 5: return [2 /*return*/];
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}); });
|
|
99
|
+
// Endpoint for React app to poll for commands
|
|
100
|
+
app.get('/poll-commands', function (req, res) {
|
|
101
|
+
if (commandQueue.length > 0) {
|
|
102
|
+
var command = commandQueue.shift();
|
|
103
|
+
res.json(command || null);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
res.json(null);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Endpoint for React app to send command results
|
|
110
|
+
app.post('/command-result', function (req, res) {
|
|
111
|
+
var _a = req.body, commandId = _a.commandId, result = _a.result;
|
|
112
|
+
commandResults.set(commandId, result);
|
|
113
|
+
res.json({ status: 'ok' });
|
|
114
|
+
});
|
|
115
|
+
// Health check
|
|
116
|
+
app.get('/health', function (req, res) {
|
|
117
|
+
res.json({
|
|
118
|
+
status: 'healthy',
|
|
119
|
+
queueLength: commandQueue.length,
|
|
120
|
+
timestamp: new Date(),
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
app.listen(PORT, function () {
|
|
124
|
+
console.log('COMMAND SERVER (TYPESCRIPT VERSION)');
|
|
125
|
+
console.log("Command server running on http://localhost:".concat(PORT));
|
|
126
|
+
console.log('Endpoints:');
|
|
127
|
+
console.log(' POST /send-command - Receive batch commands');
|
|
128
|
+
console.log(' GET /poll-commands - React app polls for commands');
|
|
129
|
+
console.log(' POST /command-result - React app sends results');
|
|
130
|
+
console.log(' GET /health - Server health check');
|
|
131
|
+
});
|
|
132
|
+
exports.default = app;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import express, { Response, Request } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
|
|
4
|
+
// Type definitions
|
|
5
|
+
interface CommandRequest {
|
|
6
|
+
command: string;
|
|
7
|
+
data?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface CommandResponse {
|
|
11
|
+
status: 'success' | 'error';
|
|
12
|
+
message?: string;
|
|
13
|
+
result?: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface QueuedCommand {
|
|
17
|
+
id: string;
|
|
18
|
+
command: string;
|
|
19
|
+
data?: any;
|
|
20
|
+
timestamp: Date;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CommandResultRequest {
|
|
24
|
+
commandId: string;
|
|
25
|
+
result: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HealthResponse {
|
|
29
|
+
status: string;
|
|
30
|
+
queueLength: number;
|
|
31
|
+
timestamp: Date;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const app: express.Application = express();
|
|
35
|
+
const PORT: number = 3010;
|
|
36
|
+
|
|
37
|
+
// Middleware
|
|
38
|
+
app.use(cors());
|
|
39
|
+
app.use(express.json());
|
|
40
|
+
|
|
41
|
+
// Store for command queue and results
|
|
42
|
+
let commandQueue: QueuedCommand[] = [];
|
|
43
|
+
let commandResults: Map<string, any> = new Map();
|
|
44
|
+
|
|
45
|
+
// Endpoint to receive commands from batch processor
|
|
46
|
+
app.post(
|
|
47
|
+
'/send-command',
|
|
48
|
+
async (
|
|
49
|
+
req: Request<{}, CommandResponse, CommandRequest>,
|
|
50
|
+
res: Response<CommandResponse>,
|
|
51
|
+
) => {
|
|
52
|
+
try {
|
|
53
|
+
const { command, data } = req.body;
|
|
54
|
+
console.log('Received command:', command, data);
|
|
55
|
+
|
|
56
|
+
// For browser-based commands, we'll use a different approach
|
|
57
|
+
// Store the command and let the React app poll for it
|
|
58
|
+
const commandId: string = Date.now().toString();
|
|
59
|
+
|
|
60
|
+
commandQueue.push({
|
|
61
|
+
id: commandId,
|
|
62
|
+
command,
|
|
63
|
+
data,
|
|
64
|
+
timestamp: new Date(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Wait for result (with timeout)
|
|
68
|
+
let attempts: number = 0;
|
|
69
|
+
const maxAttempts: number = 50; // 5 seconds timeout
|
|
70
|
+
|
|
71
|
+
while (attempts < maxAttempts) {
|
|
72
|
+
if (commandResults.has(commandId)) {
|
|
73
|
+
const result = commandResults.get(commandId);
|
|
74
|
+
commandResults.delete(commandId);
|
|
75
|
+
return res.json(result);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
79
|
+
attempts++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Timeout
|
|
83
|
+
res.status(408).json({
|
|
84
|
+
status: 'error',
|
|
85
|
+
message: 'Command timeout',
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Server error:', error);
|
|
89
|
+
res.status(500).json({
|
|
90
|
+
status: 'error',
|
|
91
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Endpoint for React app to poll for commands
|
|
98
|
+
app.get(
|
|
99
|
+
'/poll-commands',
|
|
100
|
+
(req: Request, res: Response<QueuedCommand | null>) => {
|
|
101
|
+
if (commandQueue.length > 0) {
|
|
102
|
+
const command: QueuedCommand | undefined = commandQueue.shift();
|
|
103
|
+
res.json(command || null);
|
|
104
|
+
} else {
|
|
105
|
+
res.json(null);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Endpoint for React app to send command results
|
|
111
|
+
app.post(
|
|
112
|
+
'/command-result',
|
|
113
|
+
(
|
|
114
|
+
req: Request<{}, { status: string }, CommandResultRequest>,
|
|
115
|
+
res: Response<{ status: string }>,
|
|
116
|
+
) => {
|
|
117
|
+
const { commandId, result } = req.body;
|
|
118
|
+
commandResults.set(commandId, result);
|
|
119
|
+
res.json({ status: 'ok' });
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Health check
|
|
124
|
+
app.get('/health', (req: Request, res: Response<HealthResponse>) => {
|
|
125
|
+
res.json({
|
|
126
|
+
status: 'healthy',
|
|
127
|
+
queueLength: commandQueue.length,
|
|
128
|
+
timestamp: new Date(),
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
app.listen(PORT, () => {
|
|
133
|
+
console.log('COMMAND SERVER (TYPESCRIPT VERSION)');
|
|
134
|
+
|
|
135
|
+
console.log(`Command server running on http://localhost:${PORT}`);
|
|
136
|
+
console.log('Endpoints:');
|
|
137
|
+
console.log(' POST /send-command - Receive batch commands');
|
|
138
|
+
console.log(' GET /poll-commands - React app polls for commands');
|
|
139
|
+
console.log(' POST /command-result - React app sends results');
|
|
140
|
+
console.log(' GET /health - Server health check');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export default app;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const cors_1 = __importDefault(require("cors"));
|
|
8
|
+
const app = (0, express_1.default)();
|
|
9
|
+
const PORT = 3010;
|
|
10
|
+
// Middleware
|
|
11
|
+
app.use((0, cors_1.default)());
|
|
12
|
+
app.use(express_1.default.json());
|
|
13
|
+
// Store for command queue and results
|
|
14
|
+
let commandQueue = [];
|
|
15
|
+
let commandResults = new Map();
|
|
16
|
+
// Endpoint to receive commands from batch processor
|
|
17
|
+
app.post('/send-command', async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const { command, data } = req.body;
|
|
20
|
+
console.log('Received command:', command, data);
|
|
21
|
+
// For browser-based commands, we'll use a different approach
|
|
22
|
+
// Store the command and let the React app poll for it
|
|
23
|
+
const commandId = Date.now().toString();
|
|
24
|
+
commandQueue.push({
|
|
25
|
+
id: commandId,
|
|
26
|
+
command,
|
|
27
|
+
data,
|
|
28
|
+
timestamp: new Date(),
|
|
29
|
+
});
|
|
30
|
+
// Wait for result (with timeout)
|
|
31
|
+
let attempts = 0;
|
|
32
|
+
const maxAttempts = 50; // 5 seconds timeout
|
|
33
|
+
while (attempts < maxAttempts) {
|
|
34
|
+
if (commandResults.has(commandId)) {
|
|
35
|
+
const result = commandResults.get(commandId);
|
|
36
|
+
commandResults.delete(commandId);
|
|
37
|
+
return res.json(result);
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
40
|
+
attempts++;
|
|
41
|
+
}
|
|
42
|
+
// Timeout
|
|
43
|
+
res.status(408).json({
|
|
44
|
+
status: 'error',
|
|
45
|
+
message: 'Command timeout',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Server error:', error);
|
|
50
|
+
res.status(500).json({
|
|
51
|
+
status: 'error',
|
|
52
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Endpoint for React app to poll for commands
|
|
57
|
+
app.get('/poll-commands', (req, res) => {
|
|
58
|
+
if (commandQueue.length > 0) {
|
|
59
|
+
const command = commandQueue.shift();
|
|
60
|
+
res.json(command || null);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
res.json(null);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// Endpoint for React app to send command results
|
|
67
|
+
app.post('/command-result', (req, res) => {
|
|
68
|
+
const { commandId, result } = req.body;
|
|
69
|
+
commandResults.set(commandId, result);
|
|
70
|
+
res.json({ status: 'ok' });
|
|
71
|
+
});
|
|
72
|
+
// Health check
|
|
73
|
+
app.get('/health', (req, res) => {
|
|
74
|
+
res.json({
|
|
75
|
+
status: 'healthy',
|
|
76
|
+
queueLength: commandQueue.length,
|
|
77
|
+
timestamp: new Date(),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
app.listen(PORT, () => {
|
|
81
|
+
console.log('COMMAND SERVER (TYPESCRIPT VERSION)');
|
|
82
|
+
console.log(`Command server running on http://localhost:${PORT}`);
|
|
83
|
+
console.log('Endpoints:');
|
|
84
|
+
console.log(' POST /send-command - Receive batch commands');
|
|
85
|
+
console.log(' GET /poll-commands - React app polls for commands');
|
|
86
|
+
console.log(' POST /command-result - React app sends results');
|
|
87
|
+
console.log(' GET /health - Server health check');
|
|
88
|
+
});
|
|
89
|
+
exports.default = app;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TClick, TClickOnCanvas, TScaleFrame } from './types';
|
|
2
|
+
export declare class Simulator {
|
|
3
|
+
constructor();
|
|
4
|
+
findHTMLElement(testId: string, elementType: typeof HTMLElement, type?: string): HTMLElement | null;
|
|
5
|
+
simulateClickOnCanvas(data: TClickOnCanvas): void;
|
|
6
|
+
simulateUserClick(data: TClick): void;
|
|
7
|
+
simulateUserCheck(data: TClick): void;
|
|
8
|
+
simulateUserInput(testId: string, value: string): {
|
|
9
|
+
status: string;
|
|
10
|
+
} | undefined;
|
|
11
|
+
simulateScaleFrame(scaleValues: TScaleFrame): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Simulator = void 0;
|
|
4
|
+
const frame_constants_1 = require("frame.constants");
|
|
5
|
+
class Simulator {
|
|
6
|
+
constructor() {
|
|
7
|
+
console.log('Simulator initialized');
|
|
8
|
+
}
|
|
9
|
+
findHTMLElement(testId, elementType, type) {
|
|
10
|
+
if (type) {
|
|
11
|
+
console.log(`${frame_constants_1.green}Simulating ${type} on element with testId: ${testId}${frame_constants_1.reset}`);
|
|
12
|
+
}
|
|
13
|
+
const element = document.querySelector(`[data-testid="${testId}"]`);
|
|
14
|
+
if (element) {
|
|
15
|
+
if (element instanceof elementType) {
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(`Element found but not a ${elementType.name} for ${type} simulation: ${testId}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.error(`Element not found for ${type} simulation: ${testId}`);
|
|
23
|
+
}
|
|
24
|
+
return element;
|
|
25
|
+
}
|
|
26
|
+
simulateClickOnCanvas(data) {
|
|
27
|
+
const { x, y } = data;
|
|
28
|
+
const canvas = this.findHTMLElement('canvas.draw', HTMLCanvasElement, 'click');
|
|
29
|
+
if (canvas) {
|
|
30
|
+
const rect = canvas.getBoundingClientRect();
|
|
31
|
+
const event = new MouseEvent('click', {
|
|
32
|
+
clientX: rect.left + rect.width / 2 + x, // Convert canvas-relative to screen coordinates
|
|
33
|
+
clientY: rect.top + rect.height / 2 - y, // Convert canvas-relative to screen coordinates (flip Y)
|
|
34
|
+
bubbles: true,
|
|
35
|
+
});
|
|
36
|
+
// Set batch properties
|
|
37
|
+
// @ts-ignore - Add custom properties for batch identification
|
|
38
|
+
event.isBatch = true;
|
|
39
|
+
// @ts-ignore - Store original canvas-relative coordinates
|
|
40
|
+
event.batchX = x;
|
|
41
|
+
// @ts-ignore - Store original canvas-relative coordinates
|
|
42
|
+
event.batchY = y;
|
|
43
|
+
canvas.dispatchEvent(event);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
simulateUserClick(data) {
|
|
47
|
+
const { testId } = data;
|
|
48
|
+
const element = this.findHTMLElement(testId, HTMLButtonElement, 'click');
|
|
49
|
+
if (element) {
|
|
50
|
+
const event = new MouseEvent('click', { bubbles: true });
|
|
51
|
+
// @ts-ignore - Add custom property to identify this as a batch click
|
|
52
|
+
event.isBatch = true;
|
|
53
|
+
element.click();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
simulateUserCheck(data) {
|
|
57
|
+
const { testId } = data;
|
|
58
|
+
const element = this.findHTMLElement(testId, HTMLInputElement, 'check');
|
|
59
|
+
if (element) {
|
|
60
|
+
const event = new MouseEvent('click', { bubbles: true });
|
|
61
|
+
// @ts-ignore - Add custom property to identify this as a batch click
|
|
62
|
+
event.isBatch = true;
|
|
63
|
+
element.dispatchEvent(event);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
simulateUserInput(testId, value) {
|
|
67
|
+
const element = this.findHTMLElement(testId, HTMLDivElement, 'input');
|
|
68
|
+
if (element) {
|
|
69
|
+
if (element instanceof HTMLInputElement) {
|
|
70
|
+
element.value = value.toString();
|
|
71
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
72
|
+
return { status: 'ok' };
|
|
73
|
+
}
|
|
74
|
+
else if (element instanceof HTMLDivElement) {
|
|
75
|
+
const childElement = element.firstChild;
|
|
76
|
+
if (childElement instanceof HTMLInputElement) {
|
|
77
|
+
childElement.value = value || '';
|
|
78
|
+
childElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
simulateScaleFrame(scaleValues) {
|
|
84
|
+
const { length, height } = scaleValues;
|
|
85
|
+
console.log(`${frame_constants_1.red}Simulating scale frame with length: ${length}, height: ${height}${frame_constants_1.reset}`);
|
|
86
|
+
this.simulateUserInput('ScaleInput.input.length', length.toString());
|
|
87
|
+
this.simulateUserInput('ScaleInput.input.height', height.toString());
|
|
88
|
+
this.simulateUserCheck({ testId: 'ShowOptions.cbx.scaledBox' });
|
|
89
|
+
this.simulateUserClick({ testId: 'ScaleForm.button.apply' });
|
|
90
|
+
this.simulateUserCheck({ testId: 'ShowOptions.cbx.points' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.Simulator = Simulator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../command-server.ts","../simulator.ts","../types.ts"],"errors":true,"version":"5.6.3"}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frame.simulator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simulator for testing form interactions in a controlled environment.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -b",
|
|
9
|
+
"command-server": "tsx command-server.ts"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"frame.constants": "^1.0.5"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"express": "^5.2.1",
|
|
17
|
+
"frame.constants": "^1.0.5",
|
|
18
|
+
"tsx": "^4.21.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/simulator.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { green, red, reset } from 'frame.constants';
|
|
2
|
+
import { TClick, TClickOnCanvas, TScaleFrame } from './types';
|
|
3
|
+
|
|
4
|
+
export class Simulator {
|
|
5
|
+
constructor() {
|
|
6
|
+
console.log('Simulator initialized');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
findHTMLElement(
|
|
10
|
+
testId: string,
|
|
11
|
+
elementType: typeof HTMLElement,
|
|
12
|
+
type?: string,
|
|
13
|
+
): HTMLElement | null {
|
|
14
|
+
if (type) {
|
|
15
|
+
console.log(
|
|
16
|
+
`${green}Simulating ${type} on element with testId: ${testId}${reset}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const element = document.querySelector(`[data-testid="${testId}"]`);
|
|
20
|
+
if (element) {
|
|
21
|
+
if (element instanceof elementType) {
|
|
22
|
+
} else {
|
|
23
|
+
console.error(
|
|
24
|
+
`Element found but not a ${elementType.name} for ${type} simulation: ${testId}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
console.error(`Element not found for ${type} simulation: ${testId}`);
|
|
29
|
+
}
|
|
30
|
+
return element as HTMLElement;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
simulateClickOnCanvas(data: TClickOnCanvas) {
|
|
34
|
+
const { x, y } = data;
|
|
35
|
+
const canvas = this.findHTMLElement(
|
|
36
|
+
'canvas.draw',
|
|
37
|
+
HTMLCanvasElement,
|
|
38
|
+
'click',
|
|
39
|
+
);
|
|
40
|
+
if (canvas) {
|
|
41
|
+
const rect = canvas.getBoundingClientRect();
|
|
42
|
+
const event = new MouseEvent('click', {
|
|
43
|
+
clientX: rect.left + rect.width / 2 + x, // Convert canvas-relative to screen coordinates
|
|
44
|
+
clientY: rect.top + rect.height / 2 - y, // Convert canvas-relative to screen coordinates (flip Y)
|
|
45
|
+
bubbles: true,
|
|
46
|
+
});
|
|
47
|
+
// Set batch properties
|
|
48
|
+
// @ts-ignore - Add custom properties for batch identification
|
|
49
|
+
event.isBatch = true;
|
|
50
|
+
// @ts-ignore - Store original canvas-relative coordinates
|
|
51
|
+
event.batchX = x;
|
|
52
|
+
// @ts-ignore - Store original canvas-relative coordinates
|
|
53
|
+
event.batchY = y;
|
|
54
|
+
canvas.dispatchEvent(event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
simulateUserClick(data: TClick) {
|
|
59
|
+
const { testId } = data;
|
|
60
|
+
const element = this.findHTMLElement(testId, HTMLButtonElement, 'click');
|
|
61
|
+
if (element) {
|
|
62
|
+
const event = new MouseEvent('click', { bubbles: true });
|
|
63
|
+
// @ts-ignore - Add custom property to identify this as a batch click
|
|
64
|
+
event.isBatch = true;
|
|
65
|
+
element.click();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
simulateUserCheck(data: TClick) {
|
|
70
|
+
const { testId } = data;
|
|
71
|
+
const element = this.findHTMLElement(testId, HTMLInputElement, 'check');
|
|
72
|
+
if (element) {
|
|
73
|
+
const event = new MouseEvent('click', { bubbles: true });
|
|
74
|
+
// @ts-ignore - Add custom property to identify this as a batch click
|
|
75
|
+
event.isBatch = true;
|
|
76
|
+
element.dispatchEvent(event);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
simulateUserInput(testId: string, value: string) {
|
|
81
|
+
const element = this.findHTMLElement(testId, HTMLDivElement, 'input');
|
|
82
|
+
|
|
83
|
+
if (element) {
|
|
84
|
+
if (element instanceof HTMLInputElement) {
|
|
85
|
+
element.value = value.toString();
|
|
86
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
87
|
+
return { status: 'ok' };
|
|
88
|
+
} else if (element instanceof HTMLDivElement) {
|
|
89
|
+
const childElement = element.firstChild as HTMLElement;
|
|
90
|
+
if (childElement instanceof HTMLInputElement) {
|
|
91
|
+
childElement.value = value || '';
|
|
92
|
+
childElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
simulateScaleFrame(scaleValues: TScaleFrame) {
|
|
99
|
+
const { length, height } = scaleValues;
|
|
100
|
+
console.log(
|
|
101
|
+
`${red}Simulating scale frame with length: ${length}, height: ${height}${reset}`,
|
|
102
|
+
);
|
|
103
|
+
this.simulateUserInput('ScaleInput.input.length', length.toString());
|
|
104
|
+
this.simulateUserInput('ScaleInput.input.height', height.toString());
|
|
105
|
+
this.simulateUserCheck({ testId: 'ShowOptions.cbx.scaledBox' });
|
|
106
|
+
this.simulateUserClick({ testId: 'ScaleForm.button.apply' });
|
|
107
|
+
this.simulateUserCheck({ testId: 'ShowOptions.cbx.points' });
|
|
108
|
+
}
|
|
109
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["*.ts"],
|
|
13
|
+
"exclude": ["node_modules", "dist"]
|
|
14
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type TClick = {
|
|
2
|
+
testId: string;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export type TClickOnCanvas = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type TScaleFrame = {
|
|
11
|
+
length: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TUserAction = TClickOnCanvas | TScaleFrame | TClick;
|