UNPKG

26.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const webserver_1 = require("../webserver");
4const cli_1 = require("./cli");
5const config_1 = require("./config");
6const url_1 = require("url");
7const flagpoleexecutionoptions_1 = require("../flagpoleexecutionoptions");
8const suiteexecution_1 = require("./suiteexecution");
9const flagpoleexecutionoptions_2 = require("../flagpoleexecutionoptions");
10const open = require("open");
11const qs = require("querystring");
12const possibleEnvironments = [
13 "dev",
14 "stag",
15 "prod",
16 "qa",
17 "rc",
18 "preprod",
19 "alpha",
20 "beta"
21];
22const routes = {
23 "GET /api/suites": (url, response) => {
24 sendJson(response, { suites: cli_1.Cli.config.suites });
25 },
26 "GET /import/suite": (url, response) => {
27 sendGetImport(response);
28 },
29 "POST /import/suite": (url, response, postData) => {
30 const name = postData.name;
31 if (name) {
32 cli_1.Cli.config.addSuite({
33 name: name
34 });
35 cli_1.Cli.config.save();
36 return sendOutput(response, `<p>Imported new suite ${name} from file ${name}.js</p><p><a href="/">Back</a>`);
37 }
38 fileNotFound(response);
39 },
40 "GET /init": (url, response) => {
41 sendGetInit(response);
42 },
43 "POST /init": (url, response, postData) => {
44 initProject(response, postData);
45 },
46 "GET /add/scenario": (url, response) => {
47 const suiteName = url.searchParams.get("suite");
48 const suite = getSuite(response, suiteName);
49 if (suite) {
50 return sendGetAddScenario(response, suite);
51 }
52 fileNotFound(response);
53 },
54 "POST /add/scenario": (url, response, postData) => {
55 if (!postData.suite ||
56 !postData.description ||
57 !postData.path ||
58 !postData.type) {
59 return sendOutput(response, `<p>All fields are required.</p>`);
60 }
61 const suite = getSuite(response, postData.suite);
62 if (!suite) {
63 return fileNotFound(response);
64 }
65 cli_1.Cli.addScenario(suite, {
66 description: postData.description,
67 path: postData.path,
68 type: postData.type
69 })
70 .then(() => {
71 return sendOutput(response, `
72 <p>Added scenario to <em>${suite.name}</em></p>
73 <p><a href="/">Back</a></p>
74 `);
75 })
76 .catch(err => {
77 return sendOutput(response, `<p>${err}</p>`);
78 });
79 },
80 "GET /add/suite": (url, response) => {
81 sendGetAddSuite(response);
82 },
83 "POST /add/suite": (url, response, postData) => {
84 if (!postData.suiteName ||
85 !postData.suiteDescription ||
86 !postData.scenarioDescription ||
87 !postData.scenarioPath ||
88 !postData.scenarioType) {
89 return sendOutput(response, `<p>All fields are required. <a href="/add/suite">Try again</a></p>`);
90 }
91 let domains = {};
92 Object.keys(postData).forEach((key) => {
93 if (key.startsWith("baseDomain[") && key.endsWith("]")) {
94 let envName = key.split("[")[1].slice(0, -1);
95 domains[envName] = postData[key];
96 }
97 });
98 if (Object.keys(domains).length == 0) {
99 domains.dev = "http://localhost";
100 }
101 addSuite(response, {
102 name: postData.suiteName,
103 description: postData.suiteDescription
104 }, {
105 description: postData.scenarioDescription,
106 path: postData.scenarioPath,
107 type: postData.scenarioType
108 });
109 },
110 "GET /suite": (url, response) => {
111 const suite = getSuite(response, url.searchParams.get("name"));
112 if (suite) {
113 return sendGetEditSuite(response, suite);
114 }
115 fileNotFound(response);
116 },
117 "POST /suite": (url, response, postData) => {
118 const suite = getSuite(response, postData.name);
119 if (suite) {
120 cli_1.Cli.config.suites[suite.name].clearTags();
121 String(postData.tags)
122 .split(",")
123 .forEach(tag => {
124 cli_1.Cli.config.suites[suite.name].addTag(tag);
125 });
126 cli_1.Cli.config
127 .save()
128 .then(() => {
129 sendOutput(response, `<p>Saved changes to suite.</p><p><a href="/">Back</a>`);
130 })
131 .catch(err => {
132 sendOutput(response, `<p>Error adding tag. ${err}</p><p><a href="/suite?name=${suite.name}">Back</a>`);
133 });
134 }
135 },
136 "POST /rm": (url, response) => {
137 const env = url.searchParams.get("env");
138 const suite = url.searchParams.get("suite");
139 if (suite) {
140 return removeSuite(response, suite);
141 }
142 else if (env) {
143 return removeEnv(response, env);
144 }
145 fileNotFound(response);
146 },
147 "GET /add/env": (url, response) => {
148 sendGetAddEnv(response);
149 },
150 "POST /add/env": (url, response, postData) => {
151 const envName = postData.name;
152 const defaultDomain = postData.domain;
153 if (!envName || !defaultDomain) {
154 return sendOutput(response, `<p>Imported new suite ${name} from file ${name}.js</p><p><a href="/">Back</a>`);
155 }
156 if (envName) {
157 addEnv(response, new config_1.EnvConfig(cli_1.Cli.config, {
158 name: envName,
159 defaultDomain: defaultDomain
160 }));
161 }
162 },
163 "POST /run": (url, response, postData) => {
164 const suiteName = postData.suite;
165 const envName = postData.env;
166 if (suiteName && cli_1.Cli.config.suites[suiteName]) {
167 return runSuite(response, suiteName, envName || flagpoleexecutionoptions_2.FlagpoleExecution.opts.environment);
168 }
169 fileNotFound(response);
170 }
171};
172const getSuite = (response, suiteName) => {
173 if (!suiteName) {
174 return sendOutput(response, `<p>No suite name. <a href="/">Back</a></p>`);
175 }
176 const suite = cli_1.Cli.config.suites[suiteName];
177 if (!suite) {
178 return sendOutput(response, `<p>That suite does not exist. <a href="/">Back</a></p>`);
179 }
180 return suite;
181};
182const getTemplate = (httpResponse) => {
183 const response = webserver_1.WebResponse.createFromTemplate(httpResponse, `${__dirname}/report.html`);
184 response.replace("nav", '<a href="/">Project Home</a>');
185 return response;
186};
187const sendOutput = (response, output) => {
188 getTemplate(response).send({
189 output: output
190 });
191};
192const sendJson = (response, json) => {
193 webserver_1.WebResponse.createFromInput(response, JSON.stringify(json)).send();
194};
195const fileNotFound = (response) => {
196 sendOutput(response, "File not found.");
197};
198const sendGetInit = (response) => {
199 let output = `
200 <h2>Initialize Flagpole</h2>
201 <p>
202 Flagpole has not yet been set up in this project. Complete the form below to configure.
203 </p>
204 <form method="POST" action="/init" id="frm">
205 <div class="field">
206 <label for="name">Project Name</label>
207 <input type="text" name="projectName" id="name" value="${process
208 .cwd()
209 .split("/")
210 .pop()}">
211 </div>
212 <div class="field">
213 <label for="folder">Tests Folder</label>
214 <input type="text" name="testsPath" id="folder" value="tests">
215 </div>
216 <div class="field">
217 Environments (check all that you want to use)
218 </div>`;
219 possibleEnvironments.forEach((envName) => {
220 output += `
221 <div class="field">
222 <input type="checkbox" name="envName[${envName}]" id="env_${envName}" value="${envName}">
223 <label for="env_${envName}">${envName}</label>
224 <input type="text" name="envDomain[${envName}]" id="domain_${envName}" placeholder="https://www.flagpolejs.com">
225 </div>
226 `;
227 });
228 output += `
229 <div class="field button">
230 <button type="submit">Initialize Project</button>
231 </div>
232 </form>`;
233 sendOutput(response, output);
234};
235const sendGetAddEnv = (response) => {
236 let output = `
237 <h2>Add Environment</h2>
238 <form method="POST" action="/add/env" id="frm">
239 <div class="field">
240 <label for="name">Environment Name</label>
241 <input type="text" name="name" id="name" placeholder="dev">
242 </div>
243 <div class="field">
244 <label for="domain">Base Path</label>
245 <input type="url" name="domain" id="domain" placeholder="http://www.google.com/">
246 </div>
247 <div class="field button">
248 <button type="submit">Add Environment</button>
249 <button type="button" onclick="window.location.href='/'">Cancel</button>
250 </div>
251 </form>`;
252 sendOutput(response, output);
253};
254const sendGetEditSuite = (response, suite) => {
255 let output = `
256 <h2>Edit Suite</h2>
257 <form method="POST" action="/suite" id="frm">
258 <div class="field">
259 <label for="name">Suite Name</label>
260 <input type="text" name="name" id="name" value="${suite.name}" readonly>
261 </div>
262 <div class="field">
263 <label for="tags">Tags (comma-separated)</label>
264 <input type="text" name="tags" id="tags" placeholder="tag1, tag2" value="${suite.tags.join(", ")}">
265 </div>
266 <div class="field button">
267 <button type="submit">Save Changes</button>
268 <button type="button" onclick="window.location.href='/'">Cancel</button>
269 </div>
270 </form>`;
271 sendOutput(response, output);
272};
273const sendGetAddScenario = (response, suite) => {
274 let output = `
275 <h2>Add Scenario</h2>
276 <p>Appending a new scenario to suite ${suite.name}.</p>
277 <form method="POST" action="/add/scenario" id="frm">
278 <div class="field">
279 <label for="suite">Suite</label>
280 <input type="text" readonly name="suite" id="suite" value="${suite.name}">
281 </div>
282 <div class="field">
283 <label for="description">Description</label>
284 <input type="text" name="description" id="description" placeholder="Make sure homepage loads">
285 </div>
286 <div class="field">
287 <label for="path">Path</label>
288 <input type="text" name="path" id="path" value="/" placeholder="/">
289 </div>
290 <div class="field">
291 <label for="type">Type</label>
292 <select name="type" id="type">
293 <option value="html">HTML/DOM (Cheerio)</option>
294 <option value="browser">Browser (Puppeteer)</option>
295 <option value="json">JSON/REST API</option>
296 </select>
297 </div>
298 <div class="field button">
299 <button type="submit">Add Scenario</button>
300 <button type="button" onclick="window.location.href = '/'">Cancel</button>
301 </div>
302 </form>
303 `;
304 return sendOutput(response, output);
305};
306const sendGetAddSuite = (response) => {
307 let output = `
308 <h2>New Suite</h2>
309 <form method="POST" action="/add/suite" id="frm">
310 <div class="field">
311 <label for="suiteName">Suite Name</label>
312 <input type="text" name="suiteName" id="suiteName" placeholder="smoke">
313 </div>
314 <div class="field">
315 <label for="suiteDescription">Suite Description</label>
316 <input type="text" name="suiteDescription" id="suiteDescription" placeholder="Basic smoke test of the site">
317 </div>
318 <fieldset>
319 <legend>Base Domain</legend>`;
320 cli_1.Cli.config.getEnvironments().forEach((env) => {
321 output += `
322 <div class="field">
323 <label for="env_${env.name}">${env.name}</label>
324 <input type="text" name="baseDomain[${env.name}]" id="env_${env.name}" value="${env.defaultDomain}">
325 </div>`;
326 });
327 output += `</fieldset>
328 <fieldset>
329 <legend>First Scenario</legend>
330 <div class="field">
331 <label for="scenarioDescription">Title</label>
332 <input type="text" name="scenarioDescription" id="scenarioDescription" placeholder="Make sure homepage loads">
333 </div>
334 <div class="field">
335 <label for="scenarioPath">Path</label>
336 <input type="text" name="scenarioPath" id="scenarioPath" value="/" placeholder="/">
337 </div>
338 <div class="field">
339 <label for="scenarioType">Type</label>
340 <select name="scenarioType" id="scenarioType">
341 <option value="html">HTML/DOM (Cheerio)</option>
342 <option value="browser">Browser (Puppeteer)</option>
343 <option value="json">JSON/REST API</option>
344 </select>
345 </div>
346 </fieldset>
347 <div class="field button">
348 <button type="submit">Add Suite</button>
349 <button type="button" onclick="document.location.href='/'">Cancel</button>
350 </div>
351 </form>
352 `;
353 sendOutput(response, output);
354};
355const sendGetImport = (response) => {
356 let output = `
357 <h2>Import Suite</h2>
358 `;
359 const detachedSuites = cli_1.Cli.findDetachedSuites();
360 if (detachedSuites.length == 0) {
361 output += "<p>There are no unattached *.js files in test folder.</p>";
362 }
363 else {
364 output += `
365 <script>
366 function importSuite() {
367 var form = document.getElementById("frmImport");
368 var e = document.getElementById("ddFile");
369 var file = e.options[e.selectedIndex].value;
370 if (file) {
371 if (confirm('Import this file ' + file + '.js?')) {
372 form.submit();
373 }
374 }
375 else {
376 alert('No file selected.');
377 }
378 }
379 </script>
380 <form method="POST" id="frmImport" action="/import/suite">
381 <div class="field">
382 <label for="ddFile">File to Import</label>
383 <select name="suite" id="ddFile">
384 `;
385 detachedSuites.forEach((file) => {
386 output += `
387 <option value="${file}">${file}</option>
388 `;
389 });
390 output += `
391 </select>
392 </div>
393 <div class="field button">
394 <button type="button" onclick="importSuite()">Import</button>
395 <button type="button" onclick="window.location.href = '/'">Cancel</button>
396 </div>
397 </form>
398 `;
399 }
400 sendOutput(response, output);
401};
402const removeSuite = (response, suiteName) => {
403 cli_1.Cli.config.removeSuite(suiteName);
404 cli_1.Cli.config
405 .save()
406 .then(() => {
407 sendOutput(response, `Removed suite <em>${suiteName}</em>, but did not delete the file. <a href="/">Back</a>`);
408 })
409 .catch(ex => {
410 sendOutput(response, `Error: ${ex}`);
411 });
412};
413const removeEnv = (response, envName) => {
414 cli_1.Cli.config.removeEnvironment(envName);
415 cli_1.Cli.config
416 .save()
417 .then(() => {
418 sendOutput(response, `Removed environment <em>${envName}</em>, no test scenarios were altered. <a href="/">Back</a>`);
419 })
420 .catch(ex => {
421 sendOutput(response, `Error: ${ex}`);
422 });
423};
424const initProject = (response, postData) => {
425 if (!postData.projectName || !postData.testsPath) {
426 return sendOutput(response, `<p>Project name and test path are required. <a href="/init">Try again</a></p>`);
427 }
428 cli_1.Cli.init({
429 project: {
430 name: postData.projectName,
431 path: postData.testsPath
432 },
433 environments: []
434 })
435 .then(() => {
436 let countEnvs = 0;
437 possibleEnvironments.forEach(env => {
438 if (postData[`envName[${env}]`]) {
439 const domain = postData[`envDomain[${env}]`];
440 cli_1.Cli.config.addEnvironment({
441 name: env,
442 defaultDomain: domain
443 });
444 countEnvs++;
445 }
446 });
447 if (countEnvs == 0) {
448 cli_1.Cli.config.addEnvironment({
449 name: "dev"
450 });
451 countEnvs++;
452 }
453 cli_1.Cli.config
454 .save()
455 .then(() => {
456 sendOutput(response, `
457 <p><em>Awesome!</em> You're ready to get going. Flagpole has been initialized in this project.</p>
458 <p>Next, we recommend you <strong><a href="/add/suite">add your first test suite</a></strong>.</p>
459 <p>Or you can <a href="/">skip this step</a>.</p>
460 `);
461 })
462 .catch(err => {
463 sendOutput(response, `
464 <p>Flagpole was initialized, but there was a problem saving environments: ${err}</p>
465 <p><a href="/">Continue</a></p>
466 `);
467 });
468 })
469 .catch(err => {
470 sendOutput(response, `<p>Error initializing: ${err}</p>p><a href="/init">Try again</a></p>`);
471 });
472};
473const addSuite = (response, suite, scenario) => {
474 cli_1.Cli.addSuite(suite, scenario)
475 .then(() => {
476 sendOutput(response, `Added new suite <em>${suite.name}</em>. <a href="/">Back</a>`);
477 })
478 .catch(err => {
479 sendOutput(response, `Error: ${err}`);
480 });
481};
482const addEnv = (response, env) => {
483 if (cli_1.Cli.config.environments[env.name]) {
484 sendOutput(response, "Error: Environment name is already taken.");
485 }
486 else {
487 cli_1.Cli.config.addEnvironment({
488 name: env.name,
489 defaultDomain: env.defaultDomain
490 });
491 cli_1.Cli.config
492 .save()
493 .then(() => {
494 sendOutput(response, `Added new environment <em>${env.name}</em>. <a href="/">Back</a>`);
495 })
496 .catch(ex => {
497 sendOutput(response, `Error: ${ex}`);
498 });
499 }
500};
501const runSuite = (response, suiteName, envName) => {
502 let opts = flagpoleexecutionoptions_1.FlagpoleExecutionOptions.createFromString(`-h -o json -e ${envName} -x`);
503 const execution = suiteexecution_1.SuiteExecution.executePath(cli_1.Cli.config.suites[suiteName].getTestPath(), opts);
504 execution.result
505 .then((result) => {
506 const json = JSON.parse(result.output.join(" "));
507 let output = `<h2>${json.title}</h2>`;
508 json.scenarios.forEach((scenario) => {
509 output += `<article><h3>${scenario.title}</h3>`;
510 output += "<ul>";
511 scenario.log.forEach((logLine) => {
512 output += `<li class="${logLine.type.toLowerCase()}">${logLine.message}</li>`;
513 });
514 output += "</ul></article>";
515 });
516 output += `<p><a href="/">Back</a></p>`;
517 sendOutput(response, output);
518 })
519 .catch(err => {
520 sendOutput(response, err);
521 });
522};
523const sendIndex = (response) => {
524 const suites = cli_1.Cli.config.getSuites();
525 let output = `
526 <ul>
527 <li>Project Name: ${cli_1.Cli.config.project.name}</li>
528 <li>Config Path: ${cli_1.Cli.configPath}</li>
529 <li>Project Path: ${cli_1.Cli.projectPath}</li>
530 <li>Environment: ${flagpoleexecutionoptions_2.FlagpoleExecution.opts.environment}</li>
531 </ul>
532 <h2>List of Suites</h2>
533 <table>
534 <thead>
535 <tr>
536 <th>Name</th>
537 <th>Tags</th>
538 <th>Actions</th>
539 </tr>
540 </thead>
541 <tbody>
542 `;
543 suites.forEach((suite) => {
544 output += `
545 <tr>
546 <td>${suite.name}</td>
547 <td>
548 ${suite.tags.length
549 ? suite.tags.join(", ")
550 : "<em>no tags</em>"}
551 </td>
552 <td>
553 <form method="POST" action="/run">
554 <button type="submit" name="suite" value="${suite.name}">Run</button>
555 </form>
556 <form method="GET" action="/suite">
557 <button type="submit" name="name" value="${suite.name}">Edit</button>
558 </form>
559 <form method="GET" action="/add/scenario">
560 <button type="submit" name="suite" value="${suite.name}">Add Scenario</button>
561 </form>
562 <form method="POST" id="rm_suite_${suite.name}">
563 <button type="button" onclick="removeSuite('${suite.name}')">Remove</button>
564 </form>
565 </td>
566 </tr>
567 `;
568 });
569 output += `
570 </tbody>
571 </table>
572 <div class="field button">
573 <form method="GET" action="/add/suite">
574 <button type="subit">New Suite</button>
575 </form>
576 <form method="GET" action="/import/suite">
577 <button type="submit">Import Suite</button>
578 </form>
579 </div>
580 `;
581 output += `
582 <h2>List of Environments</h2>
583 <table>
584 <thead>
585 <tr>
586 <th>Name</th>
587 <th>Base Path</th>
588 <th>Actions</th>
589 </tr>
590 </thead>
591 <tbody>
592 `;
593 cli_1.Cli.config.getEnvironments().forEach((env) => {
594 output += `
595 <tr>
596 <td>${env.name}</td>
597 <td>
598 <a href="${env.defaultDomain}" target="_new">${env.defaultDomain}</a>
599 </td>
600 <td>
601 <form method="POST" action="/rm?env=${env.name}" id="rm_env_${env.name}">
602 <button type="button" onclick="removeEnv('${env.name}')">Remove</button>
603 </form>
604 </td>
605 </tr>
606 `;
607 });
608 output += `
609 </tbody>
610 </table>
611 <div class="field button">
612 <form method="GET" id="addEnv" action="/add/env">
613 <button type="submit">Add Environment</button>
614 </form>
615 </aside>
616 <script>
617 function removeEnv(envName) {
618 const yes = confirm('Remove this environment ' + envName + '?')
619 if (yes) {
620 const form = document.querySelector('#rm_env_' + envName);
621 form.setAttribute('action', '/rm?env=' + envName);
622 form.submit();
623 }
624 }
625 function removeSuite(suiteName) {
626 const yes = confirm('Remove this suite ' + suiteName + '?')
627 if (yes) {
628 const form = document.querySelector('#rm_suite_' + suiteName);
629 form.setAttribute('action', '/rm?suite=' + suiteName);
630 form.submit();
631 }
632 }
633 </script>
634 `;
635 sendOutput(response, output);
636};
637function serve(port = 3000) {
638 const handler = (request, response) => {
639 const requestPath = request.url || "/";
640 const method = (request.method || "GET").toUpperCase();
641 const url = new url_1.URL(requestPath, `http://localhost:${server.httpPort}`);
642 const route = `${method} ${url.pathname}`;
643 function respond(postData) {
644 return routes[route]
645 ? routes[route](url, response, postData)
646 : sendIndex(response);
647 }
648 if (!cli_1.Cli.isInitialized() && requestPath != "/init") {
649 return sendGetInit(response);
650 }
651 else if (method == "POST") {
652 let body = "";
653 request.on("data", function (data) {
654 body += String(data);
655 if (body.length > 1e6) {
656 request.connection.destroy();
657 }
658 });
659 request.on("end", function () {
660 respond(qs.parse(body));
661 });
662 }
663 else {
664 respond();
665 }
666 };
667 const server = new webserver_1.WebServer(handler);
668 server.listen(port).then(() => {
669 const url = `http://localhost:${server.httpPort}/`;
670 console.log(`Flagpole Web Server running at: ${url}`);
671 open(url);
672 });
673}
674exports.serve = serve;
675//# sourceMappingURL=serve.js.map
\No newline at end of file