diff --git a/site/e2e/hooks.ts b/site/e2e/hooks.ts index 62cf3f3d60..53bbe3e80e 100644 --- a/site/e2e/hooks.ts +++ b/site/e2e/hooks.ts @@ -3,44 +3,40 @@ import type { BrowserContext, Page } from "@playwright/test"; import { coderPort, gitAuth } from "./constants"; export const beforeCoderTest = (page: Page) => { - page.on("console", (msg) => console.info(`[onConsole] ${msg.text()}`)); - - page.on("request", (request) => { - if (!isApiCall(request.url())) { + page.on("console", (msg) => { + const location = msg.location(); + // Filters out a bunch of junk warnings the browser produces. + if (!location.url) { return; } - - console.info( - `[onRequest] method=${request.method()} url=${request.url()} postData=${ - request.postData() ? request.postData() : "" - }`, - ); + // Filters out the gigantic CODER logo we print on every page load, as well + // as some other noise. + if (msg.type() === "info") { + return; + } + console.info(`[console][${msg.type()}] ${msg.text()}`); }); + page.on("response", async (response) => { + // Don't log responses for static assets. if (!isApiCall(response.url())) { return; } + // Don't log successful responses. Those are almost always less interesting. + if (response.ok()) { + return; + } - const shouldLogResponse = - !response.url().endsWith("/api/v2/deployment/config") && - !response.url().endsWith("/api/v2/debug/health?force=false"); - - let responseText = ""; + let responseText: string; try { - if (shouldLogResponse) { - const buffer = await response.body(); - responseText = buffer.toString("utf-8"); - responseText = responseText.replace(/\n$/g, ""); - } else { - responseText = "skipped..."; - } - } catch (error) { - console.error(error); - responseText = "not_available"; + responseText = await response.text(); + responseText = responseText.replaceAll("\n", ""); + } catch { + responseText = ""; } console.info( - `[onResponse] url=${response.url()} status=${response.status()} body=${responseText}`, + `[response] url=${response.url()} status=${response.status()} body=${responseText}`, ); }); }; diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index fffc80b160..1454eb25c0 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -47,7 +47,7 @@ export default defineConfig({ timeout: 30_000, }, ], - reporter: [["./reporter.ts"]], + reporter: [["list"], ["./reporter.ts"]], use: { actionTimeout: 5000, baseURL: `http://localhost:${coderPort}`, diff --git a/site/e2e/reporter.ts b/site/e2e/reporter.ts index e81c88ab10..40383ce355 100644 --- a/site/e2e/reporter.ts +++ b/site/e2e/reporter.ts @@ -1,146 +1,27 @@ import * as fs from "node:fs/promises"; -import type { Writable } from "node:stream"; -import type { - FullConfig, - FullResult, - Reporter, - Suite, - TestCase, - TestError, - TestResult, -} from "@playwright/test/reporter"; +import type { Reporter, TestCase, TestResult } from "@playwright/test/reporter"; import { API } from "api/api"; -import { coderdPProfPort, license } from "./constants"; +import { coderdPProfPort } from "./constants"; class CoderReporter implements Reporter { - config: FullConfig | null = null; - testOutput = new Map>(); - passedCount = 0; - skippedCount = 0; - failedTests: TestCase[] = []; - timedOutTests: TestCase[] = []; - - onBegin(config: FullConfig, suite: Suite) { - this.config = config; - console.info(`==> Running ${suite.allTests().length} tests`); - } - - onTestBegin(test: TestCase) { - this.testOutput.set(test.id, []); - console.info(`==> Starting test ${test.title}`); - } - - onStdOut(chunk: string, test?: TestCase, _?: TestResult): void { - // If there's no associated test, just print it now - if (!test) { - for (const line of logLines(chunk)) { - console.info(`[stdout] ${line}`); - } - return; - } - // Will be printed if the test fails - this.testOutput.get(test.id)!.push([process.stdout, chunk]); - } - - onStdErr(chunk: string, test?: TestCase, _?: TestResult): void { - // If there's no associated test, just print it now - if (!test) { - for (const line of logLines(chunk)) { - console.error(`[stderr] ${line}`); - } - return; - } - // Will be printed if the test fails - this.testOutput.get(test.id)!.push([process.stderr, chunk]); - } - async onTestEnd(test: TestCase, result: TestResult) { - try { - if (test.expectedStatus === "skipped") { - console.info(`==> Skipping test ${test.title}`); - this.skippedCount++; - return; - } - - console.info(`==> Finished test ${test.title}: ${result.status}`); - - if (result.status === "passed") { - this.passedCount++; - return; - } - - if (result.status === "failed") { - this.failedTests.push(test); - } - - if (result.status === "timedOut") { - this.timedOutTests.push(test); - } - - const fsTestTitle = test.title.replaceAll(" ", "-"); - const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`; - await exportDebugPprof(outputFile); - - console.info(`Data from pprof has been saved to ${outputFile}`); - console.info("==> Output"); - const output = this.testOutput.get(test.id)!; - for (const [target, chunk] of output) { - target.write(`${chunk.replace(/\n$/g, "")}\n`); - } - - if (result.errors.length > 0) { - console.info("==> Errors"); - for (const error of result.errors) { - reportError(error); - } - } - - if (result.attachments.length > 0) { - console.info("==> Attachments"); - for (const attachment of result.attachments) { - console.info(attachment); - } - } - } finally { - this.testOutput.delete(test.id); + if (test.expectedStatus === "skipped") { + return; } - } - onEnd(result: FullResult) { - console.info(`==> Tests ${result.status}`); - if (!license) { - console.info( - "==> Tests that require a license were skipped, because no license was provided", - ); - } - console.info(`${this.passedCount} passed`); - if (this.skippedCount > 0) { - console.info(`${this.skippedCount} skipped`); - } - if (this.failedTests.length > 0) { - console.info(`${this.failedTests.length} failed`); - for (const test of this.failedTests) { - console.info(` ${test.location.file} › ${test.title}`); - } - } - if (this.timedOutTests.length > 0) { - console.info(`${this.timedOutTests.length} timed out`); - for (const test of this.timedOutTests) { - console.info(` ${test.location.file} › ${test.title}`); - } + if (result.status === "passed") { + return; } + + const fsTestTitle = test.title.replaceAll(" ", "-"); + const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`; + await exportDebugPprof(outputFile); + + console.info(`Data from pprof has been saved to ${outputFile}`); + console.info("==> Output"); } } -const logLines = (chunk: string | Buffer): string[] => { - if (chunk instanceof Buffer) { - // When running in a debugger, the input to this is a Buffer instead of a string. - // Unsure why, but this prevents the `trimEnd` from throwing an error. - return [chunk.toString()]; - } - return chunk.trimEnd().split("\n"); -}; - const exportDebugPprof = async (outputFile: string) => { const axiosInstance = API.getAxiosInstance(); const response = await axiosInstance.get( @@ -154,19 +35,4 @@ const exportDebugPprof = async (outputFile: string) => { await fs.writeFile(outputFile, response.data); }; -const reportError = (error: TestError) => { - if (error.location) { - console.info(`${error.location.file}:${error.location.line}:`); - } - if (error.snippet) { - console.info(error.snippet); - } - - if (error.message) { - console.info(error.message); - } else { - console.info(error); - } -}; - export default CoderReporter;