@crvouga/atlas 1.1.0 → 1.3.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/CHANGELOG.md +12 -0
- package/dist/reports/src/artifact-query.d.ts +123 -0
- package/dist/reports/src/artifact-query.js +234 -0
- package/dist/reports/src/evidence-detail.d.ts +14 -3
- package/dist/reports/src/evidence-detail.js +34 -19
- package/dist/reports/src/evidence-scope.d.ts +83 -0
- package/dist/reports/src/evidence-scope.js +129 -0
- package/dist/reports/src/evidence-types.d.ts +5 -1
- package/dist/reports/src/explorer-types.d.ts +1 -1
- package/dist/reports/src/layer-merge.js +3 -2
- package/dist/reports/src/layer-types.d.ts +1 -1
- package/dist/reports/src/publish-checks.js +1 -0
- package/dist/reports/src/reporter.d.ts +1 -1
- package/dist/reports/src/reports-explorer-route.d.ts +276 -0
- package/dist/reports/src/reports-route.d.ts +276 -0
- package/dist/reports/src/test-catalog.d.ts +108 -6
- package/dist/reports/src/test-catalog.js +155 -8
- package/dist/schema/src/api.d.ts +745 -0
- package/dist/schema/src/api.js +48 -6
- package/dist/schema/src/layer.d.ts +981 -0
- package/dist/schema/src/layer.js +43 -4
- package/dist/server/src/app.js +16 -5
- package/dist/server/src/openapi.js +42 -3
- package/docs/extending.md +1 -0
- package/docs/reference/http-api.md +86 -2
- package/docs/reference/reports.md +550 -14
- package/docs/reference/schema.md +55 -0
- package/docs/rich-evidence.md +78 -0
- package/docs/server.md +4 -1
- package/docs/ui.md +1 -1
- package/llms-full.txt +170 -4
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +673 -1
- package/packages/schema/schemas/effective-delta.schema.json +206 -0
- package/packages/schema/schemas/effective-evidence.schema.json +206 -0
- package/packages/schema/schemas/evidence-page.schema.json +103 -0
- package/packages/schema/schemas/layer-rows.schema.json +103 -0
- package/packages/schema/schemas/layer.schema.json +1598 -62
- package/packages/schema/schemas/publish-checks.schema.json +768 -0
- package/packages/schema/schemas/report-run.schema.json +131 -0
- package/packages/schema/schemas/report-runs.schema.json +221 -0
- package/packages/schema/schemas/test-catalog.schema.json +135 -0
- package/packages/schema/schemas/test-history.schema.json +216 -0
- package/packages/server/openapi.json +4731 -735
- package/packages/visualizer/src/evidence/artifacts.tsx +24 -18
- package/packages/visualizer/src/evidence/evidence-scope.tsx +106 -0
- package/packages/visualizer/src/evidence/format.ts +11 -0
- package/packages/visualizer/src/explorer/ArtifactsView.tsx +162 -0
- package/packages/visualizer/src/explorer/Explorer.tsx +47 -3
- package/packages/visualizer/src/explorer/RunsView.tsx +43 -19
- package/packages/visualizer/src/explorer/catalog.tsx +97 -12
- package/packages/visualizer/src/explorer/filters.tsx +279 -0
- package/packages/visualizer/src/explorer/search.ts +85 -6
- package/packages/visualizer/src/explorer/views.ts +75 -0
package/dist/schema/src/layer.js
CHANGED
|
@@ -88,6 +88,31 @@ export const MomentSchema = z.looseObject({
|
|
|
88
88
|
step: z.string().optional().describe('The `id` of a step'),
|
|
89
89
|
offsetMs: z.number().nonnegative().optional()
|
|
90
90
|
});
|
|
91
|
+
/**
|
|
92
|
+
* What a piece of evidence does and does not show, declared by the producer that made it: where it ran, whether its
|
|
93
|
+
* data and dependencies were real, whether audio was captured, how it was paced and which assertions the result
|
|
94
|
+
* stands on. Every field is optional and a field that is absent is unknown, never assumed live, physical or end to end.
|
|
95
|
+
*
|
|
96
|
+
* It is a declaration, not a finding: Atlas shows it as the producer's word, apart from the dimensions the producer
|
|
97
|
+
* names in `verified` (facts something independent checked). It describes evidence and never decides a status, a slot
|
|
98
|
+
* or a winner. The nearest declaration wins as a whole, never field by field: an attachment's `scope`, else its
|
|
99
|
+
* attempt's, else its check's, else its part's, else its layer's.
|
|
100
|
+
*/
|
|
101
|
+
export const EvidenceScopeSchema = z.looseObject({
|
|
102
|
+
surface: z.string().min(1).optional().describe('Where the product ran, in an open vocabulary: `browser`, `native-app`, `api`, `cli`…'),
|
|
103
|
+
deviceKind: z.string().min(1).optional().describe('What it ran on, in an open vocabulary: `simulator`, `emulator`, `physical`, `virtual-machine`, `desktop`… A simulator and a physical phone stay distinct values; they are filtered separately'),
|
|
104
|
+
syntheticData: z.boolean().optional().describe('True: the data was made up for the run. False: real data. Absent: unknown'),
|
|
105
|
+
dependencies: z.string().min(1).optional().describe('Whether what the product talks to was `scripted` (replies fixed in advance), `live` or `mixed`. An open vocabulary; absent is unknown'),
|
|
106
|
+
audioCaptured: z.boolean().optional().describe('True: the recording has real audio. False: it is silent. Absent: unknown'),
|
|
107
|
+
pacing: z.string().min(1).optional().describe('How time ran, in an open vocabulary: `paced` (slowed or scripted for a viewer), `real-time`. Absent: unknown'),
|
|
108
|
+
asserted: z.array(z.string()).optional().describe('The boundaries the result actually asserts. Activity visible in a recording beyond them is not verified behavior'),
|
|
109
|
+
limitations: z.array(z.string()).optional().describe('Known limits of what this evidence shows, in the producer\'s words'),
|
|
110
|
+
declaredBy: z.string().optional().describe('Who or what made this declaration: a tool, a team, a person'),
|
|
111
|
+
verified: z.array(z.string()).optional().describe('The names of the fields above (`surface`, `deviceKind`, `syntheticData`, `dependencies`, `audioCaptured`, `pacing`) that something independent of the producer checked. Every other field is only declared'),
|
|
112
|
+
verifiedBy: z.string().optional().describe('What checked the `verified` fields'),
|
|
113
|
+
provenance: z.array(LinkSchema).optional().describe('Where the declaration comes from and how to check it: a document, a CI step, a device log'),
|
|
114
|
+
extensions: ExtensionsSchema.optional()
|
|
115
|
+
});
|
|
91
116
|
/**
|
|
92
117
|
* A file or a value that goes with a result: a screenshot, a video, a trace, a log, a DOM or accessibility
|
|
93
118
|
* snapshot, a HAR, a coverage report, a diff, anything. `contentType` says how to read it; `role` says what
|
|
@@ -110,6 +135,7 @@ export const AttachmentSchema = z.looseObject({
|
|
|
110
135
|
durationMs: z.number().nonnegative().optional().describe('For video and audio'),
|
|
111
136
|
description: z.string().optional(),
|
|
112
137
|
omitted: z.string().optional().describe('Why the producer did not include its bytes, in an open vocabulary: too-large, not-found, not-allowed, over-budget, policy… Present on a metadata-only attachment, so a reader can tell "left out" from "never recorded"'),
|
|
138
|
+
scope: EvidenceScopeSchema.optional().describe('What this one attachment (usually a recording) does and does not show. Overrides the scope of its attempt, check, part and layer as a whole'),
|
|
113
139
|
tags: z.array(z.string()).optional().describe('Free labels of this one attachment, such as `demo`: shown as `#demo`, searchable, and normalized by `normalizeTag` (a leading `#` and letter case never matter). Unlike the tags of a check they say nothing about its result'),
|
|
114
140
|
at: MomentSchema.optional(),
|
|
115
141
|
poster: z.string().optional().describe('The `id` of an attachment that stands for this one before it is opened'),
|
|
@@ -190,6 +216,7 @@ export const AttemptSchema = z.looseObject({
|
|
|
190
216
|
durationMs: z.number().nonnegative().optional(),
|
|
191
217
|
startedAtMs: z.number().nonnegative().optional(),
|
|
192
218
|
detail: z.string().optional(),
|
|
219
|
+
scope: EvidenceScopeSchema.optional().describe('What this try\'s evidence shows, when it differs from the other tries (a retry on another device). Overrides the check\'s scope for this try\'s attachments'),
|
|
193
220
|
steps: z.array(StepSchema).optional(),
|
|
194
221
|
attachments: z.array(AttachmentSchema).optional(),
|
|
195
222
|
annotations: z.array(AnnotationSchema).optional(),
|
|
@@ -202,6 +229,7 @@ const evidenceShape = {
|
|
|
202
229
|
links: z.array(LinkSchema).optional(),
|
|
203
230
|
attachments: z.array(AttachmentSchema).optional(),
|
|
204
231
|
annotations: z.array(AnnotationSchema).optional(),
|
|
232
|
+
scope: EvidenceScopeSchema.optional().describe('What the evidence below this level does and does not show: where it ran, how real its data and dependencies were, whether audio was captured, and what is asserted. Absent: unknown, never assumed live, physical or end to end'),
|
|
205
233
|
extensions: ExtensionsSchema.optional()
|
|
206
234
|
};
|
|
207
235
|
/**
|
|
@@ -329,17 +357,27 @@ function lenientList(value, schema, prepare = (item) => item) {
|
|
|
329
357
|
});
|
|
330
358
|
}
|
|
331
359
|
const object = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null);
|
|
332
|
-
/**
|
|
360
|
+
/** A scope that cannot be read is no scope: the evidence is kept and its scope is unknown. */
|
|
361
|
+
function lenientScope(holder) {
|
|
362
|
+
if (!('scope' in holder))
|
|
363
|
+
return holder;
|
|
364
|
+
const { scope, ...rest } = holder;
|
|
365
|
+
const parsed = EvidenceScopeSchema.safeParse(scope);
|
|
366
|
+
return parsed.success ? { ...rest, scope: parsed.data } : rest;
|
|
367
|
+
}
|
|
368
|
+
/** An attachment whose `tags` or `scope` cannot be read keeps its file: only the tags that are strings are kept, and an unreadable scope is dropped. */
|
|
333
369
|
function lenientAttachment(item) {
|
|
334
370
|
const attachment = object(item);
|
|
335
|
-
if (!attachment
|
|
371
|
+
if (!attachment)
|
|
336
372
|
return item;
|
|
337
|
-
const { tags, ...rest } = attachment;
|
|
373
|
+
const { tags, ...rest } = lenientScope(attachment);
|
|
374
|
+
if (!('tags' in attachment))
|
|
375
|
+
return rest;
|
|
338
376
|
const kept = lenientList(tags, z.string());
|
|
339
377
|
return kept ? { ...rest, tags: kept } : rest;
|
|
340
378
|
}
|
|
341
379
|
function lenientEvidence(holder, nested = []) {
|
|
342
|
-
const out =
|
|
380
|
+
const out = lenientScope(holder);
|
|
343
381
|
const set = (key, value) => { if (value === undefined)
|
|
344
382
|
delete out[key];
|
|
345
383
|
else
|
|
@@ -468,6 +506,7 @@ export const PublishChecksSchema = z.looseObject({
|
|
|
468
506
|
links: evidenceShape.links,
|
|
469
507
|
attachments: z.array(AttachmentInputSchema).optional().describe('Attached to the whole layer: a CI log, a coverage report, a trace archive'),
|
|
470
508
|
annotations: evidenceShape.annotations,
|
|
509
|
+
scope: evidenceShape.scope,
|
|
471
510
|
extensions: evidenceShape.extensions
|
|
472
511
|
});
|
|
473
512
|
const CountsSchema = z.object({
|
package/dist/server/src/app.js
CHANGED
|
@@ -27,7 +27,8 @@ import { ReportIndex } from '../../reports/src/report-index.js';
|
|
|
27
27
|
import { listRunSummaries, readRunFile } from '../../reports/src/run-bundle.js';
|
|
28
28
|
import { latestSpecSet, readSpecBody } from '../../reports/src/spec-set.js';
|
|
29
29
|
import { catalogHealth, healthMarkdown, parseRootMappings } from '../../reports/src/catalog-health.js';
|
|
30
|
-
import {
|
|
30
|
+
import { parseArtifactFilter } from '../../reports/src/artifact-query.js';
|
|
31
|
+
import { artifactList, artifactsMarkdown, catalogMarkdown, catalogModel, historyMarkdown, parseTestQuery, runDetail, runList, runMarkdown, runsMarkdown, testCatalog, testHistory } from '../../reports/src/test-catalog.js';
|
|
31
32
|
import { llmsTxt } from './llms.js';
|
|
32
33
|
import { openApiDocument } from './openapi.js';
|
|
33
34
|
import { asProblem, problemResponse } from './problem.js';
|
|
@@ -256,7 +257,7 @@ function createProjectApplication(options) {
|
|
|
256
257
|
name: 'atlas', version: options.version ?? null, role: identity?.role ?? 'read', open: auth.open,
|
|
257
258
|
write: options.write, ready: index.ready, viewers: events.count, time: now(),
|
|
258
259
|
backends: snapshot.backends.map(({ id, label, available }) => ({ id, label, available, write: id === options.write })),
|
|
259
|
-
...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true }
|
|
260
|
+
...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true, artifactSearch: true }
|
|
260
261
|
});
|
|
261
262
|
});
|
|
262
263
|
app.get('/api/live', async (c) => c.json({ runs: await live.list() }, 200, { 'cache-control': 'no-store' }));
|
|
@@ -471,9 +472,19 @@ function createProjectApplication(options) {
|
|
|
471
472
|
return notReady(c);
|
|
472
473
|
const found = await catalog();
|
|
473
474
|
const limit = /^\d+$/.test(c.req.query('limit') ?? '') ? Number(c.req.query('limit')) : 100;
|
|
474
|
-
const
|
|
475
|
-
const document = () => runList(index.history, found.version, limit,
|
|
476
|
-
return answer(c, found.version, document, () => runsMarkdown(document()), `runs|${
|
|
475
|
+
const search = new URL(c.req.url).searchParams;
|
|
476
|
+
const document = () => runList(index.history, found.version, limit, parseArtifactFilter(search));
|
|
477
|
+
return answer(c, found.version, document, () => runsMarkdown(document()), `runs|${search.toString()}|${wantsMarkdown(c)}`);
|
|
478
|
+
});
|
|
479
|
+
app.get('/api/reports/artifacts', async (c) => {
|
|
480
|
+
if (!index.ready)
|
|
481
|
+
return notReady(c);
|
|
482
|
+
const found = await catalog();
|
|
483
|
+
const search = new URL(c.req.url).searchParams;
|
|
484
|
+
const number = (name, fallback) => (/^\d+$/.test(search.get(name) ?? '') ? Number(search.get(name)) : fallback);
|
|
485
|
+
const domains = new Map(found.facets.specs.map(spec => [spec.path, spec.domain]));
|
|
486
|
+
const document = () => artifactList(index.history, found.version, parseArtifactFilter(search), { limit: number('limit', 100), offset: number('offset', 0), domainOf: path => domains.get(path) });
|
|
487
|
+
return answer(c, found.version, document, () => artifactsMarkdown(document()), `artifacts|${search.toString()}|${wantsMarkdown(c)}`);
|
|
477
488
|
});
|
|
478
489
|
app.get('/api/reports/runs/detail', async (c) => {
|
|
479
490
|
if (!index.ready)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod/v4';
|
|
16
16
|
import { ProjectsSchema } from '../../schema/src/project.js';
|
|
17
|
-
import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, RunListSchema, RunDetailSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema } from '../../schema/src/api.js';
|
|
17
|
+
import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, RunListSchema, RunDetailSchema, ArtifactListSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema } from '../../schema/src/api.js';
|
|
18
18
|
import { LiveRunSchema, LiveRunStartSchema, LiveRunUpdateSchema, ServerEditableSettingsSchema, ServerSettingsSnapshotSchema, GcPolicySchema, GcSnapshotSchema, GcRunResultSchema, AtlasChangeSchema, PublishChecksSchema, ReportSourcesSchema, ReportStatusSchema, RunsIndexSchema, SpecIndexSchema } from '../../schema/src/index.js';
|
|
19
19
|
/** The version of the API described, `major.minor.patch`: the minor grows when an operation or field is added, the major never within `/api`. */
|
|
20
20
|
export const API_VERSION = '1.5.0';
|
|
@@ -37,7 +37,7 @@ const COMPONENTS = {
|
|
|
37
37
|
StoreListing: [StoreListingSchema, 'output'], StoreWriteResult: [StoreWriteResultSchema, 'output'],
|
|
38
38
|
Outline: [OutlineSchema, 'output'], EvidencePage: [EvidencePageSchema, 'output'], LayerRows: [LayerRowsSchema, 'output'], CheckDetail: [CheckDetailSchema, 'output'],
|
|
39
39
|
ArtifactTagEdit: [ArtifactTagEditSchema, 'input'], ArtifactTagEntry: [ArtifactTagEntrySchema, 'output'], ArtifactTags: [ArtifactTagsSchema, 'output'],
|
|
40
|
-
TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
|
|
40
|
+
TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], ArtifactList: [ArtifactListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
|
|
41
41
|
SpecIndex: [SpecIndexSchema, 'output'], ReportSources: [ReportSourcesSchema, 'output'], RunsIndex: [RunsIndexSchema, 'output'],
|
|
42
42
|
RunEvent: [LiveRunSchema, 'output'], HelloEvent: [HelloEventSchema, 'output'], ReportEvent: [ReportEventSchema, 'output'], AtlasChangeEvent: [AtlasChangeSchema, 'output'], PingEvent: [PingEventSchema, 'output']
|
|
43
43
|
};
|
|
@@ -293,6 +293,7 @@ const BASE_OPERATIONS = [
|
|
|
293
293
|
{ name: 'tag', in: 'query', description: 'Only tests with this tag. Repeatable or comma-separated.', schema: str },
|
|
294
294
|
{ name: 'artifactTag', in: 'query', description: 'Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.', schema: str, example: 'demo' },
|
|
295
295
|
{ name: 'inherited', in: 'query', description: 'With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.', schema: { type: 'string', enum: ['1', 'true'] } },
|
|
296
|
+
{ name: 'scope', in: 'query', description: 'Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.', schema: str, example: 'device:physical,audio:recorded' },
|
|
296
297
|
{ name: 'run', in: 'query', description: 'Only tests recorded in this run (a `key` from `/api/reports/runs`).', schema: str },
|
|
297
298
|
{ name: 'sort', in: 'query', description: 'The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.', schema: { type: 'string', enum: ['spec', 'title', 'recent', 'slowest', 'failures', 'flaky', 'runs'] } },
|
|
298
299
|
{ name: 'offset', in: 'query', description: 'Skip this many matching tests.', schema: { type: 'integer', minimum: 0 } },
|
|
@@ -343,14 +344,52 @@ const BASE_OPERATIONS = [
|
|
|
343
344
|
{
|
|
344
345
|
route: 'GET /api/reports/runs', path: '/api/reports/runs', operationId: 'getReportRuns', tag: 'Report', role: 'read',
|
|
345
346
|
summary: 'The runs that recorded the report',
|
|
346
|
-
description: 'Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures
|
|
347
|
+
description: 'Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension\'s values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.',
|
|
347
348
|
parameters: [
|
|
348
349
|
{ name: 'limit', in: 'query', description: 'At most this many runs (default 100).', schema: { type: 'integer', minimum: 0 } },
|
|
349
350
|
{ name: 'video', in: 'query', description: '`true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.', schema: { type: 'boolean' } },
|
|
351
|
+
{ name: 'artifactTag', in: 'query', description: 'Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\\,` or `\\\\`. OR within the parameter, AND with the other filters.', schema: str },
|
|
352
|
+
{ name: 'inherited', in: 'query', description: '`1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.', schema: { type: 'boolean' } },
|
|
353
|
+
{ name: 'type', in: 'query', description: 'Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer\'s own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.', schema: str },
|
|
354
|
+
{ name: 'label', in: 'query', description: '`key=value`, or `key` for any value.', schema: str },
|
|
355
|
+
{ name: 'environment', in: 'query', description: 'Environments or devices (`ios`, `desktop-web`).', schema: str },
|
|
356
|
+
{ name: 'producer', in: 'query', description: 'Producers.', schema: str },
|
|
357
|
+
{ name: 'branch', in: 'query', description: 'Branches.', schema: str },
|
|
358
|
+
{ name: 'sha', in: 'query', description: 'Revisions: a full commit id or a prefix of at least seven characters.', schema: str },
|
|
359
|
+
{ name: 'outcome', in: 'query', description: 'Statuses: `passed`, `failed`, `running`, `not-run`. A run\'s outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.', schema: str },
|
|
360
|
+
{ name: 'q', in: 'query', description: 'Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.', schema: str },
|
|
361
|
+
{ name: 'since', in: 'query', description: 'Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result\'s recording time; a run\'s end.', schema: str },
|
|
362
|
+
{ name: 'until', in: 'query', description: 'Only records at or before this time (same forms as `since`).', schema: str },
|
|
350
363
|
{ name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
|
|
351
364
|
],
|
|
352
365
|
responses: { '200': json('The runs.', 'RunList'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
|
|
353
366
|
},
|
|
367
|
+
{
|
|
368
|
+
route: 'GET /api/reports/artifacts', path: '/api/reports/artifacts', operationId: 'getReportArtifacts', tag: 'Report', role: 'read',
|
|
369
|
+
summary: 'Recorded results that carry evidence, across the whole history',
|
|
370
|
+
description: 'Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension\'s values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.',
|
|
371
|
+
parameters: [
|
|
372
|
+
{ name: 'limit', in: 'query', description: 'At most this many results (default 100).', schema: { type: 'integer', minimum: 0 } },
|
|
373
|
+
{ name: 'offset', in: 'query', description: 'Skip this many matches (default 0).', schema: { type: 'integer', minimum: 0 } },
|
|
374
|
+
{ name: 'video', in: 'query', description: '`true` for results with a playable video recording (the same as `type=video`).', schema: { type: 'boolean' } },
|
|
375
|
+
{ name: 'domain', in: 'query', description: 'The feature domain of the result\'s spec.', schema: str },
|
|
376
|
+
{ name: 'spec', in: 'query', description: 'Spec paths.', schema: str },
|
|
377
|
+
{ name: 'artifactTag', in: 'query', description: 'Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\\,` or `\\\\`. OR within the parameter, AND with the other filters.', schema: str },
|
|
378
|
+
{ name: 'inherited', in: 'query', description: '`1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.', schema: { type: 'boolean' } },
|
|
379
|
+
{ name: 'type', in: 'query', description: 'Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer\'s own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.', schema: str },
|
|
380
|
+
{ name: 'label', in: 'query', description: '`key=value`, or `key` for any value.', schema: str },
|
|
381
|
+
{ name: 'environment', in: 'query', description: 'Environments or devices (`ios`, `desktop-web`).', schema: str },
|
|
382
|
+
{ name: 'producer', in: 'query', description: 'Producers.', schema: str },
|
|
383
|
+
{ name: 'branch', in: 'query', description: 'Branches.', schema: str },
|
|
384
|
+
{ name: 'sha', in: 'query', description: 'Revisions: a full commit id or a prefix of at least seven characters.', schema: str },
|
|
385
|
+
{ name: 'outcome', in: 'query', description: 'Statuses: `passed`, `failed`, `running`, `not-run`. A run\'s outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.', schema: str },
|
|
386
|
+
{ name: 'q', in: 'query', description: 'Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.', schema: str },
|
|
387
|
+
{ name: 'since', in: 'query', description: 'Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result\'s recording time; a run\'s end.', schema: str },
|
|
388
|
+
{ name: 'until', in: 'query', description: 'Only records at or before this time (same forms as `since`).', schema: str },
|
|
389
|
+
{ name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
|
|
390
|
+
],
|
|
391
|
+
responses: { '200': json('The matching results and the facets.', 'ArtifactList'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
|
|
392
|
+
},
|
|
354
393
|
{
|
|
355
394
|
route: 'GET /api/reports/runs/detail', path: '/api/reports/runs/detail', operationId: 'getReportRun', tag: 'Report', role: 'read',
|
|
356
395
|
summary: 'Every test one run recorded',
|
package/docs/extending.md
CHANGED
|
@@ -31,6 +31,7 @@ carry any of these. Every one is optional.
|
|
|
31
31
|
| `attempts[]` | Every try, when the check was retried. |
|
|
32
32
|
| `tags[]` | Free labels to filter by: `smoke`, `payments`. |
|
|
33
33
|
| `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. |
|
|
34
|
+
| `scope{}` | What the evidence does and does not show: surface, device kind, synthetic data, scripted dependencies, audio, pacing, what was asserted and its limits. Unknown when absent. See [Evidence scope](rich-evidence.md#evidence-scope). |
|
|
34
35
|
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
35
36
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
36
37
|
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
@@ -497,6 +497,7 @@ Send `format=markdown` (or `Accept: text/markdown`) for the same as a Markdown r
|
|
|
497
497
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
498
498
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
499
499
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
500
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
500
501
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
501
502
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
502
503
|
- `offset` (query): Skip this many matching tests.
|
|
@@ -528,6 +529,7 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
528
529
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
529
530
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
530
531
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
532
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
531
533
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
532
534
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
533
535
|
- `offset` (query): Skip this many matching tests.
|
|
@@ -644,10 +646,22 @@ Responses: `201` The recorded entry.; `401` No token, or an unknown one. Send `A
|
|
|
644
646
|
|
|
645
647
|
`GET /api/reports/runs` · role: read
|
|
646
648
|
|
|
647
|
-
Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures
|
|
649
|
+
Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension's values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.
|
|
648
650
|
|
|
649
651
|
- `limit` (query): At most this many runs (default 100).
|
|
650
652
|
- `video` (query): `true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.
|
|
653
|
+
- `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
|
|
654
|
+
- `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
|
|
655
|
+
- `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
|
|
656
|
+
- `label` (query): `key=value`, or `key` for any value.
|
|
657
|
+
- `environment` (query): Environments or devices (`ios`, `desktop-web`).
|
|
658
|
+
- `producer` (query): Producers.
|
|
659
|
+
- `branch` (query): Branches.
|
|
660
|
+
- `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
|
|
661
|
+
- `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
|
|
662
|
+
- `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
|
|
663
|
+
- `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
|
|
664
|
+
- `until` (query): Only records at or before this time (same forms as `since`).
|
|
651
665
|
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
652
666
|
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
653
667
|
|
|
@@ -657,17 +671,87 @@ Responses: `200` The runs.; `304` Unchanged since the `If-None-Match` ETag.; `40
|
|
|
657
671
|
|
|
658
672
|
`GET /api/projects/{project}/reports/runs` · role: read
|
|
659
673
|
|
|
660
|
-
Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures
|
|
674
|
+
Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension's values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.
|
|
661
675
|
|
|
662
676
|
Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
|
|
663
677
|
|
|
664
678
|
- `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
665
679
|
- `limit` (query): At most this many runs (default 100).
|
|
666
680
|
- `video` (query): `true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.
|
|
681
|
+
- `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
|
|
682
|
+
- `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
|
|
683
|
+
- `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
|
|
684
|
+
- `label` (query): `key=value`, or `key` for any value.
|
|
685
|
+
- `environment` (query): Environments or devices (`ios`, `desktop-web`).
|
|
686
|
+
- `producer` (query): Producers.
|
|
687
|
+
- `branch` (query): Branches.
|
|
688
|
+
- `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
|
|
689
|
+
- `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
|
|
690
|
+
- `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
|
|
691
|
+
- `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
|
|
692
|
+
- `until` (query): Only records at or before this time (same forms as `since`).
|
|
667
693
|
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
668
694
|
|
|
669
695
|
Responses: `200` The runs.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
670
696
|
|
|
697
|
+
### Recorded results that carry evidence, across the whole history
|
|
698
|
+
|
|
699
|
+
`GET /api/reports/artifacts` · role: read
|
|
700
|
+
|
|
701
|
+
Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension's values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.
|
|
702
|
+
|
|
703
|
+
- `limit` (query): At most this many results (default 100).
|
|
704
|
+
- `offset` (query): Skip this many matches (default 0).
|
|
705
|
+
- `video` (query): `true` for results with a playable video recording (the same as `type=video`).
|
|
706
|
+
- `domain` (query): The feature domain of the result's spec.
|
|
707
|
+
- `spec` (query): Spec paths.
|
|
708
|
+
- `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
|
|
709
|
+
- `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
|
|
710
|
+
- `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
|
|
711
|
+
- `label` (query): `key=value`, or `key` for any value.
|
|
712
|
+
- `environment` (query): Environments or devices (`ios`, `desktop-web`).
|
|
713
|
+
- `producer` (query): Producers.
|
|
714
|
+
- `branch` (query): Branches.
|
|
715
|
+
- `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
|
|
716
|
+
- `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
|
|
717
|
+
- `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
|
|
718
|
+
- `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
|
|
719
|
+
- `until` (query): Only records at or before this time (same forms as `since`).
|
|
720
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
721
|
+
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
722
|
+
|
|
723
|
+
Responses: `200` The matching results and the facets.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
724
|
+
|
|
725
|
+
### Recorded results that carry evidence, across the whole history
|
|
726
|
+
|
|
727
|
+
`GET /api/projects/{project}/reports/artifacts` · role: read
|
|
728
|
+
|
|
729
|
+
Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension's values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.
|
|
730
|
+
|
|
731
|
+
Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
|
|
732
|
+
|
|
733
|
+
- `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
734
|
+
- `limit` (query): At most this many results (default 100).
|
|
735
|
+
- `offset` (query): Skip this many matches (default 0).
|
|
736
|
+
- `video` (query): `true` for results with a playable video recording (the same as `type=video`).
|
|
737
|
+
- `domain` (query): The feature domain of the result's spec.
|
|
738
|
+
- `spec` (query): Spec paths.
|
|
739
|
+
- `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
|
|
740
|
+
- `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
|
|
741
|
+
- `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
|
|
742
|
+
- `label` (query): `key=value`, or `key` for any value.
|
|
743
|
+
- `environment` (query): Environments or devices (`ios`, `desktop-web`).
|
|
744
|
+
- `producer` (query): Producers.
|
|
745
|
+
- `branch` (query): Branches.
|
|
746
|
+
- `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
|
|
747
|
+
- `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
|
|
748
|
+
- `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
|
|
749
|
+
- `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
|
|
750
|
+
- `until` (query): Only records at or before this time (same forms as `since`).
|
|
751
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
752
|
+
|
|
753
|
+
Responses: `200` The matching results and the facets.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
754
|
+
|
|
671
755
|
### Every test one run recorded
|
|
672
756
|
|
|
673
757
|
`GET /api/reports/runs/detail` · role: read
|