0
# Programmatic API
1
2
Percy CLI provides extensive programmatic APIs through its core packages, enabling deep integration with custom tools, testing frameworks, and automation pipelines. These APIs are primarily intended for SDK developers and advanced users who need direct access to Percy's core functionality.
3
4
## Core Imports
5
6
```javascript
7
// Main Percy CLI entry point
8
import { percy, checkForUpdate } from "@percy/cli";
9
10
// Core functionality
11
import Percy from "@percy/core";
12
import * as PercyUtils from "@percy/core/utils";
13
14
// Configuration system
15
import { load, validate, migrate } from "@percy/config";
16
17
// API client
18
import PercyClient from "@percy/client";
19
20
// Environment detection
21
import PercyEnv from "@percy/env";
22
23
// DOM serialization
24
import { serializeDOM } from "@percy/dom";
25
26
// Logging
27
import logger from "@percy/logger";
28
29
// SDK utilities
30
import * as SDKUtils from "@percy/sdk-utils";
31
```
32
33
## Capabilities
34
35
### Percy Core Class
36
37
The main Percy class provides the core visual testing engine with browser automation, asset discovery, and snapshot coordination.
38
39
```javascript { .api }
40
/**
41
* Main Percy class for visual testing operations
42
* Manages browser sessions, asset discovery, and snapshot uploading
43
*/
44
class Percy {
45
constructor(options?: PercyOptions);
46
47
// Lifecycle methods
48
start(): Promise<void>;
49
stop(): Promise<void>;
50
idle(): Promise<void>;
51
52
// Snapshot methods
53
snapshot(options: SnapshotOptions): Promise<void>;
54
upload(files: FileUploadOptions): Promise<void>;
55
56
// Server methods
57
server(options?: ServerOptions): Promise<Server>;
58
59
// Utility methods
60
isRunning(): boolean;
61
address(): string;
62
port(): number;
63
64
// Getters
65
get client(): PercyClient;
66
get browser(): Browser;
67
get config(): PercyConfig;
68
}
69
70
interface PercyOptions {
71
token?: string;
72
config?: string | PercyConfig;
73
server?: boolean;
74
port?: number;
75
skipDiscovery?: boolean;
76
delayUploads?: boolean;
77
deferUploads?: boolean;
78
dryRun?: boolean;
79
labels?: string[];
80
}
81
82
interface SnapshotOptions {
83
name: string;
84
url?: string;
85
domSnapshot?: string;
86
clientInfo?: string;
87
environmentInfo?: string;
88
enableJavaScript?: boolean;
89
cliEnableJavaScript?: boolean;
90
disableShadowDOM?: boolean;
91
minHeight?: number;
92
scope?: string;
93
sync?: boolean;
94
testCase?: string;
95
thTestCaseExecutionId?: string;
96
additionalSnapshots?: AdditionalSnapshot[];
97
requestHeaders?: Record<string, string>;
98
authorization?: {
99
username: string;
100
password: string;
101
};
102
}
103
104
interface AdditionalSnapshot {
105
prefix?: string;
106
suffix?: string;
107
name?: string;
108
waitForTimeout?: number;
109
waitForSelector?: string;
110
execute?: string | Function;
111
percyCSS?: string;
112
ignoreRegions?: IgnoreRegion[];
113
}
114
115
interface IgnoreRegion {
116
ignoreRegion?: string;
117
customIgnoreRegions?: CustomIgnoreRegion[];
118
}
119
120
interface CustomIgnoreRegion {
121
selector?: string;
122
xpath?: string;
123
coordinates?: {
124
top: number;
125
bottom: number;
126
left: number;
127
right: number;
128
};
129
}
130
```
131
132
### Percy Utilities
133
134
Core utility functions for network operations, resource creation, async operations, and more.
135
136
```javascript { .api }
137
// Network utilities
138
function request(url: string, options?: RequestOptions): Promise<Response>;
139
function hostname(url: string): string;
140
function normalizeURL(url: string, baseURL?: string): string;
141
function hostnameMatches(hostname: string, patterns: string[]): boolean;
142
143
// Resource creation
144
function createResource(url: string, content: Buffer, options?: ResourceOptions): Resource;
145
function createRootResource(url: string, content: string): Resource;
146
function createPercyCSSResource(css: string): Resource;
147
function createLogResource(logs: LogEntry[]): Resource;
148
149
// Server utilities
150
class Server {
151
constructor(percy: Percy, port?: number);
152
listen(port?: number): Promise<void>;
153
close(): Promise<void>;
154
address(): string;
155
port(): number;
156
}
157
158
function createServer(percy: Percy, port?: number): Server;
159
160
// Async utilities with generator support
161
function generatePromise<T>(generator: Generator<any, T>): Promise<T>;
162
function yieldTo(promise: Promise<any>): Generator<Promise<any>, any>;
163
function yieldAll(promises: Promise<any>[]): Generator<Promise<any[]>, any[]>;
164
function yieldFor(condition: () => boolean, timeout?: number): Generator<Promise<boolean>, boolean>;
165
function waitFor(condition: () => boolean, options?: WaitOptions): Promise<boolean>;
166
function waitForTimeout(timeout: number): Promise<void>;
167
168
// Browser utilities
169
function serializeFunction(fn: Function): string;
170
function waitForSelectorInsideBrowser(selector: string, timeout?: number): string;
171
172
// Encoding utilities
173
function base64encode(data: string | Buffer): string;
174
function isGzipped(data: Buffer): boolean;
175
176
// URL utilities
177
function decodeAndEncodeURLWithLogging(url: string): string;
178
179
// Security utilities
180
function redactSecrets(data: any): any;
181
182
// Helper classes
183
class AbortController {
184
signal: AbortSignal;
185
abort(): void;
186
}
187
188
class AbortError extends Error {
189
name: "AbortError";
190
}
191
192
class DefaultMap<K, V> extends Map<K, V> {
193
constructor(defaultValue: () => V);
194
get(key: K): V;
195
}
196
197
// Retry utilities
198
function withRetries<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
199
200
// Type utilities
201
function isGenerator(obj: any): obj is Generator;
202
function compareObjectTypes(obj1: any, obj2: any): boolean;
203
function normalizeOptions<T>(options: T, schema: any): T;
204
205
interface RequestOptions {
206
method?: string;
207
headers?: Record<string, string>;
208
body?: string | Buffer;
209
timeout?: number;
210
retries?: number;
211
retryBackoff?: number;
212
}
213
214
interface ResourceOptions {
215
mimetype?: string;
216
root?: boolean;
217
percyCSS?: boolean;
218
}
219
220
interface Resource {
221
url: string;
222
content: Buffer;
223
mimetype: string;
224
root?: boolean;
225
percyCSS?: boolean;
226
}
227
228
interface WaitOptions {
229
timeout?: number;
230
interval?: number;
231
}
232
233
interface RetryOptions {
234
retries?: number;
235
backoff?: number;
236
onRetry?: (error: Error, attempt: number) => void;
237
}
238
```
239
240
### Configuration System
241
242
Advanced configuration loading, validation, and migration system.
243
244
```javascript { .api }
245
// Configuration loading
246
function load(options?: LoadOptions): Promise<PercyConfig>;
247
function search(options?: SearchOptions): Promise<string | null>;
248
249
// Validation system
250
function validate(config: any, schema?: ConfigSchema): ValidationResult;
251
function addSchema(name: string, schema: any): void;
252
253
// Migration system
254
function migrate(config: any, options?: MigrateOptions): any;
255
function addMigration(version: number, migration: MigrationFunction): void;
256
257
// Configuration utilities
258
function getDefaults(): PercyConfig;
259
function merge(...configs: any[]): PercyConfig;
260
function normalize(config: any): PercyConfig;
261
function stringify(config: any, format?: 'json' | 'yaml' | 'js'): string;
262
263
interface LoadOptions {
264
path?: string;
265
search?: boolean;
266
bail?: boolean;
267
}
268
269
interface SearchOptions {
270
start?: string;
271
stop?: string;
272
names?: string[];
273
}
274
275
interface ValidationResult {
276
valid: boolean;
277
errors: ValidationError[];
278
config: PercyConfig;
279
}
280
281
interface ValidationError {
282
path: string[];
283
message: string;
284
value: any;
285
}
286
287
interface MigrateOptions {
288
dry?: boolean;
289
version?: number;
290
}
291
292
type MigrationFunction = (config: any) => any;
293
294
interface ConfigSchema {
295
type: string;
296
properties?: Record<string, any>;
297
additionalProperties?: boolean;
298
required?: string[];
299
}
300
```
301
302
### Percy Client
303
304
HTTP client for Percy API communication with authentication, retries, and error handling.
305
306
```javascript { .api }
307
/**
308
* Percy API client for interacting with Percy's visual testing service
309
* Handles authentication, request/response processing, and error handling
310
*/
311
class PercyClient {
312
constructor(options?: ClientOptions);
313
314
// Build methods
315
createBuild(options: CreateBuildOptions): Promise<Build>;
316
getBuild(buildId: string): Promise<Build>;
317
finalizeBuild(buildId: string): Promise<Build>;
318
319
// Snapshot methods
320
createSnapshot(buildId: string, snapshot: CreateSnapshotOptions): Promise<Snapshot>;
321
uploadResources(buildId: string, resources: Resource[]): Promise<void>;
322
createComparison(buildId: string, snapshotId: string, options: ComparisonOptions): Promise<Comparison>;
323
324
// Build management
325
approveBuild(buildId: string): Promise<Build>;
326
327
// Utility methods
328
request(path: string, options?: RequestOptions): Promise<any>;
329
}
330
331
interface ClientOptions {
332
token?: string;
333
apiUrl?: string;
334
clientInfo?: string;
335
environmentInfo?: string;
336
userAgent?: string;
337
}
338
339
interface CreateBuildOptions {
340
type?: 'web' | 'app';
341
branch?: string;
342
targetBranch?: string;
343
targetCommitSha?: string;
344
commitSha?: string;
345
commitCommittedAt?: string;
346
commitAuthorName?: string;
347
commitAuthorEmail?: string;
348
commitMessage?: string;
349
pullRequestNumber?: string;
350
parallel?: boolean;
351
partialBuild?: boolean;
352
}
353
354
interface Build {
355
id: string;
356
number: number;
357
url: string;
358
state: 'pending' | 'processing' | 'finished' | 'failed';
359
reviewState?: 'unreviewed' | 'approved' | 'rejected';
360
reviewStateReason?: string;
361
totalComparisons?: number;
362
totalComparisonsFinished?: number;
363
totalComparisonsDiff?: number;
364
}
365
366
interface CreateSnapshotOptions {
367
name: string;
368
clientInfo?: string;
369
environmentInfo?: string;
370
widths?: number[];
371
minHeight?: number;
372
enableJavaScript?: boolean;
373
percyCSS?: string;
374
scope?: string;
375
domSnapshot?: string;
376
}
377
378
interface Snapshot {
379
id: string;
380
name: string;
381
url?: string;
382
widths: number[];
383
minHeight: number;
384
enableJavaScript: boolean;
385
}
386
387
interface ComparisonOptions {
388
tag?: string;
389
tiles?: Tile[];
390
externalDebugUrl?: string;
391
ignoredElementsData?: IgnoreElementData[];
392
consideredElementsData?: ConsiderElementData[];
393
}
394
395
interface Tile {
396
filepath?: string;
397
sha?: string;
398
statusBarHeight?: number;
399
navBarHeight?: number;
400
headerHeight?: number;
401
footerHeight?: number;
402
fullscreen?: boolean;
403
}
404
405
interface Comparison {
406
id: string;
407
tag: string;
408
state: 'pending' | 'processing' | 'finished' | 'failed';
409
diffRatio?: number;
410
}
411
```
412
413
### Environment Detection
414
415
Comprehensive CI/CD environment detection and configuration.
416
417
```javascript { .api }
418
/**
419
* Environment detection for CI/CD systems and local development
420
* Automatically detects 15+ popular CI systems and extracts build information
421
*/
422
class PercyEnv {
423
constructor();
424
425
// Environment detection
426
get ci(): string | null;
427
get branch(): string | null;
428
get commit(): string | null;
429
get pullRequest(): string | null;
430
get parallel(): { nonce: string; total: number } | null;
431
get partial(): boolean;
432
433
// CI system specific info
434
get info(): EnvironmentInfo;
435
436
// Static methods
437
static current(): PercyEnv;
438
static load(path?: string): void;
439
}
440
441
interface EnvironmentInfo {
442
ci?: string;
443
project?: string;
444
branch?: string;
445
commit?: string;
446
commitMessage?: string;
447
committerName?: string;
448
committerEmail?: string;
449
authorName?: string;
450
authorEmail?: string;
451
pullRequest?: string;
452
parallel?: {
453
nonce: string;
454
total: number;
455
};
456
partial?: boolean;
457
url?: string;
458
}
459
460
// Supported CI systems:
461
// - Travis CI, Jenkins, CircleCI, GitHub Actions, GitLab CI
462
// - Azure DevOps, Buildkite, Drone, Semaphore, TeamCity
463
// - AppVeyor, Bitbucket Pipelines, Heroku CI, Netlify, Codeship
464
```
465
466
### DOM Serialization
467
468
Browser-compatible DOM serialization for visual testing.
469
470
```javascript { .api }
471
// DOM serialization functions
472
function serializeDOM(options?: SerializeOptions): string;
473
const serialize = serializeDOM; // Alias
474
475
function waitForResize(timeout?: number): Promise<void>;
476
function loadAllSrcsetLinks(): Promise<void>;
477
478
interface SerializeOptions {
479
enableJavaScript?: boolean;
480
disableShadowDOM?: boolean;
481
domTransformation?: (dom: Document) => Document;
482
reshuffleInvalidTags?: boolean;
483
}
484
```
485
486
### SDK Utilities
487
488
Utilities for Percy SDK development and integration.
489
490
```javascript { .api }
491
// Logger for SDK development
492
const logger: Logger;
493
494
// Percy utilities
495
function isPercyEnabled(): boolean;
496
function waitForPercyIdle(): Promise<void>;
497
498
// Percy communication
499
function fetchPercyDOM(): Promise<string>;
500
function postSnapshot(name: string, options?: PostSnapshotOptions): Promise<void>;
501
function postComparison(name: string, tag: string, tiles?: Tile[]): Promise<void>;
502
function postBuildEvents(events: BuildEvent[]): Promise<void>;
503
function flushSnapshots(): Promise<void>;
504
505
// Screenshot capture
506
function captureAutomateScreenshot(options: AutomateScreenshotOptions): Promise<Buffer>;
507
508
// HTTP utilities
509
function request(url: string, options?: RequestOptions): Promise<Response>;
510
511
interface PostSnapshotOptions {
512
url?: string;
513
domSnapshot?: string;
514
clientInfo?: string;
515
environmentInfo?: string;
516
widths?: number[];
517
minHeight?: number;
518
enableJavaScript?: boolean;
519
percyCSS?: string;
520
scope?: string;
521
}
522
523
interface BuildEvent {
524
type: string;
525
message: string;
526
timestamp?: number;
527
level?: 'debug' | 'info' | 'warn' | 'error';
528
}
529
530
interface AutomateScreenshotOptions {
531
sessionId: string;
532
commandId?: string;
533
screenshotName?: string;
534
ignoreRegions?: IgnoreRegion[];
535
customIgnoreRegions?: CustomIgnoreRegion[];
536
options?: {
537
freezeAnimatedImage?: boolean;
538
freezeImageBySelectors?: string[];
539
freezeImageByXpaths?: string[];
540
ignoreRegionSelectors?: string[];
541
ignoreRegionXpaths?: string[];
542
customIgnoreRegions?: CustomIgnoreRegionOptions[];
543
considerRegionSelectors?: string[];
544
considerRegionXpaths?: string[];
545
customConsiderRegions?: CustomConsiderRegionOptions[];
546
scrollToTopBottom?: boolean;
547
scrollToTopBottomMargin?: number;
548
};
549
}
550
551
interface CustomIgnoreRegionOptions {
552
top: number;
553
left: number;
554
bottom: number;
555
right: number;
556
}
557
558
interface CustomConsiderRegionOptions {
559
top: number;
560
left: number;
561
bottom: number;
562
right: number;
563
}
564
```
565
566
### Logger System
567
568
Structured logging system with group support and configurable output.
569
570
```javascript { .api }
571
/**
572
* Percy logger with group support and structured output
573
* Provides consistent logging across all Percy packages
574
*/
575
interface Logger {
576
// Logging methods
577
debug(message: string, meta?: any): void;
578
info(message: string, meta?: any): void;
579
warn(message: string, meta?: any): void;
580
error(message: string, error?: Error, meta?: any): void;
581
582
// Properties
583
stdout: NodeJS.WriteStream;
584
stderr: NodeJS.WriteStream;
585
586
// Methods
587
query(filter?: LogFilter): LogEntry[];
588
format(entry: LogEntry): string;
589
loglevel(level?: string): string;
590
timeit(label: string): () => void;
591
measure(label: string, fn: () => any): any;
592
measure(label: string, fn: () => Promise<any>): Promise<any>;
593
594
// Group management
595
group(name: string): Logger;
596
}
597
598
interface LogEntry {
599
level: 'debug' | 'info' | 'warn' | 'error';
600
message: string;
601
timestamp: number;
602
meta?: any;
603
group?: string;
604
}
605
606
interface LogFilter {
607
level?: string;
608
group?: string;
609
since?: number;
610
}
611
612
// Create logger instance
613
function createLogger(group?: string): Logger;
614
615
// Default export is root logger
616
const logger: Logger;
617
export default logger;
618
```
619
620
## Usage Examples
621
622
### Basic Percy Automation
623
624
```javascript
625
import Percy from "@percy/core";
626
627
const percy = new Percy({
628
token: process.env.PERCY_TOKEN,
629
server: false
630
});
631
632
await percy.start();
633
634
// Take a snapshot
635
await percy.snapshot({
636
name: "Homepage",
637
url: "http://localhost:3000",
638
widths: [1280, 768]
639
});
640
641
await percy.stop();
642
```
643
644
### Advanced Configuration
645
646
```javascript
647
import { load, validate, migrate } from "@percy/config";
648
649
// Load configuration with validation
650
const config = await load({
651
path: '.percy.yml',
652
search: true
653
});
654
655
// Validate custom configuration
656
const result = validate(config);
657
if (!result.valid) {
658
console.error('Config errors:', result.errors);
659
}
660
661
// Migrate old configuration
662
const migratedConfig = migrate(oldConfig);
663
```
664
665
### Custom CI Integration
666
667
```javascript
668
import PercyEnv from "@percy/env";
669
import PercyClient from "@percy/client";
670
671
const env = new PercyEnv();
672
const client = new PercyClient({ token: process.env.PERCY_TOKEN });
673
674
if (env.ci) {
675
const build = await client.createBuild({
676
branch: env.branch,
677
commit: env.commit,
678
pullRequest: env.pullRequest
679
});
680
681
console.log(`Created build: ${build.url}`);
682
}
683
```
684
685
### SDK Development
686
687
```javascript
688
import { isPercyEnabled, postSnapshot, logger } from "@percy/sdk-utils";
689
690
async function takeScreenshot(name, options = {}) {
691
if (!isPercyEnabled()) {
692
logger.info("Percy disabled, skipping screenshot");
693
return;
694
}
695
696
try {
697
await postSnapshot(name, {
698
url: options.url,
699
widths: options.widths || [1280]
700
});
701
702
logger.info(`Snapshot taken: ${name}`);
703
} catch (error) {
704
logger.error("Snapshot failed", error);
705
}
706
}
707
```