create_test_case
Create new test cases in TestCollab by specifying title, steps, priority, and optional details like tags or custom fields for structured testing documentation.
Instructions
Create a new test case in TestCollab. Tip: Call get_project_context first to resolve suite/tag/custom field names to IDs.
Required: title Optional: project_id, suite (ID or title), description, priority (0=Low, 1=Normal, 2=High), steps, tags, requirements, custom_fields, attachments
Steps format: [{ "step": "action", "expected_result": "result" }]
Custom fields format: [{ "id": 5, "name": "field_name", "value": "value", "valueLabel": "display" }]
Example: { "title": "Verify login", "priority": 2, "steps": [ { "step": "Navigate to login", "expected_result": "Page loads" }, { "step": "Enter credentials", "expected_result": "Login succeeds" } ] }
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Project ID (optional if TC_DEFAULT_PROJECT is set) | |
| title | Yes | Test case title (required) | |
| suite | No | Suite ID or suite title | |
| description | No | Test case description (HTML supported) | |
| priority | No | Priority: 0=Low, 1=Normal, 2=High | |
| steps | No | Array of test steps | |
| tags | No | Array of tag IDs or names | |
| requirements | No | Array of requirement IDs or names | |
| custom_fields | No | Array of custom field values (id optional if name provided) | |
| attachments | No | Array of attachment file IDs |
Implementation Reference
- src/tools/test-cases/create.ts:409-707 (handler)The handler function for the 'create_test_case' tool, which parses input, resolves dependencies (suites, tags, custom fields), calls the API client to create the test case, and formats the output.
export async function handleCreateTestCase( args: unknown ): Promise<{ content: Array<{ type: "text"; text: string }> }> { // Validate input const parsed = createTestCaseSchema.safeParse(args); if (!parsed.success) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "VALIDATION_ERROR", message: "Invalid input parameters", details: parsed.error.errors, }, }), }, ], }; } const { project_id, title, suite, description, priority, steps, tags, requirements, custom_fields, attachments, } = parsed.data; // Resolve project ID: check request context first (HTTP), then env config (stdio) const requestContext = getRequestContext(); const envConfig = requestContext ? null : getConfig(); const resolvedProjectId = project_id ?? requestContext?.defaultProjectId ?? envConfig?.defaultProjectId; if (!resolvedProjectId) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "MISSING_PROJECT_ID", message: "project_id is required. Either provide it in the request or set TC_DEFAULT_PROJECT environment variable.", }, }), }, ], }; } try { const client = getApiClient(); const suiteInput = suite; const suiteNumericId = toNumberId(suiteInput); const suiteTitle = normalizeString(suiteInput); const suiteNeedsLookup = suiteNumericId === undefined && suiteTitle !== undefined; const tagsNeedLookup = tags?.some(isNonNumericString) ?? false; const requirementsNeedLookup = requirements?.some(isNonNumericString) ?? false; const customFieldsNeedLookup = custom_fields?.some((cf) => cf.id === undefined || isNonNumericString(cf.id)) ?? false; const needsCompanyId = tagsNeedLookup || requirementsNeedLookup || customFieldsNeedLookup; const [suitesList, projectForCompany] = await Promise.all([ suiteNeedsLookup ? client.listSuites(resolvedProjectId) : Promise.resolve(null), needsCompanyId ? client.getProject(resolvedProjectId) : Promise.resolve(null), ]); const companyId = projectForCompany ? getCompanyIdFromProject(projectForCompany) : undefined; const [tagsList, requirementsList, customFieldsList] = await Promise.all([ tagsNeedLookup ? client.listTags(resolvedProjectId) : Promise.resolve(null), requirementsNeedLookup ? client.listRequirements(resolvedProjectId) : Promise.resolve(null), customFieldsNeedLookup ? client.listProjectCustomFields(resolvedProjectId, companyId) : Promise.resolve(null), ]); let resolvedSuiteId = suiteNumericId; if (suiteNeedsLookup && suitesList) { const normalizedSuiteTitle = suiteTitle?.toLowerCase(); const match = suitesList.find((suite) => { const title = normalizeString(getField<string>(suite, "title")); return ( title !== undefined && normalizedSuiteTitle !== undefined && title.toLowerCase() === normalizedSuiteTitle ); }); resolvedSuiteId = toNumberId(match ? getField(match, "id") : undefined); if (resolvedSuiteId === undefined) { return { content: [ { type: "text", text: JSON.stringify({ error: { code: "SUITE_NOT_FOUND", message: `Suite not found with title "${suiteTitle}" in that project`, }, }), }, ], }; } } const resolvedTags = tags ? tags .map((tag) => { const numericId = toNumberId(tag); if (numericId !== undefined) { return numericId; } if (!tagsList || typeof tag !== "string") { return undefined; } const match = tagsList.find( (t) => getField<string>(t, "name") === tag ); return toNumberId(match ? getField(match, "id") : undefined); }) .filter((id): id is number => typeof id === "number") : undefined; const resolvedRequirements = requirements ? requirements .map((req) => { const numericId = toNumberId(req); if (numericId !== undefined) { return numericId; } if (!requirementsList || typeof req !== "string") { return undefined; } const match = requirementsList.find((r) => { const key = getField<string>(r, "requirement_key"); const reqId = getField<string>(r, "requirement_id"); const title = getField<string>(r, "title"); return key === req || reqId === req || title === req; }); return toNumberId(match ? getField(match, "id") : undefined); }) .filter((id): id is number => typeof id === "number") : undefined; const customFieldMap = customFieldsList ? customFieldsList.reduce((map, cf) => { const name = getField<string>(cf, "name"); const id = toNumberId(getField(cf, "id")); if (!name || id === undefined) { return map; } const fieldType = getField<string>(cf, "field_type") ?? getField<string>(cf, "type"); const options = getCustomFieldOptions(cf); map.set(name, { id, name, label: getField<string>(cf, "label"), fieldType, options, }); return map; }, new Map<string, { id: number; name: string; label?: string; fieldType?: string; options?: unknown[] | null }>()) : null; const resolvedCustomFields = custom_fields ? custom_fields .map((cf) => { const numericId = toNumberId(cf.id); if (numericId !== undefined) { return { id: numericId, name: cf.name, value: cf.value, ...(cf.label !== undefined ? { label: cf.label } : {}), ...(cf.valueLabel !== undefined ? { valueLabel: cf.valueLabel } : {}), ...(cf.color !== undefined ? { color: cf.color } : {}), }; } if (!customFieldMap) { return undefined; } const match = customFieldMap.get(cf.name); if (!match) { return undefined; } const { value: resolvedValue, valueLabel: resolvedValueLabel } = resolveDropdownValue( match.fieldType, match.options, cf.value, cf.valueLabel ); return { id: match.id, name: match.name, value: resolvedValue, ...(cf.label !== undefined ? { label: cf.label } : {}), ...(resolvedValueLabel !== undefined ? { valueLabel: resolvedValueLabel } : {}), ...(cf.color !== undefined ? { color: cf.color } : {}), ...(cf.label === undefined && match.label !== undefined ? { label: match.label } : {}), }; }) .filter( ( cf ): cf is { id: number; name: string; label?: string; value: string | number | null; valueLabel?: string; color?: string; } => cf !== undefined ) : undefined; const result = await client.createTestCase({ projectId: resolvedProjectId, title, suiteId: resolvedSuiteId, description, priority, steps: steps?.map((s) => ({ step: s.step, expected_result: s.expected_result, })), tags: resolvedTags, requirements: resolvedRequirements, customFields: resolvedCustomFields, attachments, }); return { content: [ { type: "text", text: JSON.stringify( { success: true, message: `Test case created successfully`, testCase: { id: result.id, title: result.title, project: result.project, suite: result.suite, priority: result.priority, }, }, null, 2 ), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ error: { code: "API_ERROR", message: message, }, }), }, ], }; } } - src/tools/test-cases/create.ts:41-78 (schema)Zod schema defining the input validation for 'create_test_case'.
export const createTestCaseSchema = z.object({ project_id: z .number() .optional() .describe("Project ID (uses TC_DEFAULT_PROJECT env var if not specified)"), title: z.string().min(1).describe("Test case title (required)"), suite: z .union([z.string(), z.number()]) .optional() .describe("Suite ID or suite title"), description: z.string().optional().describe("Test case description (HTML supported)"), priority: z .number() .min(0) .max(2) .optional() .describe("Priority: 0=Low, 1=Normal, 2=High (default: 1)"), steps: z .array(stepSchema) .optional() .describe("Array of test steps with actions and expected results"), tags: z .array(z.union([z.number(), z.string()])) .optional() .describe("Array of tag IDs or names to associate"), requirements: z .array(z.union([z.number(), z.string()])) .optional() .describe("Array of requirement IDs or names to link"), custom_fields: z .array(customFieldSchema) .optional() .describe("Array of custom field values"), attachments: z .array(z.string()) .optional() .describe("Array of attachment file IDs"), }); - src/tools/test-cases/create.ts:86-219 (registration)Tool object containing the name, description, and input JSON schema for the 'create_test_case' tool.
export const createTestCaseTool = { name: "create_test_case", description: `Create a new test case in TestCollab. Required fields: - title: Test case title Optional fields: - project_id: Project ID (uses TC_DEFAULT_PROJECT if not specified) - suite: Suite ID or suite title - description: HTML-formatted description - priority: 0 (Low), 1 (Normal), 2 (High) - default is 1 - steps: Array of { step: "action", expected_result: "result" } - tags: Array of tag IDs or names - requirements: Array of requirement IDs or names - custom_fields: Array of custom field objects (id optional if name provided) - attachments: Array of file IDs Custom field format: { "id": 5, "name": "env_dropdown", "label": "Environment", "value": 1, "valueLabel": "staging" } Example: { "title": "Verify login with valid credentials", "suite": 123, "priority": 2, "description": "<p>Test user login functionality</p>", "steps": [ { "step": "Navigate to login page", "expected_result": "Login page loads" }, { "step": "Enter valid credentials", "expected_result": "Fields accept input" }, { "step": "Click Login button", "expected_result": "User is logged in" } ], "custom_fields": [ { "id": 5, "name": "env", "value": 1, "valueLabel": "staging" } ] }`, inputSchema: { type: "object" as const, properties: { project_id: { type: "number", description: "Project ID (optional if TC_DEFAULT_PROJECT env var is set)", }, title: { type: "string", description: "Test case title (required)", }, suite: { oneOf: [{ type: "number" }, { type: "string" }], description: "Suite ID or suite title", }, description: { type: "string", description: "Test case description (HTML supported)", }, priority: { type: "number", description: "Priority: 0=Low, 1=Normal, 2=High (default: 1)", enum: [0, 1, 2], }, steps: { type: "array", description: "Array of test steps", items: { type: "object", properties: { step: { type: "string", description: "Step action/description", }, expected_result: { type: "string", description: "Expected result for this step", }, }, required: ["step"], }, }, tags: { type: "array", description: "Array of tag IDs or names", items: { oneOf: [{ type: "number" }, { type: "string" }] }, }, requirements: { type: "array", description: "Array of requirement IDs or names", items: { oneOf: [{ type: "number" }, { type: "string" }] }, }, custom_fields: { type: "array", description: "Array of custom field values", items: { type: "object", properties: { id: { oneOf: [{ type: "number" }, { type: "string" }], description: "Custom field ID or name", }, name: { type: "string", description: "Custom field system name" }, label: { type: "string", description: "Custom field display label" }, value: { oneOf: [ { type: "string" }, { type: "number" }, { type: "null" }, ], description: "Custom field value", }, valueLabel: { type: "string", description: "Display label for the value", }, color: { type: "string", description: "Color for the value label" }, }, required: ["name", "value"], }, }, attachments: { type: "array", description: "Array of attachment file IDs", items: { type: "string" }, }, }, required: ["title"], }, };