-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
558ba41
commit f071c09
Showing
15 changed files
with
4,974 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
node_modules | ||
fixtures | ||
docker-image | ||
build | ||
env | ||
minionbot | ||
minionhome | ||
.env |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
const { scheduleJob: nodeScheduleJob } = require('node-schedule'); | ||
const { execSync } = require('child_process'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const jobStore = new Map(); // In-memory store for jobs | ||
const SCRIPTS_DIR = path.join(__dirname, '../scripts'); | ||
|
||
// Ensure scripts directory exists | ||
if (!fs.existsSync(SCRIPTS_DIR)) { | ||
fs.mkdirSync(SCRIPTS_DIR, { recursive: true }); | ||
} | ||
|
||
const triggerJob = async (req, res) => { | ||
try { | ||
const { jobName, arguments } = req.body; | ||
|
||
if (!jobStore.has(jobName)) { | ||
return res.status(404).json({ error: 'Job not found' }); | ||
} | ||
|
||
const job = jobStore.get(jobName); | ||
const scriptPath = path.join(SCRIPTS_DIR, `${jobName}.spec.ts`); | ||
|
||
// Execute the Playwright test with arguments | ||
const result = await executePlaywrightTest(scriptPath, arguments); | ||
|
||
res.json({ | ||
status: 'success', | ||
jobName, | ||
result | ||
}); | ||
} catch (error) { | ||
res.status(500).json({ | ||
error: error.message | ||
}); | ||
} | ||
}; | ||
|
||
const addJob = async (req, res) => { | ||
try { | ||
const { jobName, requiredArguments, script } = req.body; | ||
|
||
if (jobStore.has(jobName)) { | ||
return res.status(400).json({ error: 'Job already exists' }); | ||
} | ||
|
||
// Save script to file | ||
const scriptPath = path.join(SCRIPTS_DIR, `${jobName}.spec.ts`); | ||
fs.writeFileSync(scriptPath, script); | ||
|
||
// Store job metadata | ||
jobStore.set(jobName, { | ||
requiredArguments, | ||
scriptPath, | ||
created: new Date(), | ||
}); | ||
|
||
res.json({ | ||
status: 'success', | ||
jobName, | ||
requiredArguments | ||
}); | ||
} catch (error) { | ||
res.status(500).json({ | ||
error: error.message | ||
}); | ||
} | ||
}; | ||
|
||
const schedulePlaywrightJob = async (req, res) => { | ||
try { | ||
const { jobName, cronExpression, arguments } = req.body; | ||
|
||
if (!jobStore.has(jobName)) { | ||
return res.status(404).json({ error: 'Job not found' }); | ||
} | ||
|
||
const scheduledJob = nodeScheduleJob(cronExpression, async () => { | ||
const scriptPath = path.join(SCRIPTS_DIR, `${jobName}.spec.ts`); | ||
await executePlaywrightTest(scriptPath, arguments); | ||
}); | ||
|
||
res.json({ | ||
status: 'success', | ||
jobName, | ||
nextInvocation: scheduledJob.nextInvocation() | ||
}); | ||
} catch (error) { | ||
res.status(500).json({ | ||
error: error.message | ||
}); | ||
} | ||
}; | ||
|
||
async function executePlaywrightTest(scriptPath, args = {}) { | ||
try { | ||
// Convert arguments to environment variables | ||
const env = { | ||
...process.env, | ||
...Object.entries(args).reduce((acc, [key, value]) => { | ||
acc[`TEST_ARG_${key.toUpperCase()}`] = value; | ||
return acc; | ||
}, {}) | ||
}; | ||
|
||
const result = execSync(`npx playwright test ${scriptPath}`, { | ||
stdio: 'pipe', | ||
env, | ||
encoding: 'utf-8' | ||
}); | ||
|
||
return { success: true, output: result }; | ||
} catch (error) { | ||
throw new Error(`Test execution failed: ${error.message}`); | ||
} | ||
} | ||
|
||
module.exports = { | ||
triggerJob, | ||
addJob, | ||
schedulePlaywrightJob | ||
}; |
File renamed without changes.
File renamed without changes.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
const express = require('express'); | ||
const cors = require('cors'); | ||
const morgan = require('morgan'); // for logging | ||
const { createLogger, format, transports } = require('winston'); | ||
|
||
// Initialize Express app | ||
const app = express(); | ||
const router = express.Router(); | ||
|
||
// Initialize logger | ||
const logger = createLogger({ | ||
format: format.combine( | ||
format.timestamp(), | ||
format.json() | ||
), | ||
transports: [ | ||
new transports.Console(), | ||
new transports.File({ filename: 'playwright-service.log' }) | ||
] | ||
}); | ||
|
||
// Middleware | ||
app.use(cors()); | ||
app.use(express.json({ limit: '50mb' })); // Increased limit for large scripts | ||
app.use(morgan('dev')); // HTTP request logging | ||
|
||
// Error handling middleware | ||
app.use((err, req, res, next) => { | ||
logger.error('Unhandled error:', err); | ||
res.status(500).json({ | ||
error: 'Internal Server Error', | ||
message: err.message | ||
}); | ||
}); | ||
|
||
// Health check endpoint | ||
router.get('/health', (req, res) => { | ||
res.json({ status: 'ok', timestamp: new Date().toISOString() }); | ||
}); | ||
|
||
// Import controllers | ||
const { triggerJob, addJob, scheduleJob, listJobs, deleteJob } = require('./controllers/jobController'); | ||
|
||
// API Routes | ||
router.post('/trigger', async (req, res) => { | ||
try { | ||
logger.info('Triggering job', { jobName: req.body.jobName }); | ||
const result = await triggerJob(req, res); | ||
logger.info('Job triggered successfully', { jobName: req.body.jobName, result }); | ||
} catch (error) { | ||
logger.error('Error triggering job', { | ||
jobName: req.body.jobName, | ||
error: error.message | ||
}); | ||
res.status(500).json({ error: error.message }); | ||
} | ||
}); | ||
|
||
router.post('/add', async (req, res) => { | ||
try { | ||
logger.info('Adding new job', { jobName: req.body.jobName }); | ||
|
||
// Validate required fields | ||
const { jobName, requiredArguments, script } = req.body; | ||
if (!jobName || !script) { | ||
throw new Error('jobName and script are required'); | ||
} | ||
|
||
const result = await addJob(req, res); | ||
logger.info('Job added successfully', { jobName: req.body.jobName }); | ||
} catch (error) { | ||
logger.error('Error adding job', { | ||
jobName: req.body.jobName, | ||
error: error.message | ||
}); | ||
res.status(400).json({ error: error.message }); | ||
} | ||
}); | ||
|
||
router.post('/schedule', async (req, res) => { | ||
try { | ||
logger.info('Scheduling job', { | ||
jobName: req.body.jobName, | ||
cronExpression: req.body.cronExpression | ||
}); | ||
|
||
// Validate cron expression | ||
const { jobName, cronExpression } = req.body; | ||
if (!jobName || !cronExpression) { | ||
throw new Error('jobName and cronExpression are required'); | ||
} | ||
|
||
const result = await scheduleJob(req, res); | ||
logger.info('Job scheduled successfully', { | ||
jobName: req.body.jobName, | ||
nextInvocation: result.nextInvocation | ||
}); | ||
} catch (error) { | ||
logger.error('Error scheduling job', { | ||
jobName: req.body.jobName, | ||
error: error.message | ||
}); | ||
res.status(400).json({ error: error.message }); | ||
} | ||
}); | ||
|
||
// Additional useful endpoints | ||
router.get('/jobs', async (req, res) => { | ||
try { | ||
const jobs = await listJobs(); | ||
res.json(jobs); | ||
} catch (error) { | ||
logger.error('Error listing jobs', { error: error.message }); | ||
res.status(500).json({ error: error.message }); | ||
} | ||
}); | ||
|
||
router.delete('/jobs/:jobName', async (req, res) => { | ||
try { | ||
const { jobName } = req.params; | ||
await deleteJob(jobName); | ||
logger.info('Job deleted successfully', { jobName }); | ||
res.json({ status: 'success', message: `Job ${jobName} deleted` }); | ||
} catch (error) { | ||
logger.error('Error deleting job', { | ||
jobName: req.params.jobName, | ||
error: error.message | ||
}); | ||
res.status(400).json({ error: error.message }); | ||
} | ||
}); | ||
|
||
// Mount router | ||
app.use('/api/playwright', router); | ||
|
||
// Start server | ||
const PORT = process.env.PORT || 3000; | ||
app.listen(PORT, () => { | ||
logger.info(`Playwright service running on port ${PORT}`); | ||
}); | ||
|
||
// Handle uncaught exceptions | ||
process.on('uncaughtException', (error) => { | ||
logger.error('Uncaught Exception:', error); | ||
process.exit(1); | ||
}); | ||
|
||
process.on('unhandledRejection', (error) => { | ||
logger.error('Unhandled Rejection:', error); | ||
process.exit(1); | ||
}); | ||
|
||
module.exports = app; |
Oops, something went wrong.