From 69d16c9a941d45e93d1cda029f11d9d97cacdf58 Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Wed, 16 Sep 2026 12:33:16 +0300 Subject: [PATCH] feat(scenario): export scenarios as standalone playwright e2e test projects - Added E2eExportService to package scenarios as plug-and-play zip archives containing package.json, playwright.config.ts, test.spec.ts, .env, credentials.json, snippets, and referenced scenario files - Only bundles credentials, snippets, and files actually referenced (directly or transitively) by the scenario's steps, reducing archive size - Exported test runs entirely offline using a context shim that mirrors liqa's API (page, getCredential, runSnippet, getScenarioFiles, downloadFile, assert, etc.) - Added GET /scenarios/:id/export-e2e HTTP endpoint and export_e2e_test MCP tool - Added getUsedSnippets() endpoint to list snippets referenced by a scenario - Added "Used Snippets" section on scenario detail page - Added archiver@^7.0.1 dependency for zip archive creation - Bumped version to 1.11.0 --- CHANGELOG.md | 10 + client/src/api/client.ts | 6 + client/src/i18n/locales/en.json | 2 + .../src/pages/scenario/ScenarioDetailPage.tsx | 52 ++ package-lock.json | 673 +++++++++++++++++- package.json | 2 +- server/package.json | 2 + server/src/common/snippet-usage.ts | 34 + server/src/mcp/mcp.service.ts | 36 + server/src/scenario/e2e-export.service.ts | 473 ++++++++++++ server/src/scenario/scenario.controller.ts | 33 +- server/src/scenario/scenario.module.ts | 5 +- server/src/scenario/scenario.service.ts | 23 + server/src/snippet/snippet.service.ts | 8 +- 14 files changed, 1348 insertions(+), 11 deletions(-) create mode 100644 server/src/common/snippet-usage.ts create mode 100644 server/src/scenario/e2e-export.service.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb0b7c..807321d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## 1.11.0 - 2026-09-15 + +Changes since 1.10.1: + +### Added + +- Added export of a scenario as a standalone, plug-and-play Playwright e2e test project (zip archive with `package.json`, `playwright.config.ts`, `test.spec.ts`, `README.md`, `.env`, `credentials.json`, `snippets/*.js`, and any attached scenario files) that runs entirely independently of the liqa backend. Available as a "Export e2e test" button on the scenario detail page, `GET /scenarios/:id/export-e2e`, and the `export_e2e_test` MCP tool. +- The exported test only bundles the credentials, snippets, and attached files actually referenced (directly or transitively) by the scenario's steps. +- Added a "Used Snippets" section on the scenario detail page listing the snippets referenced by the scenario's steps, linking to each snippet's detail page. Backed by `GET /scenarios/:id/used-snippets`. + ## 1.10.1 - 2026-09-15 Changes since 1.10.0: diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 9a3d7c9..945247a 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -261,6 +261,12 @@ export const scenarios = { body: JSON.stringify(payload), }); }, + exportE2eTestUrl(id: string): string { + return `${BASE_URL}/scenarios/${id}/export-e2e`; + }, + usedSnippets(id: string): Promise { + return request(`/scenarios/${id}/used-snippets`); + }, }; // ── Runs ───────────────────────────────────────────────────────────────────── diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index b5856a1..f11faa6 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -149,6 +149,7 @@ "action_edit": "Edit", "action_runs": "Runs", "action_export": "Export", + "action_export_e2e": "Export e2e test", "action_import": "Import", "import_error": "Failed to import scenario", "action_save": "Create", @@ -165,6 +166,7 @@ "form_name_placeholder": "e.g. Login flow", "form_name_required": "Name is required", "credentials_heading": "Credentials", + "used_snippets_heading": "Used Snippets", "cred_empty": "No credentials assigned.", "cred_col_alias": "Alias", "cred_col_name": "Credential", diff --git a/client/src/pages/scenario/ScenarioDetailPage.tsx b/client/src/pages/scenario/ScenarioDetailPage.tsx index eb61602..36ab9d7 100644 --- a/client/src/pages/scenario/ScenarioDetailPage.tsx +++ b/client/src/pages/scenario/ScenarioDetailPage.tsx @@ -12,6 +12,7 @@ import { Upload, GripVertical, ClipboardList, + FileArchive, } from 'lucide-react'; import { environments, @@ -28,6 +29,7 @@ import type { Credential, Environment, FileMetadata, + Snippet, } from '../../api'; import { Breadcrumbs, @@ -94,6 +96,8 @@ export function ScenarioDetailPage() { const [uploadExpiresAt, setUploadExpiresAt] = useState(''); const [uploading, setUploading] = useState(false); + const [usedSnippets, setUsedSnippets] = useState([]); + const loadFiles = useCallback(async () => { if (!id) return; setFilesLoading(true); @@ -116,6 +120,10 @@ export function ScenarioDetailPage() { }) .finally(() => setLoading(false)); void loadFiles(); + scenarios + .usedSnippets(id) + .then(setUsedSnippets) + .catch(() => {}); credentialsApi .list(1, 200) .then((r) => setAllCredentials(r.data)) @@ -142,6 +150,10 @@ export function ScenarioDetailPage() { const s = await scenarios.get(id); setScenario(s); setScenarioCreds(s.scenarioCredentials ?? []); + scenarios + .usedSnippets(id) + .then(setUsedSnippets) + .catch(() => {}); }; const handleRun = async () => { @@ -456,6 +468,16 @@ export function ScenarioDetailPage() { {t('scenarios.action_export')} +