@clawdtesting/puppeteer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # ClawdTest - Puppeteer SDK
2
+
3
+ Official Puppeteer SDK for ClawdTest Platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @clawdtesting/puppeteer puppeteer
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ const puppeteer = require('puppeteer');
15
+ const { ClawdTest } = require('@clawdtesting/puppeteer');
16
+
17
+ // Initialize client
18
+ const clawdTest = new ClawdTest({
19
+ apiKey: 'your-api-key',
20
+ apiUrl: 'http://localhost:3001/api/v1',
21
+ projectId: 'your-project-id',
22
+ });
23
+
24
+ (async () => {
25
+ // Launch browser
26
+ const browser = await puppeteer.launch();
27
+ const page = await browser.newPage();
28
+
29
+ // Navigate to page
30
+ await page.goto('https://example.com');
31
+
32
+ // Quick check (single screenshot)
33
+ const result = await clawdTest.check(page, 'Homepage Test', 'Landing Page');
34
+
35
+ console.log('Test result:', result.status); // 'passed', 'failed', or 'new'
36
+
37
+ await browser.close();
38
+ })();
39
+ ```
40
+
41
+ ## Advanced Usage
42
+
43
+ ### Session-based Testing
44
+
45
+ ```javascript
46
+ const session = await clawdTest.session(page, {
47
+ testName: 'User Flow Test',
48
+ viewport: { width: 1920, height: 1080 },
49
+ baselineBranch: 'main',
50
+ });
51
+
52
+ // Take multiple screenshots
53
+ await page.goto('https://example.com');
54
+ await session.check('Homepage');
55
+
56
+ await page.click('#login-button');
57
+ await session.check('Login Modal');
58
+
59
+ await page.type('#username', 'user@example.com');
60
+ await page.type('#password', 'password123');
61
+ await session.check('Login Form Filled');
62
+
63
+ await page.click('#submit');
64
+ await session.checkAfterNavigation('Dashboard');
65
+
66
+ // Complete session
67
+ const sessionResult = await session.close();
68
+
69
+ console.log('Session completed:', {
70
+ status: sessionResult.status,
71
+ totalSteps: sessionResult.totalSteps,
72
+ passedSteps: sessionResult.passedSteps,
73
+ failedSteps: sessionResult.failedSteps,
74
+ newSteps: sessionResult.newSteps,
75
+ });
76
+ ```
77
+
78
+ ### Screenshot Options
79
+
80
+ ```javascript
81
+ // Full page screenshot
82
+ await session.check({
83
+ stepName: 'Full Page',
84
+ fullPage: true,
85
+ });
86
+
87
+ // Specific element
88
+ await session.check({
89
+ stepName: 'Header',
90
+ selector: '#header',
91
+ });
92
+
93
+ // Custom clip region
94
+ await session.check({
95
+ stepName: 'Hero Section',
96
+ clip: { x: 0, y: 0, width: 1200, height: 600 },
97
+ });
98
+
99
+ // With metadata
100
+ await session.check({
101
+ stepName: 'Product Page',
102
+ metadata: {
103
+ productId: '12345',
104
+ category: 'electronics',
105
+ },
106
+ });
107
+ ```
108
+
109
+ ### Multiple Checks
110
+
111
+ ```javascript
112
+ const results = await session.checkMultiple([
113
+ 'Homepage',
114
+ { stepName: 'Header', selector: '#header' },
115
+ { stepName: 'Footer', selector: '#footer' },
116
+ { stepName: 'Sidebar', selector: '.sidebar' },
117
+ ]);
118
+
119
+ results.forEach((result) => {
120
+ console.log(`${result.stepId}: ${result.status}`);
121
+ });
122
+ ```
123
+
124
+ ### Wait for Elements
125
+
126
+ ```javascript
127
+ // Wait for selector before checking
128
+ await session.checkAfterSelector('Product List', '.product-grid');
129
+
130
+ // Wait for navigation before checking
131
+ await page.click('#next-page');
132
+ await session.checkAfterNavigation('Page 2');
133
+ ```
134
+
135
+ ## Configuration
136
+
137
+ ### ClawdTestConfig
138
+
139
+ ```typescript
140
+ interface ClawdTestConfig {
141
+ apiKey: string; // Required: Your API key
142
+ apiUrl?: string; // Optional: API endpoint (default: http://localhost:3001/api/v1)
143
+ projectId: string; // Required: Project ID
144
+ browserName?: string; // Optional: Browser name (default: 'chromium')
145
+ viewport?: { // Optional: Default viewport
146
+ width: number;
147
+ height: number;
148
+ };
149
+ baselineBranch?: string; // Optional: Baseline branch (default: 'main')
150
+ }
151
+ ```
152
+
153
+ ### SessionOptions
154
+
155
+ ```typescript
156
+ interface SessionOptions {
157
+ testName: string; // Required: Test name
158
+ browserName?: string; // Optional: Override browser name
159
+ viewport?: { // Optional: Override viewport
160
+ width: number;
161
+ height: number;
162
+ };
163
+ baselineBranch?: string; // Optional: Override baseline branch
164
+ }
165
+ ```
166
+
167
+ ### CheckOptions
168
+
169
+ ```typescript
170
+ interface CheckOptions {
171
+ stepName: string; // Required: Step name
172
+ fullPage?: boolean; // Optional: Full page screenshot
173
+ selector?: string; // Optional: CSS selector for element
174
+ clip?: { // Optional: Screenshot region
175
+ x: number;
176
+ y: number;
177
+ width: number;
178
+ height: number;
179
+ };
180
+ metadata?: Record<string, any>; // Optional: Custom metadata
181
+ }
182
+ ```
183
+
184
+ ## API Reference
185
+
186
+ ### ClawdTest Class
187
+
188
+ #### `constructor(config: ClawdTestConfig)`
189
+ Create a new ClawdTest client.
190
+
191
+ #### `async session(page: Page, options: SessionOptions): Promise<ClawdTestSession>`
192
+ Create a new test session.
193
+
194
+ #### `async check(page: Page, testName: string, stepName: string)`
195
+ Quick check - creates session, takes screenshot, and closes session.
196
+
197
+ #### `async getSession(sessionId: string)`
198
+ Get session details.
199
+
200
+ ### ClawdTestSession Class
201
+
202
+ #### `async check(stepNameOrOptions: string | CheckOptions): Promise<StepResult>`
203
+ Take a screenshot and compare with baseline.
204
+
205
+ #### `async checkMultiple(checks: Array<string | CheckOptions>): Promise<StepResult[]>`
206
+ Check multiple elements in sequence.
207
+
208
+ #### `async checkAfterNavigation(stepName: string, options?: Omit<CheckOptions, 'stepName'>): Promise<StepResult>`
209
+ Wait for navigation and then check.
210
+
211
+ #### `async checkAfterSelector(stepName: string, selector: string, options?: Omit<CheckOptions, 'stepName' | 'selector'>): Promise<StepResult>`
212
+ Wait for selector and then check.
213
+
214
+ #### `async close(): Promise<SessionResult>`
215
+ Complete the session.
216
+
217
+ #### `getSessionId(): string`
218
+ Get current session ID.
219
+
220
+ #### `getStepCount(): number`
221
+ Get number of steps taken.
222
+
223
+ ## TypeScript Support
224
+
225
+ This SDK is written in TypeScript and includes type definitions.
226
+
227
+ ```typescript
228
+ import { ClawdTest, ClawdTestSession, StepResult } from '@clawdtesting/puppeteer';
229
+ ```
230
+
231
+ ## Error Handling
232
+
233
+ ```javascript
234
+ try {
235
+ const result = await session.check('Homepage');
236
+
237
+ if (result.status === 'failed') {
238
+ console.error('Visual regression detected!');
239
+ console.log('Diff score:', result.diffScore);
240
+ console.log('Diff image:', result.diffImageUrl);
241
+ }
242
+ } catch (error) {
243
+ console.error('Test failed:', error.message);
244
+ }
245
+ ```
246
+
247
+ ## Examples
248
+
249
+ See the [examples](./examples) directory for more usage examples:
250
+ - Basic usage
251
+ - CI/CD integration
252
+ - Custom viewport testing
253
+ - Multi-page flows
254
+
255
+ ## License
256
+
257
+ MIT
@@ -0,0 +1,27 @@
1
+ import { Page } from 'puppeteer';
2
+ import { ClawdTestConfig, SessionOptions } from './types';
3
+ import { ClawdTestSession } from './session';
4
+ export declare class ClawdTest {
5
+ private apiClient;
6
+ private config;
7
+ constructor(config: ClawdTestConfig);
8
+ /**
9
+ * Create a new test session
10
+ */
11
+ session(page: Page, options: SessionOptions): Promise<ClawdTestSession>;
12
+ /**
13
+ * Quick check - creates session, takes screenshot, and closes session
14
+ */
15
+ check(page: Page, testName: string, stepName: string): Promise<{
16
+ sessionResult: import("./types").SessionResult;
17
+ stepId: string;
18
+ status: "passed" | "failed" | "new";
19
+ diffScore: number;
20
+ isNew: boolean;
21
+ diffImageUrl: string | null;
22
+ }>;
23
+ /**
24
+ * Get session details
25
+ */
26
+ getSession(sessionId: string): Promise<any>;
27
+ }
package/dist/client.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClawdTest = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const session_1 = require("./session");
9
+ class ClawdTest {
10
+ constructor(config) {
11
+ this.config = {
12
+ apiUrl: config.apiUrl || 'http://localhost:3001/api/v1',
13
+ apiKey: config.apiKey,
14
+ projectId: config.projectId,
15
+ browserName: config.browserName || 'chromium',
16
+ viewport: config.viewport || { width: 1280, height: 720 },
17
+ baselineBranch: config.baselineBranch || 'main',
18
+ };
19
+ this.apiClient = axios_1.default.create({
20
+ baseURL: this.config.apiUrl,
21
+ headers: {
22
+ 'x-api-key': this.config.apiKey,
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ });
26
+ }
27
+ /**
28
+ * Create a new test session
29
+ */
30
+ async session(page, options) {
31
+ const sessionOptions = {
32
+ browserName: options.browserName || this.config.browserName,
33
+ viewport: options.viewport || this.config.viewport,
34
+ baselineBranch: options.baselineBranch || this.config.baselineBranch,
35
+ };
36
+ const response = await this.apiClient.post('/sessions', {
37
+ projectId: this.config.projectId,
38
+ testName: options.testName,
39
+ browserName: sessionOptions.browserName,
40
+ viewport: sessionOptions.viewport,
41
+ baselineBranch: sessionOptions.baselineBranch,
42
+ });
43
+ const { sessionId } = response.data;
44
+ return new session_1.ClawdTestSession(sessionId, page, this.apiClient, sessionOptions.viewport);
45
+ }
46
+ /**
47
+ * Quick check - creates session, takes screenshot, and closes session
48
+ */
49
+ async check(page, testName, stepName) {
50
+ const session = await this.session(page, { testName });
51
+ const result = await session.check(stepName);
52
+ const sessionResult = await session.close();
53
+ return {
54
+ ...result,
55
+ sessionResult,
56
+ };
57
+ }
58
+ /**
59
+ * Get session details
60
+ */
61
+ async getSession(sessionId) {
62
+ const response = await this.apiClient.get(`/sessions/${sessionId}`);
63
+ return response.data;
64
+ }
65
+ }
66
+ exports.ClawdTest = ClawdTest;
@@ -0,0 +1,3 @@
1
+ export { ClawdTest } from './client';
2
+ export { ClawdTestSession } from './session';
3
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ClawdTestSession = exports.ClawdTest = void 0;
18
+ var client_1 = require("./client");
19
+ Object.defineProperty(exports, "ClawdTest", { enumerable: true, get: function () { return client_1.ClawdTest; } });
20
+ var session_1 = require("./session");
21
+ Object.defineProperty(exports, "ClawdTestSession", { enumerable: true, get: function () { return session_1.ClawdTestSession; } });
22
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,42 @@
1
+ import { Page } from 'puppeteer';
2
+ import { AxiosInstance } from 'axios';
3
+ import { CheckOptions, StepResult, SessionResult } from './types';
4
+ export declare class ClawdTestSession {
5
+ private sessionId;
6
+ private page;
7
+ private apiClient;
8
+ private viewport;
9
+ private stepCount;
10
+ constructor(sessionId: string, page: Page, apiClient: AxiosInstance, viewport: {
11
+ width: number;
12
+ height: number;
13
+ });
14
+ /**
15
+ * Take a screenshot and compare with baseline
16
+ */
17
+ check(stepNameOrOptions: string | CheckOptions): Promise<StepResult>;
18
+ /**
19
+ * Check multiple elements in sequence
20
+ */
21
+ checkMultiple(checks: Array<string | CheckOptions>): Promise<StepResult[]>;
22
+ /**
23
+ * Wait for navigation and then check
24
+ */
25
+ checkAfterNavigation(stepName: string, options?: Omit<CheckOptions, 'stepName'>): Promise<StepResult>;
26
+ /**
27
+ * Wait for selector and then check
28
+ */
29
+ checkAfterSelector(stepName: string, selector: string, options?: Omit<CheckOptions, 'stepName' | 'selector'>): Promise<StepResult>;
30
+ /**
31
+ * Complete the session
32
+ */
33
+ close(): Promise<SessionResult>;
34
+ /**
35
+ * Get current session ID
36
+ */
37
+ getSessionId(): string;
38
+ /**
39
+ * Get number of steps taken
40
+ */
41
+ getStepCount(): number;
42
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClawdTestSession = void 0;
7
+ const form_data_1 = __importDefault(require("form-data"));
8
+ class ClawdTestSession {
9
+ constructor(sessionId, page, apiClient, viewport) {
10
+ this.stepCount = 0;
11
+ this.sessionId = sessionId;
12
+ this.page = page;
13
+ this.apiClient = apiClient;
14
+ this.viewport = viewport;
15
+ }
16
+ /**
17
+ * Take a screenshot and compare with baseline
18
+ */
19
+ async check(stepNameOrOptions) {
20
+ const options = typeof stepNameOrOptions === 'string'
21
+ ? { stepName: stepNameOrOptions }
22
+ : stepNameOrOptions;
23
+ // Set viewport
24
+ await this.page.setViewport(this.viewport);
25
+ // Take screenshot
26
+ let screenshot;
27
+ if (options.selector) {
28
+ const element = await this.page.$(options.selector);
29
+ if (!element) {
30
+ throw new Error(`Element not found: ${options.selector}`);
31
+ }
32
+ screenshot = await element.screenshot({ encoding: 'binary' });
33
+ }
34
+ else if (options.clip) {
35
+ screenshot = await this.page.screenshot({
36
+ clip: options.clip,
37
+ encoding: 'binary',
38
+ });
39
+ }
40
+ else {
41
+ screenshot = await this.page.screenshot({
42
+ fullPage: options.fullPage || false,
43
+ encoding: 'binary',
44
+ });
45
+ }
46
+ // Upload screenshot to API
47
+ const formData = new form_data_1.default();
48
+ formData.append('stepName', options.stepName);
49
+ formData.append('screenshot', screenshot, {
50
+ filename: `${options.stepName}.png`,
51
+ contentType: 'image/png',
52
+ });
53
+ if (options.metadata) {
54
+ formData.append('metadata', JSON.stringify(options.metadata));
55
+ }
56
+ const response = await this.apiClient.post(`/sessions/${this.sessionId}/steps`, formData, {
57
+ headers: formData.getHeaders(),
58
+ });
59
+ this.stepCount++;
60
+ return response.data;
61
+ }
62
+ /**
63
+ * Check multiple elements in sequence
64
+ */
65
+ async checkMultiple(checks) {
66
+ const results = [];
67
+ for (const check of checks) {
68
+ const result = await this.check(check);
69
+ results.push(result);
70
+ }
71
+ return results;
72
+ }
73
+ /**
74
+ * Wait for navigation and then check
75
+ */
76
+ async checkAfterNavigation(stepName, options) {
77
+ await this.page.waitForNavigation({ waitUntil: 'networkidle0' });
78
+ return this.check({ stepName, ...options });
79
+ }
80
+ /**
81
+ * Wait for selector and then check
82
+ */
83
+ async checkAfterSelector(stepName, selector, options) {
84
+ await this.page.waitForSelector(selector);
85
+ return this.check({ stepName, selector, ...options });
86
+ }
87
+ /**
88
+ * Complete the session
89
+ */
90
+ async close() {
91
+ const response = await this.apiClient.post(`/sessions/${this.sessionId}/complete`);
92
+ return response.data;
93
+ }
94
+ /**
95
+ * Get current session ID
96
+ */
97
+ getSessionId() {
98
+ return this.sessionId;
99
+ }
100
+ /**
101
+ * Get number of steps taken
102
+ */
103
+ getStepCount() {
104
+ return this.stepCount;
105
+ }
106
+ }
107
+ exports.ClawdTestSession = ClawdTestSession;
@@ -0,0 +1,47 @@
1
+ export interface ClawdTestConfig {
2
+ apiKey: string;
3
+ apiUrl?: string;
4
+ projectId: string;
5
+ browserName?: string;
6
+ viewport?: {
7
+ width: number;
8
+ height: number;
9
+ };
10
+ baselineBranch?: string;
11
+ }
12
+ export interface SessionOptions {
13
+ testName: string;
14
+ browserName?: string;
15
+ viewport?: {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ baselineBranch?: string;
20
+ }
21
+ export interface StepResult {
22
+ stepId: string;
23
+ status: 'passed' | 'failed' | 'new';
24
+ diffScore: number;
25
+ isNew: boolean;
26
+ diffImageUrl: string | null;
27
+ }
28
+ export interface SessionResult {
29
+ sessionId: string;
30
+ status: 'passed' | 'failed' | 'unresolved';
31
+ totalSteps: number;
32
+ passedSteps: number;
33
+ failedSteps: number;
34
+ newSteps: number;
35
+ }
36
+ export interface CheckOptions {
37
+ stepName: string;
38
+ fullPage?: boolean;
39
+ selector?: string;
40
+ clip?: {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ };
46
+ metadata?: Record<string, any>;
47
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,50 @@
1
+ const puppeteer = require('puppeteer');
2
+ const { ClawdTest } = require('../dist');
3
+
4
+ async function basicExample() {
5
+ // Initialize ClawdTest client
6
+ const clawdTest = new ClawdTest({
7
+ apiKey: process.env.CLAWDTEST_API_KEY || 'your-api-key',
8
+ apiUrl: process.env.CLAWDTEST_API_URL || 'http://localhost:3001/api/v1',
9
+ projectId: process.env.CLAWDTEST_PROJECT_ID || 'your-project-id',
10
+ });
11
+
12
+ // Launch browser
13
+ const browser = await puppeteer.launch({
14
+ headless: true,
15
+ });
16
+
17
+ const page = await browser.newPage();
18
+
19
+ try {
20
+ // Navigate to page
21
+ await page.goto('https://example.com');
22
+
23
+ // Quick check
24
+ const result = await clawdTest.check(
25
+ page,
26
+ 'Example.com Test',
27
+ 'Homepage',
28
+ );
29
+
30
+ console.log('✓ Test completed');
31
+ console.log(' Status:', result.status);
32
+ console.log(' Diff Score:', result.diffScore);
33
+
34
+ if (result.status === 'failed') {
35
+ console.log(' Diff Image:', result.diffImageUrl);
36
+ }
37
+
38
+ // Exit with error code if test failed
39
+ if (result.sessionResult.status === 'failed') {
40
+ process.exit(1);
41
+ }
42
+ } catch (error) {
43
+ console.error('✗ Test failed:', error.message);
44
+ process.exit(1);
45
+ } finally {
46
+ await browser.close();
47
+ }
48
+ }
49
+
50
+ basicExample();
@@ -0,0 +1,72 @@
1
+ const puppeteer = require('puppeteer');
2
+ const { ClawdTest } = require('../dist');
3
+
4
+ async function sessionExample() {
5
+ const clawdTest = new ClawdTest({
6
+ apiKey: process.env.CLAWDTEST_API_KEY || 'your-api-key',
7
+ apiUrl: process.env.CLAWDTEST_API_URL || 'http://localhost:3001/api/v1',
8
+ projectId: process.env.CLAWDTEST_PROJECT_ID || 'your-project-id',
9
+ });
10
+
11
+ const browser = await puppeteer.launch({ headless: true });
12
+ const page = await browser.newPage();
13
+
14
+ try {
15
+ // Create session
16
+ const session = await clawdTest.session(page, {
17
+ testName: 'Multi-Step User Flow',
18
+ viewport: { width: 1920, height: 1080 },
19
+ });
20
+
21
+ console.log('Session created:', session.getSessionId());
22
+
23
+ // Step 1: Homepage
24
+ await page.goto('https://example.com');
25
+ let result = await session.check('Homepage');
26
+ console.log('Step 1 - Homepage:', result.status);
27
+
28
+ // Step 2: Scroll to section
29
+ await page.evaluate(() => window.scrollTo(0, 500));
30
+ result = await session.check({
31
+ stepName: 'Middle Section',
32
+ metadata: { scrollY: 500 },
33
+ });
34
+ console.log('Step 2 - Middle Section:', result.status);
35
+
36
+ // Step 3: Check specific element
37
+ result = await session.check({
38
+ stepName: 'Main Content',
39
+ selector: 'main',
40
+ });
41
+ console.log('Step 3 - Main Content:', result.status);
42
+
43
+ // Step 4: Full page
44
+ result = await session.check({
45
+ stepName: 'Full Page',
46
+ fullPage: true,
47
+ });
48
+ console.log('Step 4 - Full Page:', result.status);
49
+
50
+ // Complete session
51
+ const sessionResult = await session.close();
52
+
53
+ console.log('\n✓ Session completed');
54
+ console.log(' Total Steps:', sessionResult.totalSteps);
55
+ console.log(' Passed:', sessionResult.passedSteps);
56
+ console.log(' Failed:', sessionResult.failedSteps);
57
+ console.log(' New:', sessionResult.newSteps);
58
+ console.log(' Status:', sessionResult.status);
59
+
60
+ // Exit with error code if any failures
61
+ if (sessionResult.status === 'failed') {
62
+ process.exit(1);
63
+ }
64
+ } catch (error) {
65
+ console.error('✗ Test failed:', error.message);
66
+ process.exit(1);
67
+ } finally {
68
+ await browser.close();
69
+ }
70
+ }
71
+
72
+ sessionExample();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@clawdtesting/puppeteer",
3
+ "version": "1.0.0",
4
+ "description": "Visual Testing SDK for Puppeteer",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "test": "jest",
11
+ "prepublish": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "visual-testing",
15
+ "puppeteer",
16
+ "screenshot",
17
+ "regression-testing"
18
+ ],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "puppeteer": "^21.9.0",
23
+ "axios": "^1.6.5",
24
+ "form-data": "^4.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.11.0",
28
+ "@types/puppeteer": "^7.0.4",
29
+ "typescript": "^5.3.3",
30
+ "jest": "^29.7.0",
31
+ "ts-jest": "^29.1.1"
32
+ },
33
+ "peerDependencies": {
34
+ "puppeteer": "^21.0.0"
35
+ }
36
+ }
package/src/client.ts ADDED
@@ -0,0 +1,78 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import { Page } from 'puppeteer';
3
+ import { ClawdTestConfig, SessionOptions } from './types';
4
+ import { ClawdTestSession } from './session';
5
+
6
+ export class ClawdTest {
7
+ private apiClient: AxiosInstance;
8
+ private config: Required<ClawdTestConfig>;
9
+
10
+ constructor(config: ClawdTestConfig) {
11
+ this.config = {
12
+ apiUrl: config.apiUrl || 'http://localhost:3001/api/v1',
13
+ apiKey: config.apiKey,
14
+ projectId: config.projectId,
15
+ browserName: config.browserName || 'chromium',
16
+ viewport: config.viewport || { width: 1280, height: 720 },
17
+ baselineBranch: config.baselineBranch || 'main',
18
+ };
19
+
20
+ this.apiClient = axios.create({
21
+ baseURL: this.config.apiUrl,
22
+ headers: {
23
+ 'x-api-key': this.config.apiKey,
24
+ 'Content-Type': 'application/json',
25
+ },
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Create a new test session
31
+ */
32
+ async session(page: Page, options: SessionOptions): Promise<ClawdTestSession> {
33
+ const sessionOptions = {
34
+ browserName: options.browserName || this.config.browserName,
35
+ viewport: options.viewport || this.config.viewport,
36
+ baselineBranch: options.baselineBranch || this.config.baselineBranch,
37
+ };
38
+
39
+ const response = await this.apiClient.post('/sessions', {
40
+ projectId: this.config.projectId,
41
+ testName: options.testName,
42
+ browserName: sessionOptions.browserName,
43
+ viewport: sessionOptions.viewport,
44
+ baselineBranch: sessionOptions.baselineBranch,
45
+ });
46
+
47
+ const { sessionId } = response.data;
48
+
49
+ return new ClawdTestSession(
50
+ sessionId,
51
+ page,
52
+ this.apiClient,
53
+ sessionOptions.viewport,
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Quick check - creates session, takes screenshot, and closes session
59
+ */
60
+ async check(page: Page, testName: string, stepName: string) {
61
+ const session = await this.session(page, { testName });
62
+ const result = await session.check(stepName);
63
+ const sessionResult = await session.close();
64
+
65
+ return {
66
+ ...result,
67
+ sessionResult,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Get session details
73
+ */
74
+ async getSession(sessionId: string) {
75
+ const response = await this.apiClient.get(`/sessions/${sessionId}`);
76
+ return response.data;
77
+ }
78
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { ClawdTest } from './client';
2
+ export { ClawdTestSession } from './session';
3
+ export * from './types';
package/src/session.ts ADDED
@@ -0,0 +1,147 @@
1
+ import { Page } from 'puppeteer';
2
+ import { AxiosInstance } from 'axios';
3
+ import FormData from 'form-data';
4
+ import { CheckOptions, StepResult, SessionResult } from './types';
5
+
6
+ export class ClawdTestSession {
7
+ private sessionId: string;
8
+ private page: Page;
9
+ private apiClient: AxiosInstance;
10
+ private viewport: { width: number; height: number };
11
+ private stepCount: number = 0;
12
+
13
+ constructor(
14
+ sessionId: string,
15
+ page: Page,
16
+ apiClient: AxiosInstance,
17
+ viewport: { width: number; height: number },
18
+ ) {
19
+ this.sessionId = sessionId;
20
+ this.page = page;
21
+ this.apiClient = apiClient;
22
+ this.viewport = viewport;
23
+ }
24
+
25
+ /**
26
+ * Take a screenshot and compare with baseline
27
+ */
28
+ async check(
29
+ stepNameOrOptions: string | CheckOptions,
30
+ ): Promise<StepResult> {
31
+ const options: CheckOptions =
32
+ typeof stepNameOrOptions === 'string'
33
+ ? { stepName: stepNameOrOptions }
34
+ : stepNameOrOptions;
35
+
36
+ // Set viewport
37
+ await this.page.setViewport(this.viewport);
38
+
39
+ // Take screenshot
40
+ let screenshot: Buffer;
41
+
42
+ if (options.selector) {
43
+ const element = await this.page.$(options.selector);
44
+ if (!element) {
45
+ throw new Error(`Element not found: ${options.selector}`);
46
+ }
47
+ screenshot = await element.screenshot({ encoding: 'binary' }) as Buffer;
48
+ } else if (options.clip) {
49
+ screenshot = await this.page.screenshot({
50
+ clip: options.clip,
51
+ encoding: 'binary',
52
+ }) as Buffer;
53
+ } else {
54
+ screenshot = await this.page.screenshot({
55
+ fullPage: options.fullPage || false,
56
+ encoding: 'binary',
57
+ }) as Buffer;
58
+ }
59
+
60
+ // Upload screenshot to API
61
+ const formData = new FormData();
62
+ formData.append('stepName', options.stepName);
63
+ formData.append('screenshot', screenshot, {
64
+ filename: `${options.stepName}.png`,
65
+ contentType: 'image/png',
66
+ });
67
+
68
+ if (options.metadata) {
69
+ formData.append('metadata', JSON.stringify(options.metadata));
70
+ }
71
+
72
+ const response = await this.apiClient.post(
73
+ `/sessions/${this.sessionId}/steps`,
74
+ formData,
75
+ {
76
+ headers: formData.getHeaders(),
77
+ },
78
+ );
79
+
80
+ this.stepCount++;
81
+
82
+ return response.data as StepResult;
83
+ }
84
+
85
+ /**
86
+ * Check multiple elements in sequence
87
+ */
88
+ async checkMultiple(
89
+ checks: Array<string | CheckOptions>,
90
+ ): Promise<StepResult[]> {
91
+ const results: StepResult[] = [];
92
+
93
+ for (const check of checks) {
94
+ const result = await this.check(check);
95
+ results.push(result);
96
+ }
97
+
98
+ return results;
99
+ }
100
+
101
+ /**
102
+ * Wait for navigation and then check
103
+ */
104
+ async checkAfterNavigation(
105
+ stepName: string,
106
+ options?: Omit<CheckOptions, 'stepName'>,
107
+ ): Promise<StepResult> {
108
+ await this.page.waitForNavigation({ waitUntil: 'networkidle0' });
109
+ return this.check({ stepName, ...options });
110
+ }
111
+
112
+ /**
113
+ * Wait for selector and then check
114
+ */
115
+ async checkAfterSelector(
116
+ stepName: string,
117
+ selector: string,
118
+ options?: Omit<CheckOptions, 'stepName' | 'selector'>,
119
+ ): Promise<StepResult> {
120
+ await this.page.waitForSelector(selector);
121
+ return this.check({ stepName, selector, ...options });
122
+ }
123
+
124
+ /**
125
+ * Complete the session
126
+ */
127
+ async close(): Promise<SessionResult> {
128
+ const response = await this.apiClient.post(
129
+ `/sessions/${this.sessionId}/complete`,
130
+ );
131
+ return response.data as SessionResult;
132
+ }
133
+
134
+ /**
135
+ * Get current session ID
136
+ */
137
+ getSessionId(): string {
138
+ return this.sessionId;
139
+ }
140
+
141
+ /**
142
+ * Get number of steps taken
143
+ */
144
+ getStepCount(): number {
145
+ return this.stepCount;
146
+ }
147
+ }
package/src/types.ts ADDED
@@ -0,0 +1,51 @@
1
+ export interface ClawdTestConfig {
2
+ apiKey: string;
3
+ apiUrl?: string;
4
+ projectId: string;
5
+ browserName?: string;
6
+ viewport?: {
7
+ width: number;
8
+ height: number;
9
+ };
10
+ baselineBranch?: string;
11
+ }
12
+
13
+ export interface SessionOptions {
14
+ testName: string;
15
+ browserName?: string;
16
+ viewport?: {
17
+ width: number;
18
+ height: number;
19
+ };
20
+ baselineBranch?: string;
21
+ }
22
+
23
+ export interface StepResult {
24
+ stepId: string;
25
+ status: 'passed' | 'failed' | 'new';
26
+ diffScore: number;
27
+ isNew: boolean;
28
+ diffImageUrl: string | null;
29
+ }
30
+
31
+ export interface SessionResult {
32
+ sessionId: string;
33
+ status: 'passed' | 'failed' | 'unresolved';
34
+ totalSteps: number;
35
+ passedSteps: number;
36
+ failedSteps: number;
37
+ newSteps: number;
38
+ }
39
+
40
+ export interface CheckOptions {
41
+ stepName: string;
42
+ fullPage?: boolean;
43
+ selector?: string;
44
+ clip?: {
45
+ x: number;
46
+ y: number;
47
+ width: number;
48
+ height: number;
49
+ };
50
+ metadata?: Record<string, any>;
51
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "moduleResolution": "node"
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist", "**/*.spec.ts"]
18
+ }