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>
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import type {
|
|
BrowserStackConfig,
|
|
BrowserStackPlan,
|
|
BrowserStackBrowser,
|
|
BrowserStackBuild,
|
|
BrowserStackSession,
|
|
BrowserStackSessionUpdate,
|
|
} from './types.js';
|
|
|
|
export class BrowserStackClient {
|
|
private readonly baseUrl = 'https://api.browserstack.com';
|
|
private readonly headers: Record<string, string>;
|
|
|
|
constructor(config: BrowserStackConfig) {
|
|
const auth = Buffer.from(`${config.username}:${config.accessKey}`).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}${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(
|
|
`BrowserStack API error ${response.status}: ${errorText}`,
|
|
);
|
|
}
|
|
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
async getPlan(): Promise<BrowserStackPlan> {
|
|
return this.request<BrowserStackPlan>('GET', '/automate/plan.json');
|
|
}
|
|
|
|
async getBrowsers(): Promise<BrowserStackBrowser[]> {
|
|
return this.request<BrowserStackBrowser[]>('GET', '/automate/browsers.json');
|
|
}
|
|
|
|
async getBuilds(limit?: number, status?: string): Promise<BrowserStackBuild[]> {
|
|
let endpoint = '/automate/builds.json';
|
|
const params: string[] = [];
|
|
if (limit) params.push(`limit=${limit}`);
|
|
if (status) params.push(`status=${status}`);
|
|
if (params.length > 0) endpoint += `?${params.join('&')}`;
|
|
return this.request<BrowserStackBuild[]>('GET', endpoint);
|
|
}
|
|
|
|
async getSessions(buildId: string, limit?: number): Promise<BrowserStackSession[]> {
|
|
let endpoint = `/automate/builds/${buildId}/sessions.json`;
|
|
if (limit) endpoint += `?limit=${limit}`;
|
|
return this.request<BrowserStackSession[]>('GET', endpoint);
|
|
}
|
|
|
|
async getSession(sessionId: string): Promise<BrowserStackSession> {
|
|
return this.request<BrowserStackSession>(
|
|
'GET',
|
|
`/automate/sessions/${sessionId}.json`,
|
|
);
|
|
}
|
|
|
|
async updateSession(
|
|
sessionId: string,
|
|
update: BrowserStackSessionUpdate,
|
|
): Promise<BrowserStackSession> {
|
|
return this.request<BrowserStackSession>(
|
|
'PUT',
|
|
`/automate/sessions/${sessionId}.json`,
|
|
update,
|
|
);
|
|
}
|
|
|
|
async getSessionLogs(sessionId: string): Promise<string> {
|
|
const url = `${this.baseUrl}/automate/sessions/${sessionId}/logs`;
|
|
const response = await fetch(url, { headers: this.headers });
|
|
if (!response.ok) {
|
|
throw new Error(`BrowserStack logs error ${response.status}`);
|
|
}
|
|
return response.text();
|
|
}
|
|
}
|