From 2263b974ba7610159e4b081763c70aa51748bb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Tue, 16 Dec 2025 16:19:29 +0100 Subject: [PATCH 1/7] Execute test cases locally --- src/cli.ts | 31 ++++++++++++++- src/debugtopus/index.ts | 81 +++++++++++++++++++++++++++++++++++++++ src/tools/sync/yml.ts | 5 ++- src/tools/test-targets.ts | 4 ++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 70ce270..6be5942 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,7 +11,7 @@ import { testTargetIdCompleter, uninstallCompletion, } from "./completion"; -import { runDebugtopus } from "./debugtopus"; +import { executeLocalTestCases, runDebugtopus } from "./debugtopus"; import { resolveTestTargetId } from "./helpers"; import { startPrivateLocationWorker, stopPLW } from "./plw"; import { @@ -170,6 +170,35 @@ export const buildCmd = (): CompletableCommand => { ) .action(addTestTargetWrapper(executeTests)); + createCommandWithCommonOptions(program, "execute-local") + .completer(environmentIdCompleter) + .completer(testTargetIdCompleter) + .completer(optionsCompleter) + .description("Execute local YAML test cases") + .helpGroup("execute") + .requiredOption("-u, --url ", "url the tests should run against") + .option( + "-e, --environment-id [uuid]", + "id of the environment you want to run against, if not provided will run all test cases against the default environment", + ) + .option( + "-t, --test-target-id [uuid]", + "id of the test target of the test case, if not provided will use the test target id from the config", + ) + .option( + "--headless", + "if we should run headless without the UI of playwright and the browser", + ) + .option("--bypass-proxy", "bypass proxy when accessing the test target") + .option("--browser [CHROMIUM, FIREFOX, SAFARI]", "Browser type", "CHROMIUM") + .option("--breakpoint [DESKTOP, MOBILE, TABLET]", "Breakpoint", "DESKTOP") + .option( + "-s, --source ", + "Source directory (defaults to current directory)", + "./.octomind", + ) + .action(addTestTargetWrapper(executeLocalTestCases)); + createCommandWithCommonOptions(program, "test-report") .completer(testReportIdCompleter) .completer(testTargetIdCompleter) diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index a671fa0..db5341e 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -260,3 +260,84 @@ export const runDebugtopus = async (options: DebugtopusOptions) => { }); await runTests({ ...dirs, runMode: options.headless ? "headless" : "ui" }); }; + +export const executeLocalTestCases = async ( + options: DebugtopusOptions & { source: string }, +): Promise => { + const testCases = readTestCasesFromDir(options.source); + const body = { + testCases, + testTargetId: options.testTargetId, + executionUrl: options.url, + environmentId: options.environmentId, + }; + const { error, response } = await client.POST( + "/apiKey/beta/test-targets/{testTargetId}/code", + { + params: { + path: { + testTargetId: options.testTargetId, + }, + }, + body, + parseAs: "stream", + }, + ); + + if (error || !response?.body) { + handleError(error); + } + + const dirs = await prepareDirectories(); + + // Persist the ZIP to disk first to avoid streaming issues with unzipper + const zipPath = path.join(dirs.testDirectory, "bundle.zip"); + const zipWriteStream = createWriteStream(zipPath); + await pipeline( + Readable.fromWeb(response.body as ReadableStream), + zipWriteStream, + ); + + // Basic validation: check file size and ZIP magic (PK\x03\x04) + const stats = await fs.stat(zipPath); + if (stats.size < 4) { + throw new Error( + `Received ZIP is too small (${stats.size} bytes). Saved at ${zipPath}`, + ); + } + const header = await fs.readFile(zipPath); + const isZip = header[0] === 0x50 && header[1] === 0x4b; // 'P''K' + if (!isZip) { + throw new Error( + `File at ${zipPath} does not appear to be a ZIP (magic: ${header.slice(0, 4).toString("hex")}).`, + ); + } + + // Extract using unzipper's higher-level API + try { + const zipBuffer = await fs.readFile(zipPath); + const directory = await Open.buffer(zipBuffer); + await directory.extract({ path: dirs.testDirectory }); + } catch { + throw new Error(`Failed to extract ZIP at ${zipPath}`); + } + + const config = await getPlaywrightConfig({ + testTargetId: options.testTargetId, + url: options.url, + outputDir: dirs.outputDir, + environmentId: options.environmentId, + headless: options.headless, + bypassProxy: options.bypassProxy, + browser: options.browser, + breakpoint: options.breakpoint, + }); + + if (!config) { + handleError("no config found"); + } + + writeFileSync(dirs.configFilePath, config); + + await runTests({ ...dirs, runMode: options.headless ? "headless" : "ui" }); +}; diff --git a/src/tools/sync/yml.ts b/src/tools/sync/yml.ts index fd2015c..a58dbf8 100644 --- a/src/tools/sync/yml.ts +++ b/src/tools/sync/yml.ts @@ -131,15 +131,16 @@ export const buildFilename = ( return candidate; }; -const collectYamlFiles = (startDir: string): string[] => { +const collectYamlFiles = (startDir?: string): string[] => { const files: string[] = []; - const stack: string[] = [startDir]; + const stack: string[] = [startDir ?? "./"]; let current = stack.pop(); while (current) { let entries: fs.Dirent[] = []; try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch { + current = stack.pop(); continue; } for (const entry of entries) { diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index db256a6..cf9b52a 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -59,6 +59,10 @@ export const pullTestTarget = async ( }, ); + console.log( + `Pulling test target ${options.testTargetId} to ${options.destination}`, + ); + handleError(error); if (!data) { From 1d61e1d888a84ede687bd76514c38088947ab71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Tue, 16 Dec 2025 16:23:30 +0100 Subject: [PATCH 2/7] Remove console logs --- src/tools/sync/yml.ts | 4 ++-- src/tools/test-targets.ts | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tools/sync/yml.ts b/src/tools/sync/yml.ts index a58dbf8..ba78d26 100644 --- a/src/tools/sync/yml.ts +++ b/src/tools/sync/yml.ts @@ -131,9 +131,9 @@ export const buildFilename = ( return candidate; }; -const collectYamlFiles = (startDir?: string): string[] => { +const collectYamlFiles = (startDir: string): string[] => { const files: string[] = []; - const stack: string[] = [startDir ?? "./"]; + const stack: string[] = [startDir]; let current = stack.pop(); while (current) { let entries: fs.Dirent[] = []; diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index cf9b52a..db256a6 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -59,10 +59,6 @@ export const pullTestTarget = async ( }, ); - console.log( - `Pulling test target ${options.testTargetId} to ${options.destination}`, - ); - handleError(error); if (!data) { From 73c0ecdcdcfcd04bc6550346de15aaee0746726a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Wed, 17 Dec 2025 11:53:31 +0100 Subject: [PATCH 3/7] Switch to get request --- src/debugtopus/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index db5341e..8ebb5af 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -271,7 +271,7 @@ export const executeLocalTestCases = async ( executionUrl: options.url, environmentId: options.environmentId, }; - const { error, response } = await client.POST( + const { error, response } = await client.GET( "/apiKey/beta/test-targets/{testTargetId}/code", { params: { From 16ae6e57daec520b045d64f210c455c079802250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Wed, 17 Dec 2025 11:54:10 +0100 Subject: [PATCH 4/7] Update readme --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1129ff..06c07d2 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ This way even entityIds like environmentIds or testCaseIds will be autocompleted # octomind -Octomind cli tool. Version: 3.4.0. Additional documentation see https://octomind.dev/docs/api-reference/ +Octomind cli tool. Version: 3.3.0. Additional documentation see https://octomind.dev/docs/api-reference/ **Usage:** `octomind [options] [command]` @@ -225,6 +225,26 @@ Execute test cases to create a test report | `-b, --browser [type]` | Browser type [CHROMIUM, FIREFOX, SAFARI] | No | CHROMIUM | | `-r, --breakpoint [name]` | Breakpoint [DESKTOP, MOBILE, TABLET] | No | DESKTOP | +## execute-local + +Execute local YAML test cases + +**Usage:** `execute-local [options]` + +### Options + +| Option | Description | Required | Default | +|:-------|:----------|:---------|:--------| +| `-j, --json` | Output raw JSON response | No | | +| `-u, --url ` | url the tests should run against | Yes | | +| `-e, --environment-id [uuid]` | id of the environment you want to run against, if not provided will run all test cases against the default environment | No | | +| `-t, --test-target-id [uuid]` | id of the test target of the test case, if not provided will use the test target id from the config | No | | +| `--headless` | if we should run headless without the UI of playwright and the browser | No | | +| `--bypass-proxy` | bypass proxy when accessing the test target | No | | +| `--browser [CHROMIUM, FIREFOX, SAFARI]` | Browser type | No | CHROMIUM | +| `--breakpoint [DESKTOP, MOBILE, TABLET]` | Breakpoint | No | DESKTOP | +| `-s, --source ` | Source directory (defaults to current directory) | Yes | ./.octomind | + ## create-discovery Create a new test case discovery From 25eef376e473577a5c20c330280d1c7423bccc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Thu, 18 Dec 2025 11:59:53 +0100 Subject: [PATCH 5/7] PR review --- src/debugtopus/index.ts | 54 +++----- tests/debugtopous/index.spec.ts | 239 ++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 32 deletions(-) create mode 100644 tests/debugtopous/index.spec.ts diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 8ebb5af..0c70812 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -271,7 +271,7 @@ export const executeLocalTestCases = async ( executionUrl: options.url, environmentId: options.environmentId, }; - const { error, response } = await client.GET( + const { error, response } = await client.POST( "/apiKey/beta/test-targets/{testTargetId}/code", { params: { @@ -290,37 +290,7 @@ export const executeLocalTestCases = async ( const dirs = await prepareDirectories(); - // Persist the ZIP to disk first to avoid streaming issues with unzipper - const zipPath = path.join(dirs.testDirectory, "bundle.zip"); - const zipWriteStream = createWriteStream(zipPath); - await pipeline( - Readable.fromWeb(response.body as ReadableStream), - zipWriteStream, - ); - - // Basic validation: check file size and ZIP magic (PK\x03\x04) - const stats = await fs.stat(zipPath); - if (stats.size < 4) { - throw new Error( - `Received ZIP is too small (${stats.size} bytes). Saved at ${zipPath}`, - ); - } - const header = await fs.readFile(zipPath); - const isZip = header[0] === 0x50 && header[1] === 0x4b; // 'P''K' - if (!isZip) { - throw new Error( - `File at ${zipPath} does not appear to be a ZIP (magic: ${header.slice(0, 4).toString("hex")}).`, - ); - } - - // Extract using unzipper's higher-level API - try { - const zipBuffer = await fs.readFile(zipPath); - const directory = await Open.buffer(zipBuffer); - await directory.extract({ path: dirs.testDirectory }); - } catch { - throw new Error(`Failed to extract ZIP at ${zipPath}`); - } + await readZipFromResponseBody(dirs, response); const config = await getPlaywrightConfig({ testTargetId: options.testTargetId, @@ -341,3 +311,23 @@ export const executeLocalTestCases = async ( await runTests({ ...dirs, runMode: options.headless ? "headless" : "ui" }); }; + +export const readZipFromResponseBody = async (dirs: TestDirectories, response: Response): Promise => { + // Persist the ZIP to disk first to avoid streaming issues with unzipper + const zipPath = path.join(dirs.testDirectory, "bundle.zip"); + const zipWriteStream = createWriteStream(zipPath); + await pipeline( + Readable.fromWeb(response.body as ReadableStream), + zipWriteStream + ); + + // Extract using unzipper's higher-level API + try { + const zipBuffer = await fs.readFile(zipPath); + const directory = await Open.buffer(zipBuffer); + await directory.extract({ path: dirs.testDirectory }); + } catch { + throw new Error(`Failed to extract ZIP at ${zipPath}`); + } +} + diff --git a/tests/debugtopous/index.spec.ts b/tests/debugtopous/index.spec.ts new file mode 100644 index 0000000..d167fdb --- /dev/null +++ b/tests/debugtopous/index.spec.ts @@ -0,0 +1,239 @@ +import { readZipFromResponseBody, executeLocalTestCases } from "../../src/debugtopus/index"; +import { ReadableStream } from "stream/web"; +import fs from "fs/promises"; +import { createWriteStream, existsSync, writeFileSync } from "fs"; +import { Open } from "unzipper"; +import { pipeline } from "node:stream/promises"; +import { client, handleError } from "../../src/tools/client"; +import { readTestCasesFromDir } from "../../src/tools/sync/yml"; +import { getPlaywrightConfig } from "../../src/tools/playwright"; +import { createMockSyncTestCase } from "../mocks"; +import { DeepMockProxy, mockDeep, mock } from "jest-mock-extended"; +import { ensureChromiumIsInstalled } from "../../src/debugtopus/installation"; + +jest.mock("fs/promises"); +jest.mock("fs"); +jest.mock("unzipper"); +jest.mock("../../src/tools/client"); +jest.mock("../../src/tools/sync/yml"); +jest.mock("../../src/tools/playwright"); +jest.mock("../../src/debugtopus/installation"); +jest.mock("child_process"); +jest.mock("node:stream/promises"); + +const mockedFs = fs as jest.Mocked; +const mockedCreateWriteStream = createWriteStream as jest.MockedFunction; +const mockedExistsSync = existsSync as jest.MockedFunction; +const mockedWriteFileSync = writeFileSync as jest.MockedFunction; +const mockedOpen = Open as jest.Mocked; +const mockedPipeline = pipeline as jest.MockedFunction; +const mockedClient = client as DeepMockProxy; +const mockedReadTestCasesFromDir = readTestCasesFromDir as jest.MockedFunction; +const mockedGetPlaywrightConfig = getPlaywrightConfig as jest.MockedFunction; +const mockedHandleError = handleError as jest.MockedFunction; +const mockedEnsureChromiumIsInstalled = ensureChromiumIsInstalled as jest.MockedFunction; + +describe("debugtopus", () => { + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("readZipFromResponseBody", () => { + + it("should read a zip from a response body", async () => { + // Create a mock zip buffer (minimal valid zip file) + // This is a minimal ZIP file structure: local file header + central directory + const mockZipBuffer = Buffer.from([ + 0x50, 0x4B, 0x03, 0x04, // Local file header signature + 0x14, 0x00, // Version needed to extract + 0x00, 0x00, // General purpose bit flag + 0x08, 0x00, // Compression method + 0x00, 0x00, 0x00, 0x00, // Last mod file time and date + 0x00, 0x00, 0x00, 0x00, // CRC-32 + 0x05, 0x00, 0x00, 0x00, // Compressed size + 0x05, 0x00, 0x00, 0x00, // Uncompressed size + 0x04, 0x00, // File name length + 0x00, 0x00, // Extra field length + 0x74, 0x65, 0x73, 0x74, // File name: "test" + 0x68, 0x65, 0x6C, 0x6C, 0x6F, // File content: "hello" + ]); + + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + + const mockResponse = { + body: mockReadableStream, + } as Response; + + const mockDirs = { + testDirectory: "/tmp/test-dir", + outputDir: "/tmp/test-dir/output", + configFilePath: "/tmp/test-dir/config.ts", + packageRootDir: "/tmp", + }; + + const mockWriteStream = { + write: jest.fn(), + end: jest.fn(), + on: jest.fn((event, callback) => { + if (event === "finish") { + setTimeout(() => callback(), 0); + } + return mockWriteStream; + }), + } as unknown as NodeJS.WritableStream; + + const mockDirectory = { + extract: jest.fn().mockResolvedValue(undefined), + }; + + mockedCreateWriteStream.mockReturnValue(mockWriteStream as any); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); + + await readZipFromResponseBody(mockDirs, mockResponse); + + expect(mockedCreateWriteStream).toHaveBeenCalledWith( + "/tmp/test-dir/bundle.zip" + ); + expect(mockedPipeline).toHaveBeenCalled(); + expect(mockedFs.readFile).toHaveBeenCalledWith("/tmp/test-dir/bundle.zip"); + expect(mockedOpen.buffer).toHaveBeenCalledWith(mockZipBuffer); + expect(mockDirectory.extract).toHaveBeenCalledWith({ + path: "/tmp/test-dir", + }); + }); + + it("should throw an error if zip extraction fails", async () => { + const mockZipBuffer = Buffer.from([0x50, 0x4B, 0x03, 0x04]); + + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + + const mockResponse = { + body: mockReadableStream, + } as Response; + + const mockDirs = { + testDirectory: "/tmp/test-dir", + outputDir: "/tmp/test-dir/output", + configFilePath: "/tmp/test-dir/config.ts", + packageRootDir: "/tmp", + }; + + const mockWriteStream = { + write: jest.fn(), + end: jest.fn(), + on: jest.fn((event, callback) => { + if (event === "finish") { + setTimeout(() => callback(), 0); + } + return mockWriteStream; + }), + } as unknown as NodeJS.WritableStream; + + mockedCreateWriteStream.mockReturnValue(mockWriteStream as any); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockRejectedValue(new Error("Invalid zip")); + + await expect(readZipFromResponseBody(mockDirs, mockResponse)).rejects.toThrow( + "Failed to extract ZIP at /tmp/test-dir/bundle.zip" + ); + }); + + }) + + describe("executeLocalTestCases", () => { + + it("should execute local test cases from zip response body", async () => { + const mockTestCases = [createMockSyncTestCase()]; + const mockZipBuffer = Buffer.from([0x50, 0x4B, 0x03, 0x04]); + const mockConfig = "export default { testDir: './tests' };"; + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + const mockResponse = mockDeep(); + Object.defineProperty(mockResponse, "body", { value: mockReadableStream, writable: true, configurable: true }); + const mockWriteStream = { write: jest.fn(), end: jest.fn(), on: jest.fn() } as any; + const mockDirectory = { extract: jest.fn().mockResolvedValue(undefined) }; + + mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); + mockedClient.POST.mockResolvedValue({ error: undefined, response: mockResponse, data: undefined }); + mockedExistsSync.mockImplementation((path: Parameters[0]) => { + const pathStr = typeof path === "string" ? path : path.toString(); + return pathStr.includes("node_modules"); + }); + mockedFs.mkdir.mockResolvedValue(undefined); + mockedFs.rm.mockResolvedValue(undefined); + mockedCreateWriteStream.mockReturnValue(mockWriteStream); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); + mockedGetPlaywrightConfig.mockResolvedValue(mockConfig); + mockedEnsureChromiumIsInstalled.mockResolvedValue(undefined); + + const { exec } = require("child_process"); + (exec as jest.MockedFunction).mockImplementation((_command: string, _options: any, callback?: any) => { + if (callback) setImmediate(() => callback(null, { stdout: "", stderr: "" })); + return {} as any; + }); + + await executeLocalTestCases({ + testTargetId: "test-target-id", + url: "https://example.com", + source: "/test/source", + headless: true, + }); + + expect(mockedReadTestCasesFromDir).toHaveBeenCalledWith("/test/source"); + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/code", + expect.objectContaining({ + params: { path: { testTargetId: "test-target-id" } }, + body: { testCases: mockTestCases, testTargetId: "test-target-id", executionUrl: "https://example.com", environmentId: undefined }, + parseAs: "stream", + }) + ); + expect(mockedGetPlaywrightConfig).toHaveBeenCalled(); + expect(mockedWriteFileSync).toHaveBeenCalledWith(expect.stringContaining("config.ts"), mockConfig); + }); + + it("should handle error when response is missing", async () => { + const mockTestCases = [createMockSyncTestCase()]; + + mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); + mockedClient.POST.mockResolvedValue({ + error: undefined, + response: null as any, + data: undefined, + }); + + await expect( + executeLocalTestCases({ + testTargetId: "test-target-id", + url: "https://example.com", + source: "/test/source", + headless: true, + }) + ).rejects.toThrow(); + + expect(mockedHandleError).toHaveBeenCalled(); + }); + + }) + +}) From 7c6db66c7b94b9cbe60cc0b944c4346bd48dee7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Thu, 18 Dec 2025 12:00:49 +0100 Subject: [PATCH 6/7] Version bump --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06c07d2..766b390 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ This way even entityIds like environmentIds or testCaseIds will be autocompleted # octomind -Octomind cli tool. Version: 3.3.0. Additional documentation see https://octomind.dev/docs/api-reference/ +Octomind cli tool. Version: 3.4.0. Additional documentation see https://octomind.dev/docs/api-reference/ **Usage:** `octomind [options] [command]` diff --git a/package.json b/package.json index def5b15..549a191 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@octomind/octomind", - "version": "3.5.1", + "version": "3.6.0", "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", From fd1dec08146e3a3c253e5d26072f1c1f9d27ad2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Thu, 18 Dec 2025 12:01:48 +0100 Subject: [PATCH 7/7] Biome --- src/debugtopus/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 0c70812..c8de521 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -312,13 +312,16 @@ export const executeLocalTestCases = async ( await runTests({ ...dirs, runMode: options.headless ? "headless" : "ui" }); }; -export const readZipFromResponseBody = async (dirs: TestDirectories, response: Response): Promise => { +export const readZipFromResponseBody = async ( + dirs: TestDirectories, + response: Response, +): Promise => { // Persist the ZIP to disk first to avoid streaming issues with unzipper const zipPath = path.join(dirs.testDirectory, "bundle.zip"); const zipWriteStream = createWriteStream(zipPath); await pipeline( Readable.fromWeb(response.body as ReadableStream), - zipWriteStream + zipWriteStream, ); // Extract using unzipper's higher-level API @@ -329,5 +332,4 @@ export const readZipFromResponseBody = async (dirs: TestDirectories, response: R } catch { throw new Error(`Failed to extract ZIP at ${zipPath}`); } -} - +};