feat: Add Playwright MCP testing suite with various automation scripts
- Implemented test client for Playwright MCP Server in `test-server.js` - Created global automation script for Playwright MCP-like functionality in `playwright-mcp-global.js` - Configured Playwright settings in `playwright.config.js` - Added quick start script for testing setup in `quick-start.sh` - Developed initial setup script for Playwright MCP configuration in `setup.js` - Created basic connectivity and configuration tests in `tests/basic.spec.js` and `tests/configuration.spec.js` - Implemented dashboard and plotting tests in `tests/dashboard.spec.js` and `tests/plotting.spec.js` - Added data streaming feature tests in `tests/streaming.spec.js`
This commit is contained in:
parent
1b6528977a
commit
01489aec59
|
@ -1194,8 +1194,716 @@
|
|||
"read_time_avg": 0.028721141815185546,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:24:49.597012",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.002967834472656,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994066092140317,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.2,
|
||||
"cpu_max": 0.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.027550315856933592,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:24:59.600185",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.003173112869263,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9993655787351754,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.2,
|
||||
"cpu_max": 0.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.024491679668426514,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:09.602871",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.3% CPU",
|
||||
"details": {
|
||||
"duration": 10.002686023712158,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994629395132886,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.3,
|
||||
"cpu_max": 0.3,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02641488313674927,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:19.605776",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.00290560722351,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994190473573175,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02399500608444214,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:29.608737",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.002960920333862,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994079912223104,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.2,
|
||||
"cpu_max": 0.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.03274095058441162,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:39.612644",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.003398895263672,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9993204519185412,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.2,
|
||||
"cpu_max": 0.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.024168848991394043,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:49.615990",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.3% CPU",
|
||||
"details": {
|
||||
"duration": 10.003854274749756,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.999229442044256,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.3,
|
||||
"cpu_max": 0.3,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.024407362937927245,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:25:59.618730",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.002739667892456,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994522164959967,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.030548858642578124,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:09.621902",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.6% CPU",
|
||||
"details": {
|
||||
"duration": 10.003172159194946,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9993657693490698,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.6,
|
||||
"cpu_max": 0.6,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.025050425529479982,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:19.624848",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.002945184707642,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9994111364896523,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02940622568130493,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:29.627925",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.003077507019043,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.999384687958904,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.2,
|
||||
"cpu_max": 0.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02933335304260254,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:39.631482",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.003049612045288,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9993902635369085,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.025867748260498046,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:49.634184",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.003209590911865,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9993582877809977,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.025532746315002443,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:26:59.638474",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.003774642944336,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9992453562621988,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.024468183517456055,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:09.642164",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.5% CPU",
|
||||
"details": {
|
||||
"duration": 10.004204988479614,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9991593557939973,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.5,
|
||||
"cpu_max": 0.5,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02346416711807251,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:19.646286",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 20 points saved, 0 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.00360369682312,
|
||||
"points_saved": 20,
|
||||
"points_rate": 1.9992795202744258,
|
||||
"variables_saved": 80,
|
||||
"udp_points_sent": 80,
|
||||
"points_lost": 0,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 0,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.02898404598236084,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:27.072110",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.02567434310913086,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:28.103420",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.028438329696655273,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:29.136603",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.02920389175415039,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:29.649444",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 14 points saved, 2 lost, 0.0% CPU",
|
||||
"details": {
|
||||
"duration": 10.003676652908325,
|
||||
"points_saved": 14,
|
||||
"points_rate": 1.3994854577721523,
|
||||
"variables_saved": 56,
|
||||
"udp_points_sent": 56,
|
||||
"points_lost": 2,
|
||||
"cpu_average": 0.0,
|
||||
"cpu_max": 0.0,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 3,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.028916682515825545,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:30.172484",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.03212475776672363,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:31.202046",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.025721073150634766,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:32.233564",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.028671979904174805,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:33.267227",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.030137062072753906,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:34.293528",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.022943496704101562,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:35.326557",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.029216527938842773,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:36.355204",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.02415752410888672,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:37.390098",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.03090071678161621,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:38.423909",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.030216217041015625,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:39.454574",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.027634859085083008,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:39.653724",
|
||||
"level": "info",
|
||||
"event_type": "performance_report",
|
||||
"message": "Performance report: 0 points saved, 10 lost, 1.2% CPU",
|
||||
"details": {
|
||||
"duration": 10.004280090332031,
|
||||
"points_saved": 0,
|
||||
"points_rate": 0.0,
|
||||
"variables_saved": 0,
|
||||
"udp_points_sent": 0,
|
||||
"points_lost": 10,
|
||||
"cpu_average": 1.2,
|
||||
"cpu_max": 1.2,
|
||||
"delay_average": 0.0,
|
||||
"delay_max": 0.0,
|
||||
"read_errors": 10,
|
||||
"csv_errors": 0,
|
||||
"udp_errors": 0,
|
||||
"read_time_avg": 0.0,
|
||||
"csv_write_time_avg": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:40.489019",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.03096294403076172,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:41.522151",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.030019760131835938,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:42.553337",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.027550458908081055,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:43.585686",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.02818918228149414,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:44.621062",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.03146553039550781,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:45.652077",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.028136253356933594,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:46.683027",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.027202129364013672,
|
||||
"variables_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-27T09:27:47.716461",
|
||||
"level": "error",
|
||||
"event_type": "dataset_loop_error",
|
||||
"message": "🚨 CRITICAL ERROR: Dataset 'DAR' recording loop error: cannot schedule new futures after shutdown",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"error": "cannot schedule new futures after shutdown",
|
||||
"consecutive_errors": 1,
|
||||
"priority": "CRITICAL",
|
||||
"read_time": 0.02987360954284668,
|
||||
"variables_count": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-27T09:24:39.594044",
|
||||
"total_entries": 54
|
||||
"last_updated": "2025-08-27T09:27:47.716461",
|
||||
"total_entries": 93
|
||||
}
|
|
@ -537,9 +537,7 @@ class ConfigManager:
|
|||
self.save_configuration()
|
||||
return {"old_config": old_config, "new_config": self.csv_config}
|
||||
|
||||
def get_csv_directory_path(self) -> str:
|
||||
"""Get the configured CSV directory path"""
|
||||
return self.csv_config["records_directory"]
|
||||
|
||||
|
||||
def get_csv_file_directory_path(self) -> str:
|
||||
"""Get the directory path for current day's CSV files"""
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"playwright-automation": {
|
||||
"command": "node",
|
||||
"args": ["src/server.js"],
|
||||
"cwd": "testing/mcp-server",
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
{
|
||||
"last_state": {
|
||||
"should_connect": true,
|
||||
"should_connect": false,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"DAR"
|
||||
]
|
||||
"active_datasets": []
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-27T09:24:15.915232",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
"last_update": "2025-08-27T09:27:26.829523"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
# Playwright
|
||||
test-results/
|
||||
playwright-report/
|
||||
playwright/.cache/
|
||||
|
||||
# Screenshots y videos
|
||||
screenshots/*.png
|
||||
videos/*.webm
|
||||
|
||||
# Reportes y logs
|
||||
*.json
|
||||
*.log
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env.local
|
|
@ -0,0 +1,291 @@
|
|||
# Playwright MCP Automation for PLC Streamer
|
||||
|
||||
Este directorio contiene la configuración completa de Playwright para automatización y testing del proyecto PLC Streamer, con funcionalidad similar a MCP (Model Context Protocol) para interacción con navegadores.
|
||||
|
||||
## 🚀 Instalación Global
|
||||
|
||||
### 1. Instalar Playwright globalmente
|
||||
```bash
|
||||
npm install -g playwright
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
### 2. Configurar para uso en otros proyectos
|
||||
```bash
|
||||
# Copiar el script global a un directorio en PATH
|
||||
cp playwright-mcp-global.js ~/.local/bin/playwright-mcp
|
||||
chmod +x ~/.local/bin/playwright-mcp
|
||||
|
||||
# O en Windows, copiar a un directorio en PATH
|
||||
copy playwright-mcp-global.js C:\tools\playwright-mcp.js
|
||||
```
|
||||
|
||||
## 📁 Estructura del Proyecto
|
||||
|
||||
```
|
||||
testing/
|
||||
├── package.json # Dependencias de testing
|
||||
├── playwright.config.js # Configuración principal
|
||||
├── browser-automation.js # Automatización específica del proyecto
|
||||
├── playwright-mcp-global.js # Script global reutilizable
|
||||
├── tests/ # Tests específicos
|
||||
│ ├── dashboard.spec.js
|
||||
│ ├── configuration.spec.js
|
||||
│ ├── streaming.spec.js
|
||||
│ └── plotting.spec.js
|
||||
├── screenshots/ # Capturas de pantalla
|
||||
├── videos/ # Grabaciones de video
|
||||
└── README.md # Este archivo
|
||||
```
|
||||
|
||||
## 🛠️ Uso Local (Proyecto PLC Streamer)
|
||||
|
||||
### Ejecutar Tests
|
||||
```bash
|
||||
cd testing
|
||||
|
||||
# Ejecutar todos los tests
|
||||
npm test
|
||||
|
||||
# Ejecutar con interfaz visual
|
||||
npm run test:headed
|
||||
|
||||
# Ejecutar con modo debug
|
||||
npm run test:debug
|
||||
|
||||
# Ejecutar con UI de Playwright
|
||||
npm run ui
|
||||
```
|
||||
|
||||
### Generar Código de Test
|
||||
```bash
|
||||
# Generar código interactivo
|
||||
npm run codegen
|
||||
|
||||
# Generar código específico para la app
|
||||
npm run codegen:app
|
||||
```
|
||||
|
||||
### Automatización Específica del Proyecto
|
||||
```bash
|
||||
# Ejecutar automatización completa
|
||||
node browser-automation.js
|
||||
|
||||
# O importar en otro script
|
||||
import PLCStreamerBrowserAutomation from './browser-automation.js';
|
||||
|
||||
const automation = new PLCStreamerBrowserAutomation();
|
||||
await automation.initBrowser('chromium');
|
||||
await automation.testConfigurationFlow();
|
||||
```
|
||||
|
||||
## 🌐 Uso Global (Otros Proyectos)
|
||||
|
||||
### Script CLI Global
|
||||
```bash
|
||||
# Ejecutar tests básicos
|
||||
node playwright-mcp-global.js test --url http://localhost:3000 --browser chromium
|
||||
|
||||
# Capturar screenshots
|
||||
node playwright-mcp-global.js capture --url http://localhost:8080 --output ./captures
|
||||
|
||||
# Monitorear performance
|
||||
node playwright-mcp-global.js monitor --url http://localhost:3000 --duration 120
|
||||
```
|
||||
|
||||
### Ejemplo de Integración en Otros Proyectos
|
||||
```javascript
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
// Para React apps
|
||||
const testReactApp = async () => {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto('http://localhost:3000');
|
||||
await page.waitForSelector('[data-testid="app"]');
|
||||
|
||||
// Tu lógica de testing aquí
|
||||
|
||||
await browser.close();
|
||||
};
|
||||
|
||||
// Para Vue apps
|
||||
const testVueApp = async () => {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto('http://localhost:8080');
|
||||
await page.waitForSelector('#app');
|
||||
|
||||
// Tu lógica de testing aquí
|
||||
|
||||
await browser.close();
|
||||
};
|
||||
```
|
||||
|
||||
## 🔧 Funcionalidades MCP-like
|
||||
|
||||
### 1. Automatización de Navegador
|
||||
- Control completo del navegador (Chromium, Firefox, WebKit)
|
||||
- Interacción con elementos de la página
|
||||
- Manejo de formularios y eventos
|
||||
- Navegación automática entre páginas
|
||||
|
||||
### 2. Captura de Datos
|
||||
- Screenshots automáticos
|
||||
- Grabación de video de sesiones
|
||||
- Métricas de performance
|
||||
- Logs de consola y errores
|
||||
|
||||
### 3. Monitoreo en Tiempo Real
|
||||
- Estado de conexión PLC
|
||||
- Performance de la aplicación
|
||||
- Uso de memoria
|
||||
- Tiempos de carga
|
||||
|
||||
### 4. Reporting Automático
|
||||
- Generación de reportes JSON
|
||||
- Métricas de performance
|
||||
- Estados de la aplicación
|
||||
- Capturas de evidencia
|
||||
|
||||
## 📊 Tests Específicos del PLC Streamer
|
||||
|
||||
### Dashboard Tests (`dashboard.spec.js`)
|
||||
- Carga correcta de la aplicación
|
||||
- Navegación entre tabs
|
||||
- Estado de conexión PLC
|
||||
- Elementos de interfaz
|
||||
|
||||
### Configuration Tests (`configuration.spec.js`)
|
||||
- Formularios de configuración
|
||||
- Validación de datos
|
||||
- Guardado de configuraciones
|
||||
- Manejo de errores
|
||||
|
||||
### Streaming Tests (`streaming.spec.js`)
|
||||
- Control de streaming
|
||||
- Gestión de variables
|
||||
- Configuración de datasets
|
||||
- Estados de streaming
|
||||
|
||||
### Plotting Tests (`plotting.spec.js`)
|
||||
- Creación de plots
|
||||
- Actualización en tiempo real
|
||||
- Exportación de datos
|
||||
- Gestión de sesiones
|
||||
|
||||
## 🚀 Comandos Rápidos
|
||||
|
||||
```bash
|
||||
# Setup inicial
|
||||
npm install
|
||||
|
||||
# Test completo con reporte
|
||||
npm test -- --reporter=html
|
||||
|
||||
# Debug test específico
|
||||
npx playwright test dashboard.spec.js --debug
|
||||
|
||||
# Generar código para nueva funcionalidad
|
||||
npx playwright codegen http://localhost:5173/app
|
||||
|
||||
# Ejecutar con todos los navegadores
|
||||
npx playwright test --project=chromium --project=firefox --project=webkit
|
||||
|
||||
# Solo móviles
|
||||
npx playwright test --project="Mobile Chrome" --project="Mobile Safari"
|
||||
```
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Ver Reports HTML
|
||||
```bash
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
### Ver Trace Viewer
|
||||
```bash
|
||||
npx playwright show-trace trace.zip
|
||||
```
|
||||
|
||||
### Modo Debug Interactivo
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
## 🌟 Características Avanzadas
|
||||
|
||||
### 1. Test Paralelo
|
||||
Los tests se ejecutan en paralelo por defecto para mayor velocidad.
|
||||
|
||||
### 2. Auto-waiting
|
||||
Playwright espera automáticamente a que los elementos estén listos.
|
||||
|
||||
### 3. Cross-browser
|
||||
Soporte nativo para Chromium, Firefox y WebKit.
|
||||
|
||||
### 4. Mobile Testing
|
||||
Tests en viewports móviles incluidos.
|
||||
|
||||
### 5. Video Recording
|
||||
Grabación automática de videos en fallos.
|
||||
|
||||
### 6. Network Interception
|
||||
Capacidad de interceptar y modificar requests de red.
|
||||
|
||||
## 🔧 Configuración Personalizada
|
||||
|
||||
### Modificar `playwright.config.js`
|
||||
```javascript
|
||||
export default defineConfig({
|
||||
// Tu configuración personalizada
|
||||
testDir: './tests',
|
||||
timeout: 30000,
|
||||
retries: 2,
|
||||
use: {
|
||||
baseURL: 'http://localhost:5173',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Variables de Entorno
|
||||
```bash
|
||||
# Configurar URLs dinámicamente
|
||||
FRONTEND_URL=http://localhost:3000 npm test
|
||||
BACKEND_URL=http://localhost:8000 npm test
|
||||
```
|
||||
|
||||
## 📝 Integración con CI/CD
|
||||
|
||||
### GitHub Actions Example
|
||||
```yaml
|
||||
- name: Install Playwright
|
||||
run: npx playwright install
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
```
|
||||
|
||||
## 🤝 Contribuir
|
||||
|
||||
1. Agregar nuevos tests en `/tests/`
|
||||
2. Extender `browser-automation.js` con nuevas funcionalidades
|
||||
3. Actualizar configuración en `playwright.config.js`
|
||||
4. Documentar cambios en este README
|
||||
|
||||
## 📚 Recursos Adicionales
|
||||
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [Playwright Test](https://playwright.dev/docs/test-intro)
|
||||
- [Playwright Inspector](https://playwright.dev/docs/inspector)
|
||||
- [Playwright Trace Viewer](https://playwright.dev/docs/trace-viewer)
|
|
@ -0,0 +1,255 @@
|
|||
import { chromium, firefox, webkit } from 'playwright';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* PLC Streamer Browser Automation and Testing Suite
|
||||
* This class provides MCP-like functionality for browser automation
|
||||
*/
|
||||
class PLCStreamerBrowserAutomation {
|
||||
constructor() {
|
||||
this.browser = null;
|
||||
this.context = null;
|
||||
this.page = null;
|
||||
this.baseURL = 'http://localhost:5173';
|
||||
this.backendURL = 'http://localhost:5000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize browser instance
|
||||
* @param {string} browserType - 'chromium', 'firefox', or 'webkit'
|
||||
* @param {object} options - Browser launch options
|
||||
*/
|
||||
async initBrowser(browserType = 'chromium', options = {}) {
|
||||
const browserLaunchers = {
|
||||
chromium: chromium,
|
||||
firefox: firefox,
|
||||
webkit: webkit
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
headless: false,
|
||||
slowMo: 100,
|
||||
args: ['--disable-web-security', '--disable-features=VizDisplayCompositor']
|
||||
};
|
||||
|
||||
this.browser = await browserLaunchers[browserType].launch({
|
||||
...defaultOptions,
|
||||
...options
|
||||
});
|
||||
|
||||
this.context = await this.browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
recordVideo: { dir: 'videos/' }
|
||||
});
|
||||
|
||||
this.page = await this.context.newPage();
|
||||
|
||||
// Add console logging
|
||||
this.page.on('console', msg => {
|
||||
console.log(`Browser Console [${msg.type()}]: ${msg.text()}`);
|
||||
});
|
||||
|
||||
// Add error logging
|
||||
this.page.on('pageerror', error => {
|
||||
console.error(`Browser Error: ${error.message}`);
|
||||
});
|
||||
|
||||
return this.page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the PLC Streamer application
|
||||
*/
|
||||
async navigateToApp() {
|
||||
await this.page.goto(`${this.baseURL}/app`);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
return this.page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if backend server is running
|
||||
*/
|
||||
async checkBackendStatus() {
|
||||
try {
|
||||
const response = await this.page.request.get(`${this.backendURL}/api/status`);
|
||||
return response.ok();
|
||||
} catch (error) {
|
||||
console.error('Backend not reachable:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor PLC connection status
|
||||
*/
|
||||
async monitorPLCConnection() {
|
||||
await this.navigateToApp();
|
||||
|
||||
const connectionStatus = await this.page.locator('[data-testid="connection-status"]').textContent();
|
||||
console.log(`PLC Connection Status: ${connectionStatus}`);
|
||||
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automated configuration testing
|
||||
*/
|
||||
async testConfigurationFlow() {
|
||||
await this.navigateToApp();
|
||||
|
||||
// Navigate to configuration
|
||||
await this.page.click('text=Configuration');
|
||||
await this.page.waitForSelector('[data-testid="configuration-panel"]');
|
||||
|
||||
// Test PLC configuration
|
||||
const ipInput = this.page.locator('input[name*="ip"]').first();
|
||||
await ipInput.clear();
|
||||
await ipInput.fill('192.168.1.100');
|
||||
|
||||
// Save configuration
|
||||
await this.page.click('button:has-text("Save")');
|
||||
|
||||
// Wait for save confirmation
|
||||
await this.page.waitForSelector('text*=saved', { timeout: 5000 });
|
||||
|
||||
console.log('Configuration test completed successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automated streaming test
|
||||
*/
|
||||
async testStreamingFlow() {
|
||||
await this.navigateToApp();
|
||||
|
||||
// Navigate to streaming
|
||||
await this.page.click('text=Data Streaming');
|
||||
await this.page.waitForSelector('[data-testid="streaming-panel"]');
|
||||
|
||||
// Start streaming
|
||||
await this.page.click('button:has-text("Start Streaming")');
|
||||
|
||||
// Wait a bit for streaming to initialize
|
||||
await this.page.waitForTimeout(3000);
|
||||
|
||||
// Stop streaming
|
||||
await this.page.click('button:has-text("Stop Streaming")');
|
||||
|
||||
console.log('Streaming test completed successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take screenshots for documentation
|
||||
*/
|
||||
async captureScreenshots() {
|
||||
await this.navigateToApp();
|
||||
|
||||
const tabs = ['Dashboard', 'Configuration', 'Data Streaming', 'Plots'];
|
||||
|
||||
for (const tab of tabs) {
|
||||
await this.page.click(`text=${tab}`);
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
const screenshot = await this.page.screenshot({
|
||||
path: `screenshots/${tab.toLowerCase().replace(' ', '_')}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
console.log(`Screenshot saved: ${tab}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance monitoring
|
||||
*/
|
||||
async monitorPerformance() {
|
||||
await this.navigateToApp();
|
||||
|
||||
// Start performance monitoring
|
||||
await this.page.evaluate(() => performance.mark('test-start'));
|
||||
|
||||
// Navigate through all tabs
|
||||
const tabs = ['Configuration', 'Data Streaming', 'Plots', 'Dashboard'];
|
||||
|
||||
for (const tab of tabs) {
|
||||
const startTime = performance.now();
|
||||
await this.page.click(`text=${tab}`);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
const endTime = performance.now();
|
||||
|
||||
console.log(`Tab ${tab} load time: ${endTime - startTime}ms`);
|
||||
}
|
||||
|
||||
// Get performance metrics
|
||||
const metrics = await this.page.evaluate(() => {
|
||||
return JSON.stringify(performance.getEntriesByType('navigation'));
|
||||
});
|
||||
|
||||
await fs.writeFile('performance-metrics.json', metrics);
|
||||
console.log('Performance metrics saved');
|
||||
|
||||
return JSON.parse(metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test report
|
||||
*/
|
||||
async generateReport() {
|
||||
const backendStatus = await this.checkBackendStatus();
|
||||
const plcStatus = await this.monitorPLCConnection();
|
||||
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
backendStatus: backendStatus ? 'Online' : 'Offline',
|
||||
plcConnectionStatus: plcStatus,
|
||||
browserInfo: await this.page.evaluate(() => navigator.userAgent),
|
||||
url: this.page.url()
|
||||
};
|
||||
|
||||
await fs.writeFile('automation-report.json', JSON.stringify(report, null, 2));
|
||||
console.log('Report generated: automation-report.json');
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup resources
|
||||
*/
|
||||
async cleanup() {
|
||||
if (this.page) await this.page.close();
|
||||
if (this.context) await this.context.close();
|
||||
if (this.browser) await this.browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
export default PLCStreamerBrowserAutomation;
|
||||
|
||||
// CLI usage example
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const automation = new PLCStreamerBrowserAutomation();
|
||||
|
||||
try {
|
||||
await automation.initBrowser('chromium', { headless: false });
|
||||
|
||||
console.log('Starting automated testing...');
|
||||
|
||||
// Run all tests
|
||||
await automation.testConfigurationFlow();
|
||||
await automation.testStreamingFlow();
|
||||
await automation.captureScreenshots();
|
||||
await automation.monitorPerformance();
|
||||
await automation.generateReport();
|
||||
|
||||
console.log('Automation completed successfully!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Automation failed:', error);
|
||||
} finally {
|
||||
await automation.cleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
import PLCStreamerBrowserAutomation from './browser-automation.js';
|
||||
|
||||
/**
|
||||
* Ejemplo de uso de la automatización de Playwright para PLC Streamer
|
||||
*/
|
||||
async function runExample() {
|
||||
const automation = new PLCStreamerBrowserAutomation();
|
||||
|
||||
try {
|
||||
console.log('🚀 Iniciando automatización de ejemplo...');
|
||||
|
||||
// Inicializar navegador
|
||||
await automation.initBrowser('chromium', {
|
||||
headless: false,
|
||||
slowMo: 500 // Ralentizar para ver las acciones
|
||||
});
|
||||
|
||||
console.log('✅ Navegador inicializado');
|
||||
|
||||
// Verificar que el backend esté funcionando
|
||||
const backendStatus = await automation.checkBackendStatus();
|
||||
console.log(`🔧 Estado del backend: ${backendStatus ? 'Online' : 'Offline'}`);
|
||||
|
||||
if (!backendStatus) {
|
||||
console.log('⚠️ Backend no disponible. Asegúrate de que esté ejecutándose en puerto 5000');
|
||||
return;
|
||||
}
|
||||
|
||||
// Navegar a la aplicación
|
||||
console.log('🌐 Navegando a la aplicación...');
|
||||
await automation.navigateToApp();
|
||||
|
||||
// Monitorear estado PLC
|
||||
console.log('🔌 Verificando estado de conexión PLC...');
|
||||
const plcStatus = await automation.monitorPLCConnection();
|
||||
console.log(`PLC Status: ${plcStatus}`);
|
||||
|
||||
// Test de configuración
|
||||
console.log('⚙️ Ejecutando test de configuración...');
|
||||
await automation.testConfigurationFlow();
|
||||
|
||||
// Test de streaming
|
||||
console.log('📊 Ejecutando test de streaming...');
|
||||
await automation.testStreamingFlow();
|
||||
|
||||
// Capturar screenshots
|
||||
console.log('📸 Capturando screenshots...');
|
||||
await automation.captureScreenshots();
|
||||
|
||||
// Monitorear performance
|
||||
console.log('⚡ Analizando performance...');
|
||||
await automation.monitorPerformance();
|
||||
|
||||
// Generar reporte
|
||||
console.log('📋 Generando reporte...');
|
||||
const report = await automation.generateReport();
|
||||
|
||||
console.log('🎉 Automatización completada exitosamente!');
|
||||
console.log('📊 Reporte:', report);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error en la automatización:', error);
|
||||
} finally {
|
||||
// Cleanup
|
||||
await automation.cleanup();
|
||||
console.log('🧹 Recursos liberados');
|
||||
}
|
||||
}
|
||||
|
||||
// Función para testing específico
|
||||
async function testSpecificFeature(feature) {
|
||||
const automation = new PLCStreamerBrowserAutomation();
|
||||
|
||||
try {
|
||||
await automation.initBrowser('chromium', { headless: false });
|
||||
await automation.navigateToApp();
|
||||
|
||||
switch (feature) {
|
||||
case 'configuration':
|
||||
await automation.testConfigurationFlow();
|
||||
break;
|
||||
case 'streaming':
|
||||
await automation.testStreamingFlow();
|
||||
break;
|
||||
case 'screenshots':
|
||||
await automation.captureScreenshots();
|
||||
break;
|
||||
case 'performance':
|
||||
await automation.monitorPerformance();
|
||||
break;
|
||||
default:
|
||||
console.log('Características disponibles: configuration, streaming, screenshots, performance');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
await automation.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Función para monitoreo continuo
|
||||
async function continuousMonitoring(durationMinutes = 5) {
|
||||
const automation = new PLCStreamerBrowserAutomation();
|
||||
|
||||
try {
|
||||
await automation.initBrowser('chromium', { headless: true });
|
||||
await automation.navigateToApp();
|
||||
|
||||
const endTime = Date.now() + (durationMinutes * 60 * 1000);
|
||||
|
||||
console.log(`🔄 Iniciando monitoreo continuo por ${durationMinutes} minutos...`);
|
||||
|
||||
while (Date.now() < endTime) {
|
||||
const status = await automation.monitorPLCConnection();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
console.log(`[${timestamp}] PLC Status: ${status}`);
|
||||
|
||||
// Esperar 30 segundos antes del siguiente check
|
||||
await new Promise(resolve => setTimeout(resolve, 30000));
|
||||
}
|
||||
|
||||
console.log('✅ Monitoreo continuo completado');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error en monitoreo:', error);
|
||||
} finally {
|
||||
await automation.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Detectar argumentos de línea de comandos
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0) {
|
||||
// Ejecutar ejemplo completo
|
||||
runExample();
|
||||
} else if (args[0] === 'test' && args[1]) {
|
||||
// Ejecutar test específico
|
||||
testSpecificFeature(args[1]);
|
||||
} else if (args[0] === 'monitor') {
|
||||
// Ejecutar monitoreo continuo
|
||||
const duration = args[1] ? parseInt(args[1]) : 5;
|
||||
continuousMonitoring(duration);
|
||||
} else {
|
||||
console.log(`
|
||||
Uso:
|
||||
node example.js # Ejecutar ejemplo completo
|
||||
node example.js test <feature> # Ejecutar test específico
|
||||
node example.js monitor [minutes] # Monitoreo continuo
|
||||
|
||||
Características disponibles:
|
||||
- configuration
|
||||
- streaming
|
||||
- screenshots
|
||||
- performance
|
||||
|
||||
Ejemplos:
|
||||
node example.js test configuration
|
||||
node example.js monitor 10
|
||||
`);
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
# Playwright MCP Server para GitHub Copilot
|
||||
|
||||
Este es un servidor MCP (Model Context Protocol) que permite a GitHub Copilot controlar navegadores web usando Playwright para automatizar tu aplicación PLC Streamer.
|
||||
|
||||
## 🎯 ¿Qué hace esto?
|
||||
|
||||
Permite que GitHub Copilot:
|
||||
- **Lance navegadores** automáticamente
|
||||
- **Navegue por tu aplicación** PLC Streamer
|
||||
- **Interactúe con elementos** (clicks, llenar formularios, etc.)
|
||||
- **Capture screenshots** y datos
|
||||
- **Monitoree el estado PLC** en tiempo real
|
||||
- **Ejecute tests automáticos** de tu aplicación
|
||||
|
||||
## 🚀 Configuración para GitHub Copilot
|
||||
|
||||
### 1. Configuración Global de MCP
|
||||
|
||||
Crea o edita el archivo de configuración MCP en tu sistema:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\Code\User\globalStorage\github.copilot-chat\mcpServers.json
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```
|
||||
~/Library/Application Support/Code/User/globalStorage/github.copilot-chat/mcpServers.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/Code/User/globalStorage/github.copilot-chat/mcpServers.json
|
||||
```
|
||||
|
||||
### 2. Contenido del archivo de configuración:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"playwright-automation": {
|
||||
"command": "node",
|
||||
"args": ["src/server.js"],
|
||||
"cwd": "D:\\Proyectos\\Scripts\\Siemens\\S7_snap7_Streamer_n_Log\\testing\\mcp-server",
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ Importante:** Cambia la ruta `cwd` por tu ruta absoluta real del proyecto.
|
||||
|
||||
### 3. Reiniciar VS Code
|
||||
|
||||
Después de agregar la configuración, reinicia VS Code para que GitHub Copilot detecte el servidor MCP.
|
||||
|
||||
## 🧪 Probar el Servidor
|
||||
|
||||
### Prueba manual:
|
||||
```bash
|
||||
cd testing/mcp-server
|
||||
npm test
|
||||
```
|
||||
|
||||
### Verificar conexión:
|
||||
```bash
|
||||
cd testing/mcp-server
|
||||
npm start
|
||||
# El servidor debería mostrar: "Playwright MCP Server running on stdio"
|
||||
```
|
||||
|
||||
## 💬 Comandos para GitHub Copilot
|
||||
|
||||
Una vez configurado, puedes usar estos comandos con GitHub Copilot:
|
||||
|
||||
### Básicos:
|
||||
```
|
||||
@copilot Abre un navegador y navega a mi aplicación PLC Streamer
|
||||
@copilot Toma una captura de pantalla de la aplicación
|
||||
@copilot Obtén el estado de conexión del PLC
|
||||
@copilot Haz click en el tab "Configuration"
|
||||
```
|
||||
|
||||
### Automatización PLC:
|
||||
```
|
||||
@copilot Configura el PLC con IP 192.168.1.100
|
||||
@copilot Inicia el streaming de datos
|
||||
@copilot Monitorea el estado del streaming por 30 segundos
|
||||
@copilot Crea un plot con las variables de temperatura
|
||||
```
|
||||
|
||||
### Testing automatizado:
|
||||
```
|
||||
@copilot Ejecuta un test completo de la aplicación
|
||||
@copilot Verifica que todos los tabs funcionen correctamente
|
||||
@copilot Prueba la configuración PLC con diferentes IPs
|
||||
@copilot Captura métricas de performance de la aplicación
|
||||
```
|
||||
|
||||
### Debugging:
|
||||
```
|
||||
@copilot Navega a la aplicación y dime si hay errores en la consola
|
||||
@copilot Verifica si el backend está respondiendo
|
||||
@copilot Toma screenshots de todos los tabs de la aplicación
|
||||
```
|
||||
|
||||
## 🛠️ Herramientas Disponibles
|
||||
|
||||
El servidor MCP proporciona estas herramientas a Copilot:
|
||||
|
||||
### Navegador:
|
||||
- `launch_browser` - Lanzar navegador
|
||||
- `navigate_to` - Navegar a URL
|
||||
- `close_browser` - Cerrar navegador
|
||||
- `take_screenshot` - Capturar pantalla
|
||||
- `get_page_info` - Info de la página
|
||||
|
||||
### Interacción:
|
||||
- `click_element` - Hacer click
|
||||
- `fill_input` - Llenar campos
|
||||
- `get_text` - Obtener texto
|
||||
- `wait_for_element` - Esperar elemento
|
||||
- `execute_script` - Ejecutar JavaScript
|
||||
|
||||
### PLC Específico:
|
||||
- `get_plc_status` - Estado PLC
|
||||
- `test_configuration` - Probar config PLC
|
||||
- `monitor_streaming` - Monitorear streaming
|
||||
- `capture_performance` - Métricas de performance
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Error: "No se puede conectar al servidor MCP"
|
||||
1. Verifica que la ruta en `mcpServers.json` sea correcta
|
||||
2. Asegúrate de que Node.js esté instalado
|
||||
3. Reinicia VS Code completamente
|
||||
|
||||
### Error: "Herramientas no disponibles"
|
||||
1. Verifica que el servidor inicie sin errores
|
||||
2. Revisa los logs en la consola de VS Code
|
||||
3. Ejecuta la prueba manual: `npm test`
|
||||
|
||||
### El navegador no se abre:
|
||||
1. Verifica que Playwright esté instalado: `npx playwright install`
|
||||
2. Prueba manualmente: `node src/test-server.js`
|
||||
|
||||
### La aplicación no responde:
|
||||
1. Asegúrate de que el frontend esté corriendo: `npm run dev` en `frontend/`
|
||||
2. Verifica que el backend esté activo (puerto 5000)
|
||||
|
||||
## 📊 Ejemplo de Uso Completo
|
||||
|
||||
```bash
|
||||
# 1. Usuario pregunta a Copilot:
|
||||
"Abre mi aplicación PLC Streamer, configura el PLC con IP 192.168.1.50,
|
||||
inicia el streaming y toma una captura de pantalla"
|
||||
|
||||
# 2. Copilot ejecutará automáticamente:
|
||||
- launch_browser()
|
||||
- navigate_to("http://localhost:5173/app")
|
||||
- click_element("Configuration")
|
||||
- test_configuration({ip: "192.168.1.50"})
|
||||
- click_element("Data Streaming")
|
||||
- monitor_streaming({action: "start"})
|
||||
- take_screenshot()
|
||||
```
|
||||
|
||||
## 🔒 Seguridad
|
||||
|
||||
- El servidor solo acepta conexiones locales
|
||||
- No expone datos sensibles del PLC
|
||||
- Las sesiones de navegador se aíslan automáticamente
|
||||
- Los screenshots se guardan localmente
|
||||
|
||||
## 📈 Logs y Debugging
|
||||
|
||||
Los logs del servidor aparecen en:
|
||||
- Consola de VS Code (panel "Output" → "GitHub Copilot Chat")
|
||||
- Terminal donde ejecutes `npm start`
|
||||
- Archivos de error en `testing/mcp-server/logs/`
|
||||
|
||||
---
|
||||
|
||||
¡Con esta configuración, GitHub Copilot puede controlar tu aplicación PLC Streamer como si fuera un asistente experto en automatización industrial! 🏭🤖
|
|
@ -0,0 +1,952 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Playwright MCP Server
|
||||
* Provides browser automation capabilities to GitHub Copilot via MCP protocol
|
||||
*/
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { chromium, firefox, webkit } from 'playwright';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
class PlaywrightMCPServer {
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'playwright-automation',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.browsers = new Map();
|
||||
this.pages = new Map();
|
||||
this.sessions = new Map();
|
||||
|
||||
this.setupTools();
|
||||
}
|
||||
|
||||
setupTools() {
|
||||
// Tool: Launch Browser
|
||||
this.server.setRequestHandler('tools/call', async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
switch (name) {
|
||||
case 'launch_browser':
|
||||
return await this.launchBrowser(args);
|
||||
|
||||
case 'navigate_to':
|
||||
return await this.navigateTo(args);
|
||||
|
||||
case 'click_element':
|
||||
return await this.clickElement(args);
|
||||
|
||||
case 'fill_input':
|
||||
return await this.fillInput(args);
|
||||
|
||||
case 'get_text':
|
||||
return await this.getText(args);
|
||||
|
||||
case 'take_screenshot':
|
||||
return await this.takeScreenshot(args);
|
||||
|
||||
case 'wait_for_element':
|
||||
return await this.waitForElement(args);
|
||||
|
||||
case 'get_page_info':
|
||||
return await this.getPageInfo(args);
|
||||
|
||||
case 'close_browser':
|
||||
return await this.closeBrowser(args);
|
||||
|
||||
case 'execute_script':
|
||||
return await this.executeScript(args);
|
||||
|
||||
case 'get_plc_status':
|
||||
return await this.getPLCStatus(args);
|
||||
|
||||
case 'test_configuration':
|
||||
return await this.testConfiguration(args);
|
||||
|
||||
case 'monitor_streaming':
|
||||
return await this.monitorStreaming(args);
|
||||
|
||||
case 'capture_performance':
|
||||
return await this.capturePerformance(args);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// List available tools
|
||||
this.server.setRequestHandler('tools/list', async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: 'launch_browser',
|
||||
description: 'Launch a new browser instance',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
browserType: {
|
||||
type: 'string',
|
||||
enum: ['chromium', 'firefox', 'webkit'],
|
||||
default: 'chromium',
|
||||
description: 'Browser type to launch'
|
||||
},
|
||||
headless: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Run browser in headless mode'
|
||||
},
|
||||
viewport: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
width: { type: 'number', default: 1920 },
|
||||
height: { type: 'number', default: 1080 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'navigate_to',
|
||||
description: 'Navigate to a URL',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
url: { type: 'string', description: 'URL to navigate to' },
|
||||
waitUntil: {
|
||||
type: 'string',
|
||||
enum: ['load', 'domcontentloaded', 'networkidle'],
|
||||
default: 'load'
|
||||
}
|
||||
},
|
||||
required: ['sessionId', 'url']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'click_element',
|
||||
description: 'Click on an element',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
selector: { type: 'string', description: 'CSS selector for the element' },
|
||||
text: { type: 'string', description: 'Text content to match (alternative to selector)' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fill_input',
|
||||
description: 'Fill an input field',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
selector: { type: 'string', description: 'CSS selector for the input' },
|
||||
value: { type: 'string', description: 'Value to fill' },
|
||||
clear: { type: 'boolean', default: true, description: 'Clear field before filling' }
|
||||
},
|
||||
required: ['sessionId', 'selector', 'value']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_text',
|
||||
description: 'Get text content from an element',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
selector: { type: 'string', description: 'CSS selector for the element' }
|
||||
},
|
||||
required: ['sessionId', 'selector']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'take_screenshot',
|
||||
description: 'Take a screenshot of the page',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
path: { type: 'string', description: 'Path to save screenshot' },
|
||||
fullPage: { type: 'boolean', default: false, description: 'Capture full page' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'wait_for_element',
|
||||
description: 'Wait for an element to appear',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
selector: { type: 'string', description: 'CSS selector for the element' },
|
||||
timeout: { type: 'number', default: 30000, description: 'Timeout in milliseconds' },
|
||||
state: {
|
||||
type: 'string',
|
||||
enum: ['attached', 'detached', 'visible', 'hidden'],
|
||||
default: 'visible'
|
||||
}
|
||||
},
|
||||
required: ['sessionId', 'selector']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_page_info',
|
||||
description: 'Get information about the current page',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'close_browser',
|
||||
description: 'Close a browser session',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'execute_script',
|
||||
description: 'Execute JavaScript on the page',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
script: { type: 'string', description: 'JavaScript code to execute' }
|
||||
},
|
||||
required: ['sessionId', 'script']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_plc_status',
|
||||
description: 'Get PLC connection status from the PLC Streamer app',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'test_configuration',
|
||||
description: 'Test PLC configuration in the app',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
ip: { type: 'string', description: 'PLC IP address' },
|
||||
rack: { type: 'number', description: 'PLC rack number' },
|
||||
slot: { type: 'number', description: 'PLC slot number' }
|
||||
},
|
||||
required: ['sessionId', 'ip']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'monitor_streaming',
|
||||
description: 'Monitor data streaming status',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' },
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['start', 'stop', 'status'],
|
||||
description: 'Streaming action to perform'
|
||||
}
|
||||
},
|
||||
required: ['sessionId', 'action']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'capture_performance',
|
||||
description: 'Capture performance metrics from the application',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sessionId: { type: 'string', description: 'Browser session ID' }
|
||||
},
|
||||
required: ['sessionId']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async launchBrowser(args) {
|
||||
const { browserType = 'chromium', headless = false, viewport = { width: 1920, height: 1080 } } = args;
|
||||
|
||||
try {
|
||||
const browsers = { chromium, firefox, webkit };
|
||||
const browser = await browsers[browserType].launch({
|
||||
headless,
|
||||
slowMo: 100,
|
||||
args: ['--disable-web-security', '--disable-features=VizDisplayCompositor']
|
||||
});
|
||||
|
||||
const context = await browser.newContext({ viewport });
|
||||
const page = await context.newPage();
|
||||
|
||||
const sessionId = uuidv4();
|
||||
|
||||
this.browsers.set(sessionId, browser);
|
||||
this.pages.set(sessionId, page);
|
||||
this.sessions.set(sessionId, {
|
||||
browser,
|
||||
context,
|
||||
page,
|
||||
browserType,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
// Add console and error logging
|
||||
page.on('console', msg => {
|
||||
console.log(`[${sessionId}] Console [${msg.type()}]: ${msg.text()}`);
|
||||
});
|
||||
|
||||
page.on('pageerror', error => {
|
||||
console.error(`[${sessionId}] Page Error: ${error.message}`);
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
sessionId,
|
||||
browserType,
|
||||
message: `Browser ${browserType} launched successfully`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async navigateTo(args) {
|
||||
const { sessionId, url, waitUntil = 'load' } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
await page.goto(url, { waitUntil });
|
||||
|
||||
const title = await page.title();
|
||||
const currentUrl = page.url();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
title,
|
||||
url: currentUrl,
|
||||
message: `Navigated to ${url}`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async clickElement(args) {
|
||||
const { sessionId, selector, text } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
let locator;
|
||||
if (text) {
|
||||
locator = page.getByText(text);
|
||||
} else if (selector) {
|
||||
locator = page.locator(selector);
|
||||
} else {
|
||||
throw new Error('Either selector or text must be provided');
|
||||
}
|
||||
|
||||
await locator.click();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: `Clicked element ${selector || text}`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fillInput(args) {
|
||||
const { sessionId, selector, value, clear = true } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const input = page.locator(selector);
|
||||
|
||||
if (clear) {
|
||||
await input.clear();
|
||||
}
|
||||
|
||||
await input.fill(value);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: `Filled input ${selector} with value`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getText(args) {
|
||||
const { sessionId, selector } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const text = await page.locator(selector).textContent();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
text,
|
||||
selector
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async takeScreenshot(args) {
|
||||
const { sessionId, path, fullPage = false } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const screenshotPath = path || `screenshots/screenshot-${sessionId}-${Date.now()}.png`;
|
||||
|
||||
await page.screenshot({
|
||||
path: screenshotPath,
|
||||
fullPage
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
path: screenshotPath,
|
||||
message: 'Screenshot captured successfully'
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async waitForElement(args) {
|
||||
const { sessionId, selector, timeout = 30000, state = 'visible' } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
await page.locator(selector).waitFor({ state, timeout });
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: `Element ${selector} is ${state}`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getPageInfo(args) {
|
||||
const { sessionId } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const title = await page.title();
|
||||
const url = page.url();
|
||||
const viewport = page.viewportSize();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
title,
|
||||
url,
|
||||
viewport
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async closeBrowser(args) {
|
||||
const { sessionId } = args;
|
||||
|
||||
try {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
await session.browser.close();
|
||||
|
||||
this.browsers.delete(sessionId);
|
||||
this.pages.delete(sessionId);
|
||||
this.sessions.delete(sessionId);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: `Browser session ${sessionId} closed`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async executeScript(args) {
|
||||
const { sessionId, script } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const result = await page.evaluate(script);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
result
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// PLC-specific tools
|
||||
async getPLCStatus(args) {
|
||||
const { sessionId } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
// Navigate to PLC Streamer app if not already there
|
||||
const currentUrl = page.url();
|
||||
if (!currentUrl.includes('localhost:5173')) {
|
||||
await page.goto('http://localhost:5173/app');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
// Get connection status
|
||||
const statusElement = page.locator('[data-testid="connection-status"]');
|
||||
const status = await statusElement.textContent().catch(() => 'Unknown');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
plcStatus: status,
|
||||
timestamp: new Date().toISOString()
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async testConfiguration(args) {
|
||||
const { sessionId, ip, rack = 0, slot = 1 } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
// Navigate to configuration tab
|
||||
await page.click('text=Configuration');
|
||||
await page.waitForSelector('[data-testid="configuration-panel"]');
|
||||
|
||||
// Fill PLC configuration
|
||||
const ipInput = page.locator('input[name*="ip"]').first();
|
||||
await ipInput.clear();
|
||||
await ipInput.fill(ip);
|
||||
|
||||
if (rack !== undefined) {
|
||||
const rackInput = page.locator('input[name*="rack"]').first();
|
||||
await rackInput.clear();
|
||||
await rackInput.fill(rack.toString());
|
||||
}
|
||||
|
||||
if (slot !== undefined) {
|
||||
const slotInput = page.locator('input[name*="slot"]').first();
|
||||
await slotInput.clear();
|
||||
await slotInput.fill(slot.toString());
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
await page.click('button:has-text("Save")');
|
||||
await page.waitForSelector('text*=saved', { timeout: 5000 });
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: `PLC configuration updated: IP=${ip}, Rack=${rack}, Slot=${slot}`
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async monitorStreaming(args) {
|
||||
const { sessionId, action } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
// Navigate to streaming tab
|
||||
await page.click('text=Data Streaming');
|
||||
await page.waitForSelector('[data-testid="streaming-panel"]');
|
||||
|
||||
let result;
|
||||
switch (action) {
|
||||
case 'start':
|
||||
await page.click('button:has-text("Start Streaming")');
|
||||
result = 'Streaming started';
|
||||
break;
|
||||
case 'stop':
|
||||
await page.click('button:has-text("Stop Streaming")');
|
||||
result = 'Streaming stopped';
|
||||
break;
|
||||
case 'status':
|
||||
// Get current streaming status
|
||||
const statusText = await page.locator('[data-testid="streaming-status"]').textContent().catch(() => 'Unknown');
|
||||
result = `Streaming status: ${statusText}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
action,
|
||||
result
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async capturePerformance(args) {
|
||||
const { sessionId } = args;
|
||||
|
||||
try {
|
||||
const page = this.pages.get(sessionId);
|
||||
if (!page) {
|
||||
throw new Error(`No browser session found with ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const metrics = await page.evaluate(() => {
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
const memory = performance.memory;
|
||||
|
||||
return {
|
||||
loadTime: navigation ? navigation.loadEventEnd - navigation.loadEventStart : 0,
|
||||
domContentLoaded: navigation ? navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart : 0,
|
||||
memory: memory ? {
|
||||
usedJSHeapSize: memory.usedJSHeapSize,
|
||||
totalJSHeapSize: memory.totalJSHeapSize,
|
||||
jsHeapSizeLimit: memory.jsHeapSizeLimit
|
||||
} : null,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
metrics
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
console.error('Playwright MCP Server running on stdio');
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server
|
||||
const server = new PlaywrightMCPServer();
|
||||
server.start().catch(console.error);
|
|
@ -0,0 +1,140 @@
|
|||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
/**
|
||||
* Test client for Playwright MCP Server
|
||||
*/
|
||||
async function testMCPServer() {
|
||||
console.log('🚀 Starting Playwright MCP Server test...');
|
||||
|
||||
try {
|
||||
// Spawn the server process
|
||||
const serverProcess = spawn('node', ['src/server.js'], {
|
||||
cwd: process.cwd(),
|
||||
stdio: ['pipe', 'pipe', 'inherit']
|
||||
});
|
||||
|
||||
// Create client transport
|
||||
const transport = new StdioClientTransport({
|
||||
stdin: serverProcess.stdin,
|
||||
stdout: serverProcess.stdout
|
||||
});
|
||||
|
||||
// Create client
|
||||
const client = new Client(
|
||||
{
|
||||
name: 'playwright-test-client',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {}
|
||||
}
|
||||
);
|
||||
|
||||
// Connect to server
|
||||
console.log('📡 Connecting to MCP server...');
|
||||
await client.connect(transport);
|
||||
console.log('✅ Connected to MCP server');
|
||||
|
||||
// List available tools
|
||||
console.log('🔧 Getting available tools...');
|
||||
const tools = await client.request('tools/list', {});
|
||||
console.log(`Found ${tools.tools.length} tools:`);
|
||||
tools.tools.forEach(tool => {
|
||||
console.log(` - ${tool.name}: ${tool.description}`);
|
||||
});
|
||||
|
||||
// Test launching browser
|
||||
console.log('🌐 Testing browser launch...');
|
||||
const launchResult = await client.request('tools/call', {
|
||||
name: 'launch_browser',
|
||||
arguments: {
|
||||
browserType: 'chromium',
|
||||
headless: false
|
||||
}
|
||||
});
|
||||
|
||||
const launchData = JSON.parse(launchResult.content[0].text);
|
||||
console.log('Browser launch result:', launchData);
|
||||
|
||||
if (launchData.success) {
|
||||
const sessionId = launchData.sessionId;
|
||||
console.log(`✅ Browser launched with session ID: ${sessionId}`);
|
||||
|
||||
// Test navigation
|
||||
console.log('🧭 Testing navigation...');
|
||||
const navResult = await client.request('tools/call', {
|
||||
name: 'navigate_to',
|
||||
arguments: {
|
||||
sessionId,
|
||||
url: 'http://localhost:5173/app'
|
||||
}
|
||||
});
|
||||
|
||||
const navData = JSON.parse(navResult.content[0].text);
|
||||
console.log('Navigation result:', navData);
|
||||
|
||||
// Test taking screenshot
|
||||
console.log('📸 Testing screenshot...');
|
||||
const screenshotResult = await client.request('tools/call', {
|
||||
name: 'take_screenshot',
|
||||
arguments: {
|
||||
sessionId,
|
||||
path: 'test-screenshot.png'
|
||||
}
|
||||
});
|
||||
|
||||
const screenshotData = JSON.parse(screenshotResult.content[0].text);
|
||||
console.log('Screenshot result:', screenshotData);
|
||||
|
||||
// Test getting page info
|
||||
console.log('ℹ️ Testing page info...');
|
||||
const infoResult = await client.request('tools/call', {
|
||||
name: 'get_page_info',
|
||||
arguments: {
|
||||
sessionId
|
||||
}
|
||||
});
|
||||
|
||||
const infoData = JSON.parse(infoResult.content[0].text);
|
||||
console.log('Page info result:', infoData);
|
||||
|
||||
// Test PLC status (if app is running)
|
||||
console.log('🔌 Testing PLC status...');
|
||||
const plcResult = await client.request('tools/call', {
|
||||
name: 'get_plc_status',
|
||||
arguments: {
|
||||
sessionId
|
||||
}
|
||||
});
|
||||
|
||||
const plcData = JSON.parse(plcResult.content[0].text);
|
||||
console.log('PLC status result:', plcData);
|
||||
|
||||
// Close browser
|
||||
console.log('🔒 Closing browser...');
|
||||
await client.request('tools/call', {
|
||||
name: 'close_browser',
|
||||
arguments: {
|
||||
sessionId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ All tests completed successfully!');
|
||||
|
||||
// Close client connection
|
||||
await client.close();
|
||||
serverProcess.kill();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Test failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run test if this file is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
testMCPServer();
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Global Playwright MCP-like Automation Script
|
||||
* This script can be used across multiple projects
|
||||
*/
|
||||
|
||||
import { chromium, firefox, webkit } from 'playwright';
|
||||
import { program } from 'commander';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
program
|
||||
.name('playwright-mcp')
|
||||
.description('Playwright MCP-like automation tool for web applications')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.description('Run automated tests')
|
||||
.option('-b, --browser <type>', 'Browser type (chromium, firefox, webkit)', 'chromium')
|
||||
.option('-h, --headless', 'Run in headless mode', false)
|
||||
.option('-u, --url <url>', 'Base URL to test', 'http://localhost:3000')
|
||||
.action(async (options) => {
|
||||
const { browser, headless, url } = options;
|
||||
console.log(`Running tests with ${browser} browser on ${url}`);
|
||||
|
||||
// Your test logic here
|
||||
await runTests({ browser, headless, url });
|
||||
});
|
||||
|
||||
program
|
||||
.command('capture')
|
||||
.description('Capture screenshots of application')
|
||||
.option('-b, --browser <type>', 'Browser type', 'chromium')
|
||||
.option('-u, --url <url>', 'URL to capture', 'http://localhost:3000')
|
||||
.option('-o, --output <dir>', 'Output directory', './screenshots')
|
||||
.action(async (options) => {
|
||||
await captureScreenshots(options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('monitor')
|
||||
.description('Monitor application performance')
|
||||
.option('-b, --browser <type>', 'Browser type', 'chromium')
|
||||
.option('-u, --url <url>', 'URL to monitor', 'http://localhost:3000')
|
||||
.option('-d, --duration <seconds>', 'Monitoring duration', '60')
|
||||
.action(async (options) => {
|
||||
await monitorPerformance(options);
|
||||
});
|
||||
|
||||
async function runTests({ browser, headless, url }) {
|
||||
const browserInstance = await launchBrowser(browser, { headless });
|
||||
const context = await browserInstance.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
await page.goto(url);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Basic connectivity test
|
||||
const title = await page.title();
|
||||
console.log(`Page title: ${title}`);
|
||||
|
||||
// Check for common elements
|
||||
const links = await page.locator('a').count();
|
||||
const buttons = await page.locator('button').count();
|
||||
|
||||
console.log(`Found ${links} links and ${buttons} buttons`);
|
||||
|
||||
// Performance metrics
|
||||
const metrics = await page.evaluate(() => {
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
return {
|
||||
loadTime: navigation.loadEventEnd - navigation.loadEventStart,
|
||||
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
|
||||
firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime || 0
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Performance metrics:', metrics);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test failed:', error);
|
||||
} finally {
|
||||
await context.close();
|
||||
await browserInstance.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function captureScreenshots({ browser, url, output }) {
|
||||
const browserInstance = await launchBrowser(browser, { headless: true });
|
||||
const context = await browserInstance.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
await page.goto(url);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Create output directory
|
||||
await fs.mkdir(output, { recursive: true });
|
||||
|
||||
// Full page screenshot
|
||||
await page.screenshot({
|
||||
path: path.join(output, 'full-page.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// Viewport screenshot
|
||||
await page.screenshot({
|
||||
path: path.join(output, 'viewport.png')
|
||||
});
|
||||
|
||||
console.log(`Screenshots saved to ${output}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Screenshot capture failed:', error);
|
||||
} finally {
|
||||
await context.close();
|
||||
await browserInstance.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function monitorPerformance({ browser, url, duration }) {
|
||||
const browserInstance = await launchBrowser(browser, { headless: true });
|
||||
const context = await browserInstance.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
const metrics = [];
|
||||
const startTime = Date.now();
|
||||
const durationMs = parseInt(duration) * 1000;
|
||||
|
||||
try {
|
||||
await page.goto(url);
|
||||
|
||||
// Monitor performance every 5 seconds
|
||||
const interval = setInterval(async () => {
|
||||
const currentMetrics = await page.evaluate(() => {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
memory: performance.memory ? {
|
||||
usedJSHeapSize: performance.memory.usedJSHeapSize,
|
||||
totalJSHeapSize: performance.memory.totalJSHeapSize,
|
||||
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
|
||||
} : null,
|
||||
timing: performance.timing
|
||||
};
|
||||
});
|
||||
|
||||
metrics.push(currentMetrics);
|
||||
console.log(`Memory usage: ${(currentMetrics.memory?.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`);
|
||||
}, 5000);
|
||||
|
||||
// Stop monitoring after duration
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
}, durationMs);
|
||||
|
||||
// Wait for monitoring to complete
|
||||
await new Promise(resolve => setTimeout(resolve, durationMs));
|
||||
|
||||
// Save metrics
|
||||
await fs.writeFile('performance-monitor.json', JSON.stringify(metrics, null, 2));
|
||||
console.log('Performance monitoring completed. Data saved to performance-monitor.json');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Performance monitoring failed:', error);
|
||||
} finally {
|
||||
await context.close();
|
||||
await browserInstance.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function launchBrowser(type, options = {}) {
|
||||
const browsers = {
|
||||
chromium: chromium,
|
||||
firefox: firefox,
|
||||
webkit: webkit
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
headless: false,
|
||||
slowMo: 100
|
||||
};
|
||||
|
||||
return await browsers[type].launch({ ...defaultOptions, ...options });
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
program.parse();
|
|
@ -0,0 +1,83 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://localhost:5173',
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
/* Take screenshot on failure */
|
||||
screenshot: 'only-on-failure',
|
||||
/* Record video on failure */
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
{
|
||||
name: 'Mobile Chrome',
|
||||
use: { ...devices['Pixel 5'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile Safari',
|
||||
use: { ...devices['iPhone 12'] },
|
||||
},
|
||||
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: 'Microsoft Edge',
|
||||
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
},
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: 'npm run dev',
|
||||
cwd: '../frontend',
|
||||
port: 5173,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
{
|
||||
command: 'python main.py',
|
||||
cwd: '..',
|
||||
port: 5000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
}
|
||||
],
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script de inicio rápido para testing con Playwright
|
||||
|
||||
echo "🚀 Iniciando PLC Streamer Testing Suite"
|
||||
|
||||
# Verificar que los servidores estén corriendo
|
||||
echo "🔍 Verificando servidores..."
|
||||
|
||||
# Check frontend
|
||||
if curl -f http://localhost:5173 >/dev/null 2>&1; then
|
||||
echo "✅ Frontend (5173) running"
|
||||
else
|
||||
echo "❌ Frontend no está corriendo en puerto 5173"
|
||||
echo "💡 Ejecuta: cd ../frontend && npm run dev"
|
||||
fi
|
||||
|
||||
# Check backend
|
||||
if curl -f http://localhost:5000/api/status >/dev/null 2>&1; then
|
||||
echo "✅ Backend (5000) running"
|
||||
else
|
||||
echo "❌ Backend no está corriendo en puerto 5000"
|
||||
echo "💡 Ejecuta: cd .. && python main.py"
|
||||
fi
|
||||
|
||||
echo "🎬 Iniciando tests..."
|
||||
npm run test:headed
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script de configuración inicial para Playwright MCP
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
console.log('🚀 Configurando Playwright MCP para PLC Streamer...');
|
||||
|
||||
async function setup() {
|
||||
try {
|
||||
// 1. Verificar instalación de Playwright
|
||||
console.log('📦 Verificando instalación de Playwright...');
|
||||
try {
|
||||
execSync('npx playwright --version', { stdio: 'pipe' });
|
||||
console.log('✅ Playwright instalado');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Instalando Playwright...');
|
||||
execSync('npm install', { stdio: 'inherit' });
|
||||
execSync('npx playwright install', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// 2. Crear directorios necesarios
|
||||
console.log('📁 Creando directorios...');
|
||||
const dirs = ['screenshots', 'videos', 'test-results', 'playwright-report'];
|
||||
|
||||
for (const dir of dirs) {
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
console.log(`✅ Directorio creado: ${dir}`);
|
||||
} catch (error) {
|
||||
console.log(`ℹ️ Directorio ya existe: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Verificar que los servidores estén configurados
|
||||
console.log('🔧 Verificando configuración de servidores...');
|
||||
|
||||
// Check frontend
|
||||
const frontendPath = '../frontend/package.json';
|
||||
try {
|
||||
await fs.access(frontendPath);
|
||||
console.log('✅ Frontend encontrado');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Frontend no encontrado en ruta esperada');
|
||||
}
|
||||
|
||||
// Check backend
|
||||
const backendPath = '../main.py';
|
||||
try {
|
||||
await fs.access(backendPath);
|
||||
console.log('✅ Backend encontrado');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Backend no encontrado en ruta esperada');
|
||||
}
|
||||
|
||||
// 4. Crear archivo de configuración de entorno
|
||||
console.log('⚙️ Creando configuración de entorno...');
|
||||
const envConfig = {
|
||||
FRONTEND_URL: 'http://localhost:5173',
|
||||
BACKEND_URL: 'http://localhost:5000',
|
||||
PLAYWRIGHT_HEADLESS: 'false',
|
||||
PLAYWRIGHT_SLOWMO: '100',
|
||||
SCREENSHOT_PATH: './screenshots',
|
||||
VIDEO_PATH: './videos'
|
||||
};
|
||||
|
||||
await fs.writeFile('.env',
|
||||
Object.entries(envConfig)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n')
|
||||
);
|
||||
console.log('✅ Archivo .env creado');
|
||||
|
||||
// 5. Crear script de inicio rápido
|
||||
console.log('🎯 Creando script de inicio rápido...');
|
||||
const quickStart = `#!/bin/bash
|
||||
|
||||
# Script de inicio rápido para testing con Playwright
|
||||
|
||||
echo "🚀 Iniciando PLC Streamer Testing Suite"
|
||||
|
||||
# Verificar que los servidores estén corriendo
|
||||
echo "🔍 Verificando servidores..."
|
||||
|
||||
# Check frontend
|
||||
if curl -f http://localhost:5173 >/dev/null 2>&1; then
|
||||
echo "✅ Frontend (5173) running"
|
||||
else
|
||||
echo "❌ Frontend no está corriendo en puerto 5173"
|
||||
echo "💡 Ejecuta: cd ../frontend && npm run dev"
|
||||
fi
|
||||
|
||||
# Check backend
|
||||
if curl -f http://localhost:5000/api/status >/dev/null 2>&1; then
|
||||
echo "✅ Backend (5000) running"
|
||||
else
|
||||
echo "❌ Backend no está corriendo en puerto 5000"
|
||||
echo "💡 Ejecuta: cd .. && python main.py"
|
||||
fi
|
||||
|
||||
echo "🎬 Iniciando tests..."
|
||||
npm run test:headed
|
||||
`;
|
||||
|
||||
await fs.writeFile('quick-start.sh', quickStart);
|
||||
console.log('✅ Script quick-start.sh creado');
|
||||
|
||||
// 6. Crear archivo gitignore para testing
|
||||
console.log('📝 Configurando .gitignore...');
|
||||
const gitignore = `# Playwright
|
||||
test-results/
|
||||
playwright-report/
|
||||
playwright/.cache/
|
||||
|
||||
# Screenshots y videos
|
||||
screenshots/*.png
|
||||
videos/*.webm
|
||||
|
||||
# Reportes y logs
|
||||
*.json
|
||||
*.log
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env.local
|
||||
`;
|
||||
|
||||
await fs.writeFile('.gitignore', gitignore);
|
||||
console.log('✅ .gitignore configurado');
|
||||
|
||||
// 7. Mensaje final
|
||||
console.log(`
|
||||
🎉 ¡Configuración completada!
|
||||
|
||||
📋 Próximos pasos:
|
||||
|
||||
1. Asegúrate de que los servidores estén corriendo:
|
||||
Frontend: cd ../frontend && npm run dev
|
||||
Backend: cd .. && python main.py
|
||||
|
||||
2. Ejecutar tests básicos:
|
||||
npm test
|
||||
|
||||
3. Ejecutar ejemplo interactivo:
|
||||
npm run example
|
||||
|
||||
4. Generar código de test:
|
||||
npm run codegen:app
|
||||
|
||||
5. Monitoreo continuo:
|
||||
npm run monitor
|
||||
|
||||
📚 Ver README.md para más información y ejemplos.
|
||||
|
||||
🔧 Archivos creados:
|
||||
- .env (configuración)
|
||||
- .gitignore (exclusiones)
|
||||
- quick-start.sh (inicio rápido)
|
||||
|
||||
Happy testing! 🧪
|
||||
`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error durante la configuración:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
setup();
|
|
@ -0,0 +1,11 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('basic connectivity test', async ({ page }) => {
|
||||
// Test básico de conectividad
|
||||
await page.goto('http://localhost:5173');
|
||||
|
||||
// Verificar que la página cargue con el título correcto
|
||||
await expect(page).toHaveTitle(/PLC S7-31x Streamer/);
|
||||
|
||||
console.log('✅ Conectividad básica funcionando');
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('PLC Configuration Management', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/app');
|
||||
// Navigate to Configuration tab
|
||||
await page.click('text=Configuration');
|
||||
});
|
||||
|
||||
test('should display PLC configuration form', async ({ page }) => {
|
||||
// Check if PLC configuration section is visible
|
||||
await expect(page.locator('text=PLC Configuration')).toBeVisible();
|
||||
|
||||
// Check for common configuration fields
|
||||
await expect(page.locator('input[name*="ip"]')).toBeVisible();
|
||||
await expect(page.locator('input[name*="rack"]')).toBeVisible();
|
||||
await expect(page.locator('input[name*="slot"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be able to modify PLC settings', async ({ page }) => {
|
||||
// Find IP address input field
|
||||
const ipInput = page.locator('input[name*="ip"]').first();
|
||||
|
||||
// Clear and set new IP
|
||||
await ipInput.clear();
|
||||
await ipInput.fill('192.168.1.100');
|
||||
|
||||
// Verify the value was set
|
||||
await expect(ipInput).toHaveValue('192.168.1.100');
|
||||
});
|
||||
|
||||
test('should validate configuration data', async ({ page }) => {
|
||||
// Try to enter invalid IP address
|
||||
const ipInput = page.locator('input[name*="ip"]').first();
|
||||
await ipInput.clear();
|
||||
await ipInput.fill('invalid-ip');
|
||||
|
||||
// Look for validation error
|
||||
await expect(page.locator('text*=invalid')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should save configuration changes', async ({ page }) => {
|
||||
// Modify a setting
|
||||
const ipInput = page.locator('input[name*="ip"]').first();
|
||||
await ipInput.clear();
|
||||
await ipInput.fill('192.168.1.101');
|
||||
|
||||
// Click save button
|
||||
await page.click('button:has-text("Save")');
|
||||
|
||||
// Look for success message
|
||||
await expect(page.locator('text*=saved')).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('PLC Streamer Application', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to the application
|
||||
await page.goto('/app');
|
||||
});
|
||||
|
||||
test('should load the main dashboard', async ({ page }) => {
|
||||
// Wait for the main content to load
|
||||
await expect(page.locator('[data-testid="dashboard"]')).toBeVisible();
|
||||
|
||||
// Check if the title is correct
|
||||
await expect(page).toHaveTitle(/PLC Streamer/);
|
||||
});
|
||||
|
||||
test('should display navigation tabs', async ({ page }) => {
|
||||
// Check if main navigation tabs are present
|
||||
await expect(page.locator('text=Dashboard')).toBeVisible();
|
||||
await expect(page.locator('text=Configuration')).toBeVisible();
|
||||
await expect(page.locator('text=Data Streaming')).toBeVisible();
|
||||
await expect(page.locator('text=Plots')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be able to navigate between tabs', async ({ page }) => {
|
||||
// Click on Configuration tab
|
||||
await page.click('text=Configuration');
|
||||
await expect(page.locator('[data-testid="configuration-panel"]')).toBeVisible();
|
||||
|
||||
// Click on Data Streaming tab
|
||||
await page.click('text=Data Streaming');
|
||||
await expect(page.locator('[data-testid="streaming-panel"]')).toBeVisible();
|
||||
|
||||
// Click on Plots tab
|
||||
await page.click('text=Plots');
|
||||
await expect(page.locator('[data-testid="plots-panel"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show PLC connection status', async ({ page }) => {
|
||||
// Look for connection status indicator
|
||||
const connectionStatus = page.locator('[data-testid="connection-status"]');
|
||||
await expect(connectionStatus).toBeVisible();
|
||||
|
||||
// Status should be either "Connected" or "Disconnected"
|
||||
const statusText = await connectionStatus.textContent();
|
||||
expect(['Connected', 'Disconnected', 'Connecting']).toContain(statusText?.trim());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Real-time Plotting', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/app');
|
||||
// Navigate to Plots tab
|
||||
await page.click('text=Plots');
|
||||
});
|
||||
|
||||
test('should display plot management interface', async ({ page }) => {
|
||||
// Check for plot controls
|
||||
await expect(page.locator('text=Plot Management')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Create Plot")')).toBeVisible();
|
||||
|
||||
// Check for existing plots list
|
||||
await expect(page.locator('[data-testid="plots-list"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be able to create a new plot', async ({ page }) => {
|
||||
// Click create plot button
|
||||
await page.click('button:has-text("Create Plot")');
|
||||
|
||||
// Fill in plot details
|
||||
await page.fill('input[name="plotName"]', 'Test Plot');
|
||||
await page.fill('input[name="description"]', 'Test plot for automation');
|
||||
|
||||
// Select variables for the plot
|
||||
await page.check('input[name="variables"][value="Temperature"]');
|
||||
await page.check('input[name="variables"][value="Pressure"]');
|
||||
|
||||
// Save the plot
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Verify plot was created
|
||||
await expect(page.locator('text=Test Plot')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display chart canvas', async ({ page }) => {
|
||||
// Check if chart canvas is present
|
||||
await expect(page.locator('canvas')).toBeVisible();
|
||||
|
||||
// Verify chart is rendered (check for Chart.js specific elements)
|
||||
const canvas = page.locator('canvas').first();
|
||||
await expect(canvas).toHaveAttribute('style', /.*width.*height.*/);
|
||||
});
|
||||
|
||||
test('should update plot in real-time', async ({ page }) => {
|
||||
// Start a plot session
|
||||
await page.click('button:has-text("Start Plot")');
|
||||
|
||||
// Wait for some time to allow data to update
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Check if chart has data points (this is basic, real implementation would check canvas content)
|
||||
const canvas = page.locator('canvas').first();
|
||||
|
||||
// Verify canvas is still present and potentially has been updated
|
||||
await expect(canvas).toBeVisible();
|
||||
|
||||
// Stop the plot
|
||||
await page.click('button:has-text("Stop Plot")');
|
||||
});
|
||||
|
||||
test('should export plot data', async ({ page }) => {
|
||||
// Set up download handler
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
// Click export button
|
||||
await page.click('button:has-text("Export")');
|
||||
|
||||
// Wait for download to start
|
||||
const download = await downloadPromise;
|
||||
|
||||
// Verify download file name
|
||||
expect(download.suggestedFilename()).toContain('.csv');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Data Streaming Features', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/app');
|
||||
// Navigate to Data Streaming tab
|
||||
await page.click('text=Data Streaming');
|
||||
});
|
||||
|
||||
test('should display streaming controls', async ({ page }) => {
|
||||
// Check for streaming control buttons
|
||||
await expect(page.locator('button:has-text("Start Streaming")')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Stop Streaming")')).toBeVisible();
|
||||
|
||||
// Check for dataset configuration
|
||||
await expect(page.locator('text=Dataset Configuration')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be able to start and stop streaming', async ({ page }) => {
|
||||
// Start streaming
|
||||
await page.click('button:has-text("Start Streaming")');
|
||||
|
||||
// Wait for streaming to start (check for status change)
|
||||
await expect(page.locator('text*=streaming')).toBeVisible();
|
||||
|
||||
// Stop streaming
|
||||
await page.click('button:has-text("Stop Streaming")');
|
||||
|
||||
// Verify streaming stopped
|
||||
await expect(page.locator('text*=stopped')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display dataset variables', async ({ page }) => {
|
||||
// Check if dataset variables table is present
|
||||
await expect(page.locator('[data-testid="dataset-variables"]')).toBeVisible();
|
||||
|
||||
// Check for variable columns
|
||||
await expect(page.locator('text=Variable Name')).toBeVisible();
|
||||
await expect(page.locator('text=Address')).toBeVisible();
|
||||
await expect(page.locator('text=Type')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow adding new variables', async ({ page }) => {
|
||||
// Click add variable button
|
||||
await page.click('button:has-text("Add Variable")');
|
||||
|
||||
// Fill in variable details
|
||||
await page.fill('input[name="name"]', 'TestVariable');
|
||||
await page.fill('input[name="address"]', 'DB1.DBD0');
|
||||
await page.selectOption('select[name="type"]', 'REAL');
|
||||
|
||||
// Save the variable
|
||||
await page.click('button:has-text("Save")');
|
||||
|
||||
// Verify variable was added
|
||||
await expect(page.locator('text=TestVariable')).toBeVisible();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue