plum-e2e 2.8.5 → 2.9.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/backend/_scaffold/utils/browser.ts +184 -30
- package/backend/_scaffold/utils/hooks.ts +37 -8
- package/backend/app.js +0 -5
- package/backend/config/scripts/generate-report.js +2 -2
- package/backend/constants/socketEvents.js +7 -4
- package/backend/lib/reportFilename.js +1 -2
- package/backend/lib/{screenshotPoller.js → rrwebPoller.js} +7 -4
- package/backend/lib/serverBootstrap.js +14 -0
- package/backend/logs/runner-cmtbz5b1l0000mr0110b27w5k.log +8 -0
- package/backend/mcp/server.js +3 -47
- package/backend/package-lock.json +199 -1
- package/backend/package.json +3 -1
- package/backend/prisma/migrations/20260828120000_add_recording_and_split_runner_worker_count/migration.sql +39 -0
- package/backend/prisma/migrations/20260828140000_add_recording_started_ended_at/migration.sql +5 -0
- package/backend/prisma/migrations/20260828150000_strip_screenshot_refs_from_reports/migration.sql +34 -0
- package/backend/prisma/migrations/20260828160000_add_backup_include_reports/migration.sql +4 -0
- package/backend/prisma/schema.prisma +97 -70
- package/backend/routes/backup.routes.js +47 -1
- package/backend/routes/reports.routes.js +23 -0
- package/backend/routes/test-cases.routes.js +7 -2
- package/backend/server.js +1 -1
- package/backend/services/backupCronService.js +1 -1
- package/backend/services/backupService.js +134 -44
- package/backend/services/cronService.js +23 -15
- package/backend/services/nodeExecutionService.js +41 -15
- package/backend/services/nodeStreamRegistry.js +24 -0
- package/backend/services/reportService.js +174 -84
- package/backend/services/runnerService.js +19 -7
- package/backend/services/settingsService.js +6 -3
- package/backend/services/testCaseService.js +3 -2
- package/backend/services/triggerService.js +20 -13
- package/backend/services/userService.js +1 -3
- package/backend/websockets/nodeSocketHandler.js +40 -0
- package/backend/websockets/socketHandler.js +9 -7
- package/frontend/.svelte-kit/adapter-node/.vite/manifest.json +1 -1
- package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.C1TbMUws.css +6 -0
- package/frontend/.svelte-kit/adapter-node/chunks/internal.js +1 -1
- package/frontend/.svelte-kit/adapter-node/entries/pages/_page.svelte.js +22 -22
- package/frontend/.svelte-kit/adapter-node/entries/pages/settings/_page.svelte.js +3 -3
- package/frontend/.svelte-kit/adapter-node/manifest-full.js +1 -1
- package/frontend/.svelte-kit/adapter-node/manifest.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/0.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/1.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/11.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/12.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/2.js +2 -2
- package/frontend/.svelte-kit/adapter-node/nodes/3.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/5.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/6.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/8.js +1 -1
- package/frontend/.svelte-kit/adapter-node/nodes/9.js +1 -1
- package/frontend/.svelte-kit/ambient.d.ts +4 -0
- package/frontend/.svelte-kit/generated/server/internal.js +1 -1
- package/frontend/.svelte-kit/output/client/.vite/manifest.json +35 -35
- package/frontend/.svelte-kit/output/client/_app/immutable/assets/2.C1TbMUws.css +6 -0
- package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.C1TbMUws.css +6 -0
- package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{DEKAA-Fm.js → DDZw0L0T.js} +1 -1
- package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{Dhf4xQKo.js → kkA1mrsH.js} +2 -2
- package/frontend/.svelte-kit/output/client/_app/immutable/entry/{app.B0GH5ONo.js → app.KrtMZ3my.js} +2 -2
- package/frontend/.svelte-kit/output/client/_app/immutable/entry/{start.Bn25CW9X.js → start.4qxHHAcl.js} +1 -1
- package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{0.-imXt0f5.js → 0.C8DodwXs.js} +1 -1
- package/frontend/{build/client/_app/immutable/nodes/1.Dom3CKWF.js → .svelte-kit/output/client/_app/immutable/nodes/1.Df9sLw8b.js} +1 -1
- package/frontend/{build/client/_app/immutable/nodes/11.DMlayLCK.js → .svelte-kit/output/client/_app/immutable/nodes/11.BViaDK2m.js} +1 -1
- package/frontend/{build/client/_app/immutable/nodes/12.Bu89A1eX.js → .svelte-kit/output/client/_app/immutable/nodes/12.BMSCSd88.js} +1 -1
- package/frontend/.svelte-kit/output/client/_app/immutable/nodes/2.BCPSg7H4.js +6 -0
- package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{3.B86rlHXU.js → 3.8BFAJoHT.js} +1 -1
- package/frontend/{build/client/_app/immutable/nodes/5.CqTXvadV.js → .svelte-kit/output/client/_app/immutable/nodes/5.DVeH2eQP.js} +1 -1
- package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{6.BJZ62MCd.js → 6.DI7-IJhN.js} +1 -1
- package/frontend/{build/client/_app/immutable/nodes/8.B4fj2Xyu.js → .svelte-kit/output/client/_app/immutable/nodes/8.93Mb3ofT.js} +4 -4
- package/frontend/{build/client/_app/immutable/nodes/9.C57_2xq6.js → .svelte-kit/output/client/_app/immutable/nodes/9.CvfGx3Hu.js} +1 -1
- package/frontend/.svelte-kit/output/client/_app/version.json +1 -1
- package/frontend/.svelte-kit/output/server/.vite/manifest.json +1 -1
- package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.C1TbMUws.css +6 -0
- package/frontend/.svelte-kit/output/server/chunks/internal.js +1 -1
- package/frontend/.svelte-kit/output/server/entries/pages/_page.svelte.js +22 -22
- package/frontend/.svelte-kit/output/server/entries/pages/settings/_page.svelte.js +3 -3
- package/frontend/.svelte-kit/output/server/manifest-full.js +1 -1
- package/frontend/.svelte-kit/output/server/manifest.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/0.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/1.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/11.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/12.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/2.js +2 -2
- package/frontend/.svelte-kit/output/server/nodes/3.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/5.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/6.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/8.js +1 -1
- package/frontend/.svelte-kit/output/server/nodes/9.js +1 -1
- package/frontend/build/client/_app/immutable/assets/2.C1TbMUws.css +6 -0
- package/frontend/build/client/_app/immutable/assets/2.C1TbMUws.css.br +0 -0
- package/frontend/build/client/_app/immutable/assets/2.C1TbMUws.css.gz +0 -0
- package/frontend/build/client/_app/immutable/assets/_page.C1TbMUws.css +6 -0
- package/frontend/build/client/_app/immutable/assets/_page.C1TbMUws.css.br +0 -0
- package/frontend/build/client/_app/immutable/assets/_page.C1TbMUws.css.gz +0 -0
- package/frontend/build/client/_app/immutable/chunks/{DEKAA-Fm.js → DDZw0L0T.js} +1 -1
- package/frontend/build/client/_app/immutable/chunks/DDZw0L0T.js.br +0 -0
- package/frontend/build/client/_app/immutable/chunks/DDZw0L0T.js.gz +0 -0
- package/frontend/build/client/_app/immutable/chunks/{Dhf4xQKo.js → kkA1mrsH.js} +2 -2
- package/frontend/build/client/_app/immutable/chunks/kkA1mrsH.js.br +0 -0
- package/frontend/build/client/_app/immutable/chunks/kkA1mrsH.js.gz +0 -0
- package/frontend/build/client/_app/immutable/entry/{app.B0GH5ONo.js → app.KrtMZ3my.js} +2 -2
- package/frontend/build/client/_app/immutable/entry/app.KrtMZ3my.js.br +0 -0
- package/frontend/build/client/_app/immutable/entry/app.KrtMZ3my.js.gz +0 -0
- package/frontend/build/client/_app/immutable/entry/{start.Bn25CW9X.js → start.4qxHHAcl.js} +1 -1
- package/frontend/build/client/_app/immutable/entry/{start.Bn25CW9X.js.br → start.4qxHHAcl.js.br} +0 -0
- package/frontend/build/client/_app/immutable/entry/{start.Bn25CW9X.js.gz → start.4qxHHAcl.js.gz} +0 -0
- package/frontend/build/client/_app/immutable/nodes/{0.-imXt0f5.js → 0.C8DodwXs.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/0.C8DodwXs.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/0.C8DodwXs.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/1.Dom3CKWF.js → build/client/_app/immutable/nodes/1.Df9sLw8b.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/1.Df9sLw8b.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/1.Df9sLw8b.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/11.DMlayLCK.js → build/client/_app/immutable/nodes/11.BViaDK2m.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/11.BViaDK2m.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/11.BViaDK2m.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/12.Bu89A1eX.js → build/client/_app/immutable/nodes/12.BMSCSd88.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/12.BMSCSd88.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/{12.Bu89A1eX.js.gz → 12.BMSCSd88.js.gz} +0 -0
- package/frontend/build/client/_app/immutable/nodes/2.BCPSg7H4.js +6 -0
- package/frontend/build/client/_app/immutable/nodes/2.BCPSg7H4.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/2.BCPSg7H4.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/{3.B86rlHXU.js → 3.8BFAJoHT.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/3.8BFAJoHT.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/3.8BFAJoHT.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/5.CqTXvadV.js → build/client/_app/immutable/nodes/5.DVeH2eQP.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/5.DVeH2eQP.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/5.DVeH2eQP.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/{6.BJZ62MCd.js → 6.DI7-IJhN.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/6.DI7-IJhN.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/6.DI7-IJhN.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/8.B4fj2Xyu.js → build/client/_app/immutable/nodes/8.93Mb3ofT.js} +4 -4
- package/frontend/build/client/_app/immutable/nodes/8.93Mb3ofT.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/8.93Mb3ofT.js.gz +0 -0
- package/frontend/{.svelte-kit/output/client/_app/immutable/nodes/9.C57_2xq6.js → build/client/_app/immutable/nodes/9.CvfGx3Hu.js} +1 -1
- package/frontend/build/client/_app/immutable/nodes/9.CvfGx3Hu.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/9.CvfGx3Hu.js.gz +0 -0
- package/frontend/build/client/_app/version.json +1 -1
- package/frontend/build/client/_app/version.json.br +0 -0
- package/frontend/build/client/_app/version.json.gz +0 -0
- package/frontend/build/server/chunks/{0-DG1Uv07a.js → 0-D5YWFygh.js} +2 -2
- package/frontend/build/server/chunks/{0-DG1Uv07a.js.map → 0-D5YWFygh.js.map} +1 -1
- package/frontend/build/server/chunks/{1-iB0ogJU_.js → 1-Kxn9d30Z.js} +2 -2
- package/frontend/build/server/chunks/{1-iB0ogJU_.js.map → 1-Kxn9d30Z.js.map} +1 -1
- package/frontend/build/server/chunks/{11-Df9vGZ_v.js → 11-CNLerze_.js} +2 -2
- package/frontend/build/server/chunks/{11-Df9vGZ_v.js.map → 11-CNLerze_.js.map} +1 -1
- package/frontend/build/server/chunks/{12-DqvjCln4.js → 12-De6Ol6pu.js} +2 -2
- package/frontend/build/server/chunks/{12-DqvjCln4.js.map → 12-De6Ol6pu.js.map} +1 -1
- package/frontend/build/server/chunks/{2-Cte30Dgy.js → 2-Dc_rKWoK.js} +4 -4
- package/frontend/build/server/chunks/{2-Cte30Dgy.js.map → 2-Dc_rKWoK.js.map} +1 -1
- package/frontend/build/server/chunks/{3-BYKCZi4U.js → 3-DrRqVrcX.js} +2 -2
- package/frontend/build/server/chunks/{3-BYKCZi4U.js.map → 3-DrRqVrcX.js.map} +1 -1
- package/frontend/build/server/chunks/{5-B6Jc1foX.js → 5-CFeE5dPZ.js} +2 -2
- package/frontend/build/server/chunks/{5-B6Jc1foX.js.map → 5-CFeE5dPZ.js.map} +1 -1
- package/frontend/build/server/chunks/{6-dXZKtN86.js → 6-DZk37MZH.js} +2 -2
- package/frontend/build/server/chunks/{6-dXZKtN86.js.map → 6-DZk37MZH.js.map} +1 -1
- package/frontend/build/server/chunks/{8-D-Bp7HzS.js → 8-CKPVFOqJ.js} +3 -3
- package/frontend/build/server/chunks/{8-D-Bp7HzS.js.map → 8-CKPVFOqJ.js.map} +1 -1
- package/frontend/build/server/chunks/{9-BynjuGUO.js → 9-DirZG_Lu.js} +2 -2
- package/frontend/build/server/chunks/{9-BynjuGUO.js.map → 9-DirZG_Lu.js.map} +1 -1
- package/frontend/build/server/chunks/{_page.svelte-CpB-fgJl.js → _page.svelte-04WxjOuH.js} +23 -23
- package/frontend/build/server/chunks/_page.svelte-04WxjOuH.js.map +5 -0
- package/frontend/build/server/chunks/{_page.svelte-wKyUmA2e.js → _page.svelte-8gELc_B1.js} +4 -4
- package/frontend/build/server/chunks/_page.svelte-8gELc_B1.js.map +5 -0
- package/frontend/build/server/index.js +1 -1
- package/frontend/build/server/index.js.map +1 -1
- package/frontend/build/server/manifest.js +11 -11
- package/frontend/build/server/manifest.js.map +1 -1
- package/frontend/package-lock.json +121 -32
- package/frontend/package.json +1 -0
- package/frontend/src/lib/api/reports.js +13 -4
- package/frontend/src/lib/api/repository.js +2 -2
- package/frontend/src/lib/api/settings.js +16 -1
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +9 -24
- package/frontend/src/lib/components/reports/ElementInspector.svelte +141 -0
- package/frontend/src/lib/components/reports/LiveReplayer.svelte +110 -0
- package/frontend/src/lib/components/reports/MultiTabTimeline.svelte +115 -0
- package/frontend/src/lib/components/reports/RecordingPlayer.svelte +786 -0
- package/frontend/src/lib/components/reports/StepsRail.svelte +109 -0
- package/frontend/src/lib/components/ui/CodeViewer.svelte +61 -0
- package/frontend/src/lib/constants.js +0 -1
- package/frontend/src/lib/copy/reports.js +18 -8
- package/frontend/src/lib/copy/repository.js +6 -0
- package/frontend/src/lib/copy/settings.js +25 -4
- package/frontend/src/lib/socketEvents.js +7 -4
- package/frontend/src/lib/stores/runner.js +26 -3
- package/frontend/src/lib/styles/tokens.css +7 -0
- package/frontend/src/lib/utils/format.js +108 -2
- package/frontend/src/lib/utils/inspectElement.js +34 -0
- package/frontend/src/routes/+page.svelte +6 -1
- package/frontend/src/routes/reports/+page.svelte +79 -1
- package/frontend/src/routes/reports/[id]/+page.svelte +304 -495
- package/frontend/src/routes/reports/live/+page.svelte +236 -260
- package/frontend/src/routes/settings/+page.svelte +246 -8
- package/frontend/src/routes/test-repository/suites/[id]/+page.svelte +116 -0
- package/package.json +1 -1
- package/backend/playwright.config.js +0 -85
- package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.DvMkmYA-.css +0 -6
- package/frontend/.svelte-kit/output/client/_app/immutable/assets/2.DvMkmYA-.css +0 -6
- package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.DvMkmYA-.css +0 -6
- package/frontend/.svelte-kit/output/client/_app/immutable/nodes/2.BnHalSCi.js +0 -6
- package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.DvMkmYA-.css +0 -6
- package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css +0 -6
- package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css.br +0 -0
- package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css.gz +0 -0
- package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css +0 -6
- package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css.br +0 -0
- package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css.gz +0 -0
- package/frontend/build/client/_app/immutable/chunks/DEKAA-Fm.js.br +0 -0
- package/frontend/build/client/_app/immutable/chunks/DEKAA-Fm.js.gz +0 -0
- package/frontend/build/client/_app/immutable/chunks/Dhf4xQKo.js.br +0 -0
- package/frontend/build/client/_app/immutable/chunks/Dhf4xQKo.js.gz +0 -0
- package/frontend/build/client/_app/immutable/entry/app.B0GH5ONo.js.br +0 -0
- package/frontend/build/client/_app/immutable/entry/app.B0GH5ONo.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/0.-imXt0f5.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/0.-imXt0f5.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/1.Dom3CKWF.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/1.Dom3CKWF.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/11.DMlayLCK.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/11.DMlayLCK.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/12.Bu89A1eX.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js +0 -6
- package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/3.B86rlHXU.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/3.B86rlHXU.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/5.CqTXvadV.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/5.CqTXvadV.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/6.BJZ62MCd.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/6.BJZ62MCd.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/8.B4fj2Xyu.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/8.B4fj2Xyu.js.gz +0 -0
- package/frontend/build/client/_app/immutable/nodes/9.C57_2xq6.js.br +0 -0
- package/frontend/build/client/_app/immutable/nodes/9.C57_2xq6.js.gz +0 -0
- package/frontend/build/server/chunks/_page.svelte-CpB-fgJl.js.map +0 -5
- package/frontend/build/server/chunks/_page.svelte-wKyUmA2e.js.map +0 -5
|
@@ -5,21 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
<script>
|
|
7
7
|
import { page } from '$app/stores';
|
|
8
|
-
import { onMount
|
|
9
|
-
import { slide
|
|
10
|
-
import { fetchReportDetail,
|
|
8
|
+
import { onMount } from 'svelte';
|
|
9
|
+
import { slide } from 'svelte/transition';
|
|
10
|
+
import { fetchReportDetail, fetchRecordings } from '$lib/api/reports';
|
|
11
11
|
import {
|
|
12
12
|
isScheduled,
|
|
13
13
|
triggerLabel,
|
|
14
14
|
fmtDuration,
|
|
15
15
|
stagger,
|
|
16
16
|
featureFile,
|
|
17
|
-
|
|
17
|
+
groupScenariosByRunnerAndWorker,
|
|
18
18
|
parseRunnerLogs,
|
|
19
|
-
featureSuiteTag
|
|
20
|
-
scenarioHasScreenshots
|
|
19
|
+
featureSuiteTag
|
|
21
20
|
} from '$lib/utils/format';
|
|
22
|
-
import {
|
|
21
|
+
import { BROWSERS } from '$lib/constants';
|
|
23
22
|
import { pluralize } from '$lib/copy/common';
|
|
24
23
|
import {
|
|
25
24
|
DETAIL_PAGE_TITLE,
|
|
@@ -35,16 +34,12 @@
|
|
|
35
34
|
RETRY_TITLE,
|
|
36
35
|
WATCH_REPLAY_TITLE,
|
|
37
36
|
REPLAY_LABEL,
|
|
38
|
-
SCREENSHOT_TOGGLE_LABEL,
|
|
39
|
-
STEP_SCREENSHOT_ALT,
|
|
40
|
-
NO_SCREENSHOT_MESSAGE,
|
|
41
37
|
runnersBadge,
|
|
42
38
|
casesCountLabel,
|
|
43
39
|
attemptsLabel,
|
|
44
40
|
caseLabel,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
replayCounter
|
|
41
|
+
UNKNOWN_RUNNER_LABEL,
|
|
42
|
+
workerLabel
|
|
48
43
|
} from '$lib/copy/reports';
|
|
49
44
|
import Badge from '$lib/components/ui/Badge.svelte';
|
|
50
45
|
import BackLink from '$lib/components/ui/BackLink.svelte';
|
|
@@ -53,6 +48,7 @@
|
|
|
53
48
|
import StepKeyword from '$lib/components/ui/StepKeyword.svelte';
|
|
54
49
|
import StepStatusIcon from '$lib/components/ui/StepStatusIcon.svelte';
|
|
55
50
|
import BrowserIcon from '$lib/components/icons/BrowserIcon.svelte';
|
|
51
|
+
import RecordingPlayer from '$lib/components/reports/RecordingPlayer.svelte';
|
|
56
52
|
|
|
57
53
|
const reportId = parseInt($page.params.id, 10);
|
|
58
54
|
|
|
@@ -61,84 +57,41 @@
|
|
|
61
57
|
let expandedScenarios = new Set();
|
|
62
58
|
|
|
63
59
|
// Replay state
|
|
60
|
+
let allRecordings = [];
|
|
64
61
|
let replayOpen = false;
|
|
65
62
|
let replayScenario = null;
|
|
66
|
-
let
|
|
67
|
-
let
|
|
68
|
-
let replayTimer = null;
|
|
63
|
+
let scenarioRecordings = [];
|
|
64
|
+
let replayInspecting = false;
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
onDestroy(() => {
|
|
74
|
-
if (replayTimer) clearInterval(replayTimer);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
function startPlayback() {
|
|
78
|
-
replayPlaying = true;
|
|
79
|
-
replayTimer = setInterval(() => {
|
|
80
|
-
if (replayIdx < (replayScenario?.steps?.length ?? 0) - 1) {
|
|
81
|
-
replayIdx++;
|
|
82
|
-
} else {
|
|
83
|
-
stopPlayback();
|
|
84
|
-
}
|
|
85
|
-
}, REPLAY_STEP_MS);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function stopPlayback() {
|
|
89
|
-
replayPlaying = false;
|
|
90
|
-
if (replayTimer) {
|
|
91
|
-
clearInterval(replayTimer);
|
|
92
|
-
replayTimer = null;
|
|
93
|
-
}
|
|
66
|
+
function scenarioHasRecording(scenario) {
|
|
67
|
+
return allRecordings.some((r) => r.scenarioId === scenario.id);
|
|
94
68
|
}
|
|
95
69
|
|
|
96
70
|
function openReplay(scenario) {
|
|
97
|
-
stopPlayback();
|
|
98
71
|
replayScenario = scenario;
|
|
99
|
-
|
|
72
|
+
scenarioRecordings = allRecordings
|
|
73
|
+
.filter((r) => r.scenarioId === scenario.id)
|
|
74
|
+
.sort((a, b) => a.tabIndex - b.tabIndex);
|
|
100
75
|
replayOpen = true;
|
|
101
|
-
startPlayback();
|
|
102
76
|
}
|
|
103
77
|
|
|
104
78
|
function closeReplay() {
|
|
105
|
-
stopPlayback();
|
|
106
79
|
replayOpen = false;
|
|
107
80
|
replayScenario = null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
function togglePlayback() {
|
|
111
|
-
if (replayPlaying) {
|
|
112
|
-
stopPlayback();
|
|
113
|
-
} else {
|
|
114
|
-
if (replayIdx >= replaySteps.length - 1) replayIdx = 0;
|
|
115
|
-
startPlayback();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function replayPrev() {
|
|
120
|
-
stopPlayback();
|
|
121
|
-
if (replayIdx > 0) replayIdx--;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function replayNext() {
|
|
125
|
-
stopPlayback();
|
|
126
|
-
if (replayIdx < replaySteps.length - 1) replayIdx++;
|
|
81
|
+
scenarioRecordings = [];
|
|
127
82
|
}
|
|
128
83
|
|
|
129
84
|
function handleKeydown(e) {
|
|
130
85
|
if (!replayOpen) return;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
e.preventDefault();
|
|
135
|
-
togglePlayback();
|
|
136
|
-
} else if (e.key === 'Escape') closeReplay();
|
|
86
|
+
// While inspecting, RecordingPlayer's own Escape handler turns inspect
|
|
87
|
+
// mode off — only close the modal when that's not in play.
|
|
88
|
+
if (e.key === 'Escape' && !replayInspecting) closeReplay();
|
|
137
89
|
}
|
|
138
90
|
|
|
139
91
|
onMount(async () => {
|
|
140
92
|
try {
|
|
141
93
|
detail = await fetchReportDetail(reportId);
|
|
94
|
+
allRecordings = await fetchRecordings(reportId);
|
|
142
95
|
} catch {
|
|
143
96
|
error = LOAD_ERROR;
|
|
144
97
|
}
|
|
@@ -164,11 +117,11 @@
|
|
|
164
117
|
// parallel (multiple workers/runners) — only used as a fallback for reports saved
|
|
165
118
|
// before this field existed.
|
|
166
119
|
$: totalDuration = detail?.duration ?? allScenarios.reduce((s, sc) => s + sc.duration, 0);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
120
|
+
// `allRecordings` is referenced here (not just inside scenarioHasRecording)
|
|
121
|
+
// purely so Svelte's compiler tracks it as a dependency — recordings load
|
|
122
|
+
// asynchronously after `detail`, and without this the {@const groupHasReplay}
|
|
123
|
+
// checks inside the each-block tree below never re-evaluate once they arrive.
|
|
124
|
+
$: runnerGroups = detail && allRecordings ? groupScenariosByRunnerAndWorker(detail.features) : [];
|
|
172
125
|
</script>
|
|
173
126
|
|
|
174
127
|
<svelte:window on:keydown={handleKeydown} />
|
|
@@ -318,7 +271,7 @@
|
|
|
318
271
|
</span>
|
|
319
272
|
</div>
|
|
320
273
|
<div class="stat">
|
|
321
|
-
<span class="stat-num">{detail.
|
|
274
|
+
<span class="stat-num">{detail.runnerCount}</span>
|
|
322
275
|
<span class="stat-label">
|
|
323
276
|
<svg
|
|
324
277
|
width="10"
|
|
@@ -332,9 +285,32 @@
|
|
|
332
285
|
<rect x="2" y="3" width="20" height="14" rx="2" />
|
|
333
286
|
<path d="M8 21h8M12 17v4" />
|
|
334
287
|
</svg>
|
|
335
|
-
{pluralize(detail.
|
|
288
|
+
{pluralize(detail.runnerCount, 'runner')}
|
|
336
289
|
</span>
|
|
337
290
|
</div>
|
|
291
|
+
{#if detail.workerCount > 1}
|
|
292
|
+
<div class="stat">
|
|
293
|
+
<span class="stat-num">{detail.workerCount}</span>
|
|
294
|
+
<span class="stat-label">
|
|
295
|
+
<svg
|
|
296
|
+
width="10"
|
|
297
|
+
height="10"
|
|
298
|
+
viewBox="0 0 24 24"
|
|
299
|
+
fill="none"
|
|
300
|
+
stroke="currentColor"
|
|
301
|
+
stroke-width="2"
|
|
302
|
+
stroke-linecap="round"
|
|
303
|
+
stroke-linejoin="round"
|
|
304
|
+
>
|
|
305
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
306
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
307
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
308
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
309
|
+
</svg>
|
|
310
|
+
{pluralize(detail.workerCount, 'worker')}
|
|
311
|
+
</span>
|
|
312
|
+
</div>
|
|
313
|
+
{/if}
|
|
338
314
|
</div>
|
|
339
315
|
</div>
|
|
340
316
|
</div>
|
|
@@ -389,175 +365,228 @@
|
|
|
389
365
|
</details>
|
|
390
366
|
{/if}
|
|
391
367
|
|
|
392
|
-
{#each
|
|
393
|
-
|
|
394
|
-
class="
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
368
|
+
{#each runnerGroups as runnerGroup, ri}
|
|
369
|
+
{#if runnerGroups.length > 1}
|
|
370
|
+
<div class="group-header runner-group-header">
|
|
371
|
+
<svg
|
|
372
|
+
class="group-icon"
|
|
373
|
+
width="12"
|
|
374
|
+
height="12"
|
|
375
|
+
viewBox="0 0 24 24"
|
|
376
|
+
fill="none"
|
|
377
|
+
stroke="currentColor"
|
|
378
|
+
stroke-width="2"
|
|
379
|
+
stroke-linecap="round"
|
|
380
|
+
>
|
|
381
|
+
<rect x="2" y="3" width="20" height="14" rx="2" />
|
|
382
|
+
<path d="M8 21h8M12 17v4" />
|
|
383
|
+
</svg>
|
|
384
|
+
{runnerGroup.runnerName ?? UNKNOWN_RUNNER_LABEL}
|
|
385
|
+
</div>
|
|
386
|
+
{/if}
|
|
387
|
+
{#each runnerGroup.workers as workerGroup, wi}
|
|
388
|
+
{#if runnerGroup.workers.length > 1}
|
|
389
|
+
<div class="group-header worker-group-header">
|
|
401
390
|
<svg
|
|
402
|
-
|
|
403
|
-
|
|
391
|
+
class="group-icon"
|
|
392
|
+
width="11"
|
|
393
|
+
height="11"
|
|
404
394
|
viewBox="0 0 24 24"
|
|
405
395
|
fill="none"
|
|
406
396
|
stroke="currentColor"
|
|
407
397
|
stroke-width="2"
|
|
408
398
|
stroke-linecap="round"
|
|
409
399
|
stroke-linejoin="round"
|
|
410
|
-
class="feature-icon"
|
|
411
400
|
>
|
|
412
|
-
<
|
|
413
|
-
<
|
|
401
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
402
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
403
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
404
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
414
405
|
</svg>
|
|
415
|
-
{
|
|
416
|
-
{#if featureSuiteTag(feature)}
|
|
417
|
-
<span class="suite-tag">{featureSuiteTag(feature)}</span>
|
|
418
|
-
{/if}
|
|
419
|
-
</h2>
|
|
420
|
-
<div class="feature-right">
|
|
421
|
-
<span class="feature-file" title={feature.uri}>{featureFile(feature.uri)}</span>
|
|
422
|
-
<Badge variant={feature.status === 'passed' ? 'pass' : 'fail'}>
|
|
423
|
-
{feature.status}
|
|
424
|
-
</Badge>
|
|
406
|
+
{workerLabel(workerGroup.workerId)}
|
|
425
407
|
</div>
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
{
|
|
431
|
-
{
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
class
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
</span>
|
|
453
|
-
|
|
454
|
-
<div class="scenario-tags">
|
|
455
|
-
{#each group.tags as tag}
|
|
456
|
-
<TagChip {tag} />
|
|
457
|
-
{/each}
|
|
458
|
-
</div>
|
|
459
|
-
|
|
460
|
-
<span class="scenario-duration">{fmtDuration(group.duration)}</span>
|
|
461
|
-
|
|
462
|
-
<svg
|
|
463
|
-
width="13"
|
|
464
|
-
height="13"
|
|
465
|
-
viewBox="0 0 24 24"
|
|
466
|
-
fill="none"
|
|
467
|
-
stroke="currentColor"
|
|
468
|
-
stroke-width="2"
|
|
469
|
-
stroke-linecap="round"
|
|
470
|
-
class="chevron"
|
|
471
|
-
class:rotated={open}
|
|
472
|
-
>
|
|
473
|
-
<polyline points="9 18 15 12 9 6" />
|
|
474
|
-
</svg>
|
|
475
|
-
</button>
|
|
476
|
-
|
|
477
|
-
{#if groupHasReplay && group.scenarios.length === 1}
|
|
478
|
-
<button
|
|
479
|
-
class="replay-btn"
|
|
480
|
-
title={WATCH_REPLAY_TITLE}
|
|
481
|
-
on:click={() => openReplay(group.scenarios[0])}
|
|
482
|
-
>
|
|
483
|
-
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
484
|
-
<polygon points="5,3 19,12 5,21" />
|
|
485
|
-
</svg>
|
|
486
|
-
{REPLAY_LABEL}
|
|
487
|
-
</button>
|
|
408
|
+
{/if}
|
|
409
|
+
{#each workerGroup.features as feature, fi}
|
|
410
|
+
<div
|
|
411
|
+
class="feature"
|
|
412
|
+
class:feature-pass={feature.status === 'passed'}
|
|
413
|
+
class:feature-fail={feature.status !== 'passed'}
|
|
414
|
+
style={stagger(fi, 60)}
|
|
415
|
+
>
|
|
416
|
+
<div class="feature-header">
|
|
417
|
+
<h2 class="feature-name">
|
|
418
|
+
<svg
|
|
419
|
+
width="13"
|
|
420
|
+
height="13"
|
|
421
|
+
viewBox="0 0 24 24"
|
|
422
|
+
fill="none"
|
|
423
|
+
stroke="currentColor"
|
|
424
|
+
stroke-width="2"
|
|
425
|
+
stroke-linecap="round"
|
|
426
|
+
stroke-linejoin="round"
|
|
427
|
+
class="feature-icon"
|
|
428
|
+
>
|
|
429
|
+
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
|
|
430
|
+
<polyline points="14 2 14 8 20 8" />
|
|
431
|
+
</svg>
|
|
432
|
+
{feature.name}
|
|
433
|
+
{#if featureSuiteTag(feature)}
|
|
434
|
+
<span class="suite-tag">{featureSuiteTag(feature)}</span>
|
|
488
435
|
{/if}
|
|
436
|
+
</h2>
|
|
437
|
+
<div class="feature-right">
|
|
438
|
+
<span class="feature-file" title={feature.uri}>{featureFile(feature.uri)}</span>
|
|
439
|
+
<Badge variant={feature.status === 'passed' ? 'pass' : 'fail'}>
|
|
440
|
+
{feature.status}
|
|
441
|
+
</Badge>
|
|
489
442
|
</div>
|
|
443
|
+
</div>
|
|
490
444
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
445
|
+
<div class="scenarios">
|
|
446
|
+
{#each feature.scenarioGroups as group, si}
|
|
447
|
+
{@const scenId = `${ri}-${wi}-${fi}-${group.key}`}
|
|
448
|
+
{@const open = expandedScenarios.has(scenId)}
|
|
449
|
+
{@const groupHasReplay = group.scenarios.some(scenarioHasRecording)}
|
|
450
|
+
<div
|
|
451
|
+
class="scenario"
|
|
452
|
+
class:scenario-fail={group.status === 'failed'}
|
|
453
|
+
style={stagger(fi * 5 + si, 40)}
|
|
454
|
+
>
|
|
455
|
+
<div class="scenario-row">
|
|
456
|
+
<button class="scenario-header" on:click={() => toggleScenario(scenId)}>
|
|
457
|
+
<StatusDot status={group.status} />
|
|
458
|
+
|
|
459
|
+
<span class="scenario-name">
|
|
460
|
+
{group.name}
|
|
461
|
+
{#if group.scenarios.length > 1}
|
|
462
|
+
<span class="scenario-count">{casesCountLabel(group.scenarios.length)}</span
|
|
503
463
|
>
|
|
504
|
-
<svg
|
|
505
|
-
width="9"
|
|
506
|
-
height="9"
|
|
507
|
-
viewBox="0 0 24 24"
|
|
508
|
-
fill="currentColor"
|
|
509
|
-
stroke="none"
|
|
510
|
-
>
|
|
511
|
-
<polygon points="5,3 19,12 5,21" />
|
|
512
|
-
</svg>
|
|
513
|
-
{REPLAY_LABEL}
|
|
514
|
-
</button>
|
|
515
464
|
{/if}
|
|
465
|
+
{#if group.scenarios[0]?.attempts > 1}
|
|
466
|
+
<span class="scenario-count" title={RETRY_TITLE}>
|
|
467
|
+
{attemptsLabel(group.scenarios[0].attempts)}
|
|
468
|
+
</span>
|
|
469
|
+
{/if}
|
|
470
|
+
</span>
|
|
471
|
+
|
|
472
|
+
<div class="scenario-tags">
|
|
473
|
+
{#each group.tags as tag}
|
|
474
|
+
<TagChip {tag} />
|
|
475
|
+
{/each}
|
|
516
476
|
</div>
|
|
517
|
-
{/if}
|
|
518
477
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
478
|
+
<span class="scenario-duration">{fmtDuration(group.duration)}</span>
|
|
479
|
+
|
|
480
|
+
<svg
|
|
481
|
+
width="13"
|
|
482
|
+
height="13"
|
|
483
|
+
viewBox="0 0 24 24"
|
|
484
|
+
fill="none"
|
|
485
|
+
stroke="currentColor"
|
|
486
|
+
stroke-width="2"
|
|
487
|
+
stroke-linecap="round"
|
|
488
|
+
class="chevron"
|
|
489
|
+
class:rotated={open}
|
|
524
490
|
>
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
{
|
|
533
|
-
|
|
534
|
-
|
|
491
|
+
<polyline points="9 18 15 12 9 6" />
|
|
492
|
+
</svg>
|
|
493
|
+
</button>
|
|
494
|
+
|
|
495
|
+
{#if groupHasReplay && group.scenarios.length === 1}
|
|
496
|
+
<button
|
|
497
|
+
class="replay-btn"
|
|
498
|
+
title={WATCH_REPLAY_TITLE}
|
|
499
|
+
on:click={() => openReplay(group.scenarios[0])}
|
|
500
|
+
>
|
|
501
|
+
<svg
|
|
502
|
+
width="11"
|
|
503
|
+
height="11"
|
|
504
|
+
viewBox="0 0 24 24"
|
|
505
|
+
fill="currentColor"
|
|
506
|
+
stroke="none"
|
|
507
|
+
>
|
|
508
|
+
<polygon points="5,3 19,12 5,21" />
|
|
509
|
+
</svg>
|
|
510
|
+
{REPLAY_LABEL}
|
|
511
|
+
</button>
|
|
512
|
+
{/if}
|
|
513
|
+
</div>
|
|
535
514
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
515
|
+
{#if open}
|
|
516
|
+
<div class="steps" transition:slide={{ duration: 200 }}>
|
|
517
|
+
{#each group.scenarios as scenario, exampleIndex}
|
|
518
|
+
{#if group.scenarios.length > 1}
|
|
519
|
+
<div class="example-header">
|
|
520
|
+
<StatusDot status={scenario.status} />
|
|
521
|
+
<span>{caseLabel(exampleIndex + 1)}</span>
|
|
522
|
+
<span class="scenario-duration">{fmtDuration(scenario.duration)}</span>
|
|
523
|
+
{#if scenarioHasRecording(scenario)}
|
|
524
|
+
<button
|
|
525
|
+
class="replay-btn replay-btn-sm"
|
|
526
|
+
on:click={() => openReplay(scenario)}
|
|
527
|
+
>
|
|
528
|
+
<svg
|
|
529
|
+
width="9"
|
|
530
|
+
height="9"
|
|
531
|
+
viewBox="0 0 24 24"
|
|
532
|
+
fill="currentColor"
|
|
533
|
+
stroke="none"
|
|
534
|
+
>
|
|
535
|
+
<polygon points="5,3 19,12 5,21" />
|
|
536
|
+
</svg>
|
|
537
|
+
{REPLAY_LABEL}
|
|
538
|
+
</button>
|
|
539
|
+
{/if}
|
|
540
|
+
</div>
|
|
545
541
|
{/if}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
542
|
+
|
|
543
|
+
{#each scenario.steps as step}
|
|
544
|
+
<div
|
|
545
|
+
class="step"
|
|
546
|
+
class:step-fail={step.status === 'failed'}
|
|
547
|
+
class:step-skip={step.status === 'skipped' || step.status === 'pending'}
|
|
548
|
+
>
|
|
549
|
+
<div class="step-row">
|
|
550
|
+
<StepStatusIcon status={step.status} />
|
|
551
|
+
<StepKeyword keyword={step.keyword} />
|
|
552
|
+
<span class="step-name">{step.name}</span>
|
|
553
|
+
<span class="step-duration">{fmtDuration(step.duration)}</span>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
{#if step.error}
|
|
557
|
+
<pre class="step-error">{step.error}</pre>
|
|
558
|
+
{/if}
|
|
559
|
+
|
|
560
|
+
{#if step.dataTable}
|
|
561
|
+
<table class="step-datatable">
|
|
562
|
+
<tbody>
|
|
563
|
+
{#each step.dataTable as row, ri}
|
|
564
|
+
<tr class:step-datatable-header={ri === 0}>
|
|
565
|
+
{#each row as cell}
|
|
566
|
+
<td>{cell}</td>
|
|
567
|
+
{/each}
|
|
568
|
+
</tr>
|
|
569
|
+
{/each}
|
|
570
|
+
</tbody>
|
|
571
|
+
</table>
|
|
572
|
+
{/if}
|
|
573
|
+
</div>
|
|
574
|
+
{/each}
|
|
575
|
+
{/each}
|
|
576
|
+
</div>
|
|
577
|
+
{/if}
|
|
549
578
|
</div>
|
|
550
|
-
{/
|
|
579
|
+
{/each}
|
|
551
580
|
</div>
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
581
|
+
</div>
|
|
582
|
+
{/each}
|
|
583
|
+
{/each}
|
|
555
584
|
{/each}
|
|
556
585
|
{/if}
|
|
557
586
|
|
|
558
587
|
{#if replayOpen && replayScenario}
|
|
559
588
|
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
|
|
560
|
-
<div class="replay-overlay"
|
|
589
|
+
<div class="replay-overlay" on:click={closeReplay}>
|
|
561
590
|
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
|
|
562
591
|
<div class="replay-modal" on:click|stopPropagation>
|
|
563
592
|
<div class="replay-modal-header">
|
|
@@ -577,98 +606,16 @@
|
|
|
577
606
|
</button>
|
|
578
607
|
</div>
|
|
579
608
|
|
|
580
|
-
<div class="replay-
|
|
581
|
-
{#if
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
609
|
+
<div class="replay-body">
|
|
610
|
+
{#if scenarioRecordings.length > 0}
|
|
611
|
+
<RecordingPlayer
|
|
612
|
+
{reportId}
|
|
613
|
+
recordings={scenarioRecordings}
|
|
614
|
+
steps={replayScenario.steps}
|
|
615
|
+
bind:inspecting={replayInspecting}
|
|
586
616
|
/>
|
|
587
|
-
{:else}
|
|
588
|
-
<div class="replay-no-screenshot">{NO_SCREENSHOT_MESSAGE}</div>
|
|
589
617
|
{/if}
|
|
590
618
|
</div>
|
|
591
|
-
|
|
592
|
-
<div class="replay-timeline">
|
|
593
|
-
<input
|
|
594
|
-
type="range"
|
|
595
|
-
class="replay-slider"
|
|
596
|
-
min="0"
|
|
597
|
-
max={replaySteps.length - 1}
|
|
598
|
-
value={replayIdx}
|
|
599
|
-
style="--fill: {replaySteps.length > 1
|
|
600
|
-
? (replayIdx / (replaySteps.length - 1)) * 100
|
|
601
|
-
: 0}%"
|
|
602
|
-
on:input={(e) => {
|
|
603
|
-
stopPlayback();
|
|
604
|
-
replayIdx = Number(e.target.value);
|
|
605
|
-
}}
|
|
606
|
-
/>
|
|
607
|
-
</div>
|
|
608
|
-
|
|
609
|
-
<div class="replay-step-info">
|
|
610
|
-
<StepStatusIcon status={currentReplayStep?.status ?? 'skipped'} />
|
|
611
|
-
<StepKeyword keyword={currentReplayStep?.keyword ?? ''} />
|
|
612
|
-
<span class="replay-step-name">{currentReplayStep?.name}</span>
|
|
613
|
-
</div>
|
|
614
|
-
|
|
615
|
-
<div class="replay-nav">
|
|
616
|
-
<button class="replay-nav-btn" on:click={replayPrev} disabled={replayIdx === 0}>
|
|
617
|
-
<svg
|
|
618
|
-
width="16"
|
|
619
|
-
height="16"
|
|
620
|
-
viewBox="0 0 24 24"
|
|
621
|
-
fill="none"
|
|
622
|
-
stroke="currentColor"
|
|
623
|
-
stroke-width="2"
|
|
624
|
-
stroke-linecap="round"
|
|
625
|
-
>
|
|
626
|
-
<polyline points="15 18 9 12 15 6" />
|
|
627
|
-
</svg>
|
|
628
|
-
</button>
|
|
629
|
-
|
|
630
|
-
<button
|
|
631
|
-
class="replay-play-btn"
|
|
632
|
-
on:click={togglePlayback}
|
|
633
|
-
title={pauseOrPlayTitle(replayPlaying)}
|
|
634
|
-
>
|
|
635
|
-
{#if replayPlaying}
|
|
636
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
637
|
-
<rect x="6" y="4" width="4" height="16" rx="1" /><rect
|
|
638
|
-
x="14"
|
|
639
|
-
y="4"
|
|
640
|
-
width="4"
|
|
641
|
-
height="16"
|
|
642
|
-
rx="1"
|
|
643
|
-
/>
|
|
644
|
-
</svg>
|
|
645
|
-
{:else}
|
|
646
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
647
|
-
<polygon points="5,3 19,12 5,21" />
|
|
648
|
-
</svg>
|
|
649
|
-
{/if}
|
|
650
|
-
</button>
|
|
651
|
-
|
|
652
|
-
<button
|
|
653
|
-
class="replay-nav-btn"
|
|
654
|
-
on:click={replayNext}
|
|
655
|
-
disabled={replayIdx === replaySteps.length - 1}
|
|
656
|
-
>
|
|
657
|
-
<svg
|
|
658
|
-
width="16"
|
|
659
|
-
height="16"
|
|
660
|
-
viewBox="0 0 24 24"
|
|
661
|
-
fill="none"
|
|
662
|
-
stroke="currentColor"
|
|
663
|
-
stroke-width="2"
|
|
664
|
-
stroke-linecap="round"
|
|
665
|
-
>
|
|
666
|
-
<polyline points="9 18 15 12 9 6" />
|
|
667
|
-
</svg>
|
|
668
|
-
</button>
|
|
669
|
-
|
|
670
|
-
<span class="replay-counter">{replayCounter(replayIdx + 1, replaySteps.length)}</span>
|
|
671
|
-
</div>
|
|
672
619
|
</div>
|
|
673
620
|
</div>
|
|
674
621
|
{/if}
|
|
@@ -826,6 +773,36 @@
|
|
|
826
773
|
color: var(--text-muted);
|
|
827
774
|
}
|
|
828
775
|
|
|
776
|
+
.group-header {
|
|
777
|
+
display: flex;
|
|
778
|
+
align-items: center;
|
|
779
|
+
gap: 0.4rem;
|
|
780
|
+
padding-bottom: 0.4rem;
|
|
781
|
+
margin-top: 1.75rem;
|
|
782
|
+
margin-bottom: 0.75rem;
|
|
783
|
+
font-family: 'JetBrains Mono', monospace;
|
|
784
|
+
text-transform: uppercase;
|
|
785
|
+
letter-spacing: 0.05em;
|
|
786
|
+
border-bottom: 1px solid var(--border);
|
|
787
|
+
}
|
|
788
|
+
.group-header:first-child {
|
|
789
|
+
margin-top: 0;
|
|
790
|
+
}
|
|
791
|
+
.group-icon {
|
|
792
|
+
flex-shrink: 0;
|
|
793
|
+
opacity: 0.7;
|
|
794
|
+
}
|
|
795
|
+
.runner-group-header {
|
|
796
|
+
font-size: 0.78rem;
|
|
797
|
+
font-weight: 600;
|
|
798
|
+
color: var(--text);
|
|
799
|
+
}
|
|
800
|
+
.worker-group-header {
|
|
801
|
+
font-size: 0.7rem;
|
|
802
|
+
font-weight: 500;
|
|
803
|
+
color: var(--text-muted);
|
|
804
|
+
}
|
|
805
|
+
|
|
829
806
|
.feature {
|
|
830
807
|
margin-bottom: 1.25rem;
|
|
831
808
|
background: var(--bg-elevated);
|
|
@@ -1085,22 +1062,23 @@
|
|
|
1085
1062
|
border-left: 2px solid var(--fail);
|
|
1086
1063
|
}
|
|
1087
1064
|
|
|
1088
|
-
.
|
|
1089
|
-
margin: 0.
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
.screenshot-toggle {
|
|
1065
|
+
.step-datatable {
|
|
1066
|
+
margin: 0.375rem 0 0.25rem 1.75rem;
|
|
1067
|
+
border-collapse: collapse;
|
|
1068
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1093
1069
|
font-size: 0.75rem;
|
|
1094
|
-
color: var(--text-muted);
|
|
1095
|
-
cursor: pointer;
|
|
1096
|
-
user-select: none;
|
|
1097
1070
|
}
|
|
1098
1071
|
|
|
1099
|
-
.
|
|
1100
|
-
|
|
1101
|
-
max-width: 100%;
|
|
1102
|
-
border-radius: var(--radius-sm);
|
|
1072
|
+
.step-datatable td {
|
|
1073
|
+
padding: 0.35rem 0.7rem;
|
|
1103
1074
|
border: 1px solid var(--border);
|
|
1075
|
+
color: var(--text);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.step-datatable-header td {
|
|
1079
|
+
background: var(--bg-subtle);
|
|
1080
|
+
color: var(--text-muted);
|
|
1081
|
+
font-weight: 600;
|
|
1104
1082
|
}
|
|
1105
1083
|
|
|
1106
1084
|
.loading-state {
|
|
@@ -1252,23 +1230,18 @@
|
|
|
1252
1230
|
.replay-overlay {
|
|
1253
1231
|
position: fixed;
|
|
1254
1232
|
inset: 0;
|
|
1255
|
-
background:
|
|
1233
|
+
background: var(--bg-elevated);
|
|
1256
1234
|
display: flex;
|
|
1257
|
-
|
|
1258
|
-
justify-content: center;
|
|
1259
|
-
z-index: 200;
|
|
1260
|
-
padding: 1rem;
|
|
1235
|
+
z-index: 500;
|
|
1261
1236
|
}
|
|
1262
1237
|
|
|
1263
1238
|
.replay-modal {
|
|
1264
1239
|
background: var(--bg-elevated);
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
width: min(920px, 100%);
|
|
1268
|
-
max-height: 90vh;
|
|
1240
|
+
width: 100%;
|
|
1241
|
+
height: 100%;
|
|
1269
1242
|
display: flex;
|
|
1270
1243
|
flex-direction: column;
|
|
1271
|
-
overflow: hidden;
|
|
1244
|
+
overflow: hidden auto;
|
|
1272
1245
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
|
|
1273
1246
|
}
|
|
1274
1247
|
|
|
@@ -1308,175 +1281,11 @@
|
|
|
1308
1281
|
color: var(--text);
|
|
1309
1282
|
}
|
|
1310
1283
|
|
|
1311
|
-
.replay-
|
|
1284
|
+
.replay-body {
|
|
1312
1285
|
flex: 1;
|
|
1313
1286
|
min-height: 0;
|
|
1314
|
-
background: var(--bg-subtle);
|
|
1315
|
-
display: flex;
|
|
1316
|
-
align-items: center;
|
|
1317
|
-
justify-content: center;
|
|
1318
1287
|
overflow: hidden;
|
|
1319
|
-
position: relative;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
.replay-screenshot {
|
|
1323
|
-
max-width: 100%;
|
|
1324
|
-
max-height: 100%;
|
|
1325
|
-
object-fit: contain;
|
|
1326
|
-
display: block;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
.replay-no-screenshot {
|
|
1330
|
-
color: var(--text-muted);
|
|
1331
|
-
font-size: 0.8125rem;
|
|
1332
|
-
font-family: 'JetBrains Mono', monospace;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
.replay-step-info {
|
|
1336
1288
|
display: flex;
|
|
1337
|
-
|
|
1338
|
-
gap: 0.5rem;
|
|
1339
|
-
padding: 0.75rem 1.25rem;
|
|
1340
|
-
border-top: 1px solid var(--border);
|
|
1341
|
-
flex-shrink: 0;
|
|
1342
|
-
min-width: 0;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
.replay-step-name {
|
|
1346
|
-
font-family: 'JetBrains Mono', monospace;
|
|
1347
|
-
font-size: 0.8125rem;
|
|
1348
|
-
color: var(--text);
|
|
1349
|
-
overflow: hidden;
|
|
1350
|
-
text-overflow: ellipsis;
|
|
1351
|
-
white-space: nowrap;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
/* ── Timeline slider ── */
|
|
1355
|
-
.replay-timeline {
|
|
1356
|
-
padding: 0.5rem 1.25rem;
|
|
1357
|
-
border-top: 1px solid var(--border);
|
|
1358
|
-
flex-shrink: 0;
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
.replay-slider {
|
|
1362
|
-
display: block;
|
|
1363
|
-
width: 100%;
|
|
1364
|
-
height: 4px;
|
|
1365
|
-
-webkit-appearance: none;
|
|
1366
|
-
appearance: none;
|
|
1367
|
-
background: transparent;
|
|
1368
|
-
border-radius: 2px;
|
|
1369
|
-
cursor: pointer;
|
|
1370
|
-
outline: none;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
.replay-slider::-webkit-slider-runnable-track {
|
|
1374
|
-
height: 4px;
|
|
1375
|
-
border-radius: 2px;
|
|
1376
|
-
background: linear-gradient(
|
|
1377
|
-
to right,
|
|
1378
|
-
var(--accent) var(--fill, 0%),
|
|
1379
|
-
var(--border) var(--fill, 0%)
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
.replay-slider::-webkit-slider-thumb {
|
|
1384
|
-
-webkit-appearance: none;
|
|
1385
|
-
width: 14px;
|
|
1386
|
-
height: 14px;
|
|
1387
|
-
margin-top: -5px;
|
|
1388
|
-
border-radius: 50%;
|
|
1389
|
-
background: var(--accent);
|
|
1390
|
-
cursor: pointer;
|
|
1391
|
-
transition: transform 0.1s;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
.replay-slider:hover::-webkit-slider-thumb {
|
|
1395
|
-
transform: scale(1.25);
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
.replay-slider::-moz-range-track {
|
|
1399
|
-
height: 4px;
|
|
1400
|
-
border-radius: 2px;
|
|
1401
|
-
background: var(--border);
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
.replay-slider::-moz-range-progress {
|
|
1405
|
-
height: 4px;
|
|
1406
|
-
border-radius: 2px;
|
|
1407
|
-
background: var(--accent);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
.replay-slider::-moz-range-thumb {
|
|
1411
|
-
width: 14px;
|
|
1412
|
-
height: 14px;
|
|
1413
|
-
border-radius: 50%;
|
|
1414
|
-
background: var(--accent);
|
|
1415
|
-
border: none;
|
|
1416
|
-
cursor: pointer;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
/* ── Nav bar ── */
|
|
1420
|
-
.replay-nav {
|
|
1421
|
-
display: flex;
|
|
1422
|
-
align-items: center;
|
|
1423
|
-
justify-content: center;
|
|
1424
|
-
gap: 0.75rem;
|
|
1425
|
-
padding: 0.5rem 1.25rem 0.625rem;
|
|
1426
|
-
border-top: 1px solid var(--border);
|
|
1427
|
-
flex-shrink: 0;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
.replay-nav-btn {
|
|
1431
|
-
display: flex;
|
|
1432
|
-
align-items: center;
|
|
1433
|
-
justify-content: center;
|
|
1434
|
-
width: 30px;
|
|
1435
|
-
height: 30px;
|
|
1436
|
-
background: var(--bg-subtle);
|
|
1437
|
-
border: 1px solid var(--border);
|
|
1438
|
-
border-radius: var(--radius-sm);
|
|
1439
|
-
color: var(--text);
|
|
1440
|
-
cursor: pointer;
|
|
1441
|
-
transition: background var(--duration-fast);
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
.replay-nav-btn:hover:not(:disabled) {
|
|
1445
|
-
background: var(--bg-elevated);
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
.replay-nav-btn:disabled {
|
|
1449
|
-
opacity: 0.35;
|
|
1450
|
-
cursor: default;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
.replay-play-btn {
|
|
1454
|
-
display: flex;
|
|
1455
|
-
align-items: center;
|
|
1456
|
-
justify-content: center;
|
|
1457
|
-
width: 38px;
|
|
1458
|
-
height: 38px;
|
|
1459
|
-
background: var(--accent);
|
|
1460
|
-
border: none;
|
|
1461
|
-
border-radius: 50%;
|
|
1462
|
-
color: var(--white);
|
|
1463
|
-
cursor: pointer;
|
|
1464
|
-
transition:
|
|
1465
|
-
opacity var(--duration-fast),
|
|
1466
|
-
transform var(--duration-fast);
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
.replay-play-btn:hover {
|
|
1470
|
-
opacity: 0.85;
|
|
1471
|
-
transform: scale(1.05);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
.replay-counter {
|
|
1475
|
-
font-family: 'JetBrains Mono', monospace;
|
|
1476
|
-
font-size: 0.78rem;
|
|
1477
|
-
color: var(--text-muted);
|
|
1478
|
-
min-width: 5ch;
|
|
1479
|
-
text-align: center;
|
|
1480
|
-
margin-left: 0.25rem;
|
|
1289
|
+
flex-direction: column;
|
|
1481
1290
|
}
|
|
1482
1291
|
</style>
|