Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { init, switchTestTarget } from "./tools/init";
import { update } from "./tools/update";
import { edit } from "./tools/yamlMutations/edit";
import { version } from "./version";
import { OCTOMIND_FOLDER_NAME } from "./constants";

export const BINARY_NAME = "octomind";

Expand Down Expand Up @@ -195,11 +194,6 @@ export const buildCmd = (): CompletableCommand => {
.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 <path>",
"Source directory (defaults to current directory)",
OCTOMIND_FOLDER_NAME,
)
.action(addTestTargetWrapper(executeLocalTestCases));

createCommandWithCommonOptions(program, "test-report")
Expand Down Expand Up @@ -398,7 +392,6 @@ export const buildCmd = (): CompletableCommand => {
.description("Pull test cases from the test target")
.helpGroup("test-cases")
.addOption(testTargetIdOption)
.option("-d, --destination <path>", "Destination folder", OCTOMIND_FOLDER_NAME)
.action(addTestTargetWrapper(pullTestTarget));

// noinspection RequiredAttributes
Expand All @@ -407,11 +400,6 @@ export const buildCmd = (): CompletableCommand => {
.description("Push local YAML test cases to the test target")
.helpGroup("test-cases")
.addOption(testTargetIdOption)
.option(
"-s, --source <path>",
"Source directory (defaults to current directory)",
OCTOMIND_FOLDER_NAME,
)
.action(addTestTargetWrapper(pushTestTarget));

// noinspection RequiredAttributes
Expand Down
17 changes: 13 additions & 4 deletions src/debugtopus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
import { client, handleError } from "../tools/client";
import { readTestCasesFromDir } from "../tools/sync/yml";
import { ensureChromiumIsInstalled } from "./installation";
import { findOctomindFolder } from "../helpers";
import { OCTOMIND_FOLDER_NAME } from "../constants";

export type DebugtopusOptions = {
testCaseId?: string;
Expand Down Expand Up @@ -207,8 +209,8 @@ export const runDebugtopus = async (options: DebugtopusOptions) => {
.filter((testCase) =>
options.grep
? testCase.description
?.toLowerCase()
.includes(options.grep.toLowerCase())
?.toLowerCase()
.includes(options.grep.toLowerCase())
: true,
)
.map(async (testCase) => ({
Expand Down Expand Up @@ -262,9 +264,16 @@ export const runDebugtopus = async (options: DebugtopusOptions) => {
};

export const executeLocalTestCases = async (
options: DebugtopusOptions & { source: string },
options: DebugtopusOptions,
): Promise<void> => {
const testCases = readTestCasesFromDir(options.source);
const octomindRoot = await findOctomindFolder()
if (!octomindRoot) {
throw new Error(
`Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to execute locally`,
);
}

const testCases = readTestCasesFromDir(octomindRoot);
const body = {
testCases,
testTargetId: options.testTargetId,
Expand Down
44 changes: 44 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { stdin as input, stdout as output } from "node:process";
import { createInterface } from "node:readline";

import { loadConfig } from "./config";
import path from "node:path";
import fsPromises from "fs/promises";
import { OCTOMIND_FOLDER_NAME } from "./constants";


export function promptUser(question: string): Promise<string> {
const rl = createInterface({ input, output });
Expand Down Expand Up @@ -29,3 +33,43 @@ export const resolveTestTargetId = async (
}
return config.testTargetId;
};


const isDirectory = async (dirPath: string): Promise<boolean> => {
Comment thread
fabianboth marked this conversation as resolved.
try {
const stat = await fsPromises.stat(dirPath);
return stat.isDirectory();
} catch {
return false;
}
};

export const findOctomindFolder = async (): Promise<string | null> => {
let currentDir = process.cwd();

while (currentDir !== path.parse(currentDir).root) {
const octomindPath = path.join(currentDir, OCTOMIND_FOLDER_NAME);
if (await isDirectory(octomindPath)) {
return octomindPath;
}
currentDir = path.dirname(currentDir);
}

const rootOctomind = path.join(currentDir, OCTOMIND_FOLDER_NAME);
if (await isDirectory(rootOctomind)) {
return rootOctomind;
}

return null
};

export const getAbsoluteFilePathInOctomindRoot = async ({ filePath, octomindRoot }: { filePath: string, octomindRoot: string }): Promise<string | null> => {
try {
const resolvedPath = await fsPromises.realpath(
path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath)
);
return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null;
} catch {
return null;
}
}
48 changes: 35 additions & 13 deletions src/tools/test-cases.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import path from "path";
import type { components, paths } from "../api"; // generated by openapi-typescript
import { findOctomindFolder } from "../helpers";
import { getUrl } from "../url";
import { client, handleError, ListOptions, logJson } from "./client";
import { getEnvironments } from "./environments";
import { buildFilename, readTestCasesFromDir } from "./sync/yml";
import fsPromises from "fs/promises";

export type TestCaseResponse = components["schemas"]["TestCaseResponse"];
export type TestCasesResponse = components["schemas"]["TestCasesResponse"];
Expand All @@ -13,25 +17,43 @@ export type DeleteTestCaseParams =
export const deleteTestCase = async (
options: DeleteTestCaseParams & ListOptions,
): Promise<void> => {
const { data, error } = await client.DELETE(
"/apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}",
{
params: {
path: {
testTargetId: options.testTargetId,
testCaseId: options.testCaseId,
const octomindRoot = await findOctomindFolder()

if (!octomindRoot) {
Comment thread
fabianboth marked this conversation as resolved.
const { data, error } = await client.DELETE(
"/apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}",
{
params: {
path: {
testTargetId: options.testTargetId,
testCaseId: options.testCaseId,
},
},
},
},
);
);

handleError(error);
handleError(error);
if (options.json) {
logJson(data);
}
console.log("Test Case deleted successfully");
Comment thread
fabianboth marked this conversation as resolved.
return
}

if (options.json) {
logJson(data);
return;
const testCases = readTestCasesFromDir(octomindRoot);
const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc]));
const existingTestCase = testCasesById[options.testCaseId]

if (!existingTestCase) {
console.log(`No test case with id ${options.testCaseId} found in folder ${octomindRoot}`)
return
}

const existingTestCasePath = buildFilename(
existingTestCase,
octomindRoot,
);
await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath))
console.log("Test Case deleted successfully");
};

Expand Down
16 changes: 10 additions & 6 deletions src/tools/test-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { getUrl } from "../url";
import { client, handleError, ListOptions, logJson } from "./client";
import { push } from "./sync/push";
import { writeYaml } from "./sync/yml";
import { findOctomindFolder } from "../helpers";
import { OCTOMIND_FOLDER_NAME } from "../constants";

export const getTestTargets = async () => {
const { data, error } = await client.GET("/apiKey/v3/test-targets");
Expand Down Expand Up @@ -44,7 +46,7 @@ export const listTestTargets = async (options: ListOptions): Promise<void> => {
};

export const pullTestTarget = async (
options: { testTargetId: string; destination?: string } & ListOptions,
options: { testTargetId: string } & ListOptions,
): Promise<void> => {
const { data, error } = await client.GET(
"/apiKey/beta/test-targets/{testTargetId}/pull",
Expand All @@ -68,17 +70,19 @@ export const pullTestTarget = async (
return;
}

writeYaml(data, options.destination);
const destination = await findOctomindFolder() ?? path.join(process.cwd(), OCTOMIND_FOLDER_NAME)
writeYaml(data, destination);

console.log("Test Target pulled successfully");
};

export const pushTestTarget = async (
options: { testTargetId: string; source?: string } & ListOptions,
options: { testTargetId: string } & ListOptions,
): Promise<void> => {
const sourceDir = options.source
? path.resolve(options.source)
: process.cwd();
const sourceDir = await findOctomindFolder()
if (!sourceDir) {
throw new Error(`No ${OCTOMIND_FOLDER_NAME} folder found, please pull first.`)
}

const data = await push({
...options,
Expand Down
44 changes: 9 additions & 35 deletions src/tools/yamlMutations/edit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from "fs";
import fsPromises from "fs/promises";
import path from "path";

import yaml from "yaml";
Expand All @@ -9,40 +8,14 @@ import { checkForConsistency } from "../sync/consistency";
import { draftPush } from "../sync/push";
import { SyncTestCase } from "../sync/types";
import { readTestCasesFromDir } from "../sync/yml";
import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers";
import { OCTOMIND_FOLDER_NAME } from "../../constants";

type EditOptions = {
testTargetId: string;
filePath: string;
sourceDir: string
};

const findOctomindFolder = async (
startPath: string,
): Promise<string | null> => {
let currentDir = path.dirname(startPath);

while (currentDir !== path.parse(currentDir).root) {
const octomindPath = path.join(currentDir, OCTOMIND_FOLDER_NAME);
if (
fs.existsSync(octomindPath) &&
(await fsPromises.stat(octomindPath)).isDirectory()
) {
return octomindPath;
}
currentDir = path.dirname(currentDir);
}

const rootOctomind = path.join(currentDir, OCTOMIND_FOLDER_NAME);
if (
fs.existsSync(rootOctomind) &&
(await fsPromises.stat(rootOctomind)).isDirectory()
) {
return rootOctomind;
}

return null;
};

const getRelevantTestCases = (
testCasesById: Record<string, SyncTestCase>,
Expand Down Expand Up @@ -77,17 +50,18 @@ const loadTestCase = (testCasePath: string): SyncTestCase => {
};

export const edit = async (options: EditOptions): Promise<void> => {
const resolvedPath = path.resolve(options.filePath);

const testCaseToEdit = loadTestCase(resolvedPath);

const octomindRoot = await findOctomindFolder(options.filePath);

const octomindRoot = await findOctomindFolder();
if (!octomindRoot) {
throw new Error(
"Could not find .octomind folder, make sure to pull before trying to edit",
`Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`,
);
}
const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath })
if (!testCaseFilePath) {
throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`)
}

const testCaseToEdit = loadTestCase(testCaseFilePath);

const testCases = readTestCasesFromDir(octomindRoot);
const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc]));
Expand Down
Loading