claude-skills/engineering-team/playwright-pro/integrations/testrail-mcp/src/client.ts
Alireza Rezvani d33d03da50
feat: add playwright-pro plugin — production-grade Playwright testing toolkit (#254)
Complete Claude Code plugin with:
- 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report)
- 3 specialized agents (test-architect, test-debugger, migration-planner)
- 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility)
- TestRail MCP server (TypeScript) — 8 tools for bidirectional sync
- BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing
- Smart hooks (auto-validate tests, auto-detect Playwright projects)
- 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests)
- Leverages Claude Code built-ins (/batch, /debug, Explore subagent)
- Zero-config for core features; TestRail/BrowserStack via env vars
- Both TypeScript and JavaScript support throughout

Co-authored-by: Leo <leo@openclaw.ai>
2026-03-05 13:50:05 +01:00

147 lines
3.6 KiB
TypeScript

import type {
TestRailConfig,
TestRailProject,
TestRailSuite,
TestRailCase,
TestRailCasePayload,
TestRailRun,
TestRailRunPayload,
TestRailResult,
TestRailResultPayload,
} from './types.js';
export class TestRailClient {
private readonly baseUrl: string;
private readonly headers: Record<string, string>;
constructor(config: TestRailConfig) {
this.baseUrl = config.url.replace(/\/+$/, '');
const auth = Buffer.from(`${config.user}:${config.apiKey}`).toString('base64');
this.headers = {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/json',
};
}
private async request<T>(
method: string,
endpoint: string,
body?: unknown,
): Promise<T> {
const url = `${this.baseUrl}/index.php?/api/v2/${endpoint}`;
const options: RequestInit = {
method,
headers: this.headers,
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`TestRail API error ${response.status}: ${errorText}`,
);
}
return response.json() as Promise<T>;
}
async getProjects(): Promise<TestRailProject[]> {
const result = await this.request<{ projects: TestRailProject[] }>(
'GET',
'get_projects',
);
return result.projects ?? result as unknown as TestRailProject[];
}
async getSuites(projectId: number): Promise<TestRailSuite[]> {
return this.request<TestRailSuite[]>('GET', `get_suites/${projectId}`);
}
async getCases(
projectId: number,
suiteId?: number,
sectionId?: number,
limit?: number,
offset?: number,
filter?: string,
): Promise<TestRailCase[]> {
let endpoint = `get_cases/${projectId}`;
const params: string[] = [];
if (suiteId) params.push(`suite_id=${suiteId}`);
if (sectionId) params.push(`section_id=${sectionId}`);
if (limit) params.push(`limit=${limit}`);
if (offset) params.push(`offset=${offset}`);
if (filter) params.push(`filter=${encodeURIComponent(filter)}`);
if (params.length > 0) endpoint += `&${params.join('&')}`;
const result = await this.request<{ cases: TestRailCase[] }>(
'GET',
endpoint,
);
return result.cases ?? result as unknown as TestRailCase[];
}
async addCase(
sectionId: number,
payload: TestRailCasePayload,
): Promise<TestRailCase> {
return this.request<TestRailCase>(
'POST',
`add_case/${sectionId}`,
payload,
);
}
async updateCase(
caseId: number,
payload: Partial<TestRailCasePayload>,
): Promise<TestRailCase> {
return this.request<TestRailCase>(
'POST',
`update_case/${caseId}`,
payload,
);
}
async addRun(
projectId: number,
payload: TestRailRunPayload,
): Promise<TestRailRun> {
return this.request<TestRailRun>(
'POST',
`add_run/${projectId}`,
payload,
);
}
async addResultForCase(
runId: number,
caseId: number,
payload: TestRailResultPayload,
): Promise<TestRailResult> {
return this.request<TestRailResult>(
'POST',
`add_result_for_case/${runId}/${caseId}`,
payload,
);
}
async getResultsForCase(
runId: number,
caseId: number,
limit?: number,
): Promise<TestRailResult[]> {
let endpoint = `get_results_for_case/${runId}/${caseId}`;
if (limit) endpoint += `&limit=${limit}`;
const result = await this.request<{ results: TestRailResult[] }>(
'GET',
endpoint,
);
return result.results ?? result as unknown as TestRailResult[];
}
}