Set up comprehensive frontend testing infrastructure
- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										627
									
								
								frontend/node_modules/playwright/lib/reporters/html.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										627
									
								
								frontend/node_modules/playwright/lib/reporters/html.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,627 @@ | ||||
| "use strict"; | ||||
| var __create = Object.create; | ||||
| var __defProp = Object.defineProperty; | ||||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||||
| var __getProtoOf = Object.getPrototypeOf; | ||||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||||
| var __export = (target, all) => { | ||||
|   for (var name in all) | ||||
|     __defProp(target, name, { get: all[name], enumerable: true }); | ||||
| }; | ||||
| var __copyProps = (to, from, except, desc) => { | ||||
|   if (from && typeof from === "object" || typeof from === "function") { | ||||
|     for (let key of __getOwnPropNames(from)) | ||||
|       if (!__hasOwnProp.call(to, key) && key !== except) | ||||
|         __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||||
|   } | ||||
|   return to; | ||||
| }; | ||||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||||
|   // If the importer is in node compatibility mode or this is not an ESM | ||||
|   // file that has been converted to a CommonJS file using a Babel- | ||||
|   // compatible transform (i.e. "__esModule" has not been set), then set | ||||
|   // "default" to the CommonJS "module.exports" for node compatibility. | ||||
|   isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||||
|   mod | ||||
| )); | ||||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||||
| var html_exports = {}; | ||||
| __export(html_exports, { | ||||
|   default: () => html_default, | ||||
|   showHTMLReport: () => showHTMLReport, | ||||
|   startHtmlReportServer: () => startHtmlReportServer | ||||
| }); | ||||
| module.exports = __toCommonJS(html_exports); | ||||
| var import_fs = __toESM(require("fs")); | ||||
| var import_path = __toESM(require("path")); | ||||
| var import_stream = require("stream"); | ||||
| var import_utils = require("playwright-core/lib/utils"); | ||||
| var import_utils2 = require("playwright-core/lib/utils"); | ||||
| var import_utilsBundle = require("playwright-core/lib/utilsBundle"); | ||||
| var import_utilsBundle2 = require("playwright-core/lib/utilsBundle"); | ||||
| var import_zipBundle = require("playwright-core/lib/zipBundle"); | ||||
| var import_base = require("./base"); | ||||
| var import_babelBundle = require("../transform/babelBundle"); | ||||
| var import_util = require("../util"); | ||||
| const htmlReportOptions = ["always", "never", "on-failure"]; | ||||
| const isHtmlReportOption = (type) => { | ||||
|   return htmlReportOptions.includes(type); | ||||
| }; | ||||
| class HtmlReporter { | ||||
|   constructor(options) { | ||||
|     this._topLevelErrors = []; | ||||
|     this._options = options; | ||||
|   } | ||||
|   version() { | ||||
|     return "v2"; | ||||
|   } | ||||
|   printsToStdio() { | ||||
|     return false; | ||||
|   } | ||||
|   onConfigure(config) { | ||||
|     this.config = config; | ||||
|   } | ||||
|   onBegin(suite) { | ||||
|     const { outputFolder, open: open2, attachmentsBaseURL, host, port } = this._resolveOptions(); | ||||
|     this._outputFolder = outputFolder; | ||||
|     this._open = open2; | ||||
|     this._host = host; | ||||
|     this._port = port; | ||||
|     this._attachmentsBaseURL = attachmentsBaseURL; | ||||
|     const reportedWarnings = /* @__PURE__ */ new Set(); | ||||
|     for (const project of this.config.projects) { | ||||
|       if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) { | ||||
|         const key = outputFolder + "|" + project.outputDir; | ||||
|         if (reportedWarnings.has(key)) | ||||
|           continue; | ||||
|         reportedWarnings.add(key); | ||||
|         console.log(import_utils2.colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`)); | ||||
|         console.log(` | ||||
|     html reporter folder: ${import_utils2.colors.bold(outputFolder)} | ||||
|     test results folder: ${import_utils2.colors.bold(project.outputDir)}`); | ||||
|         console.log(""); | ||||
|         console.log(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss. | ||||
| `); | ||||
|       } | ||||
|     } | ||||
|     this.suite = suite; | ||||
|   } | ||||
|   _resolveOptions() { | ||||
|     const outputFolder = reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", this._options.configDir, this._options.outputFolder); | ||||
|     return { | ||||
|       outputFolder, | ||||
|       open: getHtmlReportOptionProcessEnv() || this._options.open || "on-failure", | ||||
|       attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || "data/", | ||||
|       host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host, | ||||
|       port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port | ||||
|     }; | ||||
|   } | ||||
|   _isSubdirectory(parentDir, dir) { | ||||
|     const relativePath = import_path.default.relative(parentDir, dir); | ||||
|     return !!relativePath && !relativePath.startsWith("..") && !import_path.default.isAbsolute(relativePath); | ||||
|   } | ||||
|   onError(error) { | ||||
|     this._topLevelErrors.push(error); | ||||
|   } | ||||
|   async onEnd(result) { | ||||
|     const projectSuites = this.suite.suites; | ||||
|     await (0, import_utils.removeFolders)([this._outputFolder]); | ||||
|     let noSnippets; | ||||
|     if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "false" || process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "0") | ||||
|       noSnippets = false; | ||||
|     else if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS) | ||||
|       noSnippets = true; | ||||
|     noSnippets = noSnippets || this._options.noSnippets; | ||||
|     const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, process.env.PLAYWRIGHT_HTML_TITLE || this._options.title, noSnippets); | ||||
|     this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors); | ||||
|   } | ||||
|   async onExit() { | ||||
|     if (process.env.CI || !this._buildResult) | ||||
|       return; | ||||
|     const { ok, singleTestId } = this._buildResult; | ||||
|     const shouldOpen = !this._options._isTestServer && (this._open === "always" || !ok && this._open === "on-failure"); | ||||
|     if (shouldOpen) { | ||||
|       await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId); | ||||
|     } else if (this._options._mode === "test" && !this._options._isTestServer) { | ||||
|       const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)(); | ||||
|       const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? "" : " " + import_path.default.relative(process.cwd(), this._outputFolder); | ||||
|       const hostArg = this._host ? ` --host ${this._host}` : ""; | ||||
|       const portArg = this._port ? ` --port ${this._port}` : ""; | ||||
|       console.log(""); | ||||
|       console.log("To open last HTML report run:"); | ||||
|       console.log(import_utils2.colors.cyan(` | ||||
|   ${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg} | ||||
| `)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| function reportFolderFromEnv() { | ||||
|   const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT; | ||||
|   return envValue ? import_path.default.resolve(envValue) : void 0; | ||||
| } | ||||
| function getHtmlReportOptionProcessEnv() { | ||||
|   const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN; | ||||
|   if (!htmlOpenEnv) | ||||
|     return void 0; | ||||
|   if (!isHtmlReportOption(htmlOpenEnv)) { | ||||
|     console.log(import_utils2.colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(", ")}`)); | ||||
|     return void 0; | ||||
|   } | ||||
|   return htmlOpenEnv; | ||||
| } | ||||
| function standaloneDefaultFolder() { | ||||
|   return reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", process.cwd(), void 0); | ||||
| } | ||||
| async function showHTMLReport(reportFolder, host = "localhost", port, testId) { | ||||
|   const folder = reportFolder ?? standaloneDefaultFolder(); | ||||
|   try { | ||||
|     (0, import_utils.assert)(import_fs.default.statSync(folder).isDirectory()); | ||||
|   } catch (e) { | ||||
|     console.log(import_utils2.colors.red(`No report found at "${folder}"`)); | ||||
|     (0, import_utils.gracefullyProcessExitDoNotHang)(1); | ||||
|     return; | ||||
|   } | ||||
|   const server = startHtmlReportServer(folder); | ||||
|   await server.start({ port, host, preferredPort: port ? void 0 : 9323 }); | ||||
|   let url = server.urlPrefix("human-readable"); | ||||
|   console.log(""); | ||||
|   console.log(import_utils2.colors.cyan(`  Serving HTML report at ${url}. Press Ctrl+C to quit.`)); | ||||
|   if (testId) | ||||
|     url += `#?testId=${testId}`; | ||||
|   url = url.replace("0.0.0.0", "localhost"); | ||||
|   await (0, import_utilsBundle.open)(url, { wait: true }).catch(() => { | ||||
|   }); | ||||
|   await new Promise(() => { | ||||
|   }); | ||||
| } | ||||
| function startHtmlReportServer(folder) { | ||||
|   const server = new import_utils.HttpServer(); | ||||
|   server.routePrefix("/", (request, response) => { | ||||
|     let relativePath = new URL("http://localhost" + request.url).pathname; | ||||
|     if (relativePath.startsWith("/trace/file")) { | ||||
|       const url = new URL("http://localhost" + request.url); | ||||
|       try { | ||||
|         return server.serveFile(request, response, url.searchParams.get("path")); | ||||
|       } catch (e) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|     if (relativePath.endsWith("/stall.js")) | ||||
|       return true; | ||||
|     if (relativePath === "/") | ||||
|       relativePath = "/index.html"; | ||||
|     const absolutePath = import_path.default.join(folder, ...relativePath.split("/")); | ||||
|     return server.serveFile(request, response, absolutePath); | ||||
|   }); | ||||
|   return server; | ||||
| } | ||||
| class HtmlBuilder { | ||||
|   constructor(config, outputDir, attachmentsBaseURL, title, noSnippets = false) { | ||||
|     this._stepsInFile = new import_utils.MultiMap(); | ||||
|     this._hasTraces = false; | ||||
|     this._config = config; | ||||
|     this._reportFolder = outputDir; | ||||
|     this._noSnippets = noSnippets; | ||||
|     import_fs.default.mkdirSync(this._reportFolder, { recursive: true }); | ||||
|     this._dataZipFile = new import_zipBundle.yazl.ZipFile(); | ||||
|     this._attachmentsBaseURL = attachmentsBaseURL; | ||||
|     this._title = title; | ||||
|   } | ||||
|   async build(metadata, projectSuites, result, topLevelErrors) { | ||||
|     const data = /* @__PURE__ */ new Map(); | ||||
|     for (const projectSuite of projectSuites) { | ||||
|       for (const fileSuite of projectSuite.suites) { | ||||
|         const fileName = this._relativeLocation(fileSuite.location).file; | ||||
|         const fileId = (0, import_utils.calculateSha1)((0, import_utils.toPosixPath)(fileName)).slice(0, 20); | ||||
|         let fileEntry = data.get(fileId); | ||||
|         if (!fileEntry) { | ||||
|           fileEntry = { | ||||
|             testFile: { fileId, fileName, tests: [] }, | ||||
|             testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() } | ||||
|           }; | ||||
|           data.set(fileId, fileEntry); | ||||
|         } | ||||
|         const { testFile, testFileSummary } = fileEntry; | ||||
|         const testEntries = []; | ||||
|         this._processSuite(fileSuite, projectSuite.project().name, [], testEntries); | ||||
|         for (const test of testEntries) { | ||||
|           testFile.tests.push(test.testCase); | ||||
|           testFileSummary.tests.push(test.testCaseSummary); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (!this._noSnippets) | ||||
|       createSnippets(this._stepsInFile); | ||||
|     let ok = true; | ||||
|     for (const [fileId, { testFile, testFileSummary }] of data) { | ||||
|       const stats = testFileSummary.stats; | ||||
|       for (const test of testFileSummary.tests) { | ||||
|         if (test.outcome === "expected") | ||||
|           ++stats.expected; | ||||
|         if (test.outcome === "skipped") | ||||
|           ++stats.skipped; | ||||
|         if (test.outcome === "unexpected") | ||||
|           ++stats.unexpected; | ||||
|         if (test.outcome === "flaky") | ||||
|           ++stats.flaky; | ||||
|         ++stats.total; | ||||
|       } | ||||
|       stats.ok = stats.unexpected + stats.flaky === 0; | ||||
|       if (!stats.ok) | ||||
|         ok = false; | ||||
|       const testCaseSummaryComparator = (t1, t2) => { | ||||
|         const w1 = (t1.outcome === "unexpected" ? 1e3 : 0) + (t1.outcome === "flaky" ? 1 : 0); | ||||
|         const w2 = (t2.outcome === "unexpected" ? 1e3 : 0) + (t2.outcome === "flaky" ? 1 : 0); | ||||
|         return w2 - w1; | ||||
|       }; | ||||
|       testFileSummary.tests.sort(testCaseSummaryComparator); | ||||
|       this._addDataFile(fileId + ".json", testFile); | ||||
|     } | ||||
|     const htmlReport = { | ||||
|       metadata, | ||||
|       title: this._title, | ||||
|       startTime: result.startTime.getTime(), | ||||
|       duration: result.duration, | ||||
|       files: [...data.values()].map((e) => e.testFileSummary), | ||||
|       projectNames: projectSuites.map((r) => r.project().name), | ||||
|       stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) }, | ||||
|       errors: topLevelErrors.map((error) => (0, import_base.formatError)(import_base.internalScreen, error).message) | ||||
|     }; | ||||
|     htmlReport.files.sort((f1, f2) => { | ||||
|       const w1 = f1.stats.unexpected * 1e3 + f1.stats.flaky; | ||||
|       const w2 = f2.stats.unexpected * 1e3 + f2.stats.flaky; | ||||
|       return w2 - w1; | ||||
|     }); | ||||
|     this._addDataFile("report.json", htmlReport); | ||||
|     let singleTestId; | ||||
|     if (htmlReport.stats.total === 1) { | ||||
|       const testFile = data.values().next().value.testFile; | ||||
|       singleTestId = testFile.tests[0].testId; | ||||
|     } | ||||
|     if (process.env.PW_HMR === "1") { | ||||
|       const redirectFile = import_path.default.join(this._reportFolder, "index.html"); | ||||
|       await this._writeReportData(redirectFile); | ||||
|       async function redirect() { | ||||
|         const hmrURL = new URL("http://localhost:44224"); | ||||
|         const popup = window.open(hmrURL); | ||||
|         const listener = (evt) => { | ||||
|           if (evt.source === popup && evt.data === "ready") { | ||||
|             popup.postMessage(window.playwrightReportBase64, hmrURL.origin); | ||||
|             window.removeEventListener("message", listener); | ||||
|             window.close(); | ||||
|           } | ||||
|         }; | ||||
|         window.addEventListener("message", listener); | ||||
|       } | ||||
|       import_fs.default.appendFileSync(redirectFile, `<script>(${redirect.toString()})()</script>`); | ||||
|       return { ok, singleTestId }; | ||||
|     } | ||||
|     const appFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "htmlReport"); | ||||
|     await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(appFolder, "index.html"), import_path.default.join(this._reportFolder, "index.html")); | ||||
|     if (this._hasTraces) { | ||||
|       const traceViewerFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "traceViewer"); | ||||
|       const traceViewerTargetFolder = import_path.default.join(this._reportFolder, "trace"); | ||||
|       const traceViewerAssetsTargetFolder = import_path.default.join(traceViewerTargetFolder, "assets"); | ||||
|       import_fs.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true }); | ||||
|       for (const file of import_fs.default.readdirSync(traceViewerFolder)) { | ||||
|         if (file.endsWith(".map") || file.includes("watch") || file.includes("assets")) | ||||
|           continue; | ||||
|         await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, file), import_path.default.join(traceViewerTargetFolder, file)); | ||||
|       } | ||||
|       for (const file of import_fs.default.readdirSync(import_path.default.join(traceViewerFolder, "assets"))) { | ||||
|         if (file.endsWith(".map") || file.includes("xtermModule")) | ||||
|           continue; | ||||
|         await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, "assets", file), import_path.default.join(traceViewerAssetsTargetFolder, file)); | ||||
|       } | ||||
|     } | ||||
|     await this._writeReportData(import_path.default.join(this._reportFolder, "index.html")); | ||||
|     return { ok, singleTestId }; | ||||
|   } | ||||
|   async _writeReportData(filePath) { | ||||
|     import_fs.default.appendFileSync(filePath, '<script>\nwindow.playwrightReportBase64 = "data:application/zip;base64,'); | ||||
|     await new Promise((f) => { | ||||
|       this._dataZipFile.end(void 0, () => { | ||||
|         this._dataZipFile.outputStream.pipe(new Base64Encoder()).pipe(import_fs.default.createWriteStream(filePath, { flags: "a" })).on("close", f); | ||||
|       }); | ||||
|     }); | ||||
|     import_fs.default.appendFileSync(filePath, '";</script>'); | ||||
|   } | ||||
|   _addDataFile(fileName, data) { | ||||
|     this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName); | ||||
|   } | ||||
|   _processSuite(suite, projectName, path2, outTests) { | ||||
|     const newPath = [...path2, suite.title]; | ||||
|     suite.entries().forEach((e) => { | ||||
|       if (e.type === "test") | ||||
|         outTests.push(this._createTestEntry(e, projectName, newPath)); | ||||
|       else | ||||
|         this._processSuite(e, projectName, newPath, outTests); | ||||
|     }); | ||||
|   } | ||||
|   _createTestEntry(test, projectName, path2) { | ||||
|     const duration = test.results.reduce((a, r) => a + r.duration, 0); | ||||
|     const location = this._relativeLocation(test.location); | ||||
|     path2 = path2.slice(1).filter((path3) => path3.length > 0); | ||||
|     const results = test.results.map((r) => this._createTestResult(test, r)); | ||||
|     return { | ||||
|       testCase: { | ||||
|         testId: test.id, | ||||
|         title: test.title, | ||||
|         projectName, | ||||
|         location, | ||||
|         duration, | ||||
|         annotations: this._serializeAnnotations(test.annotations), | ||||
|         tags: test.tags, | ||||
|         outcome: test.outcome(), | ||||
|         path: path2, | ||||
|         results, | ||||
|         ok: test.outcome() === "expected" || test.outcome() === "flaky" | ||||
|       }, | ||||
|       testCaseSummary: { | ||||
|         testId: test.id, | ||||
|         title: test.title, | ||||
|         projectName, | ||||
|         location, | ||||
|         duration, | ||||
|         annotations: this._serializeAnnotations(test.annotations), | ||||
|         tags: test.tags, | ||||
|         outcome: test.outcome(), | ||||
|         path: path2, | ||||
|         ok: test.outcome() === "expected" || test.outcome() === "flaky", | ||||
|         results: results.map((result) => { | ||||
|           return { attachments: result.attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path })) }; | ||||
|         }) | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   _serializeAttachments(attachments) { | ||||
|     let lastAttachment; | ||||
|     return attachments.map((a) => { | ||||
|       if (a.name === "trace") | ||||
|         this._hasTraces = true; | ||||
|       if ((a.name === "stdout" || a.name === "stderr") && a.contentType === "text/plain") { | ||||
|         if (lastAttachment && lastAttachment.name === a.name && lastAttachment.contentType === a.contentType) { | ||||
|           lastAttachment.body += (0, import_util.stripAnsiEscapes)(a.body); | ||||
|           return null; | ||||
|         } | ||||
|         a.body = (0, import_util.stripAnsiEscapes)(a.body); | ||||
|         lastAttachment = a; | ||||
|         return a; | ||||
|       } | ||||
|       if (a.path) { | ||||
|         let fileName = a.path; | ||||
|         try { | ||||
|           const buffer = import_fs.default.readFileSync(a.path); | ||||
|           const sha1 = (0, import_utils.calculateSha1)(buffer) + import_path.default.extname(a.path); | ||||
|           fileName = this._attachmentsBaseURL + sha1; | ||||
|           import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true }); | ||||
|           import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), buffer); | ||||
|         } catch (e) { | ||||
|         } | ||||
|         return { | ||||
|           name: a.name, | ||||
|           contentType: a.contentType, | ||||
|           path: fileName, | ||||
|           body: a.body | ||||
|         }; | ||||
|       } | ||||
|       if (a.body instanceof Buffer) { | ||||
|         if (isTextContentType(a.contentType)) { | ||||
|           const charset = a.contentType.match(/charset=(.*)/)?.[1]; | ||||
|           try { | ||||
|             const body = a.body.toString(charset || "utf-8"); | ||||
|             return { | ||||
|               name: a.name, | ||||
|               contentType: a.contentType, | ||||
|               body | ||||
|             }; | ||||
|           } catch (e) { | ||||
|           } | ||||
|         } | ||||
|         import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true }); | ||||
|         const extension = (0, import_utils.sanitizeForFilePath)(import_path.default.extname(a.name).replace(/^\./, "")) || import_utilsBundle2.mime.getExtension(a.contentType) || "dat"; | ||||
|         const sha1 = (0, import_utils.calculateSha1)(a.body) + "." + extension; | ||||
|         import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), a.body); | ||||
|         return { | ||||
|           name: a.name, | ||||
|           contentType: a.contentType, | ||||
|           path: this._attachmentsBaseURL + sha1 | ||||
|         }; | ||||
|       } | ||||
|       return { | ||||
|         name: a.name, | ||||
|         contentType: a.contentType, | ||||
|         body: a.body | ||||
|       }; | ||||
|     }).filter(Boolean); | ||||
|   } | ||||
|   _serializeAnnotations(annotations) { | ||||
|     return annotations.map((a) => ({ | ||||
|       type: a.type, | ||||
|       description: a.description === void 0 ? void 0 : String(a.description), | ||||
|       location: a.location ? { | ||||
|         file: a.location.file, | ||||
|         line: a.location.line, | ||||
|         column: a.location.column | ||||
|       } : void 0 | ||||
|     })); | ||||
|   } | ||||
|   _createTestResult(test, result) { | ||||
|     return { | ||||
|       duration: result.duration, | ||||
|       startTime: result.startTime.toISOString(), | ||||
|       retry: result.retry, | ||||
|       steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)), | ||||
|       errors: (0, import_base.formatResultFailure)(import_base.internalScreen, test, result, "").map((error) => { | ||||
|         return { | ||||
|           message: error.message, | ||||
|           codeframe: error.location ? createErrorCodeframe(error.message, error.location) : void 0 | ||||
|         }; | ||||
|       }), | ||||
|       status: result.status, | ||||
|       annotations: this._serializeAnnotations(result.annotations), | ||||
|       attachments: this._serializeAttachments([ | ||||
|         ...result.attachments, | ||||
|         ...result.stdout.map((m) => stdioAttachment(m, "stdout")), | ||||
|         ...result.stderr.map((m) => stdioAttachment(m, "stderr")) | ||||
|       ]) | ||||
|     }; | ||||
|   } | ||||
|   _createTestStep(dedupedStep, result) { | ||||
|     const { step, duration, count } = dedupedStep; | ||||
|     const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip"); | ||||
|     let title = (0, import_util.stepTitle)(step.category, step.title); | ||||
|     if (skipped) | ||||
|       title = `${title} (skipped${skipped.description ? ": " + skipped.description : ""})`; | ||||
|     const testStep = { | ||||
|       title, | ||||
|       startTime: step.startTime.toISOString(), | ||||
|       duration, | ||||
|       steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)), | ||||
|       attachments: step.attachments.map((s) => { | ||||
|         const index = result.attachments.indexOf(s); | ||||
|         if (index === -1) | ||||
|           throw new Error("Unexpected, attachment not found"); | ||||
|         return index; | ||||
|       }), | ||||
|       location: this._relativeLocation(step.location), | ||||
|       error: step.error?.message, | ||||
|       count, | ||||
|       skipped: !!skipped | ||||
|     }; | ||||
|     if (step.location) | ||||
|       this._stepsInFile.set(step.location.file, testStep); | ||||
|     return testStep; | ||||
|   } | ||||
|   _relativeLocation(location) { | ||||
|     if (!location) | ||||
|       return void 0; | ||||
|     const file = (0, import_utils.toPosixPath)(import_path.default.relative(this._config.rootDir, location.file)); | ||||
|     return { | ||||
|       file, | ||||
|       line: location.line, | ||||
|       column: location.column | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| const emptyStats = () => { | ||||
|   return { | ||||
|     total: 0, | ||||
|     expected: 0, | ||||
|     unexpected: 0, | ||||
|     flaky: 0, | ||||
|     skipped: 0, | ||||
|     ok: true | ||||
|   }; | ||||
| }; | ||||
| const addStats = (stats, delta) => { | ||||
|   stats.total += delta.total; | ||||
|   stats.skipped += delta.skipped; | ||||
|   stats.expected += delta.expected; | ||||
|   stats.unexpected += delta.unexpected; | ||||
|   stats.flaky += delta.flaky; | ||||
|   stats.ok = stats.ok && delta.ok; | ||||
|   return stats; | ||||
| }; | ||||
| class Base64Encoder extends import_stream.Transform { | ||||
|   _transform(chunk, encoding, callback) { | ||||
|     if (this._remainder) { | ||||
|       chunk = Buffer.concat([this._remainder, chunk]); | ||||
|       this._remainder = void 0; | ||||
|     } | ||||
|     const remaining = chunk.length % 3; | ||||
|     if (remaining) { | ||||
|       this._remainder = chunk.slice(chunk.length - remaining); | ||||
|       chunk = chunk.slice(0, chunk.length - remaining); | ||||
|     } | ||||
|     chunk = chunk.toString("base64"); | ||||
|     this.push(Buffer.from(chunk)); | ||||
|     callback(); | ||||
|   } | ||||
|   _flush(callback) { | ||||
|     if (this._remainder) | ||||
|       this.push(Buffer.from(this._remainder.toString("base64"))); | ||||
|     callback(); | ||||
|   } | ||||
| } | ||||
| function isTextContentType(contentType) { | ||||
|   return contentType.startsWith("text/") || contentType.startsWith("application/json"); | ||||
| } | ||||
| function stdioAttachment(chunk, type) { | ||||
|   return { | ||||
|     name: type, | ||||
|     contentType: "text/plain", | ||||
|     body: typeof chunk === "string" ? chunk : chunk.toString("utf-8") | ||||
|   }; | ||||
| } | ||||
| function dedupeSteps(steps) { | ||||
|   const result = []; | ||||
|   let lastResult = void 0; | ||||
|   for (const step of steps) { | ||||
|     const canDedupe = !step.error && step.duration >= 0 && step.location?.file && !step.steps.length; | ||||
|     const lastStep = lastResult?.step; | ||||
|     if (canDedupe && lastResult && lastStep && step.category === lastStep.category && step.title === lastStep.title && step.location?.file === lastStep.location?.file && step.location?.line === lastStep.location?.line && step.location?.column === lastStep.location?.column) { | ||||
|       ++lastResult.count; | ||||
|       lastResult.duration += step.duration; | ||||
|       continue; | ||||
|     } | ||||
|     lastResult = { step, count: 1, duration: step.duration }; | ||||
|     result.push(lastResult); | ||||
|     if (!canDedupe) | ||||
|       lastResult = void 0; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| function createSnippets(stepsInFile) { | ||||
|   for (const file of stepsInFile.keys()) { | ||||
|     let source; | ||||
|     try { | ||||
|       source = import_fs.default.readFileSync(file, "utf-8") + "\n//"; | ||||
|     } catch (e) { | ||||
|       continue; | ||||
|     } | ||||
|     const lines = source.split("\n").length; | ||||
|     const highlighted = (0, import_babelBundle.codeFrameColumns)(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 }); | ||||
|     const highlightedLines = highlighted.split("\n"); | ||||
|     const lineWithArrow = highlightedLines[highlightedLines.length - 1]; | ||||
|     for (const step of stepsInFile.get(file)) { | ||||
|       if (step.location.line < 2 || step.location.line >= lines) | ||||
|         continue; | ||||
|       const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1); | ||||
|       const index = lineWithArrow.indexOf("^"); | ||||
|       const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index); | ||||
|       snippetLines.splice(2, 0, shiftedArrow); | ||||
|       step.snippet = snippetLines.join("\n"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| function createErrorCodeframe(message, location) { | ||||
|   let source; | ||||
|   try { | ||||
|     source = import_fs.default.readFileSync(location.file, "utf-8") + "\n//"; | ||||
|   } catch (e) { | ||||
|     return; | ||||
|   } | ||||
|   return (0, import_babelBundle.codeFrameColumns)( | ||||
|     source, | ||||
|     { | ||||
|       start: { | ||||
|         line: location.line, | ||||
|         column: location.column | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       highlightCode: false, | ||||
|       linesAbove: 100, | ||||
|       linesBelow: 100, | ||||
|       message: (0, import_util.stripAnsiEscapes)(message).split("\n")[0] || void 0 | ||||
|     } | ||||
|   ); | ||||
| } | ||||
| var html_default = HtmlReporter; | ||||
| // Annotate the CommonJS export names for ESM import in node: | ||||
| 0 && (module.exports = { | ||||
|   showHTMLReport, | ||||
|   startHtmlReportServer | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user
	 anthonyrawlins
					anthonyrawlins