playwright-logbook 0.2.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/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/artifacts.d.ts +7 -0
- package/dist/artifacts.js +35 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/caseids.d.ts +12 -0
- package/dist/caseids.js +24 -0
- package/dist/caseids.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.js +6 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/debug.d.ts +7 -0
- package/dist/cli/commands/debug.js +14 -0
- package/dist/cli/commands/debug.js.map +1 -0
- package/dist/cli/commands/flaky.d.ts +8 -0
- package/dist/cli/commands/flaky.js +27 -0
- package/dist/cli/commands/flaky.js.map +1 -0
- package/dist/cli/commands/history.d.ts +8 -0
- package/dist/cli/commands/history.js +24 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +12 -0
- package/dist/cli/commands/merge.js +102 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/report.d.ts +9 -0
- package/dist/cli/commands/report.js +13 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/summary.d.ts +7 -0
- package/dist/cli/commands/summary.js +14 -0
- package/dist/cli/commands/summary.js.map +1 -0
- package/dist/cli/format.d.ts +18 -0
- package/dist/cli/format.js +41 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/program.d.ts +12 -0
- package/dist/cli/program.js +74 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/clientlib.d.ts +48 -0
- package/dist/clientlib.js +136 -0
- package/dist/clientlib.js.map +1 -0
- package/dist/collect.d.ts +101 -0
- package/dist/collect.js +50 -0
- package/dist/collect.js.map +1 -0
- package/dist/debugpacket.d.ts +31 -0
- package/dist/debugpacket.js +105 -0
- package/dist/debugpacket.js.map +1 -0
- package/dist/env.d.ts +41 -0
- package/dist/env.js +49 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/history.d.ts +32 -0
- package/dist/history.js +72 -0
- package/dist/history.js.map +1 -0
- package/dist/index.cjs +1562 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/merge.d.ts +9 -0
- package/dist/merge.js +64 -0
- package/dist/merge.js.map +1 -0
- package/dist/model.d.ts +98 -0
- package/dist/model.js +129 -0
- package/dist/model.js.map +1 -0
- package/dist/options.d.ts +23 -0
- package/dist/options.js +5 -0
- package/dist/options.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.js +17 -0
- package/dist/paths.js.map +1 -0
- package/dist/publish.d.ts +14 -0
- package/dist/publish.js +2 -0
- package/dist/publish.js.map +1 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +50 -0
- package/dist/render.js.map +1 -0
- package/dist/reporter.d.ts +53 -0
- package/dist/reporter.js +161 -0
- package/dist/reporter.js.map +1 -0
- package/dist/sanitize.d.ts +11 -0
- package/dist/sanitize.js +33 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schema.d.ts +581 -0
- package/dist/schema.js +31 -0
- package/dist/schema.js.map +1 -0
- package/dist/signals.d.ts +9 -0
- package/dist/signals.js +37 -0
- package/dist/signals.js.map +1 -0
- package/dist/store.d.ts +41 -0
- package/dist/store.js +142 -0
- package/dist/store.js.map +1 -0
- package/dist/template/css.d.ts +1 -0
- package/dist/template/css.js +17 -0
- package/dist/template/css.js.map +1 -0
- package/dist/template/icons.d.ts +1 -0
- package/dist/template/icons.js +9 -0
- package/dist/template/icons.js.map +1 -0
- package/dist/template/js/core.d.ts +1 -0
- package/dist/template/js/core.js +90 -0
- package/dist/template/js/core.js.map +1 -0
- package/dist/template/js/failures.d.ts +1 -0
- package/dist/template/js/failures.js +33 -0
- package/dist/template/js/failures.js.map +1 -0
- package/dist/template/js/help.d.ts +1 -0
- package/dist/template/js/help.js +4 -0
- package/dist/template/js/help.js.map +1 -0
- package/dist/template/js/panel.d.ts +1 -0
- package/dist/template/js/panel.js +84 -0
- package/dist/template/js/panel.js.map +1 -0
- package/dist/template/js/project.d.ts +1 -0
- package/dist/template/js/project.js +40 -0
- package/dist/template/js/project.js.map +1 -0
- package/dist/template/js/run.d.ts +1 -0
- package/dist/template/js/run.js +38 -0
- package/dist/template/js/run.js.map +1 -0
- package/dist/template/js/tests.d.ts +1 -0
- package/dist/template/js/tests.js +23 -0
- package/dist/template/js/tests.js.map +1 -0
- package/dist/template/js/trends.d.ts +1 -0
- package/dist/template/js/trends.js +57 -0
- package/dist/template/js/trends.js.map +1 -0
- package/dist/template/tokens.d.ts +45 -0
- package/dist/template/tokens.js +12 -0
- package/dist/template/tokens.js.map +1 -0
- package/dist/template.d.ts +2 -0
- package/dist/template.js +19 -0
- package/dist/template.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.js","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import type { RunRecord, RunSummaryRecord, ShardFile } from './schema.js';
|
|
3
|
+
/** Sink interface for serialized shard files. */
|
|
4
|
+
export interface ShardSink {
|
|
5
|
+
write(shard: ShardFile): Promise<string>;
|
|
6
|
+
}
|
|
7
|
+
type WriteFiles = Pick<typeof fs, 'mkdir' | 'mkdtemp' | 'writeFile' | 'readFile' | 'link' | 'rename' | 'rm'>;
|
|
8
|
+
/** Write shard files atomically below an output directory. */
|
|
9
|
+
export declare class FileShardSink implements ShardSink {
|
|
10
|
+
private readonly outputDir;
|
|
11
|
+
private readonly files;
|
|
12
|
+
constructor(outputDir: string, files?: Partial<WriteFiles>);
|
|
13
|
+
write(shard: ShardFile): Promise<string>;
|
|
14
|
+
}
|
|
15
|
+
export interface HistoryStore {
|
|
16
|
+
saveRun(run: RunRecord, options?: {
|
|
17
|
+
replace?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
listSummaries(opts?: {
|
|
20
|
+
limit?: number;
|
|
21
|
+
branch?: string;
|
|
22
|
+
}): Promise<RunSummaryRecord[]>;
|
|
23
|
+
loadRun(idOrLatest: string): Promise<RunRecord>;
|
|
24
|
+
loadRuns(runIds: string[]): Promise<RunRecord[]>;
|
|
25
|
+
}
|
|
26
|
+
/** File-backed history with an append-only summary index. */
|
|
27
|
+
export declare class FileHistoryStore implements HistoryStore {
|
|
28
|
+
private readonly outputDir;
|
|
29
|
+
constructor(outputDir: string);
|
|
30
|
+
saveRun(run: RunRecord, options?: {
|
|
31
|
+
replace?: boolean;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
private summariesFromFiles;
|
|
34
|
+
listSummaries(opts?: {
|
|
35
|
+
limit?: number;
|
|
36
|
+
branch?: string;
|
|
37
|
+
}): Promise<RunSummaryRecord[]>;
|
|
38
|
+
loadRun(idOrLatest: string): Promise<RunRecord>;
|
|
39
|
+
loadRuns(runIds: string[]): Promise<RunRecord[]>;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readRun, runSummaryRecordSchema } from './schema.js';
|
|
4
|
+
import { LogbookError } from './errors.js';
|
|
5
|
+
const exists = (error) => typeof error === 'object' && error !== null && 'code' in error && error.code === 'EEXIST';
|
|
6
|
+
const safeId = (id) => /^[A-Za-z0-9._-]+$/.test(id) && id !== '.' && id !== '..';
|
|
7
|
+
async function writeRecord(target, body, kind, files, replace = false) {
|
|
8
|
+
await files.mkdir(path.dirname(target), { recursive: true });
|
|
9
|
+
const tempDir = await files.mkdtemp(path.join(path.dirname(target), '.logbook-tmp-'));
|
|
10
|
+
try {
|
|
11
|
+
const temp = path.join(tempDir, 'record.json');
|
|
12
|
+
await files.writeFile(temp, body, 'utf8');
|
|
13
|
+
if (replace) {
|
|
14
|
+
await files.rename(temp, target);
|
|
15
|
+
return 'replaced';
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
await files.link(temp, target);
|
|
19
|
+
return 'created';
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!exists(error))
|
|
23
|
+
throw error;
|
|
24
|
+
const old = await files.readFile(target, 'utf8');
|
|
25
|
+
if (old === body)
|
|
26
|
+
return 'identical';
|
|
27
|
+
throw new LogbookError(kind === 'run' ? 'RUN_CONFLICT' : 'SHARD_CONFLICT', `${kind} ${path.basename(target)} already exists with different content`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await files.rm(tempDir, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Write shard files atomically below an output directory. */
|
|
35
|
+
export class FileShardSink {
|
|
36
|
+
outputDir;
|
|
37
|
+
files;
|
|
38
|
+
constructor(outputDir, files = fs) {
|
|
39
|
+
this.outputDir = outputDir;
|
|
40
|
+
this.files = files;
|
|
41
|
+
}
|
|
42
|
+
async write(shard) {
|
|
43
|
+
if (!safeId(shard.runId))
|
|
44
|
+
throw new LogbookError('INVALID_DATA', 'invalid run id');
|
|
45
|
+
const current = shard.shard?.current ?? 1;
|
|
46
|
+
const total = shard.shard?.total ?? 1;
|
|
47
|
+
const relative = `shards/${shard.runId}/shard-${current}-of-${total}.json`;
|
|
48
|
+
const target = path.join(this.outputDir, relative);
|
|
49
|
+
await writeRecord(target, `${JSON.stringify(shard, null, 2)}\n`, 'shard', { ...fs, ...this.files });
|
|
50
|
+
return relative;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function summaryOf(run) {
|
|
54
|
+
return { schemaVersion: 1, runId: run.runId, title: run.title, startedAt: run.startedAt, durationMs: run.durationMs, status: run.status, complete: run.complete, summary: run.summary, branch: run.env.git.branch, commit: run.env.git.commit, ciProvider: run.env.ci?.provider ?? null, buildUrl: run.env.ci?.buildUrl ?? null };
|
|
55
|
+
}
|
|
56
|
+
const missing = (error) => typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
57
|
+
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
58
|
+
/** File-backed history with an append-only summary index. */
|
|
59
|
+
export class FileHistoryStore {
|
|
60
|
+
outputDir;
|
|
61
|
+
constructor(outputDir) {
|
|
62
|
+
this.outputDir = outputDir;
|
|
63
|
+
}
|
|
64
|
+
async saveRun(run, options = {}) {
|
|
65
|
+
if (!safeId(run.runId))
|
|
66
|
+
throw new LogbookError('INVALID_DATA', 'invalid run id');
|
|
67
|
+
const target = path.join(this.outputDir, 'runs', `${run.runId}.json`);
|
|
68
|
+
const outcome = await writeRecord(target, `${JSON.stringify(run, null, 2)}\n`, 'run', fs, options.replace);
|
|
69
|
+
if (outcome !== 'identical')
|
|
70
|
+
await fs.appendFile(path.join(this.outputDir, 'index.jsonl'), `${JSON.stringify(summaryOf(run))}\n`, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
async summariesFromFiles() {
|
|
73
|
+
let names;
|
|
74
|
+
try {
|
|
75
|
+
names = await fs.readdir(path.join(this.outputDir, 'runs'));
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (missing(error))
|
|
79
|
+
return [];
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const summaries = [];
|
|
83
|
+
for (const name of names.filter((entry) => entry.endsWith('.json')).sort(compare)) {
|
|
84
|
+
try {
|
|
85
|
+
summaries.push(summaryOf(await this.loadRun(name.slice(0, -5))));
|
|
86
|
+
}
|
|
87
|
+
catch { /* Skip invalid records during index recovery. */ }
|
|
88
|
+
}
|
|
89
|
+
return summaries;
|
|
90
|
+
}
|
|
91
|
+
async listSummaries(opts = {}) {
|
|
92
|
+
let records;
|
|
93
|
+
try {
|
|
94
|
+
const text = await fs.readFile(path.join(this.outputDir, 'index.jsonl'), 'utf8');
|
|
95
|
+
records = text.split('\n').flatMap((line) => {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = runSummaryRecordSchema.safeParse(JSON.parse(line));
|
|
98
|
+
return parsed.success ? [parsed.data] : [];
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!missing(error))
|
|
107
|
+
throw error;
|
|
108
|
+
records = await this.summariesFromFiles();
|
|
109
|
+
}
|
|
110
|
+
const latest = new Map(records.map((record) => [record.runId, record]));
|
|
111
|
+
return [...latest.values()].filter((record) => opts.branch === undefined || record.branch === opts.branch)
|
|
112
|
+
.sort((a, b) => compare(b.startedAt, a.startedAt) || compare(a.runId, b.runId))
|
|
113
|
+
.slice(0, opts.limit ?? 30);
|
|
114
|
+
}
|
|
115
|
+
async loadRun(idOrLatest) {
|
|
116
|
+
const id = idOrLatest === 'latest' ? (await this.listSummaries({ limit: 1 }))[0]?.runId : idOrLatest;
|
|
117
|
+
if (!id)
|
|
118
|
+
throw new LogbookError('NO_DATA', 'no runs available');
|
|
119
|
+
if (!/^[A-Za-z0-9._-]+$/.test(id) || id === '..')
|
|
120
|
+
throw new LogbookError('RUN_NOT_FOUND', `run ${id} not found`);
|
|
121
|
+
const target = path.join(this.outputDir, 'runs', `${id}.json`);
|
|
122
|
+
let data;
|
|
123
|
+
try {
|
|
124
|
+
data = await fs.readFile(target, 'utf8');
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (missing(error))
|
|
128
|
+
throw new LogbookError('RUN_NOT_FOUND', `run ${id} not found`);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return readRun(JSON.parse(data), target);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof LogbookError)
|
|
136
|
+
throw error;
|
|
137
|
+
throw new LogbookError('INVALID_DATA', `${target}: invalid run data`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async loadRuns(runIds) { return Promise.all(runIds.map((id) => this.loadRun(id))); }
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const REPORT_CSS: string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { tokensToCss } from './tokens.js';
|
|
2
|
+
export const REPORT_CSS = `:root{${tokensToCss('light')}font-family:system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;color-scheme:light}
|
|
3
|
+
:root[data-theme="dark"]{${tokensToCss('dark')}color-scheme:dark}
|
|
4
|
+
:root[data-theme="light"]{${tokensToCss('light')}color-scheme:light}
|
|
5
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px;line-height:1.5}button,input,select{font:inherit}button{cursor:pointer}a{color:var(--accent)}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.skip-link{position:absolute;top:-80px;left:12px;z-index:30;background:var(--surface);padding:8px}.skip-link:focus{top:12px}.topbar{position:sticky;top:0;z-index:20;background:var(--surface);border-bottom:1px solid var(--border);box-shadow:var(--shadow)}.topbar-inner{max-width:1280px;margin:auto;padding:8px 20px;display:flex;align-items:center;gap:16px;min-height:56px}.wordmark{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;color:var(--text);text-decoration:none}.wordmark svg,.icon{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.topbar-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.topbar-actions{margin-left:auto;display:flex;gap:6px}.button,.topbar-actions button,.chip-button{border:1px solid var(--border);background:var(--surface);color:var(--text);border-radius:6px;padding:6px 10px;display:inline-flex;align-items:center;gap:5px}.button:hover,.topbar-actions button:hover,.chip-button:hover{background:var(--surface-2)}.container{max-width:1280px;margin:auto;padding:24px 20px 48px}.hero{display:grid;grid-template-columns:150px minmax(0,1fr);gap:24px;align-items:center}.donut{width:144px;height:144px}.donut-track{stroke:var(--border)}.donut-center{font-size:22px;font-weight:600;fill:var(--text)}.donut-label{font-size:10px;fill:var(--muted)}h1{font-size:24px;line-height:1.2;margin:0 0 8px}h2{font-size:20px;margin:0 0 14px}h3{font-size:16px;margin:20px 0 8px}.muted,small{color:var(--muted)}.meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.meta>*{border:1px solid var(--border);background:var(--surface);border-radius:6px;padding:3px 8px}.cards{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;margin:24px 0}.card{border:1px solid var(--border);border-radius:12px;background:var(--surface);padding:16px;text-align:left;color:var(--text)}.card strong{display:block;font-size:28px;line-height:1.2;margin-top:4px;font-variant-numeric:tabular-nums}.card[data-kind="passed"] strong{color:var(--passed)}.card[data-kind="failed"] strong{color:var(--failed)}.card[data-kind="flaky"] strong{color:var(--flaky)}.delta,.banner,.notice{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:12px 16px;margin:12px 0}.banner{border-color:var(--flaky);color:var(--text)}.delta{display:flex;gap:16px;flex-wrap:wrap}.delta span{white-space:nowrap}.tabs{display:flex;gap:4px;overflow-x:auto;border-bottom:1px solid var(--border);margin:24px 0 16px}.tabs button{background:none;border:0;border-bottom:3px solid transparent;color:var(--muted);padding:10px 14px;white-space:nowrap}.tabs button[aria-selected="true"]{color:var(--text);border-color:var(--accent);font-weight:600}.panel[hidden]{display:none}.toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:12px 0}.toolbar input,.toolbar select{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:7px 9px}.toolbar input{min-width:220px;flex:1}.chip-button[aria-pressed="true"]{border-color:var(--accent);background:var(--surface-2)}.table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:12px;background:var(--surface)}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:top}th{font-size:12px;color:var(--muted);font-weight:600}td{font-size:13px}tbody tr:last-child td{border-bottom:0}tr[data-test-id]{cursor:pointer}tr[data-test-id]:hover{background:var(--surface-2)}td strong{display:block;font-weight:500;max-width:620px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}td small{display:block;max-width:620px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-chip{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;border-radius:6px;padding:3px 7px;white-space:nowrap}.status-chip.passed{color:var(--passed);background:var(--passed-bg)}.status-chip.failed,.status-chip.timedout{color:var(--failed);background:var(--failed-bg)}.status-chip.flaky{color:var(--flaky);background:var(--flaky-bg)}.status-chip.skipped{color:var(--skipped);background:var(--skipped-bg)}.status-chip .mark{font-size:12px}.heat{display:block;height:3px;background:var(--surface-2);border-radius:3px;margin-top:5px}.heat span{display:block;height:100%;background:var(--accent)}.heat.hot span{background:var(--flaky)}.recent{display:flex;gap:3px}.recent span{width:9px;height:9px;border-radius:2px;background:var(--border)}.recent .passed{background:var(--passed)}.recent .failed{background:var(--failed)}.recent .flaky{background:var(--flaky)}.recent .skipped{background:var(--skipped)}.empty{padding:32px;text-align:center;color:var(--muted)}.group-header{background:var(--surface-2);font-weight:600}.section-card{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:16px;margin:12px 0}.grid-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.panel-backdrop{position:fixed;inset:0;background:var(--shadow);z-index:25}.detail-panel{position:fixed;z-index:26;right:0;top:0;bottom:0;width:min(520px,100vw);overflow:auto;background:var(--surface);border-left:1px solid var(--border);box-shadow:var(--shadow);padding:20px}.detail-panel[hidden],.panel-backdrop[hidden]{display:none}.panel-head{display:flex;align-items:center;gap:8px;position:sticky;top:-20px;background:var(--surface);padding:12px 0;z-index:1}.panel-head h2{margin:0;flex:1}.detail-panel pre{background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:12px;overflow:auto;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}.attempts{display:flex;gap:6px;flex-wrap:wrap}.attempts button[aria-pressed="true"]{border-color:var(--accent)}.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--text);color:var(--bg);border-radius:6px;padding:8px 14px;z-index:50}.toast[hidden]{display:none}.help-dialog,.lightbox{border:1px solid var(--border);border-radius:12px;background:var(--surface);color:var(--text);max-width:min(600px,95vw);box-shadow:var(--shadow)}.help-dialog::backdrop,.lightbox::backdrop{background:var(--shadow)}.chart{width:100%;height:190px;background:var(--surface);border:1px solid var(--border);border-radius:12px}.chart line,.chart path{stroke:var(--accent);fill:none;stroke-width:2}.chart rect{fill:var(--surface-2)}.chart text{fill:var(--muted);font-size:10px}.timeline{width:100%;min-width:700px;height:auto;min-height:160px}.timeline rect.passed{fill:var(--passed)}.timeline rect.failed{fill:var(--failed)}.timeline rect.flaky{fill:var(--flaky)}footer{color:var(--muted);padding:32px 0}.error-banner{border:1px solid var(--failed);color:var(--failed);padding:12px}.error-banner[hidden]{display:none}button{transition:background 140ms ease-out}svg{overflow:visible}
|
|
6
|
+
.donut-segment.passed{stroke:var(--passed)}.donut-segment.failed{stroke:var(--failed)}.donut-segment.flaky{stroke:var(--flaky)}.donut-segment.skipped{stroke:var(--skipped)}.donut-segment{transition:stroke-dasharray 400ms ease-out}.detail-panel li.failed{color:var(--failed)}.topbar .status-chip{font-size:11px}.section-card button{margin:4px}.panel .chart{margin:12px 0}
|
|
7
|
+
.card{appearance:none;background-color:var(--surface)}.card[data-card-kind="passed"] strong{color:var(--passed)}.card[data-card-kind="failed"] strong{color:var(--failed)}.card[data-card-kind="flaky"] strong{color:var(--flaky)}
|
|
8
|
+
.chart .trend-area{fill:var(--passed-bg);stroke:none}.chart .trend-line{fill:none;stroke:var(--accent);stroke-width:2}.chart .trend-dot{fill:var(--accent);stroke:none}.run-strip{display:flex;gap:4px;margin:12px 0}.run-strip span{width:14px;height:14px;border-radius:3px;background:var(--border)}.run-strip .passed{background:var(--passed)}.run-strip .failed{background:var(--failed)}.group-header{cursor:pointer}
|
|
9
|
+
.steps{padding-left:20px}.steps li{padding:5px 0;border-bottom:1px solid var(--border)}.steps li.failed{font-weight:600}
|
|
10
|
+
.tag-chip,.case-chip{display:inline-block;padding:1px 5px;margin:1px 3px 1px 0;border:1px solid var(--border);border-radius:6px;color:var(--muted);white-space:nowrap}.case-chip{background:var(--surface-2)}
|
|
11
|
+
[hidden]{display:none!important}.hero-main{min-width:0}.eyebrow{margin:0 0 5px;color:var(--muted);font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.eyebrow strong{color:var(--accent);font-weight:700;margin-left:5px}.run-facts{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0 0}.run-facts div{min-width:0;max-width:100%;padding:7px 12px;background:var(--surface);border:1px solid var(--border);border-radius:8px}.run-facts dt{color:var(--muted);font-size:11px;line-height:1.25}.run-facts dd{margin:2px 0 0;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:320px}.env-disclosure{margin-top:10px;color:var(--muted);font-size:12px}.env-disclosure summary{cursor:pointer;width:max-content}.env-disclosure summary:hover{color:var(--accent)}.env-disclosure .meta{margin-top:8px}.comparison{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:16px;margin:0 0 8px}.comparison-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.comparison h2{font-size:15px;margin:0 0 3px}.comparison p{margin:0;color:var(--muted);font-size:12px}.comparison code{font:inherit;color:var(--text)}.comparison-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin-top:14px}.comparison-metric{padding:9px 12px;background:var(--surface-2);border-radius:8px;min-width:0}.comparison-metric span{display:block;color:var(--muted);font-size:11px}.comparison-metric strong{display:block;margin-top:2px;font-size:18px;font-variant-numeric:tabular-nums}.comparison-empty{border-style:dashed}.pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:12px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:8px}.pagination p{margin:0;color:var(--muted);font-size:12px}.card:hover{border-color:var(--accent);box-shadow:0 3px 12px var(--shadow)}.card:active{transform:translateY(1px)}
|
|
12
|
+
@media(max-width:1100px){.topbar-title{display:none}}
|
|
13
|
+
@media(max-width:640px){.container{padding:16px 12px}.topbar-inner{padding:8px 12px}.topbar-actions .optional{display:none}.hero{grid-template-columns:1fr;gap:12px}.donut{width:110px;height:110px}.cards{grid-template-columns:repeat(2,minmax(0,1fr))}.detail-panel{width:100vw;border:0}.toolbar input{min-width:100%}.run-facts{gap:6px}.run-facts div{flex:1 1 135px}.run-facts dd{max-width:100%}.comparison-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.comparison-head{align-items:flex-start;flex-direction:column}.pagination{flex-direction:column;align-items:stretch}}
|
|
14
|
+
@media(max-width:640px){.run-facts div{flex:1 1 100%}}
|
|
15
|
+
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}
|
|
16
|
+
@media print{:root,:root[data-theme="dark"]{${tokensToCss('light')}color-scheme:light}body{background:var(--surface)}.topbar-actions,.tabs,.toolbar,.button,.chip-button,.panel-head button,.toast,.skip-link{display:none!important}.panel[hidden]{display:block!important}.detail-panel{position:static;width:auto;box-shadow:none;border:0}.panel-backdrop{display:none}.container{max-width:none}.section-card,tr{break-inside:avoid}a{color:var(--text)}}`;
|
|
17
|
+
//# sourceMappingURL=css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/template/css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,WAAW,CAAC,OAAO,CAAC;2BAC5B,WAAW,CAAC,MAAM,CAAC;4BAClB,WAAW,CAAC,OAAO,CAAC;;;;;;;;;;;;8CAYF,WAAW,CAAC,OAAO,CAAC,6XAA6X,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ICONS: Record<string, string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const ICONS = {
|
|
2
|
+
check: 'M5 12l4 4L19 6', x: 'M6 6l12 12M18 6L6 18', alert: 'M12 3L2 21h20L12 3zm0 6v5m0 3v1',
|
|
3
|
+
skip: 'M5 5l14 7-14 7V5z', clock: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zm0 4v5l3 2',
|
|
4
|
+
copy: 'M8 5h12v15H8zM4 16V3h12', link: 'M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-2 2M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l2-2',
|
|
5
|
+
sun: 'M12 4v2m0 12v2M4 12h2m12 0h2M6 6l1.5 1.5m9 9L18 18M18 6l-1.5 1.5m-9 9L6 18M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8',
|
|
6
|
+
moon: 'M20 15a8 8 0 0 1-11-11 8 8 0 1 0 11 11', search: 'M11 3a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm6 14 4 4',
|
|
7
|
+
chevron: 'M6 9l6 6 6-6', book: 'M4 4h6a4 4 0 0 1 4 4v12a4 4 0 0 0-4-2H4V4zm10 4a4 4 0 0 1 4-4h2v14h-2a4 4 0 0 0-4 2',
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/template/icons.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAA2B;IAC3C,KAAK,EAAE,gBAAgB,EAAE,CAAC,EAAE,sBAAsB,EAAE,KAAK,EAAE,iCAAiC;IAC5F,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,gDAAgD;IAClF,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,wFAAwF;IAC/H,GAAG,EAAE,6GAA6G;IAClH,IAAI,EAAE,wCAAwC,EAAE,MAAM,EAAE,+CAA+C;IACvG,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,qFAAqF;CACrH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CORE_JS = "\nconst model=JSON.parse(document.getElementById('lb-data').textContent);\nconst links=JSON.parse(document.getElementById('lb-links').textContent);\nconst allTests=model.run.tests;\nconst byId=new Map(allTests.map(test=>[test.testId,test]));\nconst tabs=['tests','failures','trends','flaky','run','project'];\nconst tabButtons=[...document.querySelectorAll('[data-tab]')];\nlet state=parseHash(location.hash),panelReturn=null,toastTimer=0,visibleRows=[];\nfunction h(tag,props,...children){const el=document.createElement(tag);for(const [key,value] of Object.entries(props||{})){if(value===null||value===undefined)continue;if(key==='class')el.className=String(value);else if(key==='text')el.textContent=String(value);else if(key.startsWith('on')&&typeof value==='function')el.addEventListener(key.slice(2).toLowerCase(),value);else el.setAttribute(key,String(value))}for(const child of children.flat(Infinity)){if(child===null||child===undefined)continue;el.appendChild(typeof child==='string'||typeof child==='number'?document.createTextNode(String(child)):child)}return el}\nconst svgNS='http'+':/'+'/www.w3.org/2000/svg';\nfunction s(tag,props,...children){const el=document.createElementNS(svgNS,tag);for(const [key,value] of Object.entries(props||{}))el.setAttribute(key,String(value));for(const child of children.flat(Infinity)){if(child!==null&&child!==undefined)el.appendChild(typeof child==='string'||typeof child==='number'?document.createTextNode(String(child)):child)}return el}\nfunction clear(el){while(el.firstChild)el.removeChild(el.firstChild)}\nfunction icon(name){const svg=s('svg',{viewBox:'0 0 24 24',class:'icon','aria-hidden':'true'});svg.appendChild(s('path',{d:icons[name]||icons.alert}));return svg}\nfunction chip(kind){const labels={passed:'Passed',failed:'Failed',timedout:'Timed out',flaky:'Flaky',skipped:'Skipped'};const marks={passed:'\u2713',failed:'\u00D7',timedout:'!',flaky:'\u219D',skipped:'\u2013'};return h('span',{class:'status-chip '+kind},h('span',{class:'mark','aria-hidden':'true',text:marks[kind]||'!'}),labels[kind]||kind)}\nfunction recentStrip(value){const wrap=h('span',{class:'recent','aria-label':'Previous results: '+recentToKinds(value||'').join(', ')});for(const kind of recentToKinds(value||''))wrap.appendChild(h('span',{class:kind,title:kind}));return wrap}\nfunction link(label,url){const href=safeHref(url||'');return href?h('a',{href,rel:'noopener noreferrer',text:label}):h('span',{text:label})}\nfunction setState(patch){state={...state,...patch};const hash=buildHash(state);if(location.hash!==hash)history.replaceState(null,'',location.pathname+location.search+hash);renderAll()}\nfunction validateState(){if(state.project&&!allTests.some(test=>test.project===state.project))state.project='';if(state.tag&&!allTests.some(test=>test.tags.includes(state.tag)))state.tag='';if(state.test&&!byId.has(state.test))state.test='';if(state.test&&state.attempt>=byId.get(state.test).attempts.length)state.attempt=0}\nfunction toast(message){const el=document.getElementById('lb-toast');el.textContent=message;el.hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>{el.hidden=true},2300)}\nasync function copyText(value){try{await navigator.clipboard.writeText(value);toast('Copied')}catch{const area=h('textarea',{'aria-label':'Copy text'});area.value=value;document.body.appendChild(area);area.select();try{document.execCommand('copy');toast('Copied')}catch{toast('Select and copy the text')}area.remove()}}\nfunction applyTheme(theme){document.documentElement.setAttribute('data-theme',theme);const button=document.getElementById('lb-theme');button.textContent=theme==='dark'?'Light mode':'Dark mode';button.setAttribute('aria-label',theme==='dark'?'Switch to light mode':'Switch to dark mode');try{localStorage.setItem('logbook-theme',theme)}catch{}}\nfunction toggleTheme(){const next=document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark';applyTheme(next);toast(next==='dark'?'Dark mode on':'Light mode on')}\nfunction renderAll(){for(const button of tabButtons){const active=button.dataset.tab===state.tab;button.setAttribute('aria-selected',String(active));button.tabIndex=active?0:-1;document.getElementById('tab-'+button.dataset.tab).hidden=!active}if(state.tab==='tests'){renderTests();renderPagination();decorateTestRows();makeGroupsCollapsible()}else if(state.tab==='failures')renderFailures();else if(state.tab==='trends')renderTrends();else if(state.tab==='flaky')renderFlaky();else if(state.tab==='run')renderRun();else renderProject();renderPanel()}\nfunction decorateTestRows(){for(const row of document.querySelectorAll('#lb-tests-body tr[data-test-id]')){\n const test=byId.get(row.dataset.testId),name=row.children[1],tags=row.children[3];\n if(test.titlePath.length>1)name.appendChild(h('small',{text:test.titlePath.slice(0,-1).join(' \u203A ')}));\n clear(tags);for(const tag of test.tags.slice(0,3))tags.appendChild(h('span',{class:'tag-chip',text:tag}));\n if(test.tags.length>3)tags.appendChild(h('span',{class:'tag-chip',text:'+'+(test.tags.length-3)}));\n for(const id of test.caseIds)tags.appendChild(h('span',{class:'case-chip',text:id}));\n}}\nfunction makeGroupsCollapsible(){\n if(state.group!=='file')return;\n const body=document.getElementById('lb-tests-body');\n const rows=[...body.querySelectorAll('tr[data-test-id]')];\n if(!rows.length)return;\n clear(body);\n const files=[...new Set(rows.map(row=>byId.get(row.dataset.testId).file))].sort();\n for(const file of files){\n const members=rows.filter(row=>byId.get(row.dataset.testId).file===file);\n const total=visibleRows.filter(test=>test.file===file).length;\n const worst=members.map(row=>statusKind(byId.get(row.dataset.testId))).sort((a,b)=>['failed','timedout','flaky','skipped','passed'].indexOf(a)-['failed','timedout','flaky','skipped','passed'].indexOf(b))[0];\n const header=h('tr',{class:'group-header',tabindex:'0','aria-expanded':'true'},h('td',{colspan:'7'},file+' \u00B7 '+total+' tests ',chip(worst)));\n const toggle=()=>{const expanded=header.getAttribute('aria-expanded')==='true';header.setAttribute('aria-expanded',String(!expanded));for(const row of members)row.hidden=expanded};\n header.addEventListener('click',toggle);\n header.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){event.preventDefault();toggle()}});\n body.appendChild(header);for(const row of members)body.appendChild(row);\n }\n}\nfunction selectTab(name,focus){setState({tab:name});if(focus)tabButtons[tabs.indexOf(name)].focus()}\nfunction table(headings,rows){const head=h('thead',{},h('tr',{},headings.map(name=>h('th',{text:name}))));const body=h('tbody',{},rows.map(values=>h('tr',{},values.map(value=>h('td',{},value===null||value===undefined?'':value)))));return h('div',{class:'table-wrap'},h('table',{},head,body))}\nfunction openTest(id,origin){if(origin)panelReturn=origin;setState({test:id,attempt:0})}\nfunction closePanel(){setState({test:'',attempt:0});if(panelReturn&&panelReturn.isConnected)panelReturn.focus();panelReturn=null}\nfunction handleKeys(event){if(event.metaKey||event.ctrlKey||event.altKey||/^(INPUT|TEXTAREA|SELECT)$/.test(event.target.tagName))return;const key=event.key;if(key>='1'&&key<='6'){selectTab(tabs[Number(key)-1],true);return}if(key==='Escape'){if(state.test){closePanel();return}const dialog=document.querySelector('dialog[open]');if(dialog)dialog.close();return}if(key==='t'){toggleTheme();return}if(key==='?'){showHelp();return}if(key==='/'&&state.tab==='tests'){event.preventDefault();document.getElementById('lb-search')?.focus();return}if(key==='['||key===']'){moveFailure(key===']'?1:-1);return}if(state.tab==='tests'&&(key==='j'||key==='k')){const rows=[...document.querySelectorAll('#lb-tests-body tr[data-test-id]')];const at=rows.indexOf(document.activeElement);rows[Math.max(0,Math.min(rows.length-1,at+(key==='j'?1:-1)))]?.focus();return}if((key==='Enter'||key==='o')&&document.activeElement?.dataset?.testId){openTest(document.activeElement.dataset.testId,document.activeElement)}}\nfunction moveFailure(step){const ids=allTests.filter(test=>test.outcome==='unexpected'||test.outcome==='flaky').map(test=>test.testId);if(!ids.length)return;const at=ids.indexOf(state.test);openTest(ids[(at+step+ids.length)%ids.length])}\nfunction init(){\n let saved='light';try{if(localStorage.getItem('logbook-theme')==='dark')saved='dark'}catch{}applyTheme(saved);\n for(const button of tabButtons){\n button.addEventListener('click',()=>selectTab(button.dataset.tab));\n button.addEventListener('keydown',event=>{if(event.key==='ArrowRight'||event.key==='ArrowLeft'){\n event.preventDefault();const step=event.key==='ArrowRight'?1:-1;\n selectTab(tabs[(tabs.indexOf(button.dataset.tab)+step+tabs.length)%tabs.length],true)\n }});\n }\n document.getElementById('lb-theme').addEventListener('click',toggleTheme);\n document.getElementById('lb-copy-summary').addEventListener('click',()=>copyText(model.summaryMarkdown||''));\n document.getElementById('lb-download').addEventListener('click',()=>{\n const blob=new Blob([JSON.stringify(model.run,null,2)+'\\n'],{type:'application/json'});\n const url=URL.createObjectURL(blob),a=h('a',{href:url,download:model.run.runId+'.json'});\n document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url);\n });\n document.getElementById('lb-print').addEventListener('click',()=>window.print());\n window.addEventListener('beforeprint',()=>{renderFailures();renderTrends();renderFlaky();renderRun();renderProject();for(const details of document.querySelectorAll('details'))details.open=true});\n document.getElementById('lb-help').addEventListener('click',showHelp);\n document.getElementById('lb-panel-close').addEventListener('click',closePanel);\n const panelClose=document.getElementById('lb-panel-close'),panelHead=panelClose.parentElement;\n panelHead.insertBefore(h('button',{class:'button',type:'button','aria-label':'Previous failing test',onclick:()=>moveFailure(-1),text:'\u2190'}),panelClose);\n panelHead.insertBefore(h('button',{class:'button',type:'button','aria-label':'Next failing test',onclick:()=>moveFailure(1),text:'\u2192'}),panelClose);\n document.getElementById('lb-backdrop').addEventListener('click',closePanel);\n const mark=document.querySelector('.wordmark');mark.textContent='logbook';mark.prepend(icon('book'));\n const topStatus=document.querySelector('.topbar .status-chip');topStatus.prepend(h('span',{class:'mark','aria-hidden':'true',text:model.run.status==='passed'?'\u2713':'\u00D7'}));\n document.getElementById('lb-new-failures')?.addEventListener('click',()=>selectTab('failures'));\n window.addEventListener('hashchange',()=>{state=parseHash(location.hash);validateState();renderAll()});\n document.addEventListener('keydown',handleKeys);\n for(const button of document.querySelectorAll('[data-card-kind]'))button.addEventListener('click',()=>setState({tab:'tests',status:button.dataset.cardKind==='total'?[]:[button.dataset.cardKind]}));\n const start=document.querySelector('#lb-header time');if(start){const parsed=new Date(start.dateTime);if(!Number.isNaN(parsed.getTime()))start.textContent=parsed.toLocaleString()}\n validateState();\n renderAll();\n}\n";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const CORE_JS = `
|
|
2
|
+
const model=JSON.parse(document.getElementById('lb-data').textContent);
|
|
3
|
+
const links=JSON.parse(document.getElementById('lb-links').textContent);
|
|
4
|
+
const allTests=model.run.tests;
|
|
5
|
+
const byId=new Map(allTests.map(test=>[test.testId,test]));
|
|
6
|
+
const tabs=['tests','failures','trends','flaky','run','project'];
|
|
7
|
+
const tabButtons=[...document.querySelectorAll('[data-tab]')];
|
|
8
|
+
let state=parseHash(location.hash),panelReturn=null,toastTimer=0,visibleRows=[];
|
|
9
|
+
function h(tag,props,...children){const el=document.createElement(tag);for(const [key,value] of Object.entries(props||{})){if(value===null||value===undefined)continue;if(key==='class')el.className=String(value);else if(key==='text')el.textContent=String(value);else if(key.startsWith('on')&&typeof value==='function')el.addEventListener(key.slice(2).toLowerCase(),value);else el.setAttribute(key,String(value))}for(const child of children.flat(Infinity)){if(child===null||child===undefined)continue;el.appendChild(typeof child==='string'||typeof child==='number'?document.createTextNode(String(child)):child)}return el}
|
|
10
|
+
const svgNS='http'+':/'+'/www.w3.org/2000/svg';
|
|
11
|
+
function s(tag,props,...children){const el=document.createElementNS(svgNS,tag);for(const [key,value] of Object.entries(props||{}))el.setAttribute(key,String(value));for(const child of children.flat(Infinity)){if(child!==null&&child!==undefined)el.appendChild(typeof child==='string'||typeof child==='number'?document.createTextNode(String(child)):child)}return el}
|
|
12
|
+
function clear(el){while(el.firstChild)el.removeChild(el.firstChild)}
|
|
13
|
+
function icon(name){const svg=s('svg',{viewBox:'0 0 24 24',class:'icon','aria-hidden':'true'});svg.appendChild(s('path',{d:icons[name]||icons.alert}));return svg}
|
|
14
|
+
function chip(kind){const labels={passed:'Passed',failed:'Failed',timedout:'Timed out',flaky:'Flaky',skipped:'Skipped'};const marks={passed:'✓',failed:'×',timedout:'!',flaky:'↝',skipped:'–'};return h('span',{class:'status-chip '+kind},h('span',{class:'mark','aria-hidden':'true',text:marks[kind]||'!'}),labels[kind]||kind)}
|
|
15
|
+
function recentStrip(value){const wrap=h('span',{class:'recent','aria-label':'Previous results: '+recentToKinds(value||'').join(', ')});for(const kind of recentToKinds(value||''))wrap.appendChild(h('span',{class:kind,title:kind}));return wrap}
|
|
16
|
+
function link(label,url){const href=safeHref(url||'');return href?h('a',{href,rel:'noopener noreferrer',text:label}):h('span',{text:label})}
|
|
17
|
+
function setState(patch){state={...state,...patch};const hash=buildHash(state);if(location.hash!==hash)history.replaceState(null,'',location.pathname+location.search+hash);renderAll()}
|
|
18
|
+
function validateState(){if(state.project&&!allTests.some(test=>test.project===state.project))state.project='';if(state.tag&&!allTests.some(test=>test.tags.includes(state.tag)))state.tag='';if(state.test&&!byId.has(state.test))state.test='';if(state.test&&state.attempt>=byId.get(state.test).attempts.length)state.attempt=0}
|
|
19
|
+
function toast(message){const el=document.getElementById('lb-toast');el.textContent=message;el.hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>{el.hidden=true},2300)}
|
|
20
|
+
async function copyText(value){try{await navigator.clipboard.writeText(value);toast('Copied')}catch{const area=h('textarea',{'aria-label':'Copy text'});area.value=value;document.body.appendChild(area);area.select();try{document.execCommand('copy');toast('Copied')}catch{toast('Select and copy the text')}area.remove()}}
|
|
21
|
+
function applyTheme(theme){document.documentElement.setAttribute('data-theme',theme);const button=document.getElementById('lb-theme');button.textContent=theme==='dark'?'Light mode':'Dark mode';button.setAttribute('aria-label',theme==='dark'?'Switch to light mode':'Switch to dark mode');try{localStorage.setItem('logbook-theme',theme)}catch{}}
|
|
22
|
+
function toggleTheme(){const next=document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark';applyTheme(next);toast(next==='dark'?'Dark mode on':'Light mode on')}
|
|
23
|
+
function renderAll(){for(const button of tabButtons){const active=button.dataset.tab===state.tab;button.setAttribute('aria-selected',String(active));button.tabIndex=active?0:-1;document.getElementById('tab-'+button.dataset.tab).hidden=!active}if(state.tab==='tests'){renderTests();renderPagination();decorateTestRows();makeGroupsCollapsible()}else if(state.tab==='failures')renderFailures();else if(state.tab==='trends')renderTrends();else if(state.tab==='flaky')renderFlaky();else if(state.tab==='run')renderRun();else renderProject();renderPanel()}
|
|
24
|
+
function decorateTestRows(){for(const row of document.querySelectorAll('#lb-tests-body tr[data-test-id]')){
|
|
25
|
+
const test=byId.get(row.dataset.testId),name=row.children[1],tags=row.children[3];
|
|
26
|
+
if(test.titlePath.length>1)name.appendChild(h('small',{text:test.titlePath.slice(0,-1).join(' › ')}));
|
|
27
|
+
clear(tags);for(const tag of test.tags.slice(0,3))tags.appendChild(h('span',{class:'tag-chip',text:tag}));
|
|
28
|
+
if(test.tags.length>3)tags.appendChild(h('span',{class:'tag-chip',text:'+'+(test.tags.length-3)}));
|
|
29
|
+
for(const id of test.caseIds)tags.appendChild(h('span',{class:'case-chip',text:id}));
|
|
30
|
+
}}
|
|
31
|
+
function makeGroupsCollapsible(){
|
|
32
|
+
if(state.group!=='file')return;
|
|
33
|
+
const body=document.getElementById('lb-tests-body');
|
|
34
|
+
const rows=[...body.querySelectorAll('tr[data-test-id]')];
|
|
35
|
+
if(!rows.length)return;
|
|
36
|
+
clear(body);
|
|
37
|
+
const files=[...new Set(rows.map(row=>byId.get(row.dataset.testId).file))].sort();
|
|
38
|
+
for(const file of files){
|
|
39
|
+
const members=rows.filter(row=>byId.get(row.dataset.testId).file===file);
|
|
40
|
+
const total=visibleRows.filter(test=>test.file===file).length;
|
|
41
|
+
const worst=members.map(row=>statusKind(byId.get(row.dataset.testId))).sort((a,b)=>['failed','timedout','flaky','skipped','passed'].indexOf(a)-['failed','timedout','flaky','skipped','passed'].indexOf(b))[0];
|
|
42
|
+
const header=h('tr',{class:'group-header',tabindex:'0','aria-expanded':'true'},h('td',{colspan:'7'},file+' · '+total+' tests ',chip(worst)));
|
|
43
|
+
const toggle=()=>{const expanded=header.getAttribute('aria-expanded')==='true';header.setAttribute('aria-expanded',String(!expanded));for(const row of members)row.hidden=expanded};
|
|
44
|
+
header.addEventListener('click',toggle);
|
|
45
|
+
header.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){event.preventDefault();toggle()}});
|
|
46
|
+
body.appendChild(header);for(const row of members)body.appendChild(row);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function selectTab(name,focus){setState({tab:name});if(focus)tabButtons[tabs.indexOf(name)].focus()}
|
|
50
|
+
function table(headings,rows){const head=h('thead',{},h('tr',{},headings.map(name=>h('th',{text:name}))));const body=h('tbody',{},rows.map(values=>h('tr',{},values.map(value=>h('td',{},value===null||value===undefined?'':value)))));return h('div',{class:'table-wrap'},h('table',{},head,body))}
|
|
51
|
+
function openTest(id,origin){if(origin)panelReturn=origin;setState({test:id,attempt:0})}
|
|
52
|
+
function closePanel(){setState({test:'',attempt:0});if(panelReturn&&panelReturn.isConnected)panelReturn.focus();panelReturn=null}
|
|
53
|
+
function handleKeys(event){if(event.metaKey||event.ctrlKey||event.altKey||/^(INPUT|TEXTAREA|SELECT)$/.test(event.target.tagName))return;const key=event.key;if(key>='1'&&key<='6'){selectTab(tabs[Number(key)-1],true);return}if(key==='Escape'){if(state.test){closePanel();return}const dialog=document.querySelector('dialog[open]');if(dialog)dialog.close();return}if(key==='t'){toggleTheme();return}if(key==='?'){showHelp();return}if(key==='/'&&state.tab==='tests'){event.preventDefault();document.getElementById('lb-search')?.focus();return}if(key==='['||key===']'){moveFailure(key===']'?1:-1);return}if(state.tab==='tests'&&(key==='j'||key==='k')){const rows=[...document.querySelectorAll('#lb-tests-body tr[data-test-id]')];const at=rows.indexOf(document.activeElement);rows[Math.max(0,Math.min(rows.length-1,at+(key==='j'?1:-1)))]?.focus();return}if((key==='Enter'||key==='o')&&document.activeElement?.dataset?.testId){openTest(document.activeElement.dataset.testId,document.activeElement)}}
|
|
54
|
+
function moveFailure(step){const ids=allTests.filter(test=>test.outcome==='unexpected'||test.outcome==='flaky').map(test=>test.testId);if(!ids.length)return;const at=ids.indexOf(state.test);openTest(ids[(at+step+ids.length)%ids.length])}
|
|
55
|
+
function init(){
|
|
56
|
+
let saved='light';try{if(localStorage.getItem('logbook-theme')==='dark')saved='dark'}catch{}applyTheme(saved);
|
|
57
|
+
for(const button of tabButtons){
|
|
58
|
+
button.addEventListener('click',()=>selectTab(button.dataset.tab));
|
|
59
|
+
button.addEventListener('keydown',event=>{if(event.key==='ArrowRight'||event.key==='ArrowLeft'){
|
|
60
|
+
event.preventDefault();const step=event.key==='ArrowRight'?1:-1;
|
|
61
|
+
selectTab(tabs[(tabs.indexOf(button.dataset.tab)+step+tabs.length)%tabs.length],true)
|
|
62
|
+
}});
|
|
63
|
+
}
|
|
64
|
+
document.getElementById('lb-theme').addEventListener('click',toggleTheme);
|
|
65
|
+
document.getElementById('lb-copy-summary').addEventListener('click',()=>copyText(model.summaryMarkdown||''));
|
|
66
|
+
document.getElementById('lb-download').addEventListener('click',()=>{
|
|
67
|
+
const blob=new Blob([JSON.stringify(model.run,null,2)+'\\n'],{type:'application/json'});
|
|
68
|
+
const url=URL.createObjectURL(blob),a=h('a',{href:url,download:model.run.runId+'.json'});
|
|
69
|
+
document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url);
|
|
70
|
+
});
|
|
71
|
+
document.getElementById('lb-print').addEventListener('click',()=>window.print());
|
|
72
|
+
window.addEventListener('beforeprint',()=>{renderFailures();renderTrends();renderFlaky();renderRun();renderProject();for(const details of document.querySelectorAll('details'))details.open=true});
|
|
73
|
+
document.getElementById('lb-help').addEventListener('click',showHelp);
|
|
74
|
+
document.getElementById('lb-panel-close').addEventListener('click',closePanel);
|
|
75
|
+
const panelClose=document.getElementById('lb-panel-close'),panelHead=panelClose.parentElement;
|
|
76
|
+
panelHead.insertBefore(h('button',{class:'button',type:'button','aria-label':'Previous failing test',onclick:()=>moveFailure(-1),text:'←'}),panelClose);
|
|
77
|
+
panelHead.insertBefore(h('button',{class:'button',type:'button','aria-label':'Next failing test',onclick:()=>moveFailure(1),text:'→'}),panelClose);
|
|
78
|
+
document.getElementById('lb-backdrop').addEventListener('click',closePanel);
|
|
79
|
+
const mark=document.querySelector('.wordmark');mark.textContent='logbook';mark.prepend(icon('book'));
|
|
80
|
+
const topStatus=document.querySelector('.topbar .status-chip');topStatus.prepend(h('span',{class:'mark','aria-hidden':'true',text:model.run.status==='passed'?'✓':'×'}));
|
|
81
|
+
document.getElementById('lb-new-failures')?.addEventListener('click',()=>selectTab('failures'));
|
|
82
|
+
window.addEventListener('hashchange',()=>{state=parseHash(location.hash);validateState();renderAll()});
|
|
83
|
+
document.addEventListener('keydown',handleKeys);
|
|
84
|
+
for(const button of document.querySelectorAll('[data-card-kind]'))button.addEventListener('click',()=>setState({tab:'tests',status:button.dataset.cardKind==='total'?[]:[button.dataset.cardKind]}));
|
|
85
|
+
const start=document.querySelector('#lb-header time');if(start){const parsed=new Date(start.dateTime);if(!Number.isNaN(parsed.getTime()))start.textContent=parsed.toLocaleString()}
|
|
86
|
+
validateState();
|
|
87
|
+
renderAll();
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/template/js/core.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwFtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const FAILURES_JS = "\nfunction renderFailures(){\n const panel=document.getElementById('tab-failures');clear(panel);panel.appendChild(h('h2',{text:'Failures'}));\n const comparison=model.comparison;\n if(comparison){\n const cards=h('div',{class:'grid-cards'});\n for(const [key,label] of [['newFailures','New failures'],['fixed','Fixed'],['stillFailing','Still failing'],['newTests','New tests'],['removedTests','Removed tests']]){\n const items=comparison[key]||[],card=h('div',{class:'section-card'},h('strong',{text:label+' \u00B7 '+items.length}));\n for(const item of items.slice(0,10)){\n if(key==='removedTests'||!byId.has(item.testId)){card.appendChild(h('p',{text:item.title}));continue}\n const button=h('button',{class:'button',type:'button',text:item.title});\n button.addEventListener('click',()=>openTest(item.testId,button));card.appendChild(button);\n }\n cards.appendChild(card);\n }\n panel.appendChild(cards);\n }\n const groups=model.errorGroups||[];\n if(!groups.length){panel.appendChild(h('p',{class:'empty',text:'No failures or flaky tests in this run.'}));return}\n for(const group of groups){\n const box=h('details',{class:'section-card'});\n box.appendChild(h('summary',{},group.signature+' \u00B7 '+group.count+' tests \u00B7 '+group.projects.join(', '),\n group.newCount?h('span',{class:'status-chip failed',text:' NEW '+group.newCount}):null));\n for(const id of group.testIds){\n const test=byId.get(id);if(!test)continue;\n const button=h('button',{class:'button',type:'button',text:test.title+' \u00B7 '+test.project});\n button.addEventListener('click',()=>openTest(id,button));box.appendChild(h('p',{},button));\n }\n panel.appendChild(box);\n }\n}\n";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const FAILURES_JS = `
|
|
2
|
+
function renderFailures(){
|
|
3
|
+
const panel=document.getElementById('tab-failures');clear(panel);panel.appendChild(h('h2',{text:'Failures'}));
|
|
4
|
+
const comparison=model.comparison;
|
|
5
|
+
if(comparison){
|
|
6
|
+
const cards=h('div',{class:'grid-cards'});
|
|
7
|
+
for(const [key,label] of [['newFailures','New failures'],['fixed','Fixed'],['stillFailing','Still failing'],['newTests','New tests'],['removedTests','Removed tests']]){
|
|
8
|
+
const items=comparison[key]||[],card=h('div',{class:'section-card'},h('strong',{text:label+' · '+items.length}));
|
|
9
|
+
for(const item of items.slice(0,10)){
|
|
10
|
+
if(key==='removedTests'||!byId.has(item.testId)){card.appendChild(h('p',{text:item.title}));continue}
|
|
11
|
+
const button=h('button',{class:'button',type:'button',text:item.title});
|
|
12
|
+
button.addEventListener('click',()=>openTest(item.testId,button));card.appendChild(button);
|
|
13
|
+
}
|
|
14
|
+
cards.appendChild(card);
|
|
15
|
+
}
|
|
16
|
+
panel.appendChild(cards);
|
|
17
|
+
}
|
|
18
|
+
const groups=model.errorGroups||[];
|
|
19
|
+
if(!groups.length){panel.appendChild(h('p',{class:'empty',text:'No failures or flaky tests in this run.'}));return}
|
|
20
|
+
for(const group of groups){
|
|
21
|
+
const box=h('details',{class:'section-card'});
|
|
22
|
+
box.appendChild(h('summary',{},group.signature+' · '+group.count+' tests · '+group.projects.join(', '),
|
|
23
|
+
group.newCount?h('span',{class:'status-chip failed',text:' NEW '+group.newCount}):null));
|
|
24
|
+
for(const id of group.testIds){
|
|
25
|
+
const test=byId.get(id);if(!test)continue;
|
|
26
|
+
const button=h('button',{class:'button',type:'button',text:test.title+' · '+test.project});
|
|
27
|
+
button.addEventListener('click',()=>openTest(id,button));box.appendChild(h('p',{},button));
|
|
28
|
+
}
|
|
29
|
+
panel.appendChild(box);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
//# sourceMappingURL=failures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.js","sourceRoot":"","sources":["../../../src/template/js/failures.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HELP_JS = "\nfunction showHelp(){const dialog=document.getElementById('lb-help-dialog');if(!dialog.childNodes.length){dialog.appendChild(h('h2',{text:'Keyboard shortcuts'}));dialog.appendChild(table(['Key','Action'],[['1\u20136','Switch tabs'],['/','Search tests'],['j / k','Move between rows'],['Enter / o','Open test'],['Esc','Close panel'],['[ / ]','Previous / next failure'],['t','Theme'],['?','Help']]));dialog.appendChild(h('button',{class:'button',type:'button',onclick:()=>dialog.close(),text:'Close'}))}dialog.showModal()}\n";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const HELP_JS = `
|
|
2
|
+
function showHelp(){const dialog=document.getElementById('lb-help-dialog');if(!dialog.childNodes.length){dialog.appendChild(h('h2',{text:'Keyboard shortcuts'}));dialog.appendChild(table(['Key','Action'],[['1–6','Switch tabs'],['/','Search tests'],['j / k','Move between rows'],['Enter / o','Open test'],['Esc','Close panel'],['[ / ]','Previous / next failure'],['t','Theme'],['?','Help']]));dialog.appendChild(h('button',{class:'button',type:'button',onclick:()=>dialog.close(),text:'Close'}))}dialog.showModal()}
|
|
3
|
+
`;
|
|
4
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../../src/template/js/help.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG;;CAEtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PANEL_JS = "\nlet panelShown='';\nfunction renderPanel(){\n const panel=document.getElementById('lb-panel'),backdrop=document.getElementById('lb-backdrop');\n const content=document.getElementById('lb-panel-content'),test=byId.get(state.test);\n panel.hidden=!test;backdrop.hidden=!test;if(!test){panelShown='';return}\n clear(content);\n const heading=document.getElementById('lb-panel-heading');heading.textContent=test.title;\n const fresh=panelShown!==test.testId;panelShown=test.testId;\n content.appendChild(chip(statusKind(test)));\n content.appendChild(h('p',{class:'muted',text:test.titlePath.join(' \u203A ')}));\n content.appendChild(h('p',{},test.file+':'+test.line+' \u00B7 '+(test.project||'(default)')+' \u00B7 '+formatDuration(test.durationMs)));\n if(test.tags.length||test.caseIds.length)content.appendChild(h('p',{class:'muted',text:[...test.tags,...test.caseIds].join(' \u00B7 ')}));\n content.appendChild(h('p',{class:'muted',text:test.attemptCount+' attempts'+(test.timing?' \u00B7 worker '+test.timing.workerIndex:'')}));\n content.appendChild(h('h3',{text:'Rerun'}));\n const command=rerunCommand(test);content.appendChild(h('pre',{text:command}));\n content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(command)},icon('copy'),'Copy rerun command'));\n content.appendChild(h('h3',{text:'AI-ready debug context'}));\n content.appendChild(h('p',{class:'muted',text:'Evidence only, not a diagnosis. Preview and review for secrets before sharing.'}));\n const debugPacket=buildDebugPacket(model.run,test.testId,model.recent?.[test.testId]||'',model.attachmentAvailability||{},detectSignals);\n content.appendChild(h('h3',{text:'Debugging clues'}));\n content.appendChild(h('p',{class:'muted',text:'Inferences from recorded evidence, not Playwright facts or a diagnosis.'}));\n for(const signal of debugPacket.signals)content.appendChild(h('p',{},h('strong',{text:signal.label+' \u00B7 '}),signal.explanation,h('small',{text:signal.evidenceIds.length?'Evidence: '+signal.evidenceIds.join(', '):'Insufficient evidence'})));\n const debugText=debugPacketMarkdown(debugPacket);\n const preview=h('details',{},h('summary',{text:'Preview debug context'}),h('pre',{text:debugText}));\n content.appendChild(preview);\n content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(debugText),text:'Copy debug context'}));\n const attempt=test.attempts[Math.min(state.attempt,test.attempts.length-1)];\n if(test.attempts.length>1){\n content.appendChild(h('h3',{text:'Attempts'}));const switcher=h('div',{class:'attempts'});\n test.attempts.forEach((item,index)=>switcher.appendChild(h('button',{class:'button',type:'button','aria-pressed':String(index===state.attempt),onclick:()=>setState({attempt:index}),text:'Attempt '+(index+1)+' \u00B7 '+item.status+' \u00B7 '+formatDuration(item.durationMs)})));\n content.appendChild(switcher);\n }\n const errors=attempt?.errors?.length?attempt.errors:test.firstError?[test.firstError]:[];\n if(errors.length){content.appendChild(h('h3',{text:'Error'}));for(const error of errors){\n content.appendChild(h('p',{text:error.message.split('\\n')[0]}));\n if(error.snippet)content.appendChild(h('pre',{text:error.snippet}));\n if(error.stack)content.appendChild(h('details',{},h('summary',{text:'Stack trace'}),h('pre',{text:error.stack})));\n }}\n if(test.annotations?.length){content.appendChild(h('h3',{text:'Annotations'}));for(const item of test.annotations)content.appendChild(h('p',{text:item.type+(item.description?' \u00B7 '+item.description:'')}))}\n if(attempt?.attachments?.length){content.appendChild(h('h3',{text:'Attachments'}));for(const attachment of attempt.attachments){\n const availability=attachment.path?(model.attachmentAvailability?.[attachment.path]||'unknown'):'inline';\n const href=availability==='missing'?null:attachment.path&&links[attachment.path];\n const traceAttachment=attachment.name==='trace'&&attachment.contentType==='application/zip';\n const kind=traceAttachment?'Trace':attachment.contentType?.startsWith('image/')?'Screenshot':attachment.contentType?.startsWith('video/')?'Video':'Attachment';\n content.appendChild(h('p',{},h('strong',{text:kind+' \u00B7 '}),link(attachment.name,href),h('small',{text:availability==='missing'?'File not retained':availability==='unknown'?'Link unverified':availability==='inline'?'Inline attachment':''})));\n if(traceAttachment&&attachment.path&&availability!=='missing'){\n const trace=traceCommand(attachment.path||'');content.appendChild(h('pre',{text:trace}));\n content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(trace),text:'Copy trace command'}));\n }\n if(attachment.dataUri&&new RegExp('^data:image/(png|jpeg);base64,').test(attachment.dataUri)){\n const thumb=h('img',{src:attachment.dataUri,alt:attachment.name,style:'max-width:100%;max-height:220px'});\n thumb.addEventListener('click',()=>showImage(attachment.dataUri,attachment.name));content.appendChild(thumb);\n }\n }content.appendChild(h('p',{class:'muted',text:'Traces and attachments may contain page data or secrets. Review before sharing.'}))}\n if(attempt?.steps?.length){content.appendChild(h('h3',{text:'Steps'}));const list=h('ol',{class:'steps'});\n const max=Math.max(1,...attempt.steps.map(step=>step.durationMs));let failedStep=null;\n for(const step of attempt.steps){const item=h('li',{class:step.failed?'failed':'',style:'margin-left:'+(step.depth*12)+'px'},\n h('span',{text:step.title+' \u00B7 '+formatDuration(step.durationMs)}),h('span',{class:'heat'+(step.failed?' hot':'')},h('span',{style:'width:'+Math.max(2,Math.round(step.durationMs/max*100))+'%'})));\n if(step.failed&&!failedStep)failedStep=item;list.appendChild(item)}content.appendChild(list);\n if(fresh&&failedStep)requestAnimationFrame(()=>failedStep.scrollIntoView({block:'nearest'}));\n }\n if(attempt?.stdout||attempt?.stderr){content.appendChild(h('h3',{text:'Output'}));\n const tabs=h('div',{class:'attempts'}),output=h('pre',{});\n for(const [label,value] of [['stdout',attempt.stdout],['stderr',attempt.stderr]])if(value){\n const button=h('button',{class:'button',type:'button','aria-pressed':String(!output.textContent),text:label});\n button.addEventListener('click',()=>{output.textContent=value;for(const other of tabs.children)other.setAttribute('aria-pressed',String(other===button))});\n tabs.appendChild(button);if(!output.textContent)output.textContent=value;\n }\n content.appendChild(tabs);content.appendChild(output);\n }\n if(!test.attempts.some(item=>item.steps?.length||item.stdout||item.stderr||item.attachments?.some(a=>a.dataUri)))content.appendChild(h('p',{class:'muted',text:'Enable captureDetails to see steps, output and screenshots.'}));\n content.appendChild(h('h3',{text:'Last 10 results'}));content.appendChild(recentStrip(model.recent?.[test.testId]));\n const previous=model.recent?.[test.testId]?.at(-1);\n content.appendChild(h('p',{class:'muted',text:'Previous run: '+({'p':'passed','f':'failed','k':'flaky','s':'skipped','-':'absent'}[previous]||'absent')}));\n content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(location.href)},icon('link'),'Copy link'));\n if(fresh)heading.focus();\n}\nfunction showImage(dataUri,label){const dialog=document.getElementById('lb-lightbox');clear(dialog);\n dialog.appendChild(h('button',{class:'button',type:'button',onclick:()=>dialog.close(),text:'Close'}));\n dialog.appendChild(h('img',{src:dataUri,alt:label,style:'max-width:90vw;max-height:80vh'}));dialog.showModal();\n}\n";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const PANEL_JS = `
|
|
2
|
+
let panelShown='';
|
|
3
|
+
function renderPanel(){
|
|
4
|
+
const panel=document.getElementById('lb-panel'),backdrop=document.getElementById('lb-backdrop');
|
|
5
|
+
const content=document.getElementById('lb-panel-content'),test=byId.get(state.test);
|
|
6
|
+
panel.hidden=!test;backdrop.hidden=!test;if(!test){panelShown='';return}
|
|
7
|
+
clear(content);
|
|
8
|
+
const heading=document.getElementById('lb-panel-heading');heading.textContent=test.title;
|
|
9
|
+
const fresh=panelShown!==test.testId;panelShown=test.testId;
|
|
10
|
+
content.appendChild(chip(statusKind(test)));
|
|
11
|
+
content.appendChild(h('p',{class:'muted',text:test.titlePath.join(' › ')}));
|
|
12
|
+
content.appendChild(h('p',{},test.file+':'+test.line+' · '+(test.project||'(default)')+' · '+formatDuration(test.durationMs)));
|
|
13
|
+
if(test.tags.length||test.caseIds.length)content.appendChild(h('p',{class:'muted',text:[...test.tags,...test.caseIds].join(' · ')}));
|
|
14
|
+
content.appendChild(h('p',{class:'muted',text:test.attemptCount+' attempts'+(test.timing?' · worker '+test.timing.workerIndex:'')}));
|
|
15
|
+
content.appendChild(h('h3',{text:'Rerun'}));
|
|
16
|
+
const command=rerunCommand(test);content.appendChild(h('pre',{text:command}));
|
|
17
|
+
content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(command)},icon('copy'),'Copy rerun command'));
|
|
18
|
+
content.appendChild(h('h3',{text:'AI-ready debug context'}));
|
|
19
|
+
content.appendChild(h('p',{class:'muted',text:'Evidence only, not a diagnosis. Preview and review for secrets before sharing.'}));
|
|
20
|
+
const debugPacket=buildDebugPacket(model.run,test.testId,model.recent?.[test.testId]||'',model.attachmentAvailability||{},detectSignals);
|
|
21
|
+
content.appendChild(h('h3',{text:'Debugging clues'}));
|
|
22
|
+
content.appendChild(h('p',{class:'muted',text:'Inferences from recorded evidence, not Playwright facts or a diagnosis.'}));
|
|
23
|
+
for(const signal of debugPacket.signals)content.appendChild(h('p',{},h('strong',{text:signal.label+' · '}),signal.explanation,h('small',{text:signal.evidenceIds.length?'Evidence: '+signal.evidenceIds.join(', '):'Insufficient evidence'})));
|
|
24
|
+
const debugText=debugPacketMarkdown(debugPacket);
|
|
25
|
+
const preview=h('details',{},h('summary',{text:'Preview debug context'}),h('pre',{text:debugText}));
|
|
26
|
+
content.appendChild(preview);
|
|
27
|
+
content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(debugText),text:'Copy debug context'}));
|
|
28
|
+
const attempt=test.attempts[Math.min(state.attempt,test.attempts.length-1)];
|
|
29
|
+
if(test.attempts.length>1){
|
|
30
|
+
content.appendChild(h('h3',{text:'Attempts'}));const switcher=h('div',{class:'attempts'});
|
|
31
|
+
test.attempts.forEach((item,index)=>switcher.appendChild(h('button',{class:'button',type:'button','aria-pressed':String(index===state.attempt),onclick:()=>setState({attempt:index}),text:'Attempt '+(index+1)+' · '+item.status+' · '+formatDuration(item.durationMs)})));
|
|
32
|
+
content.appendChild(switcher);
|
|
33
|
+
}
|
|
34
|
+
const errors=attempt?.errors?.length?attempt.errors:test.firstError?[test.firstError]:[];
|
|
35
|
+
if(errors.length){content.appendChild(h('h3',{text:'Error'}));for(const error of errors){
|
|
36
|
+
content.appendChild(h('p',{text:error.message.split('\\n')[0]}));
|
|
37
|
+
if(error.snippet)content.appendChild(h('pre',{text:error.snippet}));
|
|
38
|
+
if(error.stack)content.appendChild(h('details',{},h('summary',{text:'Stack trace'}),h('pre',{text:error.stack})));
|
|
39
|
+
}}
|
|
40
|
+
if(test.annotations?.length){content.appendChild(h('h3',{text:'Annotations'}));for(const item of test.annotations)content.appendChild(h('p',{text:item.type+(item.description?' · '+item.description:'')}))}
|
|
41
|
+
if(attempt?.attachments?.length){content.appendChild(h('h3',{text:'Attachments'}));for(const attachment of attempt.attachments){
|
|
42
|
+
const availability=attachment.path?(model.attachmentAvailability?.[attachment.path]||'unknown'):'inline';
|
|
43
|
+
const href=availability==='missing'?null:attachment.path&&links[attachment.path];
|
|
44
|
+
const traceAttachment=attachment.name==='trace'&&attachment.contentType==='application/zip';
|
|
45
|
+
const kind=traceAttachment?'Trace':attachment.contentType?.startsWith('image/')?'Screenshot':attachment.contentType?.startsWith('video/')?'Video':'Attachment';
|
|
46
|
+
content.appendChild(h('p',{},h('strong',{text:kind+' · '}),link(attachment.name,href),h('small',{text:availability==='missing'?'File not retained':availability==='unknown'?'Link unverified':availability==='inline'?'Inline attachment':''})));
|
|
47
|
+
if(traceAttachment&&attachment.path&&availability!=='missing'){
|
|
48
|
+
const trace=traceCommand(attachment.path||'');content.appendChild(h('pre',{text:trace}));
|
|
49
|
+
content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(trace),text:'Copy trace command'}));
|
|
50
|
+
}
|
|
51
|
+
if(attachment.dataUri&&new RegExp('^data:image/(png|jpeg);base64,').test(attachment.dataUri)){
|
|
52
|
+
const thumb=h('img',{src:attachment.dataUri,alt:attachment.name,style:'max-width:100%;max-height:220px'});
|
|
53
|
+
thumb.addEventListener('click',()=>showImage(attachment.dataUri,attachment.name));content.appendChild(thumb);
|
|
54
|
+
}
|
|
55
|
+
}content.appendChild(h('p',{class:'muted',text:'Traces and attachments may contain page data or secrets. Review before sharing.'}))}
|
|
56
|
+
if(attempt?.steps?.length){content.appendChild(h('h3',{text:'Steps'}));const list=h('ol',{class:'steps'});
|
|
57
|
+
const max=Math.max(1,...attempt.steps.map(step=>step.durationMs));let failedStep=null;
|
|
58
|
+
for(const step of attempt.steps){const item=h('li',{class:step.failed?'failed':'',style:'margin-left:'+(step.depth*12)+'px'},
|
|
59
|
+
h('span',{text:step.title+' · '+formatDuration(step.durationMs)}),h('span',{class:'heat'+(step.failed?' hot':'')},h('span',{style:'width:'+Math.max(2,Math.round(step.durationMs/max*100))+'%'})));
|
|
60
|
+
if(step.failed&&!failedStep)failedStep=item;list.appendChild(item)}content.appendChild(list);
|
|
61
|
+
if(fresh&&failedStep)requestAnimationFrame(()=>failedStep.scrollIntoView({block:'nearest'}));
|
|
62
|
+
}
|
|
63
|
+
if(attempt?.stdout||attempt?.stderr){content.appendChild(h('h3',{text:'Output'}));
|
|
64
|
+
const tabs=h('div',{class:'attempts'}),output=h('pre',{});
|
|
65
|
+
for(const [label,value] of [['stdout',attempt.stdout],['stderr',attempt.stderr]])if(value){
|
|
66
|
+
const button=h('button',{class:'button',type:'button','aria-pressed':String(!output.textContent),text:label});
|
|
67
|
+
button.addEventListener('click',()=>{output.textContent=value;for(const other of tabs.children)other.setAttribute('aria-pressed',String(other===button))});
|
|
68
|
+
tabs.appendChild(button);if(!output.textContent)output.textContent=value;
|
|
69
|
+
}
|
|
70
|
+
content.appendChild(tabs);content.appendChild(output);
|
|
71
|
+
}
|
|
72
|
+
if(!test.attempts.some(item=>item.steps?.length||item.stdout||item.stderr||item.attachments?.some(a=>a.dataUri)))content.appendChild(h('p',{class:'muted',text:'Enable captureDetails to see steps, output and screenshots.'}));
|
|
73
|
+
content.appendChild(h('h3',{text:'Last 10 results'}));content.appendChild(recentStrip(model.recent?.[test.testId]));
|
|
74
|
+
const previous=model.recent?.[test.testId]?.at(-1);
|
|
75
|
+
content.appendChild(h('p',{class:'muted',text:'Previous run: '+({'p':'passed','f':'failed','k':'flaky','s':'skipped','-':'absent'}[previous]||'absent')}));
|
|
76
|
+
content.appendChild(h('button',{class:'button',type:'button',onclick:()=>copyText(location.href)},icon('link'),'Copy link'));
|
|
77
|
+
if(fresh)heading.focus();
|
|
78
|
+
}
|
|
79
|
+
function showImage(dataUri,label){const dialog=document.getElementById('lb-lightbox');clear(dialog);
|
|
80
|
+
dialog.appendChild(h('button',{class:'button',type:'button',onclick:()=>dialog.close(),text:'Close'}));
|
|
81
|
+
dialog.appendChild(h('img',{src:dataUri,alt:label,style:'max-width:90vw;max-height:80vh'}));dialog.showModal();
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
//# sourceMappingURL=panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel.js","sourceRoot":"","sources":["../../../src/template/js/panel.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFvB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PROJECT_JS = "\nfunction renderProject(){\n const panel=document.getElementById('tab-project');clear(panel);const info=model.run.project;\n panel.appendChild(h('h2',{text:'Project'}));\n panel.appendChild(h('p',{class:'muted',text:(info.name||'(unnamed)')+' \u00B7 '+(info.configFile||'No config file')}));\n panel.appendChild(h('h3',{text:'Playwright projects'}));\n const cards=h('div',{class:'grid-cards'});\n for(const item of model.projects||[]){\n const rate=item.total?Math.round(item.passed/item.total*100):0;\n cards.appendChild(h('div',{class:'section-card'},h('strong',{text:item.name||'(default)'}),\n h('p',{text:item.total+' tests \u00B7 '+rate+'% passed'}),h('small',{text:formatDuration(item.durationMs)})));\n }\n panel.appendChild(cards);\n panel.appendChild(h('h3',{text:'Files'}));\n const sort=h('select',{'aria-label':'Sort files'});\n for(const [value,label] of [['file','File'],['failed','Most failed'],['flaky','Most flaky'],['durationMs','Slowest']])sort.appendChild(h('option',{value,text:label}));\n panel.appendChild(sort);const files=h('div',{});panel.appendChild(files);\n function drawFiles(){\n clear(files);const key=sort.value;\n const ordered=[...model.files].sort((a,b)=>key==='file'?(a.file<b.file?-1:a.file>b.file?1:0):b[key]-a[key]||(a.file<b.file?-1:1));\n const rows=ordered.map(item=>{\n const failed=h('span',{},String(item.failed),h('span',{class:'heat hot'},h('span',{style:'width:'+(item.total?Math.round(item.failed/item.total*100):0)+'%'})));\n const flaky=h('span',{},String(item.flaky),h('span',{class:'heat'},h('span',{style:'width:'+(item.total?Math.round(item.flaky/item.total*100):0)+'%'})));\n return [item.file,item.total,failed,flaky,item.skipped,formatDuration(item.durationMs)];\n });\n files.appendChild(table(['File','Total','Failed','Flaky','Skipped','Duration'],rows));\n }\n sort.addEventListener('change',drawFiles);drawFiles();\n panel.appendChild(h('h3',{text:'Tags'}));const tags=h('div',{class:'toolbar'});\n for(const item of model.tags){const button=h('button',{class:'chip-button',type:'button',text:item.tag+' \u00B7 '+item.count});\n button.addEventListener('click',()=>setState({tab:'tests',tag:item.tag}));tags.appendChild(button)}\n panel.appendChild(tags);panel.appendChild(h('h3',{text:'Slowest tests'}));\n panel.appendChild(table(['Test','File','Project','Duration'],model.slowest.map(item=>{\n const button=h('button',{class:'button',type:'button',text:item.title});\n button.addEventListener('click',()=>openTest(item.testId,button));\n return [button,item.file,item.project,formatDuration(item.durationMs)];\n })));\n}\n";
|