webguardx 0.1.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.
@@ -0,0 +1,658 @@
1
+ import { z } from 'zod';
2
+ import { Page, BrowserContext, Response } from 'playwright';
3
+
4
+ declare const AuthSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
5
+ method: z.ZodLiteral<"api-login">;
6
+ loginUrl: z.ZodString;
7
+ payload: z.ZodRecord<z.ZodString, z.ZodString>;
8
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ method: "api-login";
11
+ loginUrl: string;
12
+ payload: Record<string, string>;
13
+ headers?: Record<string, string> | undefined;
14
+ }, {
15
+ method: "api-login";
16
+ loginUrl: string;
17
+ payload: Record<string, string>;
18
+ headers?: Record<string, string> | undefined;
19
+ }>, z.ZodObject<{
20
+ method: z.ZodLiteral<"form-login">;
21
+ loginUrl: z.ZodString;
22
+ fields: z.ZodArray<z.ZodObject<{
23
+ selector: z.ZodString;
24
+ value: z.ZodString;
25
+ }, "strip", z.ZodTypeAny, {
26
+ value: string;
27
+ selector: string;
28
+ }, {
29
+ value: string;
30
+ selector: string;
31
+ }>, "many">;
32
+ submitSelector: z.ZodString;
33
+ waitAfterLogin: z.ZodOptional<z.ZodString>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ method: "form-login";
36
+ loginUrl: string;
37
+ fields: {
38
+ value: string;
39
+ selector: string;
40
+ }[];
41
+ submitSelector: string;
42
+ waitAfterLogin?: string | undefined;
43
+ }, {
44
+ method: "form-login";
45
+ loginUrl: string;
46
+ fields: {
47
+ value: string;
48
+ selector: string;
49
+ }[];
50
+ submitSelector: string;
51
+ waitAfterLogin?: string | undefined;
52
+ }>, z.ZodObject<{
53
+ method: z.ZodLiteral<"cookie">;
54
+ cookies: z.ZodArray<z.ZodObject<{
55
+ name: z.ZodString;
56
+ value: z.ZodString;
57
+ domain: z.ZodString;
58
+ path: z.ZodDefault<z.ZodString>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ value: string;
61
+ path: string;
62
+ name: string;
63
+ domain: string;
64
+ }, {
65
+ value: string;
66
+ name: string;
67
+ domain: string;
68
+ path?: string | undefined;
69
+ }>, "many">;
70
+ }, "strip", z.ZodTypeAny, {
71
+ method: "cookie";
72
+ cookies: {
73
+ value: string;
74
+ path: string;
75
+ name: string;
76
+ domain: string;
77
+ }[];
78
+ }, {
79
+ method: "cookie";
80
+ cookies: {
81
+ value: string;
82
+ name: string;
83
+ domain: string;
84
+ path?: string | undefined;
85
+ }[];
86
+ }>, z.ZodObject<{
87
+ method: z.ZodLiteral<"bearer-token">;
88
+ token: z.ZodString;
89
+ }, "strip", z.ZodTypeAny, {
90
+ method: "bearer-token";
91
+ token: string;
92
+ }, {
93
+ method: "bearer-token";
94
+ token: string;
95
+ }>, z.ZodObject<{
96
+ method: z.ZodLiteral<"none">;
97
+ }, "strip", z.ZodTypeAny, {
98
+ method: "none";
99
+ }, {
100
+ method: "none";
101
+ }>]>;
102
+ declare const PageSchema: z.ZodObject<{
103
+ name: z.ZodString;
104
+ path: z.ZodString;
105
+ expectedStatus: z.ZodDefault<z.ZodNumber>;
106
+ skipAudits: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
107
+ }, "strip", z.ZodTypeAny, {
108
+ path: string;
109
+ name: string;
110
+ expectedStatus: number;
111
+ skipAudits?: string[] | undefined;
112
+ }, {
113
+ path: string;
114
+ name: string;
115
+ expectedStatus?: number | undefined;
116
+ skipAudits?: string[] | undefined;
117
+ }>;
118
+ declare const WebguardConfigSchema: z.ZodObject<{
119
+ baseURL: z.ZodString;
120
+ pages: z.ZodArray<z.ZodObject<{
121
+ name: z.ZodString;
122
+ path: z.ZodString;
123
+ expectedStatus: z.ZodDefault<z.ZodNumber>;
124
+ skipAudits: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
125
+ }, "strip", z.ZodTypeAny, {
126
+ path: string;
127
+ name: string;
128
+ expectedStatus: number;
129
+ skipAudits?: string[] | undefined;
130
+ }, {
131
+ path: string;
132
+ name: string;
133
+ expectedStatus?: number | undefined;
134
+ skipAudits?: string[] | undefined;
135
+ }>, "many">;
136
+ auth: z.ZodDefault<z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
137
+ method: z.ZodLiteral<"api-login">;
138
+ loginUrl: z.ZodString;
139
+ payload: z.ZodRecord<z.ZodString, z.ZodString>;
140
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
141
+ }, "strip", z.ZodTypeAny, {
142
+ method: "api-login";
143
+ loginUrl: string;
144
+ payload: Record<string, string>;
145
+ headers?: Record<string, string> | undefined;
146
+ }, {
147
+ method: "api-login";
148
+ loginUrl: string;
149
+ payload: Record<string, string>;
150
+ headers?: Record<string, string> | undefined;
151
+ }>, z.ZodObject<{
152
+ method: z.ZodLiteral<"form-login">;
153
+ loginUrl: z.ZodString;
154
+ fields: z.ZodArray<z.ZodObject<{
155
+ selector: z.ZodString;
156
+ value: z.ZodString;
157
+ }, "strip", z.ZodTypeAny, {
158
+ value: string;
159
+ selector: string;
160
+ }, {
161
+ value: string;
162
+ selector: string;
163
+ }>, "many">;
164
+ submitSelector: z.ZodString;
165
+ waitAfterLogin: z.ZodOptional<z.ZodString>;
166
+ }, "strip", z.ZodTypeAny, {
167
+ method: "form-login";
168
+ loginUrl: string;
169
+ fields: {
170
+ value: string;
171
+ selector: string;
172
+ }[];
173
+ submitSelector: string;
174
+ waitAfterLogin?: string | undefined;
175
+ }, {
176
+ method: "form-login";
177
+ loginUrl: string;
178
+ fields: {
179
+ value: string;
180
+ selector: string;
181
+ }[];
182
+ submitSelector: string;
183
+ waitAfterLogin?: string | undefined;
184
+ }>, z.ZodObject<{
185
+ method: z.ZodLiteral<"cookie">;
186
+ cookies: z.ZodArray<z.ZodObject<{
187
+ name: z.ZodString;
188
+ value: z.ZodString;
189
+ domain: z.ZodString;
190
+ path: z.ZodDefault<z.ZodString>;
191
+ }, "strip", z.ZodTypeAny, {
192
+ value: string;
193
+ path: string;
194
+ name: string;
195
+ domain: string;
196
+ }, {
197
+ value: string;
198
+ name: string;
199
+ domain: string;
200
+ path?: string | undefined;
201
+ }>, "many">;
202
+ }, "strip", z.ZodTypeAny, {
203
+ method: "cookie";
204
+ cookies: {
205
+ value: string;
206
+ path: string;
207
+ name: string;
208
+ domain: string;
209
+ }[];
210
+ }, {
211
+ method: "cookie";
212
+ cookies: {
213
+ value: string;
214
+ name: string;
215
+ domain: string;
216
+ path?: string | undefined;
217
+ }[];
218
+ }>, z.ZodObject<{
219
+ method: z.ZodLiteral<"bearer-token">;
220
+ token: z.ZodString;
221
+ }, "strip", z.ZodTypeAny, {
222
+ method: "bearer-token";
223
+ token: string;
224
+ }, {
225
+ method: "bearer-token";
226
+ token: string;
227
+ }>, z.ZodObject<{
228
+ method: z.ZodLiteral<"none">;
229
+ }, "strip", z.ZodTypeAny, {
230
+ method: "none";
231
+ }, {
232
+ method: "none";
233
+ }>]>>;
234
+ audits: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
235
+ customAudits: z.ZodDefault<z.ZodArray<z.ZodAny, "many">>;
236
+ plugins: z.ZodDefault<z.ZodArray<z.ZodAny, "many">>;
237
+ wcagTags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
238
+ lighthouseThresholds: z.ZodDefault<z.ZodObject<{
239
+ performance: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
240
+ accessibility: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
241
+ bestPractices: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
242
+ seo: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
243
+ }, "strip", z.ZodTypeAny, {
244
+ performance?: number | undefined;
245
+ accessibility?: number | undefined;
246
+ bestPractices?: number | undefined;
247
+ seo?: number | undefined;
248
+ }, {
249
+ performance?: number | undefined;
250
+ accessibility?: number | undefined;
251
+ bestPractices?: number | undefined;
252
+ seo?: number | undefined;
253
+ }>>;
254
+ retry: z.ZodDefault<z.ZodObject<{
255
+ maxRetries: z.ZodDefault<z.ZodNumber>;
256
+ delayMs: z.ZodDefault<z.ZodNumber>;
257
+ }, "strip", z.ZodTypeAny, {
258
+ maxRetries: number;
259
+ delayMs: number;
260
+ }, {
261
+ maxRetries?: number | undefined;
262
+ delayMs?: number | undefined;
263
+ }>>;
264
+ runner: z.ZodDefault<z.ZodObject<{
265
+ concurrency: z.ZodDefault<z.ZodNumber>;
266
+ failFast: z.ZodDefault<z.ZodBoolean>;
267
+ }, "strip", z.ZodTypeAny, {
268
+ concurrency: number;
269
+ failFast: boolean;
270
+ }, {
271
+ concurrency?: number | undefined;
272
+ failFast?: boolean | undefined;
273
+ }>>;
274
+ browser: z.ZodDefault<z.ZodObject<{
275
+ headless: z.ZodDefault<z.ZodBoolean>;
276
+ timeout: z.ZodDefault<z.ZodNumber>;
277
+ viewport: z.ZodDefault<z.ZodObject<{
278
+ width: z.ZodDefault<z.ZodNumber>;
279
+ height: z.ZodDefault<z.ZodNumber>;
280
+ }, "strip", z.ZodTypeAny, {
281
+ width: number;
282
+ height: number;
283
+ }, {
284
+ width?: number | undefined;
285
+ height?: number | undefined;
286
+ }>>;
287
+ }, "strip", z.ZodTypeAny, {
288
+ headless: boolean;
289
+ timeout: number;
290
+ viewport: {
291
+ width: number;
292
+ height: number;
293
+ };
294
+ }, {
295
+ headless?: boolean | undefined;
296
+ timeout?: number | undefined;
297
+ viewport?: {
298
+ width?: number | undefined;
299
+ height?: number | undefined;
300
+ } | undefined;
301
+ }>>;
302
+ output: z.ZodDefault<z.ZodObject<{
303
+ dir: z.ZodDefault<z.ZodString>;
304
+ formats: z.ZodDefault<z.ZodArray<z.ZodEnum<["terminal", "html", "json", "junit"]>, "many">>;
305
+ screenshots: z.ZodDefault<z.ZodBoolean>;
306
+ screenshotOnFailOnly: z.ZodDefault<z.ZodBoolean>;
307
+ }, "strip", z.ZodTypeAny, {
308
+ dir: string;
309
+ formats: ("terminal" | "html" | "json" | "junit")[];
310
+ screenshots: boolean;
311
+ screenshotOnFailOnly: boolean;
312
+ }, {
313
+ dir?: string | undefined;
314
+ formats?: ("terminal" | "html" | "json" | "junit")[] | undefined;
315
+ screenshots?: boolean | undefined;
316
+ screenshotOnFailOnly?: boolean | undefined;
317
+ }>>;
318
+ baseline: z.ZodDefault<z.ZodObject<{
319
+ enabled: z.ZodDefault<z.ZodBoolean>;
320
+ updateOnPass: z.ZodDefault<z.ZodBoolean>;
321
+ }, "strip", z.ZodTypeAny, {
322
+ enabled: boolean;
323
+ updateOnPass: boolean;
324
+ }, {
325
+ enabled?: boolean | undefined;
326
+ updateOnPass?: boolean | undefined;
327
+ }>>;
328
+ notifications: z.ZodDefault<z.ZodArray<z.ZodAny, "many">>;
329
+ }, "strip", z.ZodTypeAny, {
330
+ baseURL: string;
331
+ pages: {
332
+ path: string;
333
+ name: string;
334
+ expectedStatus: number;
335
+ skipAudits?: string[] | undefined;
336
+ }[];
337
+ auth: {
338
+ method: "api-login";
339
+ loginUrl: string;
340
+ payload: Record<string, string>;
341
+ headers?: Record<string, string> | undefined;
342
+ } | {
343
+ method: "form-login";
344
+ loginUrl: string;
345
+ fields: {
346
+ value: string;
347
+ selector: string;
348
+ }[];
349
+ submitSelector: string;
350
+ waitAfterLogin?: string | undefined;
351
+ } | {
352
+ method: "cookie";
353
+ cookies: {
354
+ value: string;
355
+ path: string;
356
+ name: string;
357
+ domain: string;
358
+ }[];
359
+ } | {
360
+ method: "bearer-token";
361
+ token: string;
362
+ } | {
363
+ method: "none";
364
+ };
365
+ audits: Record<string, boolean>;
366
+ customAudits: any[];
367
+ plugins: any[];
368
+ wcagTags: string[];
369
+ lighthouseThresholds: {
370
+ performance?: number | undefined;
371
+ accessibility?: number | undefined;
372
+ bestPractices?: number | undefined;
373
+ seo?: number | undefined;
374
+ };
375
+ retry: {
376
+ maxRetries: number;
377
+ delayMs: number;
378
+ };
379
+ runner: {
380
+ concurrency: number;
381
+ failFast: boolean;
382
+ };
383
+ browser: {
384
+ headless: boolean;
385
+ timeout: number;
386
+ viewport: {
387
+ width: number;
388
+ height: number;
389
+ };
390
+ };
391
+ output: {
392
+ dir: string;
393
+ formats: ("terminal" | "html" | "json" | "junit")[];
394
+ screenshots: boolean;
395
+ screenshotOnFailOnly: boolean;
396
+ };
397
+ baseline: {
398
+ enabled: boolean;
399
+ updateOnPass: boolean;
400
+ };
401
+ notifications: any[];
402
+ }, {
403
+ baseURL: string;
404
+ pages: {
405
+ path: string;
406
+ name: string;
407
+ expectedStatus?: number | undefined;
408
+ skipAudits?: string[] | undefined;
409
+ }[];
410
+ auth?: {
411
+ method: "api-login";
412
+ loginUrl: string;
413
+ payload: Record<string, string>;
414
+ headers?: Record<string, string> | undefined;
415
+ } | {
416
+ method: "form-login";
417
+ loginUrl: string;
418
+ fields: {
419
+ value: string;
420
+ selector: string;
421
+ }[];
422
+ submitSelector: string;
423
+ waitAfterLogin?: string | undefined;
424
+ } | {
425
+ method: "cookie";
426
+ cookies: {
427
+ value: string;
428
+ name: string;
429
+ domain: string;
430
+ path?: string | undefined;
431
+ }[];
432
+ } | {
433
+ method: "bearer-token";
434
+ token: string;
435
+ } | {
436
+ method: "none";
437
+ } | undefined;
438
+ audits?: Record<string, boolean> | undefined;
439
+ customAudits?: any[] | undefined;
440
+ plugins?: any[] | undefined;
441
+ wcagTags?: string[] | undefined;
442
+ lighthouseThresholds?: {
443
+ performance?: number | undefined;
444
+ accessibility?: number | undefined;
445
+ bestPractices?: number | undefined;
446
+ seo?: number | undefined;
447
+ } | undefined;
448
+ retry?: {
449
+ maxRetries?: number | undefined;
450
+ delayMs?: number | undefined;
451
+ } | undefined;
452
+ runner?: {
453
+ concurrency?: number | undefined;
454
+ failFast?: boolean | undefined;
455
+ } | undefined;
456
+ browser?: {
457
+ headless?: boolean | undefined;
458
+ timeout?: number | undefined;
459
+ viewport?: {
460
+ width?: number | undefined;
461
+ height?: number | undefined;
462
+ } | undefined;
463
+ } | undefined;
464
+ output?: {
465
+ dir?: string | undefined;
466
+ formats?: ("terminal" | "html" | "json" | "junit")[] | undefined;
467
+ screenshots?: boolean | undefined;
468
+ screenshotOnFailOnly?: boolean | undefined;
469
+ } | undefined;
470
+ baseline?: {
471
+ enabled?: boolean | undefined;
472
+ updateOnPass?: boolean | undefined;
473
+ } | undefined;
474
+ notifications?: any[] | undefined;
475
+ }>;
476
+ type WebguardConfig = z.infer<typeof WebguardConfigSchema>;
477
+ type PageEntry = z.infer<typeof PageSchema>;
478
+ type AuthConfig = z.infer<typeof AuthSchema>;
479
+
480
+ type WebguardConfigInput = z.input<typeof WebguardConfigSchema>;
481
+ declare function defineConfig(config: WebguardConfigInput): WebguardConfig;
482
+
483
+ declare function loadConfig(configPath?: string): Promise<WebguardConfig>;
484
+
485
+ type AuditSeverity = "pass" | "warning" | "fail" | "skip";
486
+ interface AuditResult {
487
+ audit: string;
488
+ page: string;
489
+ passed: boolean;
490
+ severity: AuditSeverity;
491
+ message: string;
492
+ details?: unknown;
493
+ duration?: number;
494
+ }
495
+ interface PageResult {
496
+ page: string;
497
+ path: string;
498
+ url: string;
499
+ audits: AuditResult[];
500
+ screenshotPath?: string;
501
+ duration: number;
502
+ }
503
+ interface RunResult {
504
+ timestamp: string;
505
+ config: {
506
+ baseURL: string;
507
+ totalPages: number;
508
+ auditsEnabled: string[];
509
+ };
510
+ pages: PageResult[];
511
+ summary: {
512
+ totalAudits: number;
513
+ passed: number;
514
+ failed: number;
515
+ warnings: number;
516
+ skipped: number;
517
+ duration: number;
518
+ };
519
+ }
520
+
521
+ interface RunOptions {
522
+ headed?: boolean;
523
+ pagesFilter?: string[];
524
+ auditsFilter?: string[];
525
+ }
526
+ declare function run(config: WebguardConfig, options?: RunOptions): Promise<RunResult>;
527
+
528
+ interface AuditContext {
529
+ page: Page;
530
+ browserContext: BrowserContext;
531
+ pageEntry: PageEntry;
532
+ config: WebguardConfig;
533
+ runDir: string;
534
+ navigationResponse: Response | null;
535
+ consoleMessages: Array<{
536
+ type: string;
537
+ text: string;
538
+ }>;
539
+ }
540
+ interface Audit {
541
+ name: string;
542
+ description: string;
543
+ run(ctx: AuditContext): Promise<AuditResult>;
544
+ }
545
+
546
+ interface WebguardPlugin {
547
+ name: string;
548
+ audits?: Audit[];
549
+ reporters?: Reporter[];
550
+ hooks?: Partial<LifecycleHooks>;
551
+ }
552
+ interface Reporter {
553
+ name: string;
554
+ run(result: RunResult, runDir: string, config: WebguardConfig): Promise<void> | void;
555
+ }
556
+ interface LifecycleHooks {
557
+ beforeAll(ctx: {
558
+ config: WebguardConfig;
559
+ browserContext: BrowserContext;
560
+ }): Promise<void> | void;
561
+ afterAll(ctx: {
562
+ config: WebguardConfig;
563
+ result: RunResult;
564
+ }): Promise<void> | void;
565
+ beforePage(ctx: {
566
+ config: WebguardConfig;
567
+ pageEntry: PageEntry;
568
+ page: Page;
569
+ }): Promise<void> | void;
570
+ afterPage(ctx: {
571
+ config: WebguardConfig;
572
+ pageEntry: PageEntry;
573
+ pageResult: PageResult;
574
+ }): Promise<void> | void;
575
+ beforeAudit(ctx: {
576
+ audit: Audit;
577
+ auditContext: AuditContext;
578
+ }): Promise<void> | void;
579
+ afterAudit(ctx: {
580
+ audit: Audit;
581
+ result: AuditResult;
582
+ }): Promise<void> | void;
583
+ }
584
+
585
+ declare class PluginRegistry {
586
+ private plugins;
587
+ register(plugin: WebguardPlugin): void;
588
+ getPluginAudits(): Audit[];
589
+ getPluginReporters(): Reporter[];
590
+ runHook<K extends keyof LifecycleHooks>(hookName: K, ctx: Parameters<LifecycleHooks[K]>[0]): Promise<void>;
591
+ }
592
+
593
+ declare function loadPlugins(pluginEntries: Array<WebguardPlugin | string>): Promise<WebguardPlugin[]>;
594
+
595
+ interface NotificationChannel {
596
+ name: string;
597
+ send(result: RunResult, config: WebguardConfig): Promise<void>;
598
+ }
599
+
600
+ interface WebhookOptions {
601
+ method?: "POST" | "PUT";
602
+ headers?: Record<string, string>;
603
+ onlyOnFailure?: boolean;
604
+ }
605
+ declare function createWebhookNotifier(url: string, options?: WebhookOptions): NotificationChannel;
606
+
607
+ interface SlackOptions {
608
+ channel?: string;
609
+ onlyOnFailure?: boolean;
610
+ }
611
+ declare function createSlackNotifier(webhookUrl: string, options?: SlackOptions): NotificationChannel;
612
+
613
+ declare function sendNotifications(channels: NotificationChannel[], result: RunResult, config: WebguardConfig): Promise<void>;
614
+
615
+ interface BaselineComparison {
616
+ baselineTimestamp: string;
617
+ currentTimestamp: string;
618
+ changes: AuditChange[];
619
+ summary: {
620
+ regressions: number;
621
+ improvements: number;
622
+ unchanged: number;
623
+ newAudits: number;
624
+ removedAudits: number;
625
+ };
626
+ }
627
+ interface AuditChange {
628
+ page: string;
629
+ audit: string;
630
+ type: "regression" | "improvement" | "unchanged" | "new" | "removed";
631
+ baseline?: {
632
+ severity: string;
633
+ message: string;
634
+ };
635
+ current?: {
636
+ severity: string;
637
+ message: string;
638
+ };
639
+ }
640
+
641
+ declare function saveBaseline(result: RunResult, outputDir: string): void;
642
+ declare function loadBaseline(outputDir: string): RunResult | null;
643
+
644
+ declare function compareRuns(baseline: RunResult, current: RunResult): BaselineComparison;
645
+
646
+ declare const HttpStatusAudit: Audit;
647
+
648
+ declare const ContentVisibilityAudit: Audit;
649
+
650
+ declare const AccessibilityAudit: Audit;
651
+
652
+ declare const LighthouseAudit: Audit;
653
+
654
+ declare const BrokenLinksAudit: Audit;
655
+
656
+ declare const ConsoleErrorsAudit: Audit;
657
+
658
+ export { AccessibilityAudit, type Audit, type AuditChange, type AuditContext, type AuditResult, type AuditSeverity, type AuthConfig, type BaselineComparison, BrokenLinksAudit, ConsoleErrorsAudit, ContentVisibilityAudit, HttpStatusAudit, type LifecycleHooks, LighthouseAudit, type NotificationChannel, type PageEntry, type PageResult, PluginRegistry, type Reporter, type RunOptions, type RunResult, type WebguardConfig, type WebguardPlugin, compareRuns, createSlackNotifier, createWebhookNotifier, defineConfig, loadBaseline, loadConfig, loadPlugins, run, saveBaseline, sendNotifications };