@vizzly-testing/cli 0.35.3-beta.0 → 0.35.3-beta.1
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 +7 -4
- package/dist/api/client.js +20 -5
- package/dist/api/core.js +17 -3
- package/dist/cli.js +11 -51
- package/dist/commands/baselines.js +0 -7
- package/dist/commands/config-cmd.js +0 -7
- package/dist/commands/context.js +248 -306
- package/dist/commands/doctor.js +0 -8
- package/dist/commands/init.js +7 -12
- package/dist/commands/login.js +0 -13
- package/dist/commands/logout.js +0 -12
- package/dist/commands/orgs.js +0 -9
- package/dist/commands/whoami.js +0 -12
- package/dist/context/local-workspace-provider.js +276 -30
- package/dist/plugin-loader.js +1 -1
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +17 -17
- package/dist/server/routers/events.js +4 -4
- package/dist/utils/visual-context-normalizers.js +12 -3
- package/package.json +11 -10
- package/skills/vizzly/references/cli-context.md +5 -1
- package/dist/config/index.js +0 -13
- package/dist/project/index.js +0 -13
- package/dist/tdd/index.js +0 -19
- package/dist/tdd/services/baseline-downloader.js +0 -151
- package/dist/tdd/services/hotspot-service.js +0 -58
- package/dist/utils/environment.js +0 -109
package/dist/commands/doctor.js
CHANGED
|
@@ -264,12 +264,4 @@ export async function doctorCommand(options = {}, globalOptions = {}, deps = {})
|
|
|
264
264
|
output.cleanup();
|
|
265
265
|
if (hasErrors) exit(1);
|
|
266
266
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Validate doctor options (no specific validation needed)
|
|
271
|
-
* @param {Object} options - Command options
|
|
272
|
-
*/
|
|
273
|
-
export function validateDoctorOptions() {
|
|
274
|
-
return []; // No specific validation for now
|
|
275
267
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -37,7 +37,7 @@ function configureOutput(output, options) {
|
|
|
37
37
|
export function getInitConfigPath(cwd = process.cwd()) {
|
|
38
38
|
return path.join(cwd, 'vizzly.config.js');
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
function getPackagedAgentSkillPath(baseDir = commandDir) {
|
|
41
41
|
return path.resolve(baseDir, '..', '..', 'skills', 'vizzly');
|
|
42
42
|
}
|
|
43
43
|
export function getProjectAgentSkillPath(cwd = process.cwd()) {
|
|
@@ -54,10 +54,10 @@ export async function fileExists(filePath, access = fs.access) {
|
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
function getPluginsWithConfig(plugins = []) {
|
|
58
58
|
return plugins.filter(plugin => plugin.configSchema);
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
function getPluginConfigNames(plugins = []) {
|
|
61
61
|
return getPluginsWithConfig(plugins).map(plugin => plugin.name);
|
|
62
62
|
}
|
|
63
63
|
function formatObjectKey(key) {
|
|
@@ -115,7 +115,7 @@ export function formatPluginConfig(plugin, output = null) {
|
|
|
115
115
|
return '';
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
function generatePluginConfigs(plugins = [], output = null) {
|
|
119
119
|
let sections = [];
|
|
120
120
|
for (let plugin of plugins) {
|
|
121
121
|
if (plugin.configSchema) {
|
|
@@ -212,7 +212,7 @@ async function shouldInstallAgentSkill(options, deps) {
|
|
|
212
212
|
}
|
|
213
213
|
let agentGuidanceStart = '<!-- vizzly-agent-guidance -->';
|
|
214
214
|
let agentGuidanceEnd = '<!-- /vizzly-agent-guidance -->';
|
|
215
|
-
|
|
215
|
+
function createAgentGuidanceContent() {
|
|
216
216
|
return `${agentGuidanceStart}
|
|
217
217
|
## Visual Testing With Vizzly
|
|
218
218
|
|
|
@@ -232,7 +232,7 @@ function appendSection(content, section) {
|
|
|
232
232
|
}
|
|
233
233
|
return `${content.replace(/\s*$/, '')}\n\n${section}`;
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
async function upsertProjectAgentGuidance({
|
|
236
236
|
cwd,
|
|
237
237
|
access = fs.access,
|
|
238
238
|
readFile = fs.readFile,
|
|
@@ -362,16 +362,11 @@ async function loadInitPlugins(options, deps) {
|
|
|
362
362
|
}
|
|
363
363
|
try {
|
|
364
364
|
let config = await deps.loadConfig(options.config, {});
|
|
365
|
-
return await deps.loadPlugins(options.config, config
|
|
365
|
+
return await deps.loadPlugins(options.config, config);
|
|
366
366
|
} catch {
|
|
367
367
|
return [];
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
-
|
|
371
|
-
// Export factory function for CLI
|
|
372
|
-
export function createInitCommand(options) {
|
|
373
|
-
return () => init(options);
|
|
374
|
-
}
|
|
375
370
|
export async function init(options = {}, deps = {}) {
|
|
376
371
|
let resolvedDeps = createInitDeps(deps);
|
|
377
372
|
configureOutput(resolvedDeps.output, options);
|
package/dist/commands/login.js
CHANGED
|
@@ -236,17 +236,4 @@ export async function loginCommand(options = {}, globalOptions = {}, deps = {})
|
|
|
236
236
|
exit(1);
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Validate login options
|
|
243
|
-
* @param {Object} options - Command options
|
|
244
|
-
*/
|
|
245
|
-
export function validateLoginOptions() {
|
|
246
|
-
let errors = [];
|
|
247
|
-
|
|
248
|
-
// No specific validation needed for login command
|
|
249
|
-
// OAuth device flow handles everything via browser
|
|
250
|
-
|
|
251
|
-
return errors;
|
|
252
239
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -70,16 +70,4 @@ export async function logoutCommand(options = {}, globalOptions = {}, deps = {})
|
|
|
70
70
|
output.cleanup();
|
|
71
71
|
exit(1);
|
|
72
72
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validate logout options
|
|
77
|
-
* @param {Object} options - Command options
|
|
78
|
-
*/
|
|
79
|
-
export function validateLogoutOptions() {
|
|
80
|
-
let errors = [];
|
|
81
|
-
|
|
82
|
-
// No specific validation needed for logout command
|
|
83
|
-
|
|
84
|
-
return errors;
|
|
85
73
|
}
|
package/dist/commands/orgs.js
CHANGED
|
@@ -82,13 +82,4 @@ export async function orgsCommand(_options = {}, globalOptions = {}, deps = {})
|
|
|
82
82
|
output.cleanup();
|
|
83
83
|
exit(1);
|
|
84
84
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Validate orgs options
|
|
89
|
-
* @param {Object} _options - Command options
|
|
90
|
-
* @returns {string[]} Validation errors
|
|
91
|
-
*/
|
|
92
|
-
export function validateOrgsOptions(_options = {}) {
|
|
93
|
-
return [];
|
|
94
85
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -182,16 +182,4 @@ export async function whoamiCommand(options = {}, globalOptions = {}, deps = {})
|
|
|
182
182
|
exit(1);
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Validate whoami options
|
|
189
|
-
* @param {Object} options - Command options
|
|
190
|
-
*/
|
|
191
|
-
export function validateWhoamiOptions() {
|
|
192
|
-
let errors = [];
|
|
193
|
-
|
|
194
|
-
// No specific validation needed for whoami command
|
|
195
|
-
|
|
196
|
-
return errors;
|
|
197
185
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { existsSync as defaultExistsSync, readFileSync } from 'node:fs';
|
|
2
3
|
import { basename, isAbsolute, join } from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
@@ -204,31 +205,6 @@ function buildReviewState(build, reviewSummary) {
|
|
|
204
205
|
unresolved_comments: 0
|
|
205
206
|
};
|
|
206
207
|
}
|
|
207
|
-
function mapLocalScreenshot(snapshot, comparison) {
|
|
208
|
-
let mapped = mapLocalComparison(snapshot, comparison);
|
|
209
|
-
let baselineBuildId = snapshot.baselineMetadata?.buildId || null;
|
|
210
|
-
return {
|
|
211
|
-
id: mapped.screenshot.id,
|
|
212
|
-
name: mapped.screenshot.name,
|
|
213
|
-
browser: mapped.screenshot.browser,
|
|
214
|
-
viewport: {
|
|
215
|
-
width: mapped.screenshot.viewport_width,
|
|
216
|
-
height: mapped.screenshot.viewport_height
|
|
217
|
-
},
|
|
218
|
-
url: mapped.screenshot.original_url,
|
|
219
|
-
baseline: mapped.baseline ? {
|
|
220
|
-
id: mapped.baseline.id,
|
|
221
|
-
build_id: baselineBuildId,
|
|
222
|
-
name: mapped.baseline.name,
|
|
223
|
-
browser: mapped.baseline.browser,
|
|
224
|
-
viewport: {
|
|
225
|
-
width: mapped.baseline.viewport_width,
|
|
226
|
-
height: mapped.baseline.viewport_height
|
|
227
|
-
},
|
|
228
|
-
url: mapped.baseline.original_url
|
|
229
|
-
} : null
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
208
|
function mapLocalComparison(snapshot, comparison) {
|
|
233
209
|
let details = snapshot.comparisonDetails[comparison.id] || {};
|
|
234
210
|
let comparisonName = comparison.originalName || comparison.name;
|
|
@@ -306,6 +282,141 @@ function mapLocalComparison(snapshot, comparison) {
|
|
|
306
282
|
}
|
|
307
283
|
};
|
|
308
284
|
}
|
|
285
|
+
function projectLocalScreenshot(screenshot, baseline) {
|
|
286
|
+
if (!screenshot) return null;
|
|
287
|
+
return {
|
|
288
|
+
id: screenshot.id,
|
|
289
|
+
name: screenshot.name,
|
|
290
|
+
browser: screenshot.browser,
|
|
291
|
+
viewport: {
|
|
292
|
+
width: screenshot.viewport_width,
|
|
293
|
+
height: screenshot.viewport_height
|
|
294
|
+
},
|
|
295
|
+
url: screenshot.original_url,
|
|
296
|
+
baseline: baseline ? {
|
|
297
|
+
id: baseline.id,
|
|
298
|
+
build_id: baseline.build_id,
|
|
299
|
+
name: baseline.name,
|
|
300
|
+
browser: baseline.browser,
|
|
301
|
+
viewport: {
|
|
302
|
+
width: baseline.viewport_width,
|
|
303
|
+
height: baseline.viewport_height
|
|
304
|
+
},
|
|
305
|
+
url: baseline.original_url
|
|
306
|
+
} : null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function projectLocalDiff(diff, includeDiffs) {
|
|
310
|
+
let projected = {
|
|
311
|
+
percentage: diff?.percentage ?? null,
|
|
312
|
+
changed_pixels: diff?.changed_pixels ?? null,
|
|
313
|
+
total_pixels: diff?.total_pixels ?? null,
|
|
314
|
+
threshold: diff?.threshold ?? null,
|
|
315
|
+
image_url: diff?.image_url ?? null,
|
|
316
|
+
fingerprint_hash: diff?.fingerprint_hash ?? null,
|
|
317
|
+
region_count: Array.isArray(diff?.regions) ? diff.regions.length : null
|
|
318
|
+
};
|
|
319
|
+
if (includeDiffs) {
|
|
320
|
+
projected.regions = diff?.regions || [];
|
|
321
|
+
projected.cluster_metadata = diff?.cluster_metadata ?? null;
|
|
322
|
+
projected.fingerprint_data = diff?.fingerprint_data ?? null;
|
|
323
|
+
projected.diff_lines = diff?.diff_lines ?? [];
|
|
324
|
+
}
|
|
325
|
+
return projected;
|
|
326
|
+
}
|
|
327
|
+
function projectLocalEvidence(comparison, includeDiffs) {
|
|
328
|
+
return {
|
|
329
|
+
type: 'comparison',
|
|
330
|
+
id: comparison.id,
|
|
331
|
+
screenshot_name: comparison.screenshot_name,
|
|
332
|
+
status: comparison.status,
|
|
333
|
+
result: comparison.result,
|
|
334
|
+
approval_status: comparison.approval_status,
|
|
335
|
+
needs_review: comparison.needs_review,
|
|
336
|
+
build_id: comparison.build_id,
|
|
337
|
+
build_name: comparison.build_name,
|
|
338
|
+
build_branch: comparison.build_branch,
|
|
339
|
+
build_commit_sha: comparison.build_commit_sha,
|
|
340
|
+
build_created_at: comparison.build_created_at,
|
|
341
|
+
screenshot: projectLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
342
|
+
diff: projectLocalDiff(comparison.diff, includeDiffs)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function projectLocalFocusedComparison(comparison, includeDiffs) {
|
|
346
|
+
if (includeDiffs) return comparison;
|
|
347
|
+
let analysis = comparison.analysis || {};
|
|
348
|
+
let {
|
|
349
|
+
diff_regions: _diffRegions,
|
|
350
|
+
cluster_metadata: _clusterMetadata,
|
|
351
|
+
fingerprint_data: _fingerprintData,
|
|
352
|
+
diff_lines: _diffLines,
|
|
353
|
+
...summaryAnalysis
|
|
354
|
+
} = analysis;
|
|
355
|
+
return {
|
|
356
|
+
...comparison,
|
|
357
|
+
diff: projectLocalDiff(comparison.diff, false),
|
|
358
|
+
analysis: summaryAnalysis
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function projectLocalHistoryItem(comparison) {
|
|
362
|
+
return {
|
|
363
|
+
id: comparison.id,
|
|
364
|
+
screenshot_name: comparison.screenshot_name,
|
|
365
|
+
result: comparison.result,
|
|
366
|
+
status: comparison.status,
|
|
367
|
+
needs_review: comparison.needs_review,
|
|
368
|
+
build_id: comparison.build_id,
|
|
369
|
+
build_name: comparison.build_name,
|
|
370
|
+
build_branch: comparison.build_branch,
|
|
371
|
+
build_created_at: comparison.build_created_at,
|
|
372
|
+
screenshot: projectLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
373
|
+
diff: {
|
|
374
|
+
percentage: comparison.diff?.percentage ?? null,
|
|
375
|
+
fingerprint_hash: comparison.diff?.fingerprint_hash ?? null,
|
|
376
|
+
region_count: Array.isArray(comparison.diff?.regions) ? comparison.diff.regions.length : null
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function compactLocalCollection(items) {
|
|
381
|
+
return {
|
|
382
|
+
total: Array.isArray(items) ? items.length : null,
|
|
383
|
+
included: false,
|
|
384
|
+
details_available: Array.isArray(items) && items.length > 0
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function createLocalDynamicRegions(comparison = null) {
|
|
388
|
+
let confirmedRegions = comparison?.analysis?.confirmed_regions;
|
|
389
|
+
let hotspotAnalysis = comparison?.analysis?.hotspot_analysis;
|
|
390
|
+
return {
|
|
391
|
+
decision: null,
|
|
392
|
+
patterns: compactLocalCollection([]),
|
|
393
|
+
confirmed_regions: compactLocalCollection(confirmedRegions),
|
|
394
|
+
exclusions: {
|
|
395
|
+
total: null,
|
|
396
|
+
included: false,
|
|
397
|
+
details_available: false
|
|
398
|
+
},
|
|
399
|
+
hotspot_analysis: hotspotAnalysis ? {
|
|
400
|
+
total_builds_analyzed: hotspotAnalysis.total_builds_analyzed ?? null,
|
|
401
|
+
confidence: hotspotAnalysis.confidence ?? null,
|
|
402
|
+
confidence_score: hotspotAnalysis.confidence_score ?? null,
|
|
403
|
+
data_source: hotspotAnalysis.data_source ?? null,
|
|
404
|
+
coverage: hotspotAnalysis.coverage ?? null,
|
|
405
|
+
confirmed_region_coverage: hotspotAnalysis.confirmed_region_coverage ?? null
|
|
406
|
+
} : null
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function createLocalSnapshotRevision(snapshot) {
|
|
410
|
+
let revisionInput = {
|
|
411
|
+
serverInfo: snapshot.serverInfo,
|
|
412
|
+
reportData: snapshot.reportData,
|
|
413
|
+
comparisonDetails: snapshot.comparisonDetails,
|
|
414
|
+
baselineMetadata: snapshot.baselineMetadata,
|
|
415
|
+
hotspotFile: snapshot.hotspotFile,
|
|
416
|
+
regionFile: snapshot.regionFile
|
|
417
|
+
};
|
|
418
|
+
return createHash('sha256').update(JSON.stringify(revisionInput)).digest('base64url');
|
|
419
|
+
}
|
|
309
420
|
function buildReviewSummary(comparisons = []) {
|
|
310
421
|
let approved = comparisons.filter(comparison => mapApprovalStatus(comparison.status) === 'approved').length;
|
|
311
422
|
let rejected = comparisons.filter(comparison => mapApprovalStatus(comparison.status) === 'rejected').length;
|
|
@@ -405,17 +516,66 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
405
516
|
} : {})
|
|
406
517
|
};
|
|
407
518
|
}
|
|
408
|
-
function
|
|
519
|
+
function createLocalCursor(resource, target, query, stream, offset, revision) {
|
|
520
|
+
return Buffer.from(JSON.stringify({
|
|
521
|
+
version: 1,
|
|
522
|
+
resource,
|
|
523
|
+
target,
|
|
524
|
+
query,
|
|
525
|
+
stream,
|
|
526
|
+
offset,
|
|
527
|
+
revision
|
|
528
|
+
})).toString('base64url');
|
|
529
|
+
}
|
|
530
|
+
function readLocalCursor(cursor, resource, target, query, revision, streams) {
|
|
531
|
+
if (!cursor) return null;
|
|
532
|
+
try {
|
|
533
|
+
let parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString());
|
|
534
|
+
if (parsed.version !== 1 || parsed.resource !== resource || parsed.target !== target || parsed.query !== query || parsed.revision !== revision || !streams.includes(parsed.stream) || !Number.isInteger(parsed.offset) || parsed.offset < 0) {
|
|
535
|
+
throw new Error('mismatch');
|
|
536
|
+
}
|
|
537
|
+
return parsed;
|
|
538
|
+
} catch {
|
|
539
|
+
throw createLocalWorkspaceError('The local context cursor is invalid or the local results changed. Start again without --cursor.');
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function createLocalPage(items, {
|
|
543
|
+
limit,
|
|
544
|
+
offset,
|
|
545
|
+
resource,
|
|
546
|
+
target,
|
|
547
|
+
query,
|
|
548
|
+
stream,
|
|
549
|
+
revision
|
|
550
|
+
}) {
|
|
551
|
+
if (offset > items.length) {
|
|
552
|
+
throw createLocalWorkspaceError('The local context cursor is invalid or the local results changed. Start again without --cursor.');
|
|
553
|
+
}
|
|
554
|
+
let pageItems = items.slice(offset, offset + limit);
|
|
555
|
+
let nextOffset = offset + pageItems.length;
|
|
556
|
+
let hasMore = nextOffset < items.length;
|
|
557
|
+
return {
|
|
558
|
+
items: pageItems,
|
|
559
|
+
page: {
|
|
560
|
+
limit,
|
|
561
|
+
returned: pageItems.length,
|
|
562
|
+
total: items.length,
|
|
563
|
+
has_more: hasMore,
|
|
564
|
+
next_cursor: hasMore ? createLocalCursor(resource, target, query, stream, nextOffset, revision) : null
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function getBuildContext(buildId, query = {}) {
|
|
409
569
|
let snapshot = loadSnapshot();
|
|
410
570
|
let resolvedBuild = buildBuildSnapshot(snapshot);
|
|
411
571
|
if (!(buildId === 'current' || buildId === 'local' || buildId === resolvedBuild.id)) {
|
|
412
572
|
throw createLocalWorkspaceError(`Local workspace context is only available for the active local build (${resolvedBuild.id})`);
|
|
413
573
|
}
|
|
414
574
|
let mappedComparisons = snapshot.reportData.comparisons.map(comparison => mapLocalComparison(snapshot, comparison));
|
|
415
|
-
let mappedScreenshots =
|
|
575
|
+
let mappedScreenshots = mappedComparisons.map(comparison => projectLocalScreenshot(comparison.screenshot, comparison.baseline));
|
|
416
576
|
let reviewSummary = buildReviewSummary(snapshot.reportData.comparisons);
|
|
417
577
|
let reviewState = buildReviewState(resolvedBuild, reviewSummary);
|
|
418
|
-
|
|
578
|
+
let context = {
|
|
419
579
|
resource: 'build_context',
|
|
420
580
|
source: LOCAL_CONTEXT_SOURCE,
|
|
421
581
|
scope: createScope(),
|
|
@@ -446,8 +606,48 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
446
606
|
},
|
|
447
607
|
links: createBuildLinks(snapshot)
|
|
448
608
|
};
|
|
609
|
+
if (!query.details) return context;
|
|
610
|
+
let revision = createLocalSnapshotRevision(snapshot);
|
|
611
|
+
let limit = query.limit || 10;
|
|
612
|
+
let cursorQuery = `${query.details}:${limit}`;
|
|
613
|
+
let cursor = readLocalCursor(query.cursor, 'build_context', resolvedBuild.id, cursorQuery, revision, ['evidence']);
|
|
614
|
+
let includeDiffs = query.details === 'diffs';
|
|
615
|
+
let evidence = mappedComparisons.map(comparison => projectLocalEvidence(comparison, includeDiffs));
|
|
616
|
+
return {
|
|
617
|
+
resource: 'build_context',
|
|
618
|
+
source: LOCAL_CONTEXT_SOURCE,
|
|
619
|
+
scope: context.scope,
|
|
620
|
+
build: context.build,
|
|
621
|
+
baseline: context.baseline,
|
|
622
|
+
status: context.status,
|
|
623
|
+
summary: context.summary,
|
|
624
|
+
preview: null,
|
|
625
|
+
signature_properties: [],
|
|
626
|
+
dynamic_regions: {
|
|
627
|
+
exclusions: {
|
|
628
|
+
total: null,
|
|
629
|
+
included: false,
|
|
630
|
+
details_available: false
|
|
631
|
+
},
|
|
632
|
+
item_details_included: false
|
|
633
|
+
},
|
|
634
|
+
evidence: createLocalPage(evidence, {
|
|
635
|
+
limit,
|
|
636
|
+
offset: cursor?.offset || 0,
|
|
637
|
+
resource: 'build_context',
|
|
638
|
+
target: resolvedBuild.id,
|
|
639
|
+
query: cursorQuery,
|
|
640
|
+
stream: 'evidence',
|
|
641
|
+
revision
|
|
642
|
+
}),
|
|
643
|
+
comments: {
|
|
644
|
+
build: compactLocalCollection(context.comments.build),
|
|
645
|
+
screenshot_count: context.comments.screenshot_count
|
|
646
|
+
},
|
|
647
|
+
links: context.links
|
|
648
|
+
};
|
|
449
649
|
}
|
|
450
|
-
function getComparisonContext(comparisonId) {
|
|
650
|
+
function getComparisonContext(comparisonId, query = {}) {
|
|
451
651
|
let snapshot = loadSnapshot();
|
|
452
652
|
let comparison = findComparison(snapshot, comparisonId);
|
|
453
653
|
if (!comparison) {
|
|
@@ -456,7 +656,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
456
656
|
let mappedComparison = mapLocalComparison(snapshot, comparison);
|
|
457
657
|
let comparisonName = comparison.originalName || comparison.name;
|
|
458
658
|
let history = snapshot.reportData.comparisons.filter(candidate => candidate.id !== comparison.id && (candidate.originalName || candidate.name) === comparisonName).map(candidate => mapLocalComparison(snapshot, candidate));
|
|
459
|
-
|
|
659
|
+
let context = {
|
|
460
660
|
resource: 'comparison_context',
|
|
461
661
|
source: LOCAL_CONTEXT_SOURCE,
|
|
462
662
|
scope: createScope(),
|
|
@@ -485,6 +685,52 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
485
685
|
},
|
|
486
686
|
links: buildComparisonLinks(snapshot, comparison.id)
|
|
487
687
|
};
|
|
688
|
+
if (!query.details) return context;
|
|
689
|
+
let revision = createLocalSnapshotRevision(snapshot);
|
|
690
|
+
let limit = query.limit || 10;
|
|
691
|
+
let cursorQuery = `${query.details}:${limit}`;
|
|
692
|
+
let cursor = readLocalCursor(query.cursor, 'comparison_context', comparison.id, cursorQuery, revision, ['similar_by_fingerprint', 'recent_by_name']);
|
|
693
|
+
let activeStream = cursor?.stream;
|
|
694
|
+
let similarOffset = activeStream === 'similar_by_fingerprint' ? cursor.offset : 0;
|
|
695
|
+
let recentOffset = activeStream === 'recent_by_name' ? cursor.offset : 0;
|
|
696
|
+
return {
|
|
697
|
+
resource: 'comparison_context',
|
|
698
|
+
source: LOCAL_CONTEXT_SOURCE,
|
|
699
|
+
scope: context.scope,
|
|
700
|
+
build: context.build,
|
|
701
|
+
signature_properties: [],
|
|
702
|
+
comparison: projectLocalFocusedComparison(context.comparison, query.details === 'diffs'),
|
|
703
|
+
dynamic_regions: createLocalDynamicRegions(context.comparison),
|
|
704
|
+
history: {
|
|
705
|
+
active_stream: activeStream || null,
|
|
706
|
+
similar_by_fingerprint: createLocalPage([], {
|
|
707
|
+
limit,
|
|
708
|
+
offset: similarOffset,
|
|
709
|
+
resource: 'comparison_context',
|
|
710
|
+
target: comparison.id,
|
|
711
|
+
query: cursorQuery,
|
|
712
|
+
stream: 'similar_by_fingerprint',
|
|
713
|
+
revision
|
|
714
|
+
}),
|
|
715
|
+
recent_by_name: createLocalPage(history.map(projectLocalHistoryItem), {
|
|
716
|
+
limit,
|
|
717
|
+
offset: recentOffset,
|
|
718
|
+
resource: 'comparison_context',
|
|
719
|
+
target: comparison.id,
|
|
720
|
+
query: cursorQuery,
|
|
721
|
+
stream: 'recent_by_name',
|
|
722
|
+
revision
|
|
723
|
+
})
|
|
724
|
+
},
|
|
725
|
+
review: {
|
|
726
|
+
review_summary: context.review.review_summary,
|
|
727
|
+
assignments: compactLocalCollection(context.review.assignments),
|
|
728
|
+
build_comments: compactLocalCollection(context.review.build_comments),
|
|
729
|
+
screenshot_comments: compactLocalCollection(context.review.screenshot_comments)
|
|
730
|
+
},
|
|
731
|
+
links: context.links,
|
|
732
|
+
details: query.details
|
|
733
|
+
};
|
|
488
734
|
}
|
|
489
735
|
function getScreenshotContext(screenshotName) {
|
|
490
736
|
let snapshot = loadSnapshot();
|
package/dist/plugin-loader.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function loadPlugins(configPath, config) {
|
|
|
55
55
|
* Discover installed plugins from node_modules/@vizzly-testing/*
|
|
56
56
|
* @returns {Promise<Array>} Array of plugin info objects
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
async function discoverInstalledPlugins() {
|
|
59
59
|
let plugins = [];
|
|
60
60
|
try {
|
|
61
61
|
// Find all @vizzly-testing packages
|