@scenetest/dashboard 0.11.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 +64 -0
- package/dist/app.d.ts +12 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +221 -0
- package/dist/app.js.map +1 -0
- package/dist/dev-transport.d.ts +22 -0
- package/dist/dev-transport.d.ts.map +1 -0
- package/dist/dev-transport.js +69 -0
- package/dist/dev-transport.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mount.d.ts +14 -0
- package/dist/mount.d.ts.map +1 -0
- package/dist/mount.js +50 -0
- package/dist/mount.js.map +1 -0
- package/dist/store.d.ts +19 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +164 -0
- package/dist/store.js.map +1 -0
- package/dist/styles.d.ts +10 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +152 -0
- package/dist/styles.js.map +1 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Michael Snook
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @scenetest/dashboard
|
|
2
|
+
|
|
3
|
+
The scenetest dashboard as a mountable widget. The same Preact UI renders the
|
|
4
|
+
live run in dev (inside the Vite plugin's `/__scenetest` page) and in
|
|
5
|
+
scenetest-cloud (a Worker-served page) — the host supplies a DOM element and a
|
|
6
|
+
transport adapter, nothing else.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { mountDashboard, createDevTransport } from '@scenetest/dashboard'
|
|
10
|
+
|
|
11
|
+
const handle = mountDashboard(document.getElementById('root'), {
|
|
12
|
+
transport: createDevTransport(),
|
|
13
|
+
})
|
|
14
|
+
// later: handle.unmount()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The widget renders into a **shadow root** with its own styles and fonts, so it
|
|
18
|
+
drops into any host without leaking styles in either direction and without the
|
|
19
|
+
host using Preact.
|
|
20
|
+
|
|
21
|
+
## Transport adapter
|
|
22
|
+
|
|
23
|
+
The only thing that differs between dev and cloud. The widget calls the
|
|
24
|
+
adapter to fetch a snapshot and subscribe to live events, and pushes user
|
|
25
|
+
actions back as protocol commands:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
interface Transport {
|
|
29
|
+
fetchState(): Promise<RunEvent[]>
|
|
30
|
+
subscribe(onEvent: (e: RunEvent) => void, onStatus?: (s: ConnectionStatus) => void): () => void
|
|
31
|
+
sendCommand(command: Command): Promise<void>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Events and commands are the `@scenetest/protocol` vocabulary. `createDevTransport()`
|
|
36
|
+
speaks to the Vite middleware (fetch + SSE); a cloud adapter speaks to the
|
|
37
|
+
worker (fetch + WebSocket). History may arrive through either `fetchState`
|
|
38
|
+
(snapshot) or the initial `subscribe` burst — the store folds both the same
|
|
39
|
+
way, so a transport picks whichever its backend makes natural (SSE replays the
|
|
40
|
+
buffer through `subscribe`, so the dev adapter's `fetchState` returns empty).
|
|
41
|
+
|
|
42
|
+
## Theming
|
|
43
|
+
|
|
44
|
+
The widget's only theming surface is a small set of CSS custom properties,
|
|
45
|
+
passed as `theme` and applied to the shadow host:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
mountDashboard(el, {
|
|
49
|
+
transport,
|
|
50
|
+
theme: { bg: '#0b0d12', accent: '#7c93ff', font: 'IBM Plex Mono, monospace', fontSize: '12px' },
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
These map to `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size`. Nothing
|
|
55
|
+
else is reachable; they are versioned with the widget, like the wire protocol.
|
|
56
|
+
|
|
57
|
+
## Store
|
|
58
|
+
|
|
59
|
+
`mountDashboard` is the entry point, but the event-folding logic is exported
|
|
60
|
+
separately and is DOM-free — useful for tests, SSR, or computing a rollup:
|
|
61
|
+
|
|
62
|
+
- `foldEvents(events)` / `applyEvent(state, event)` — reduce protocol events into `DashboardState`
|
|
63
|
+
- `initialState()`, `completedSceneCount(state)`, `withConnection(state, status)`
|
|
64
|
+
- `sceneSummary(scene)` — the plain-text "copy failures" summary
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Scene, Transport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The dashboard root. Owns the folded state, drives it from the transport,
|
|
4
|
+
* and turns header controls into protocol commands. The same component
|
|
5
|
+
* renders in dev and cloud — only the injected `transport` differs.
|
|
6
|
+
*/
|
|
7
|
+
export declare function Dashboard({ transport }: {
|
|
8
|
+
transport: Transport;
|
|
9
|
+
}): import("preact").VNode<import("preact").Attributes> | import("preact").VNode<import("preact").Attributes>[];
|
|
10
|
+
/** Build the plain-text "copy failures" summary, matching the original dashboard. */
|
|
11
|
+
export declare function sceneSummary(scene: Scene): string;
|
|
12
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAoC,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAYpF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,+GAoChE;AAiJD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CA0BjD"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { h } from 'preact';
|
|
2
|
+
import { useEffect, useReducer, useState } from 'preact/hooks';
|
|
3
|
+
import htm from 'htm';
|
|
4
|
+
import { applyEvent, completedSceneCount, initialState, withConnection } from './store.js';
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
function reducer(state, action) {
|
|
7
|
+
return action.kind === 'event'
|
|
8
|
+
? applyEvent(state, action.event)
|
|
9
|
+
: withConnection(state, action.status);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The dashboard root. Owns the folded state, drives it from the transport,
|
|
13
|
+
* and turns header controls into protocol commands. The same component
|
|
14
|
+
* renders in dev and cloud — only the injected `transport` differs.
|
|
15
|
+
*/
|
|
16
|
+
export function Dashboard({ transport }) {
|
|
17
|
+
const [state, dispatch] = useReducer(reducer, undefined, initialState);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
let alive = true;
|
|
20
|
+
transport.fetchState().then((events) => {
|
|
21
|
+
if (!alive)
|
|
22
|
+
return;
|
|
23
|
+
for (const event of events)
|
|
24
|
+
dispatch({ kind: 'event', event });
|
|
25
|
+
});
|
|
26
|
+
const unsubscribe = transport.subscribe((event) => dispatch({ kind: 'event', event }), (status) => dispatch({ kind: 'status', status }));
|
|
27
|
+
return () => {
|
|
28
|
+
alive = false;
|
|
29
|
+
unsubscribe();
|
|
30
|
+
};
|
|
31
|
+
}, [transport]);
|
|
32
|
+
const send = (command) => {
|
|
33
|
+
void transport.sendCommand(command);
|
|
34
|
+
};
|
|
35
|
+
return html `
|
|
36
|
+
<div class="root">
|
|
37
|
+
${Header({ state, send })}
|
|
38
|
+
<main>
|
|
39
|
+
${state.scenes.length === 0
|
|
40
|
+
? html `<div class="waiting">
|
|
41
|
+
<h2>Waiting for scene run…</h2>
|
|
42
|
+
<p>Run <code>scenetest</code> to see the live timeline here.</p>
|
|
43
|
+
</div>`
|
|
44
|
+
: state.scenes.map((scene, i) => SceneCard({ scene, index: i, send }))}
|
|
45
|
+
</main>
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
function Header({ state, send }) {
|
|
50
|
+
const [team, setTeam] = useState('');
|
|
51
|
+
const [, force] = useState(0);
|
|
52
|
+
const running = state.running;
|
|
53
|
+
// Tick the elapsed clock while a run is in progress.
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!running)
|
|
56
|
+
return;
|
|
57
|
+
const id = setInterval(() => force((n) => n + 1), 200);
|
|
58
|
+
return () => clearInterval(id);
|
|
59
|
+
}, [running]);
|
|
60
|
+
const completed = completedSceneCount(state);
|
|
61
|
+
const elapsed = state.endDurationMs != null
|
|
62
|
+
? `${state.endDurationMs}ms`
|
|
63
|
+
: state.runStartTime
|
|
64
|
+
? `${Date.now() - state.runStartTime}ms`
|
|
65
|
+
: '—';
|
|
66
|
+
const pct = state.sceneCount > 0 ? Math.round((completed / state.sceneCount) * 100) : 0;
|
|
67
|
+
const progressClass = state.failCount > 0
|
|
68
|
+
? 'progress has-failures'
|
|
69
|
+
: completed === state.sceneCount && state.sceneCount > 0
|
|
70
|
+
? 'progress done'
|
|
71
|
+
: 'progress';
|
|
72
|
+
const replay = () => send({ type: 'run:replay', ...(team ? { team } : {}) });
|
|
73
|
+
return html `
|
|
74
|
+
<header class=${running ? 'running' : ''}>
|
|
75
|
+
<h1><span class="logo">S</span> Scenetest Dashboard</h1>
|
|
76
|
+
<button class="replay-all-btn" disabled=${running} onClick=${replay}>▶ Replay All</button>
|
|
77
|
+
<label class="team-select-wrap">
|
|
78
|
+
Team:
|
|
79
|
+
<select
|
|
80
|
+
value=${team}
|
|
81
|
+
onChange=${(e) => setTeam(e.target.value)}
|
|
82
|
+
>
|
|
83
|
+
<option value="">all teams</option>
|
|
84
|
+
${state.teams.map((t) => html `<option value=${t}>${t}</option>`)}
|
|
85
|
+
</select>
|
|
86
|
+
</label>
|
|
87
|
+
<button onClick=${() => send({ type: 'run:pause' })}>❚❚ Pause</button>
|
|
88
|
+
<button class="stop-btn" onClick=${() => send({ type: 'run:stop' })}>■ Stop</button>
|
|
89
|
+
<div class="spacer"></div>
|
|
90
|
+
<div class="stats">
|
|
91
|
+
<div class="stat"><span class="label">Scenes:</span><span class="value">${completed}/${state.sceneCount}</span></div>
|
|
92
|
+
<div class="stat pass"><span class="label">Pass:</span><span class="value">${state.passCount}</span></div>
|
|
93
|
+
<div class="stat fail"><span class="label">Fail:</span><span class="value">${state.failCount}</span></div>
|
|
94
|
+
<div class="stat"><span class="label">Time:</span><span class="value">${elapsed}</span></div>
|
|
95
|
+
<div
|
|
96
|
+
class=${'conn ' + state.connection}
|
|
97
|
+
title=${'SSE ' + state.connection}
|
|
98
|
+
></div>
|
|
99
|
+
</div>
|
|
100
|
+
${state.sceneCount > 0
|
|
101
|
+
? html `<div class=${progressClass}><div class="progress-fill" style=${`width:${pct}%`}></div></div>`
|
|
102
|
+
: null}
|
|
103
|
+
</header>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
function SceneCard({ scene, index, send, }) {
|
|
107
|
+
const [copied, setCopied] = useState(false);
|
|
108
|
+
const statusMark = scene.status === 'completed' ? '✓' : scene.status === 'running' ? '◷' : '✗';
|
|
109
|
+
const copy = () => {
|
|
110
|
+
copyToClipboard(sceneSummary(scene));
|
|
111
|
+
setCopied(true);
|
|
112
|
+
setTimeout(() => setCopied(false), 1200);
|
|
113
|
+
};
|
|
114
|
+
return html `
|
|
115
|
+
<div class=${'scene ' + (scene.status === 'failed' || scene.status === 'timeout' ? 'failed' : '')}>
|
|
116
|
+
<div class="scene-head">
|
|
117
|
+
<span class=${'scene-status ' + scene.status}>${statusMark}</span>
|
|
118
|
+
<span class="scene-name">${scene.name}</span>
|
|
119
|
+
${scene.file ? html `<span class="scene-file">${scene.file}</span>` : null}
|
|
120
|
+
${scene.team?.name ? html `<span class="scene-team">${scene.team.name}</span>` : null}
|
|
121
|
+
${scene.duration != null ? html `<span class="scene-dur">${scene.duration}ms</span>` : null}
|
|
122
|
+
<button
|
|
123
|
+
class=${'copy-btn' + (copied ? ' copied' : '')}
|
|
124
|
+
title="Copy scene summary"
|
|
125
|
+
onClick=${copy}
|
|
126
|
+
>
|
|
127
|
+
${copied ? '✓ Copied' : '⧉ Copy'}
|
|
128
|
+
</button>
|
|
129
|
+
${scene.file
|
|
130
|
+
? html `<button
|
|
131
|
+
class="copy-btn"
|
|
132
|
+
onClick=${() => send({ type: 'run:replay', file: scene.file })}
|
|
133
|
+
>▶ Replay</button>`
|
|
134
|
+
: null}
|
|
135
|
+
</div>
|
|
136
|
+
<div class="lanes">
|
|
137
|
+
${scene.lanes.map((lane) => html `
|
|
138
|
+
<div class="lane">
|
|
139
|
+
<span class="lane-actor">${lane.actor}</span>
|
|
140
|
+
<div class="lane-items">
|
|
141
|
+
${lane.items.map((item) => html `
|
|
142
|
+
<span class=${'pill ' + item.status} title=${item.error ?? ''}>
|
|
143
|
+
${item.action}${item.target
|
|
144
|
+
? html `<span class="tgt"> ${item.target}</span>`
|
|
145
|
+
: null}
|
|
146
|
+
</span>
|
|
147
|
+
`)}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
`)}
|
|
151
|
+
</div>
|
|
152
|
+
${scene.assertions.length > 0
|
|
153
|
+
? html `<div class="assertions">
|
|
154
|
+
${scene.assertions.map((a) => html `
|
|
155
|
+
<div class=${'assert ' + (a.result ? 'ok' : 'bad')}>
|
|
156
|
+
<span class="mark">${a.result ? '✓' : '✗'}</span>
|
|
157
|
+
${a.actor ? html `<span class="who">[${a.actor}]</span>` : null}
|
|
158
|
+
<span>${a.description}</span>
|
|
159
|
+
</div>
|
|
160
|
+
`)}
|
|
161
|
+
</div>`
|
|
162
|
+
: null}
|
|
163
|
+
${scene.error ? html `<div class="scene-error">${scene.error}</div>` : null}
|
|
164
|
+
</div>
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
/** Build the plain-text "copy failures" summary, matching the original dashboard. */
|
|
168
|
+
export function sceneSummary(scene) {
|
|
169
|
+
const lines = [`Scene: ${scene.name}`];
|
|
170
|
+
if (scene.file)
|
|
171
|
+
lines.push(`File: ${scene.file}`);
|
|
172
|
+
if (scene.status)
|
|
173
|
+
lines.push(`Status: ${scene.status}`);
|
|
174
|
+
if (scene.duration != null)
|
|
175
|
+
lines.push(`Duration: ${scene.duration}ms`);
|
|
176
|
+
const errs = [];
|
|
177
|
+
for (const lane of scene.lanes) {
|
|
178
|
+
for (const item of lane.items) {
|
|
179
|
+
if (item.error) {
|
|
180
|
+
errs.push(` ✗ ${item.action}${item.target ? `(${item.target})` : ''} — ${item.error}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (scene.error && !errs.some((l) => l.includes(scene.error))) {
|
|
185
|
+
errs.push(` ✗ ${scene.error}`);
|
|
186
|
+
}
|
|
187
|
+
if (errs.length > 0) {
|
|
188
|
+
lines.push('', 'Errors:', ...errs);
|
|
189
|
+
}
|
|
190
|
+
const failed = scene.assertions.filter((a) => !a.result);
|
|
191
|
+
if (failed.length > 0) {
|
|
192
|
+
lines.push('', 'Failed assertions:', ...failed.map((a) => ` ✗ [${a.actor ?? ''}] ${a.description}`));
|
|
193
|
+
}
|
|
194
|
+
return lines.join('\n');
|
|
195
|
+
}
|
|
196
|
+
function copyToClipboard(text) {
|
|
197
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
198
|
+
navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
fallbackCopy(text);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function fallbackCopy(text) {
|
|
205
|
+
if (typeof document === 'undefined')
|
|
206
|
+
return;
|
|
207
|
+
const ta = document.createElement('textarea');
|
|
208
|
+
ta.value = text;
|
|
209
|
+
ta.style.position = 'fixed';
|
|
210
|
+
ta.style.opacity = '0';
|
|
211
|
+
document.body.appendChild(ta);
|
|
212
|
+
ta.select();
|
|
213
|
+
try {
|
|
214
|
+
document.execCommand('copy');
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
/* ignore */
|
|
218
|
+
}
|
|
219
|
+
document.body.removeChild(ta);
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Transport } from './types.js';
|
|
2
|
+
export interface DevTransportOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Base path the Vite middleware is mounted at. Defaults to `/__scenetest`.
|
|
5
|
+
* The transport talks to `<base>/events` (SSE) and the command endpoints
|
|
6
|
+
* under `<base>`.
|
|
7
|
+
*/
|
|
8
|
+
base?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Transport adapter for dev mode: the Vite middleware, same-origin.
|
|
12
|
+
*
|
|
13
|
+
* - Live events arrive over the SSE stream at `<base>/events`, which also
|
|
14
|
+
* replays the buffered events for the current run on connect — so history
|
|
15
|
+
* comes through `subscribe`, and `fetchState` returns empty.
|
|
16
|
+
* - Commands map onto the middleware's existing endpoints:
|
|
17
|
+
* `run:replay` → POST `<base>/replay` (`{ file?, team? }`),
|
|
18
|
+
* `run:stop` → POST `<base>/stop`,
|
|
19
|
+
* `run:pause`/`run:resume` → POST `<base>/pause` (a toggle the server owns).
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDevTransport(options?: DevTransportOptions): Transport;
|
|
22
|
+
//# sourceMappingURL=dev-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-transport.d.ts","sourceRoot":"","sources":["../src/dev-transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoB,SAAS,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,SAAS,CA0D/E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isEventShaped } from '@scenetest/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Transport adapter for dev mode: the Vite middleware, same-origin.
|
|
4
|
+
*
|
|
5
|
+
* - Live events arrive over the SSE stream at `<base>/events`, which also
|
|
6
|
+
* replays the buffered events for the current run on connect — so history
|
|
7
|
+
* comes through `subscribe`, and `fetchState` returns empty.
|
|
8
|
+
* - Commands map onto the middleware's existing endpoints:
|
|
9
|
+
* `run:replay` → POST `<base>/replay` (`{ file?, team? }`),
|
|
10
|
+
* `run:stop` → POST `<base>/stop`,
|
|
11
|
+
* `run:pause`/`run:resume` → POST `<base>/pause` (a toggle the server owns).
|
|
12
|
+
*/
|
|
13
|
+
export function createDevTransport(options = {}) {
|
|
14
|
+
const base = (options.base ?? '/__scenetest').replace(/\/+$/, '');
|
|
15
|
+
return {
|
|
16
|
+
// SSE replays the run buffer on connect, so there is no separate snapshot.
|
|
17
|
+
async fetchState() {
|
|
18
|
+
return [];
|
|
19
|
+
},
|
|
20
|
+
subscribe(onEvent, onStatus) {
|
|
21
|
+
const source = new EventSource(`${base}/events`);
|
|
22
|
+
const status = (s) => onStatus?.(s);
|
|
23
|
+
status('connecting');
|
|
24
|
+
source.onopen = () => status('connected');
|
|
25
|
+
source.onerror = () => status('disconnected');
|
|
26
|
+
source.onmessage = (e) => {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(e.data);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Envelope check only — render event types newer than this widget
|
|
35
|
+
// the producer might send, rather than dropping them.
|
|
36
|
+
if (isEventShaped(parsed))
|
|
37
|
+
onEvent(parsed);
|
|
38
|
+
};
|
|
39
|
+
return () => source.close();
|
|
40
|
+
},
|
|
41
|
+
async sendCommand(command) {
|
|
42
|
+
const post = (path, body) => fetch(`${base}${path}`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
46
|
+
});
|
|
47
|
+
switch (command.type) {
|
|
48
|
+
case 'run:replay': {
|
|
49
|
+
const body = {};
|
|
50
|
+
if (command.file)
|
|
51
|
+
body.file = command.file;
|
|
52
|
+
if (command.team)
|
|
53
|
+
body.team = command.team;
|
|
54
|
+
await post('/replay', body);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case 'run:stop':
|
|
58
|
+
await post('/stop');
|
|
59
|
+
return;
|
|
60
|
+
// The dev server exposes pause as a single toggle; both map to it.
|
|
61
|
+
case 'run:pause':
|
|
62
|
+
case 'run:resume':
|
|
63
|
+
await post('/pause');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=dev-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-transport.js","sourceRoot":"","sources":["../src/dev-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAanD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA+B,EAAE;IAClE,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAEjE,OAAO;QACL,2EAA2E;QAC3E,KAAK,CAAC,UAAU;YACd,OAAO,EAAE,CAAA;QACX,CAAC;QAED,SAAS,CAAC,OAAO,EAAE,QAAQ;YACzB,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,GAAG,IAAI,SAAS,CAAC,CAAA;YAChD,MAAM,MAAM,GAAG,CAAC,CAAmB,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;YACrD,MAAM,CAAC,YAAY,CAAC,CAAA;YAEpB,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;YACzC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;YAC7C,MAAM,CAAC,SAAS,GAAG,CAAC,CAAe,EAAE,EAAE;gBACrC,IAAI,MAAe,CAAA;gBACnB,IAAI,CAAC;oBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;gBAC7B,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAM;gBACR,CAAC;gBACD,kEAAkE;gBAClE,sDAAsD;gBACtD,IAAI,aAAa,CAAC,MAAM,CAAC;oBAAE,OAAO,CAAC,MAAkB,CAAC,CAAA;YACxD,CAAC,CAAA;YAED,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;QAC7B,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,OAAgB;YAChC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,IAAc,EAAE,EAAE,CAC5C,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE;gBACtB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC5D,CAAC,CAAA;YAEJ,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,IAAI,GAAqC,EAAE,CAAA;oBACjD,IAAI,OAAO,CAAC,IAAI;wBAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;oBAC1C,IAAI,OAAO,CAAC,IAAI;wBAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;oBAC1C,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;oBAC3B,OAAM;gBACR,CAAC;gBACD,KAAK,UAAU;oBACb,MAAM,IAAI,CAAC,OAAO,CAAC,CAAA;oBACnB,OAAM;gBACR,mEAAmE;gBACnE,KAAK,WAAW,CAAC;gBACjB,KAAK,YAAY;oBACf,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAA;oBACpB,OAAM;YACV,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { mountDashboard } from './mount.js';
|
|
2
|
+
export { createDevTransport, type DevTransportOptions } from './dev-transport.js';
|
|
3
|
+
export { applyEvent, foldEvents, initialState, withConnection, completedSceneCount, } from './store.js';
|
|
4
|
+
export { sceneSummary } from './app.js';
|
|
5
|
+
export type { Transport, ConnectionStatus, DashboardState, DashboardTheme, DashboardHandle, MountOptions, Scene, Lane, ActionItem, AssertionRow, } from './types.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,EACZ,KAAK,EACL,IAAI,EACJ,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { mountDashboard } from './mount.js';
|
|
2
|
+
export { createDevTransport } from './dev-transport.js';
|
|
3
|
+
export { applyEvent, foldEvents, initialState, withConnection, completedSceneCount, } from './store.js';
|
|
4
|
+
export { sceneSummary } from './app.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAA4B,MAAM,oBAAoB,CAAA;AACjF,OAAO,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/mount.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DashboardHandle, MountOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mount the dashboard widget into `element`. The widget renders into a shadow
|
|
4
|
+
* root with its own styles and fonts, so it can drop into any host — the
|
|
5
|
+
* `/__scenetest` page, a worker-served page, a docs island — without leaking
|
|
6
|
+
* styles in either direction and without the host needing to use Preact.
|
|
7
|
+
*
|
|
8
|
+
* The host supplies only a transport adapter (the dev/cloud seam) and an
|
|
9
|
+
* optional theme; everything else is internal. Returns a handle whose
|
|
10
|
+
* `unmount()` tears down Preact, the transport subscription, and the shadow
|
|
11
|
+
* content.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mountDashboard(element: HTMLElement, options: MountOptions): DashboardHandle;
|
|
14
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAkB,YAAY,EAAE,MAAM,YAAY,CAAA;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,eAAe,CAqB3F"}
|
package/dist/mount.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { h, render } from 'preact';
|
|
2
|
+
import { Dashboard } from './app.js';
|
|
3
|
+
import { STYLES } from './styles.js';
|
|
4
|
+
/**
|
|
5
|
+
* Mount the dashboard widget into `element`. The widget renders into a shadow
|
|
6
|
+
* root with its own styles and fonts, so it can drop into any host — the
|
|
7
|
+
* `/__scenetest` page, a worker-served page, a docs island — without leaking
|
|
8
|
+
* styles in either direction and without the host needing to use Preact.
|
|
9
|
+
*
|
|
10
|
+
* The host supplies only a transport adapter (the dev/cloud seam) and an
|
|
11
|
+
* optional theme; everything else is internal. Returns a handle whose
|
|
12
|
+
* `unmount()` tears down Preact, the transport subscription, and the shadow
|
|
13
|
+
* content.
|
|
14
|
+
*/
|
|
15
|
+
export function mountDashboard(element, options) {
|
|
16
|
+
const root = element.shadowRoot ?? element.attachShadow({ mode: 'open' });
|
|
17
|
+
root.innerHTML = '';
|
|
18
|
+
const style = document.createElement('style');
|
|
19
|
+
style.textContent = STYLES;
|
|
20
|
+
root.appendChild(style);
|
|
21
|
+
if (options.theme)
|
|
22
|
+
applyTheme(root, options.theme);
|
|
23
|
+
const container = document.createElement('div');
|
|
24
|
+
root.appendChild(container);
|
|
25
|
+
render(h(Dashboard, { transport: options.transport }), container);
|
|
26
|
+
return {
|
|
27
|
+
unmount() {
|
|
28
|
+
render(null, container);
|
|
29
|
+
root.innerHTML = '';
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Apply the small `--st-*` theming surface to the shadow host. A
|
|
35
|
+
* `:host { ... }` block can't be set imperatively, so the custom properties
|
|
36
|
+
* are written onto the host element's inline style, where `:host` rules in the
|
|
37
|
+
* stylesheet pick them up as overrides.
|
|
38
|
+
*/
|
|
39
|
+
function applyTheme(root, theme) {
|
|
40
|
+
const host = root.host;
|
|
41
|
+
if (theme.bg)
|
|
42
|
+
host.style.setProperty('--st-bg', theme.bg);
|
|
43
|
+
if (theme.accent)
|
|
44
|
+
host.style.setProperty('--st-accent', theme.accent);
|
|
45
|
+
if (theme.font)
|
|
46
|
+
host.style.setProperty('--st-font', theme.font);
|
|
47
|
+
if (theme.fontSize)
|
|
48
|
+
host.style.setProperty('--st-font-size', theme.fontSize);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoB,EAAE,OAAqB;IACxE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IACzE,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;IAEnB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IAC7C,KAAK,CAAC,WAAW,GAAG,MAAM,CAAA;IAC1B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAEvB,IAAI,OAAO,CAAC,KAAK;QAAE,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAElD,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;IAE3B,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,CAAC,CAAA;IAEjE,OAAO;QACL,OAAO;YACL,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YACvB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,IAAgB,EAAE,KAAqB;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAmB,CAAA;IACrC,IAAI,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;IACzD,IAAI,KAAK,CAAC,MAAM;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACrE,IAAI,KAAK,CAAC,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAC/D,IAAI,KAAK,CAAC,QAAQ;QAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAC9E,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RunEvent } from '@scenetest/protocol';
|
|
2
|
+
import type { ConnectionStatus, DashboardState } from './types.js';
|
|
3
|
+
/** The empty state, before any events. */
|
|
4
|
+
export declare function initialState(): DashboardState;
|
|
5
|
+
/**
|
|
6
|
+
* Fold one protocol event into the state, returning a new state object (and
|
|
7
|
+
* new objects for the scenes/scene that changed) so Preact re-renders. This
|
|
8
|
+
* is the same reduction the original inline dashboard did imperatively, made
|
|
9
|
+
* pure. Unknown event types are ignored — a newer producer can add events
|
|
10
|
+
* without breaking an older widget.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyEvent(state: DashboardState, event: RunEvent): DashboardState;
|
|
13
|
+
/** Fold a snapshot of events into a fresh state, in order. */
|
|
14
|
+
export declare function foldEvents(events: RunEvent[]): DashboardState;
|
|
15
|
+
/** Return a new state with the connection liveness updated. */
|
|
16
|
+
export declare function withConnection(state: DashboardState, connection: ConnectionStatus): DashboardState;
|
|
17
|
+
/** Number of scenes that have finished (not currently running). */
|
|
18
|
+
export declare function completedSceneCount(state: DashboardState): number;
|
|
19
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAe,MAAM,YAAY,CAAA;AAE/E,0CAA0C;AAC1C,wBAAgB,YAAY,IAAI,cAAc,CAa7C;AAqBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,GAAG,cAAc,CA2GjF;AAYD,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAE7D;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,GAAG,cAAc,CAElG;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEjE"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/** The empty state, before any events. */
|
|
2
|
+
export function initialState() {
|
|
3
|
+
return {
|
|
4
|
+
scenes: [],
|
|
5
|
+
currentSceneIndex: null,
|
|
6
|
+
runStartTime: null,
|
|
7
|
+
passCount: 0,
|
|
8
|
+
failCount: 0,
|
|
9
|
+
sceneCount: 0,
|
|
10
|
+
teams: [],
|
|
11
|
+
running: false,
|
|
12
|
+
endDurationMs: null,
|
|
13
|
+
connection: 'connecting',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function cloneScene(scene) {
|
|
17
|
+
return {
|
|
18
|
+
...scene,
|
|
19
|
+
actors: scene.actors.slice(),
|
|
20
|
+
lanes: scene.lanes.map((lane) => ({ actor: lane.actor, items: lane.items.slice() })),
|
|
21
|
+
assertions: scene.assertions.slice(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function laneFor(scene, actor) {
|
|
25
|
+
let lane = scene.lanes.find((l) => l.actor === actor);
|
|
26
|
+
if (!lane) {
|
|
27
|
+
lane = { actor, items: [] };
|
|
28
|
+
scene.lanes.push(lane);
|
|
29
|
+
if (!scene.actors.includes(actor))
|
|
30
|
+
scene.actors.push(actor);
|
|
31
|
+
}
|
|
32
|
+
return lane;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Fold one protocol event into the state, returning a new state object (and
|
|
36
|
+
* new objects for the scenes/scene that changed) so Preact re-renders. This
|
|
37
|
+
* is the same reduction the original inline dashboard did imperatively, made
|
|
38
|
+
* pure. Unknown event types are ignored — a newer producer can add events
|
|
39
|
+
* without breaking an older widget.
|
|
40
|
+
*/
|
|
41
|
+
export function applyEvent(state, event) {
|
|
42
|
+
switch (event.type) {
|
|
43
|
+
case 'run:start':
|
|
44
|
+
return {
|
|
45
|
+
...initialState(),
|
|
46
|
+
connection: state.connection,
|
|
47
|
+
runStartTime: event.timestamp,
|
|
48
|
+
sceneCount: event.sceneCount,
|
|
49
|
+
running: true,
|
|
50
|
+
};
|
|
51
|
+
case 'scene:start': {
|
|
52
|
+
const scene = {
|
|
53
|
+
name: event.name,
|
|
54
|
+
file: event.file,
|
|
55
|
+
actors: (event.actors ?? []).slice(),
|
|
56
|
+
lanes: (event.actors ?? []).map((actor) => ({ actor, items: [] })),
|
|
57
|
+
assertions: [],
|
|
58
|
+
startTime: event.timestamp,
|
|
59
|
+
endTime: null,
|
|
60
|
+
status: 'running',
|
|
61
|
+
team: event.team ?? {},
|
|
62
|
+
teamIndex: event.teamIndex ?? 0,
|
|
63
|
+
};
|
|
64
|
+
const scenes = state.scenes.concat(scene);
|
|
65
|
+
const teamName = scene.team?.name;
|
|
66
|
+
const teams = teamName && !state.teams.includes(teamName) ? state.teams.concat(teamName) : state.teams;
|
|
67
|
+
return { ...state, scenes, currentSceneIndex: scenes.length - 1, teams };
|
|
68
|
+
}
|
|
69
|
+
case 'action:start': {
|
|
70
|
+
return updateCurrentScene(state, (scene) => {
|
|
71
|
+
const lane = laneFor(scene, event.actor);
|
|
72
|
+
lane.items.push({
|
|
73
|
+
action: event.action,
|
|
74
|
+
target: event.target,
|
|
75
|
+
startTime: event.timestamp,
|
|
76
|
+
endTime: null,
|
|
77
|
+
duration: null,
|
|
78
|
+
error: null,
|
|
79
|
+
status: 'running',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
case 'action:end': {
|
|
84
|
+
return updateCurrentScene(state, (scene) => {
|
|
85
|
+
const lane = scene.lanes.find((l) => l.actor === event.actor);
|
|
86
|
+
if (!lane)
|
|
87
|
+
return;
|
|
88
|
+
for (let i = lane.items.length - 1; i >= 0; i--) {
|
|
89
|
+
const item = lane.items[i];
|
|
90
|
+
if (item.status === 'running' && item.action === event.action) {
|
|
91
|
+
item.endTime = event.timestamp;
|
|
92
|
+
item.duration = event.duration;
|
|
93
|
+
item.error = event.error ?? null;
|
|
94
|
+
item.status = event.error ? 'error' : event.duration > 500 ? 'slow' : 'success';
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
case 'assertion': {
|
|
101
|
+
return updateCurrentScene(state, (scene) => {
|
|
102
|
+
scene.assertions.push({
|
|
103
|
+
actor: event.actor,
|
|
104
|
+
description: event.description,
|
|
105
|
+
result: event.result,
|
|
106
|
+
timestamp: event.timestamp,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
case 'scene:end': {
|
|
111
|
+
if (state.currentSceneIndex == null)
|
|
112
|
+
return state;
|
|
113
|
+
const next = updateCurrentScene(state, (scene) => {
|
|
114
|
+
scene.endTime = event.timestamp;
|
|
115
|
+
scene.status = event.status;
|
|
116
|
+
scene.duration = event.duration;
|
|
117
|
+
scene.error = event.error;
|
|
118
|
+
});
|
|
119
|
+
const passed = event.status === 'completed';
|
|
120
|
+
return {
|
|
121
|
+
...next,
|
|
122
|
+
currentSceneIndex: null,
|
|
123
|
+
passCount: next.passCount + (passed ? 1 : 0),
|
|
124
|
+
failCount: next.failCount + (passed ? 0 : 1),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case 'run:progress':
|
|
128
|
+
return { ...state, progress: { pct: event.pct, failing: event.failing, flaky: event.flaky } };
|
|
129
|
+
case 'run:end':
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
running: false,
|
|
133
|
+
sceneCount: event.summary?.scenes ?? state.sceneCount,
|
|
134
|
+
passCount: event.summary?.completed ?? state.passCount,
|
|
135
|
+
failCount: event.summary?.failed ?? state.failCount,
|
|
136
|
+
endDurationMs: event.duration,
|
|
137
|
+
};
|
|
138
|
+
default:
|
|
139
|
+
return state;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function updateCurrentScene(state, mutate) {
|
|
143
|
+
const idx = state.currentSceneIndex;
|
|
144
|
+
if (idx == null || idx < 0 || idx >= state.scenes.length)
|
|
145
|
+
return state;
|
|
146
|
+
const scene = cloneScene(state.scenes[idx]);
|
|
147
|
+
mutate(scene);
|
|
148
|
+
const scenes = state.scenes.slice();
|
|
149
|
+
scenes[idx] = scene;
|
|
150
|
+
return { ...state, scenes };
|
|
151
|
+
}
|
|
152
|
+
/** Fold a snapshot of events into a fresh state, in order. */
|
|
153
|
+
export function foldEvents(events) {
|
|
154
|
+
return events.reduce(applyEvent, initialState());
|
|
155
|
+
}
|
|
156
|
+
/** Return a new state with the connection liveness updated. */
|
|
157
|
+
export function withConnection(state, connection) {
|
|
158
|
+
return { ...state, connection };
|
|
159
|
+
}
|
|
160
|
+
/** Number of scenes that have finished (not currently running). */
|
|
161
|
+
export function completedSceneCount(state) {
|
|
162
|
+
return state.scenes.filter((s) => s.status !== 'running').length;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,0CAA0C;AAC1C,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,MAAM,EAAE,EAAE;QACV,iBAAiB,EAAE,IAAI;QACvB,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,CAAC;QACb,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,KAAK;QACd,aAAa,EAAE,IAAI;QACnB,UAAU,EAAE,YAAY;KACzB,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAY;IAC9B,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE;QAC5B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpF,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE;KACrC,CAAA;AACH,CAAC;AAED,SAAS,OAAO,CAAC,KAAY,EAAE,KAAa;IAC1C,IAAI,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAA;IACrD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAC3B,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7D,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAqB,EAAE,KAAe;IAC/D,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,WAAW;YACd,OAAO;gBACL,GAAG,YAAY,EAAE;gBACjB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,YAAY,EAAE,KAAK,CAAC,SAAS;gBAC7B,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,OAAO,EAAE,IAAI;aACd,CAAA;QAEH,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,KAAK,GAAU;gBACnB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE;gBACpC,KAAK,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;gBAClE,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,SAAS;gBACjB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;gBACtB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,CAAC;aAChC,CAAA;YACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,CAAA;YACjC,MAAM,KAAK,GACT,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAA;YAC1F,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,CAAA;QAC1E,CAAC;QAED,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,OAAO,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gBACzC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;gBACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACd,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,IAAI;oBACX,MAAM,EAAE,SAAS;iBAClB,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,OAAO,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gBACzC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAA;gBAC7D,IAAI,CAAC,IAAI;oBAAE,OAAM;gBACjB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;oBAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;wBAC9D,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,SAAS,CAAA;wBAC9B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;wBAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAA;wBAChC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;wBAC/E,MAAK;oBACP,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gBACzC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;oBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;iBAC3B,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,iBAAiB,IAAI,IAAI;gBAAE,OAAO,KAAK,CAAA;YACjD,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC/C,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,SAAS,CAAA;gBAC/B,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;gBAC3B,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;gBAC/B,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;YAC3B,CAAC,CAAC,CAAA;YACF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,WAAW,CAAA;YAC3C,OAAO;gBACL,GAAG,IAAI;gBACP,iBAAiB,EAAE,IAAI;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5C,SAAS,EAAE,IAAI,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aAC7C,CAAA;QACH,CAAC;QAED,KAAK,cAAc;YACjB,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;QAE/F,KAAK,SAAS;YACZ,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,UAAU;gBACrD,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS;gBACtD,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,SAAS;gBACnD,aAAa,EAAE,KAAK,CAAC,QAAQ;aAC9B,CAAA;QAEH;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAqB,EAAE,MAA8B;IAC/E,MAAM,GAAG,GAAG,KAAK,CAAC,iBAAiB,CAAA;IACnC,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACtE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3C,MAAM,CAAC,KAAK,CAAC,CAAA;IACb,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACnC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;IACnB,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,CAAA;AAC7B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAAA;AAClD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,KAAqB,EAAE,UAA4B;IAChF,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACvD,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;AAClE,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget's stylesheet, injected into its shadow root. The only theming
|
|
3
|
+
* surface is the small set of `--st-*` custom properties on `:host`; every
|
|
4
|
+
* internal color derives from them or from a fixed terminal palette. A host
|
|
5
|
+
* may set `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size` (and nothing
|
|
6
|
+
* else) to retheme the pane. These are versioned with the widget, like the
|
|
7
|
+
* wire protocol.
|
|
8
|
+
*/
|
|
9
|
+
export declare const STYLES = "\n:host {\n /* \u2500\u2500 Theming surface (host may override these four) \u2500\u2500 */\n --st-bg: #0f1117;\n --st-accent: #3b82f6;\n --st-font: 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;\n --st-font-size: 13px;\n\n /* \u2500\u2500 Internal palette (derived; not a public surface) \u2500\u2500 */\n --bg: var(--st-bg);\n --bg2: #1a1d27;\n --bg3: #252833;\n --border: #2e3140;\n --text: #e1e4ed;\n --text2: #8b8fa3;\n --green: #22c55e;\n --red: #ef4444;\n --amber: #f59e0b;\n --blue: var(--st-accent);\n\n display: block;\n font-family: var(--st-font);\n font-size: var(--st-font-size);\n color: var(--text);\n background: var(--bg);\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\n.root { min-height: 100%; background: var(--bg); }\n\nheader {\n position: sticky;\n top: 0;\n z-index: 10;\n padding: 12px 20px;\n border-bottom: 1px solid var(--border);\n background: var(--bg2);\n display: flex;\n align-items: center;\n gap: 14px;\n flex-wrap: wrap;\n}\nheader.running .logo { animation: pulse 1.2s ease-in-out infinite; }\n\nh1 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }\n.logo {\n display: inline-flex; align-items: center; justify-content: center;\n width: 22px; height: 22px; border-radius: 5px;\n background: var(--blue); color: #fff; font-weight: 700;\n}\n\nbutton {\n font-family: inherit; font-size: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--bg3); color: var(--text);\n padding: 5px 10px; border-radius: 5px; display: inline-flex; align-items: center; gap: 6px;\n}\nbutton:hover:not(:disabled) { border-color: var(--blue); }\nbutton:disabled { opacity: 0.5; cursor: default; }\n.replay-all-btn { color: var(--green); }\n.stop-btn { color: var(--red); }\n\n.team-select-wrap { font-size: 12px; color: var(--text2); display: flex; align-items: center; gap: 6px; }\nselect {\n font-family: inherit; font-size: 12px; background: var(--bg3); color: var(--text);\n border: 1px solid var(--border); border-radius: 5px; padding: 4px 6px;\n}\n\n.spacer { flex: 1; }\n\n.stats { display: flex; align-items: center; gap: 14px; font-size: 12px; }\n.stat { display: flex; align-items: center; gap: 5px; }\n.stat .label { color: var(--text2); }\n.stat .value { font-weight: 600; }\n.stat.pass .value { color: var(--green); }\n.stat.fail .value { color: var(--red); }\n\n.conn { width: 9px; height: 9px; border-radius: 50%; background: var(--text2); }\n.conn.connected { background: var(--green); }\n.conn.disconnected { background: var(--red); }\n\n.progress { flex-basis: 100%; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; }\n.progress-fill { height: 100%; width: 0; background: var(--blue); transition: width 0.2s ease; }\n.progress.done .progress-fill { background: var(--green); }\n.progress.has-failures .progress-fill { background: var(--red); }\n\nmain { padding: 16px 20px; }\n.waiting { text-align: center; color: var(--text2); padding: 60px 20px; }\n.waiting h2 { font-size: 16px; font-weight: 500; margin-bottom: 8px; color: var(--text); }\n.waiting code { background: var(--bg3); padding: 2px 6px; border-radius: 4px; }\n\n.scene {\n border: 1px solid var(--border); border-radius: 8px; background: var(--bg2);\n margin-bottom: 14px; overflow: hidden;\n}\n.scene.failed { border-color: var(--red); }\n.scene-head {\n display: flex; align-items: center; gap: 10px; padding: 10px 14px;\n border-bottom: 1px solid var(--border); background: var(--bg3);\n}\n.scene-status { font-weight: 700; }\n.scene-status.completed { color: var(--green); }\n.scene-status.failed, .scene-status.timeout { color: var(--red); }\n.scene-status.running { color: var(--amber); }\n.scene-name { font-weight: 600; }\n.scene-file { color: var(--text2); font-size: 11px; }\n.scene-team {\n font-size: 11px; color: var(--blue); border: 1px solid var(--border);\n padding: 1px 6px; border-radius: 10px;\n}\n.scene-dur { color: var(--text2); font-size: 11px; margin-left: auto; }\n.copy-btn { padding: 3px 7px; font-size: 11px; }\n.copy-btn.copied { color: var(--green); border-color: var(--green); }\n\n.lanes { padding: 8px 14px; display: flex; flex-direction: column; gap: 6px; }\n.lane { display: flex; align-items: flex-start; gap: 8px; }\n.lane-actor { color: var(--text2); min-width: 90px; font-size: 11px; padding-top: 3px; }\n.lane-items { display: flex; flex-wrap: wrap; gap: 4px; }\n.pill {\n font-size: 11px; padding: 2px 7px; border-radius: 4px;\n border: 1px solid var(--border); background: var(--bg3); color: var(--text);\n}\n.pill.running { border-color: var(--amber); color: var(--amber); }\n.pill.success { border-color: var(--green); }\n.pill.slow { border-color: var(--amber); }\n.pill.error { border-color: var(--red); color: var(--red); }\n.pill .tgt { color: var(--text2); }\n\n.assertions { padding: 0 14px 10px; display: flex; flex-direction: column; gap: 3px; }\n.assert { font-size: 12px; display: flex; gap: 6px; align-items: baseline; }\n.assert .mark { font-weight: 700; }\n.assert.ok .mark { color: var(--green); }\n.assert.bad .mark { color: var(--red); }\n.assert .who { color: var(--text2); }\n\n.scene-error {\n margin: 0 14px 12px; padding: 8px 10px; border-radius: 6px;\n background: rgba(239, 68, 68, 0.1); border: 1px solid var(--red);\n color: var(--red); font-size: 12px; white-space: pre-wrap; cursor: pointer;\n}\n\n@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }\n";
|
|
10
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,o4KA8IlB,CAAA"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget's stylesheet, injected into its shadow root. The only theming
|
|
3
|
+
* surface is the small set of `--st-*` custom properties on `:host`; every
|
|
4
|
+
* internal color derives from them or from a fixed terminal palette. A host
|
|
5
|
+
* may set `--st-bg`, `--st-accent`, `--st-font`, `--st-font-size` (and nothing
|
|
6
|
+
* else) to retheme the pane. These are versioned with the widget, like the
|
|
7
|
+
* wire protocol.
|
|
8
|
+
*/
|
|
9
|
+
export const STYLES = `
|
|
10
|
+
:host {
|
|
11
|
+
/* ── Theming surface (host may override these four) ── */
|
|
12
|
+
--st-bg: #0f1117;
|
|
13
|
+
--st-accent: #3b82f6;
|
|
14
|
+
--st-font: 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
|
|
15
|
+
--st-font-size: 13px;
|
|
16
|
+
|
|
17
|
+
/* ── Internal palette (derived; not a public surface) ── */
|
|
18
|
+
--bg: var(--st-bg);
|
|
19
|
+
--bg2: #1a1d27;
|
|
20
|
+
--bg3: #252833;
|
|
21
|
+
--border: #2e3140;
|
|
22
|
+
--text: #e1e4ed;
|
|
23
|
+
--text2: #8b8fa3;
|
|
24
|
+
--green: #22c55e;
|
|
25
|
+
--red: #ef4444;
|
|
26
|
+
--amber: #f59e0b;
|
|
27
|
+
--blue: var(--st-accent);
|
|
28
|
+
|
|
29
|
+
display: block;
|
|
30
|
+
font-family: var(--st-font);
|
|
31
|
+
font-size: var(--st-font-size);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
background: var(--bg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
37
|
+
|
|
38
|
+
.root { min-height: 100%; background: var(--bg); }
|
|
39
|
+
|
|
40
|
+
header {
|
|
41
|
+
position: sticky;
|
|
42
|
+
top: 0;
|
|
43
|
+
z-index: 10;
|
|
44
|
+
padding: 12px 20px;
|
|
45
|
+
border-bottom: 1px solid var(--border);
|
|
46
|
+
background: var(--bg2);
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 14px;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
}
|
|
52
|
+
header.running .logo { animation: pulse 1.2s ease-in-out infinite; }
|
|
53
|
+
|
|
54
|
+
h1 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
|
55
|
+
.logo {
|
|
56
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
57
|
+
width: 22px; height: 22px; border-radius: 5px;
|
|
58
|
+
background: var(--blue); color: #fff; font-weight: 700;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
button {
|
|
62
|
+
font-family: inherit; font-size: 12px; cursor: pointer;
|
|
63
|
+
border: 1px solid var(--border); background: var(--bg3); color: var(--text);
|
|
64
|
+
padding: 5px 10px; border-radius: 5px; display: inline-flex; align-items: center; gap: 6px;
|
|
65
|
+
}
|
|
66
|
+
button:hover:not(:disabled) { border-color: var(--blue); }
|
|
67
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
68
|
+
.replay-all-btn { color: var(--green); }
|
|
69
|
+
.stop-btn { color: var(--red); }
|
|
70
|
+
|
|
71
|
+
.team-select-wrap { font-size: 12px; color: var(--text2); display: flex; align-items: center; gap: 6px; }
|
|
72
|
+
select {
|
|
73
|
+
font-family: inherit; font-size: 12px; background: var(--bg3); color: var(--text);
|
|
74
|
+
border: 1px solid var(--border); border-radius: 5px; padding: 4px 6px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.spacer { flex: 1; }
|
|
78
|
+
|
|
79
|
+
.stats { display: flex; align-items: center; gap: 14px; font-size: 12px; }
|
|
80
|
+
.stat { display: flex; align-items: center; gap: 5px; }
|
|
81
|
+
.stat .label { color: var(--text2); }
|
|
82
|
+
.stat .value { font-weight: 600; }
|
|
83
|
+
.stat.pass .value { color: var(--green); }
|
|
84
|
+
.stat.fail .value { color: var(--red); }
|
|
85
|
+
|
|
86
|
+
.conn { width: 9px; height: 9px; border-radius: 50%; background: var(--text2); }
|
|
87
|
+
.conn.connected { background: var(--green); }
|
|
88
|
+
.conn.disconnected { background: var(--red); }
|
|
89
|
+
|
|
90
|
+
.progress { flex-basis: 100%; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; }
|
|
91
|
+
.progress-fill { height: 100%; width: 0; background: var(--blue); transition: width 0.2s ease; }
|
|
92
|
+
.progress.done .progress-fill { background: var(--green); }
|
|
93
|
+
.progress.has-failures .progress-fill { background: var(--red); }
|
|
94
|
+
|
|
95
|
+
main { padding: 16px 20px; }
|
|
96
|
+
.waiting { text-align: center; color: var(--text2); padding: 60px 20px; }
|
|
97
|
+
.waiting h2 { font-size: 16px; font-weight: 500; margin-bottom: 8px; color: var(--text); }
|
|
98
|
+
.waiting code { background: var(--bg3); padding: 2px 6px; border-radius: 4px; }
|
|
99
|
+
|
|
100
|
+
.scene {
|
|
101
|
+
border: 1px solid var(--border); border-radius: 8px; background: var(--bg2);
|
|
102
|
+
margin-bottom: 14px; overflow: hidden;
|
|
103
|
+
}
|
|
104
|
+
.scene.failed { border-color: var(--red); }
|
|
105
|
+
.scene-head {
|
|
106
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 14px;
|
|
107
|
+
border-bottom: 1px solid var(--border); background: var(--bg3);
|
|
108
|
+
}
|
|
109
|
+
.scene-status { font-weight: 700; }
|
|
110
|
+
.scene-status.completed { color: var(--green); }
|
|
111
|
+
.scene-status.failed, .scene-status.timeout { color: var(--red); }
|
|
112
|
+
.scene-status.running { color: var(--amber); }
|
|
113
|
+
.scene-name { font-weight: 600; }
|
|
114
|
+
.scene-file { color: var(--text2); font-size: 11px; }
|
|
115
|
+
.scene-team {
|
|
116
|
+
font-size: 11px; color: var(--blue); border: 1px solid var(--border);
|
|
117
|
+
padding: 1px 6px; border-radius: 10px;
|
|
118
|
+
}
|
|
119
|
+
.scene-dur { color: var(--text2); font-size: 11px; margin-left: auto; }
|
|
120
|
+
.copy-btn { padding: 3px 7px; font-size: 11px; }
|
|
121
|
+
.copy-btn.copied { color: var(--green); border-color: var(--green); }
|
|
122
|
+
|
|
123
|
+
.lanes { padding: 8px 14px; display: flex; flex-direction: column; gap: 6px; }
|
|
124
|
+
.lane { display: flex; align-items: flex-start; gap: 8px; }
|
|
125
|
+
.lane-actor { color: var(--text2); min-width: 90px; font-size: 11px; padding-top: 3px; }
|
|
126
|
+
.lane-items { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
127
|
+
.pill {
|
|
128
|
+
font-size: 11px; padding: 2px 7px; border-radius: 4px;
|
|
129
|
+
border: 1px solid var(--border); background: var(--bg3); color: var(--text);
|
|
130
|
+
}
|
|
131
|
+
.pill.running { border-color: var(--amber); color: var(--amber); }
|
|
132
|
+
.pill.success { border-color: var(--green); }
|
|
133
|
+
.pill.slow { border-color: var(--amber); }
|
|
134
|
+
.pill.error { border-color: var(--red); color: var(--red); }
|
|
135
|
+
.pill .tgt { color: var(--text2); }
|
|
136
|
+
|
|
137
|
+
.assertions { padding: 0 14px 10px; display: flex; flex-direction: column; gap: 3px; }
|
|
138
|
+
.assert { font-size: 12px; display: flex; gap: 6px; align-items: baseline; }
|
|
139
|
+
.assert .mark { font-weight: 700; }
|
|
140
|
+
.assert.ok .mark { color: var(--green); }
|
|
141
|
+
.assert.bad .mark { color: var(--red); }
|
|
142
|
+
.assert .who { color: var(--text2); }
|
|
143
|
+
|
|
144
|
+
.scene-error {
|
|
145
|
+
margin: 0 14px 12px; padding: 8px 10px; border-radius: 6px;
|
|
146
|
+
background: rgba(239, 68, 68, 0.1); border: 1px solid var(--red);
|
|
147
|
+
color: var(--red); font-size: 12px; white-space: pre-wrap; cursor: pointer;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
|
|
151
|
+
`;
|
|
152
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8IrB,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Command, RunEvent, TeamMeta } from '@scenetest/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Liveness of the transport's event subscription, surfaced in the header's
|
|
4
|
+
* connection indicator. Transports report this through `subscribe`'s second
|
|
5
|
+
* argument; transports that can't tell may stay `'connected'`.
|
|
6
|
+
*/
|
|
7
|
+
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
|
|
8
|
+
/**
|
|
9
|
+
* The injection point where dev and cloud differ. The widget calls the
|
|
10
|
+
* adapter to fetch a state snapshot and subscribe to live events, and pushes
|
|
11
|
+
* user actions back as protocol commands. The adapter speaks to whatever
|
|
12
|
+
* backend is present — the Vite middleware (fetch + SSE) in dev, the worker
|
|
13
|
+
* API (fetch + WebSocket) in cloud — so the dashboard behaves the same in
|
|
14
|
+
* both by construction.
|
|
15
|
+
*/
|
|
16
|
+
export interface Transport {
|
|
17
|
+
/**
|
|
18
|
+
* A snapshot of the run so far, as an ordered list of protocol events the
|
|
19
|
+
* widget folds into its initial state. Transports that deliver history
|
|
20
|
+
* through the initial `subscribe` burst instead (e.g. SSE replay) may
|
|
21
|
+
* return an empty array — the store folds both the same way.
|
|
22
|
+
*/
|
|
23
|
+
fetchState(): Promise<RunEvent[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to live events. `onEvent` receives every event after the
|
|
26
|
+
* snapshot; `onStatus`, if given, receives connection-liveness changes.
|
|
27
|
+
* Returns an unsubscribe function that tears down the underlying stream.
|
|
28
|
+
*/
|
|
29
|
+
subscribe(onEvent: (event: RunEvent) => void, onStatus?: (status: ConnectionStatus) => void): () => void;
|
|
30
|
+
/** Send a command toward the runner (replay, stop, pause, resume). */
|
|
31
|
+
sendCommand(command: Command): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** A single DSL action on one actor's lane. */
|
|
34
|
+
export interface ActionItem {
|
|
35
|
+
action: string;
|
|
36
|
+
target?: string;
|
|
37
|
+
startTime: number;
|
|
38
|
+
endTime: number | null;
|
|
39
|
+
duration: number | null;
|
|
40
|
+
error: string | null;
|
|
41
|
+
status: 'running' | 'success' | 'slow' | 'error';
|
|
42
|
+
}
|
|
43
|
+
/** One actor's ordered actions within a scene. */
|
|
44
|
+
export interface Lane {
|
|
45
|
+
actor: string;
|
|
46
|
+
items: ActionItem[];
|
|
47
|
+
}
|
|
48
|
+
/** An inline assertion result observed during a scene. */
|
|
49
|
+
export interface AssertionRow {
|
|
50
|
+
actor?: string;
|
|
51
|
+
description: string;
|
|
52
|
+
result: boolean;
|
|
53
|
+
timestamp: number;
|
|
54
|
+
}
|
|
55
|
+
/** A scene folded from its lifecycle events. */
|
|
56
|
+
export interface Scene {
|
|
57
|
+
name: string;
|
|
58
|
+
file: string;
|
|
59
|
+
actors: string[];
|
|
60
|
+
lanes: Lane[];
|
|
61
|
+
assertions: AssertionRow[];
|
|
62
|
+
startTime: number;
|
|
63
|
+
endTime: number | null;
|
|
64
|
+
status: 'running' | 'completed' | 'failed' | 'timeout' | string;
|
|
65
|
+
duration?: number;
|
|
66
|
+
error?: string;
|
|
67
|
+
team: TeamMeta;
|
|
68
|
+
teamIndex: number;
|
|
69
|
+
}
|
|
70
|
+
/** Everything the widget renders, folded from the protocol event stream. */
|
|
71
|
+
export interface DashboardState {
|
|
72
|
+
scenes: Scene[];
|
|
73
|
+
currentSceneIndex: number | null;
|
|
74
|
+
runStartTime: number | null;
|
|
75
|
+
passCount: number;
|
|
76
|
+
failCount: number;
|
|
77
|
+
sceneCount: number;
|
|
78
|
+
/** Distinct team names seen in `scene:start`, for the replay filter. */
|
|
79
|
+
teams: string[];
|
|
80
|
+
running: boolean;
|
|
81
|
+
/** Final run duration once `run:end` arrives, else null. */
|
|
82
|
+
endDurationMs: number | null;
|
|
83
|
+
/** Latest `run:progress` rollup, when the producer emits one. */
|
|
84
|
+
progress?: {
|
|
85
|
+
pct: number;
|
|
86
|
+
failing: number;
|
|
87
|
+
flaky: number;
|
|
88
|
+
};
|
|
89
|
+
connection: ConnectionStatus;
|
|
90
|
+
}
|
|
91
|
+
/** Theming surface — the only knobs a host may set. Versioned with the widget. */
|
|
92
|
+
export interface DashboardTheme {
|
|
93
|
+
/** Background of the terminal pane. */
|
|
94
|
+
bg?: string;
|
|
95
|
+
/** Accent / primary action color. */
|
|
96
|
+
accent?: string;
|
|
97
|
+
/** Font family for the pane. */
|
|
98
|
+
font?: string;
|
|
99
|
+
/** Base font size (e.g. `'13px'`). */
|
|
100
|
+
fontSize?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface MountOptions {
|
|
103
|
+
transport: Transport;
|
|
104
|
+
/** Optional theme overrides applied as `--st-*` custom properties. */
|
|
105
|
+
theme?: DashboardTheme;
|
|
106
|
+
}
|
|
107
|
+
/** Handle returned by `mountDashboard`. */
|
|
108
|
+
export interface DashboardHandle {
|
|
109
|
+
/** Tear down the widget, its subscription, and its shadow root contents. */
|
|
110
|
+
unmount(): void;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scenetest/dashboard",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Mountable Preact dashboard widget for scenetest — renders a live run into a shadow root, fed by a pluggable transport adapter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/scenetest/scenetest-js",
|
|
10
|
+
"directory": "packages/dashboard"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"testing",
|
|
14
|
+
"e2e",
|
|
15
|
+
"playwright",
|
|
16
|
+
"dashboard",
|
|
17
|
+
"preact",
|
|
18
|
+
"widget"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"htm": "^3.1.1",
|
|
37
|
+
"preact": "^10.22.0",
|
|
38
|
+
"@scenetest/protocol": "0.11.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.3.3",
|
|
42
|
+
"vite": "^6.4.2",
|
|
43
|
+
"vitest": "^4.1.4"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
}
|
|
51
|
+
}
|